Skip to content

ChangeOrder

A ChangeOrder names one defined set of changes in a Space and follows it through promotion into the Units and Revisions of other Spaces. It answers "did this change reach that variant?", which no per-Unit revision number can: numbering is per Unit.

A ChangeSet names a change within one Space and locks the Units it is open on. A ChangeOrder names the change itself, and is the only identity a change keeps as it crosses a Space boundary. The two compose: a ChangeOrder can adopt a ChangeSet's end Tag as its boundary, and neither requires the other.

A ChangeOrder is not required, and it does not perform promotion. It supplies the identity, the revision range, and the target set; the existing upgrade and resolve paths do the work.

Where it lives, and what it fixes

A ChangeOrder resides in the Space holding the changes to promote — its base. That Space is what says which Units are in scope, which is why the start of the interval can be derived rather than described Unit by Unit.

It is created after the revisions it names, at the moment you decide to promote them. Creating one derives, per Unit of its Space:

  • The Links of its UpdateType (UpgradeUnit, the clone lineage, by default; MergeUnits is the other Link type accepted) whose downstream Space is in scope.
  • The start — the revision each downstream has already taken, read from those Links' merge cursors. If they disagree, that is an error naming the Unit and the values, rather than a different range being silently promoted to each target.
  • The end — the Unit's head revision, or the revision a supplied boundary Tag marks on it.
  • Two Tags of its own, suffixed -co-start and -co-end, and the ChangeOrder stamped onto the revisions in the interval.

A Unit the change carries nothing for is marked, not dropped: both Tags land on the one revision every target is already at. SkippedUnits records the Units it carries no revisions of at all, and why of each.

Where it is headed

InScopeSpaceIDs is where the change is headed: a list of Spaces, not a standing query. A query re-asked on every read would take in a Space that arrived weeks after the change had been released everywhere and put the ChangeOrder back to InProgress with nothing having changed. What is recorded is the answer, which can be worked out in three ways:

  • Listed by the client, with --in-scope-space. A client can compute the list however it likes, for example from the stages of a ChangeWorkflow and the component's Spaces, which is what cub changeorder create --change-workflow does.
  • Selected by the server, from WhereSpace, a where expression over Spaces, and SpaceFilterID, a Filter over Spaces, which are ANDed when both are set. The server evaluates the selection with the permissions of whoever writes it and records the Spaces it picks. It is evaluated when the ChangeOrder is created, when either field changes, and when an update asks for it with --refresh-spaces, which is how Spaces that have come to match since are taken in. While a selection is set, the list is the server's to write.
  • Left empty, in which case wherever the ChangeOrder's Links reach when its scope is derived is recorded instead.

For a ChangeOrder that follows Links, every Space between its own and a Space in scope must be in scope too, since a change is propagated hop by hop; a list or selection that skips one is refused. Editing the list, or a selection that moves it, re-derives what the ChangeOrder covers, since where each Unit's interval begins is decided by the revision the Spaces in scope have already taken.

Propagating by invocation

A ChangeOrder with UpdateType Invoke propagates the other way round. There is no upstream revision to merge and no lineage to follow: the change is one Invocation, run in each Space in scope, and the ChangeOrder is created before any of it has happened. That fits routine rollouts where every variant receives the same operation, such as bumping an image tag, setting a label, or applying a policy, and where the variants need not be clones of one another at all.

  • InvocationID names what to run, and Parameters supplies the values for a parameterized Invocation's declared parameters: one set for the whole ChangeOrder, so every Space receives the same change.
  • WhereUnit and UnitFilterID narrow which Units of each Space are covered; empty covers every Unit.
  • All of these are fixed at creation, since they are what the change is.

Promoting it into a Space runs the Invocation on the covered Units there and marks the Revisions it made with the ChangeOrder's Tags, so everything else — promoting by Stage, the prerequisites, releasing with --revision ChangeOrder:<slug>, State, and undoing — reads the same as for a ChangeOrder that follows Links. Unlike the Spaces in scope, the Unit selection is asked again on every read: a Unit added to a Space after the change was made there has not had the Invocation run on it, and counts against that Space until it does.

Governed by a ChangeWorkflow

ChangeWorkflowID names the ChangeWorkflow the ChangeOrder is promoted under. It is set at creation and fixed afterwards, and the server copies the workflow's definition onto the ChangeOrder, in ChangeWorkflow, when it is set. Every promotion of the ChangeOrder, and every publish for it, is judged against that copy, so editing the workflow later changes nothing about a rollout already under way. A ChangeOrder with no workflow is promoted ungated.

Stage records the Stage the change has reached, and becomes Completed once the last Stage satisfies the workflow's Final prerequisites. PromotionOverrides records each promotion forced past gates that did not hold: who forced it, when, into which Stage, why, and which gates failed.

Promoting and undoing

A promotion marks itself: the start Tag on the target's head before the merge, the end Tag on the revision the merge arrives at, and the ChangeOrder on every revision it creates. That makes each hop an inspectable fact and makes it undoable in one step however many revisions the range produced.

A promotion also reaches the Units a variant created for itself rather than cloning from upstream, such as a database Unit that only one variant has. They have no upstream revisions for the ChangeOrder to carry, but whatever they hold goes downstream with the rest of the Space. So promoting the variant marks them as creating a ChangeOrder marks its own Space's Units: the start Tag on the revision their downstream Units have taken, and the end Tag on their head. The variants downstream then take them under the same ChangeOrder.

Promotion is idempotent. A Unit already carrying the end Tag has taken the change, whatever has happened to it since, and is passed over — which is what lets a promotion that landed partway be run again to finish it.

Setting AbortedReason aborts a ChangeOrder: the record that the change is not coming to the Spaces still waiting for it. An aborted ChangeOrder is refused by every promotion. Undoing one requires the abort, mints a third Tag (-co-restore), and cannot be taken back once anything has been restored — so a ChangeOrder travels in one direction, and a change to promote after an undoing is a new ChangeOrder.

Where it has got to

ResolvedSpaceIDs, ReleasedSpaceIDs, RestoredSpaceIDs, ReleasedRestoredSpaceIDs and State are derived when the ChangeOrder is read, not recorded as it travels, so they cannot go stale.

State reduces them to one word: New until a Space other than its own has taken it, InProgress while some have and some have not, Resolved once every Space in scope has, and Released once every Space in scope has released what it took. AbortedReason overrides all of it with Aborted, and the undoing overrides that in turn with Restored and RestoreReleased.

All of them are queryable: ResolvedSpaceIDs ? '<space-id>' asks whether one Space has it, LEN(ResolvedSpaceIDs) how many do.

Deleting a ChangeOrder takes its marks back — it is removed from every revision carrying it, and its own Tags are removed and deleted. A boundary Tag it only adopted is never touched.

See tracking and promoting changes for the workflow, and ChangeWorkflow for the definition that gives a ChangeOrder its stages.