Skip to content

feat(cd): add migration subject and events - #322

Open
sol-duara wants to merge 1 commit into
cdevents:mainfrom
Sol-Duara-Inc:feat/migration-subject
Open

feat(cd): add migration subject and events#322
sol-duara wants to merge 1 commit into
cdevents:mainfrom
Sol-Duara-Inc:feat/migration-subject

Conversation

@sol-duara

Copy link
Copy Markdown

Changes

Adds a migration subject to the Continuous Deployment vocabulary, with queued, started and finished predicates.

A migration is a run-to-completion, versioned change applied to a stateful resource — a database schema, the data it holds, or a configuration store.

Why a new subject

This came from a DataOps request: a GitOps-governed database schema change, where the operator needed to know from the event stream whether what had just landed could be undone.

Nothing in the current vocabulary answers that. For stateless work, recovery is a redeploy of a prior revision — another forward action that needs no declaration, because the option always exists. For persistent state it may not: dropped columns and deleted rows do not come back by re-running anything. A consumer that automates recovery, or that decides whether to page a human, needs to know which case it is at the moment the event arrives.

That property is routing-relevant rather than payload, which is the bar for a distinct subject. It cannot be inferred from a versioned run in general, and it cannot be inferred from the tool that emitted it.

reversible is the field the subject exists to carry

It is tri-state by design:

  • false — no clean reversal exists; recovery must be a new forward migration.
  • true — a clean reversal exists.
  • absent — recoverability is undeclared. Consumers MUST NOT infer that an omitted reversible means true.

Producers that can determine reversibility — tools that keep an explicit undo or rollback script alongside each change — SHOULD declare it. The schema does not make it required, because a producer that cannot determine it should not be forced to guess.

Scope

migration is scoped to stateful, persistent resources: the class where recoverability is genuinely in question. The broader alternative — any versioned forward step — was considered and not taken, because where recoverability is not in question there is nothing for a consumer to route on, and the subject would then overlap work taskRun already describes.

Design decisions

target carries a type alongside id and source, so consumers can route on the kind of resource that was changed, and so target references are shaped consistently across subjects. type is a free string — database, configStore — rather than a closed enum, so the subject is not coupled to a fixed list of storage kinds.

toVersion is required and fromVersion is optional. It was put to us that migration tools often do not expose "from" and "to" in their output, and that an adapter would be unable to fill them. The asymmetry is deliberate and answers that: a producer always knows the version it applied — that is the artifact it carries, and in most tools it is the subject id itself (V12__add_order_status). What a producer frequently does not know is the version it overwrote, which requires querying prior state. Requiring toVersion asks a producer only for what it has in hand; requiring fromVersion would turn the event into a pre-processing step.

There is no reverted predicate. A reversal, where possible, is itself a forward migration — a later occurrence with toVersion set to the earlier version. When reversible is false, migration.finished with outcome: success is a non-reversible commitment, so verification belongs before it fires. Applying a change against a temporary database in CI is a testCaseRun, not a migration.

A migration that finds nothing to apply emits nothing. No migration occurred, so there is no occurrence to report. This keeps the subject an occurrence model rather than a polling report.

No field describes a partially applied migration. Why a migration stopped where it did is answerable only from the surrounding workflow — the emitting step does not observe its siblings, and cannot report on them. Consumers needing it should read the enclosing workflow's completion.

target is singular, not an array. A single logical change spanning several stores was considered. One event covering N targets cannot report an outcome for each, and near-atomicity across stores is a claim the vocabulary cannot verify. These remain N occurrences; whether they form one logical unit is a property of the workflow that produced them.

No attempt identifier. A retry is a new occurrence with its own identity, and numbering attempts asks a producer to carry correlation state it would have to compute. This is also not specific to migration; if attempt identity is wanted, it belongs in the context for every subject.

change is reused unchanged from the existing change subject. No new common objects are introduced.

Out of scope

  • The authorization decision to run a migration. approval already covers accountable authorization decisions that gate execution. A migration gated by an approval is two occurrences.
  • Enforcement. Whether a migration is permitted to proceed is control-plane behavior. These events are the record of what happened.

Notes on this change

Event versions start at 0.1.0-draft.

schemas/_defs/ entries and x-cdevents-semantics are not included here. The predicate definitions this subject would need (queued, finished) are already being added by #319, and further subjects we are proposing would need created and modified; adding them here would collide. We will follow up with them once #319 lands.

Submitter Checklist

As the author of this PR, please check off the items in this checklist:

  • Has the primer doc been updated if a design decision is involved — no new concept is introduced; this applies existing vocabulary patterns
  • Have the JSON schemas been updated if the specification changed
  • Has spec version and event versions been updated according to the versioning policynew events start at 0.1.0-draft; the spec version is unchanged
  • Meets the CDEvents contributor standards

A migration is a versioned change applied to a stateful resource - a
database schema, the data it holds, or a configuration store.

What distinguishes it from other versioned work is that undoing it may
not be possible. For a service, recovery is a deployment of an earlier
artifact: another forward action, always available. For stored state it
may not be, since dropped columns and deleted rows do not come back by
running the same step again. A consumer that automates recovery, or that
decides whether to page a human, needs to know which case it is at the
moment the event arrives, and that cannot be inferred from a versioned
run in general nor from the tool that emitted it.

reversible declares that per occurrence. When absent, reversibility is
undeclared and consumers MUST NOT read the absence as true.

Three predicates: queued, started, finished. A reversal, where possible,
is itself a later migration carrying the earlier version in toVersion,
so no reverted predicate is defined. A migration that finds nothing to
apply emits nothing, keeping the subject an occurrence model.

toVersion is required and fromVersion is optional: a producer always
knows the version it applied, but frequently does not know the version
it overwrote without querying prior state.

The change object is reused from the existing change subject. Event
versions start at 0.1.0-draft.

Signed-off-by: Dadisi Sanyika <dadisi@solduara.com>
@sol-duara
sol-duara requested a review from a team as a code owner July 29, 2026 18:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant