feat(cd): add reconciliation subject and events - #325
Conversation
A reconciliation is a convergence between a declared desired state and the observed state of a target. It is what a controller performs when it brings a target into line with a revision it has been told to converge on. This came from a DataOps request in which a schema change reached production through a GitOps controller, and nothing in the event stream said the controller had done anything. environment.modified reports that a mutation occurred but carries none of the relation that defines a control loop: which desired state was being converged to, whether the target had drifted from it, and whether the convergence closed the gap. Without that a consumer cannot distinguish a target that converged cleanly from one that was changed by hand, nor tell which revision the running state corresponds to. The subject is defined over the declared-versus-observed relation generally rather than over cluster convergence specifically. A controller reconciling a cluster to a commit and a process reconciling a data store to a declared contract are the same relation. Two predicates: started and finished. There is no queued predicate, since controllers converge continuously rather than accepting discrete jobs into a queue. A cycle that finds nothing to do emits nothing, which keeps the subject an occurrence model and avoids turning a continuously polling controller into a heartbeat. Event versions start at 0.1.0-draft. Signed-off-by: Dadisi Sanyika <dadisi@solduara.com>
|
Why does reconciliation need to be its own subject with its own event types, rather than a property/object that decorates whatever event a producer already emits? The scope section says this applies generally, cluster convergence and "a process reconciling a data store to a declared contract," both in scope. But a dedicated subject only fits producers whose occurrence naturally looks like this one. A reconciliation object (desiredState, driftDetected, changed) attached to whatever event a given producer already emits seems closer to "generally applicable" than a new subject every convergence-capable system has to adopt wholesale. Counter case I'd want addressed: if it's a property instead of a subject, finding "every convergence that happened" requires already knowing which host event types might carry it, but the PR's own scope argument is that the set of host producers is open ended and host agnostic by design. Those two claims pull against each other: the more general the concept, the less a property-based design lets you query for it without enumerating every producer's native event shape in advance. A dedicated type doesn't have that problem. So: was a property based design considered, and if so, why was a new subject chosen over that? |
|
Thanks @xibz, that is the right question, and you've already worked out most of the answer yourself. Yes, a property-based design was considered; it was the first shape we looked at. Two reasons it lost, and your counter-case is the second of them. A convergence has a lifecycle. A third consequence turned up while we were working through it: a decoration fires when its host fires, not when a convergence happens. That takes the emit-on-change decision out of the producer's hands; a continuously polling controller would emit whenever its native event fires, which is the heartbeat problem arriving through a different door. On your counter-case: you put it as a tension between two of the PR's own claims, which is sharper than the form we had. We had reasoned about reachability, but not noticed that it pulls directly against the scope argument, and that framing is the more useful one, because it makes the property design fail on generality, which is the ground it looked strongest on. On why the description doesn't say any of this: it carried the shorter argument instead, that the narrow reading describes work an orchestration subject already partly covers. Both arguments are true and they land in the same place, and that one gets there in a sentence. You asked for the fuller version, which is what's above. |
|
Grounding this against spec.md directly changes the read on it. "id (subject)" is a REQUIRED, MUST-level rule: "Subsequent events associated to the same subject MUST use the same subject id." That's not something I'm inferring about the spec, it's the actual mechanism that already ties build.started/build.finished, the pipelineRun triad, and the taskRun triad together into single occurrence lifecycles, independent of links entirely. Checked against that, I think the argument here rests on a false dichotomy: a new subject vs. a bare decoration on some undisciplined host event. Every supporting point holds only for the second case, and the actual alternative on the table was the third one, fields on an existing job shaped subject's own started/finished pair, which already has the discipline being described.
So once each supporting argument is checked against what's actually normative in the spec, three of the four don't hold up, and the one that does doesn't answer the question it's being used to answer. That leaves the same open question as before: what does a dedicated reconciliation pair get you that fields on an existing started/finished pair, correlated the way the spec already requires, doesn't? |
Changes
Adds a
reconciliationsubject to the Continuous Deployment vocabulary, withstartedandfinishedpredicates.A
reconciliationis a convergence between a declared desired state and the observed state of a target. It is what a controller performs when it brings a target into line with a revision it has been told to converge on.Why a new subject
This came from a DataOps request in which a database schema change reached production through a GitOps controller, and nothing in the event stream said the controller had done anything.
environment.modifiedreports that a mutation occurred. It carries none of the relation that defines a control loop — what desired state was being converged to, whether the target had drifted from it, and whether the convergence closed the gap. Without that, a consumer cannot distinguish a target that converged cleanly from one that was mutated by hand, and cannot tell which revision the running state corresponds to.The occurrence-worthy moments are that a controller began converging toward a desired state, and that it finished.
Scope
The subject is defined over the declared-versus-observed relation generally, not over cluster convergence specifically. A controller reconciling a cluster to a commit and a process reconciling a data store to a declared contract are the same relation, and both are in scope.
This is the deliberate choice between the two readings. The narrower one — cluster and GitOps convergence only — describes work that an orchestration subject already partly covers, and would invite the question of why
taskRunis not sufficient. The general reading describes a drift relation that no existing subject expresses, which is what justifies a distinct subject.Design decisions
No
queuedpredicate. Controllers converge continuously rather than accepting discrete jobs into a queue, so there is no queueing moment to report.A cycle that finds nothing to do emits nothing. A
reconciliationis emitted when a convergence occurs — when observed state differs from desired and the controller acts.Defining an outcome value for the no-op case was considered and not taken. It would invite producers to emit on every poll, which turns an occurrence stream into a heartbeat, and a large estate would then produce continuous events reporting that nothing happened. A convergence that was expected and did not occur is found by querying for the event that is missing, not by emitting every non-event so that one can be noticed absent.
targetcarries atypealongsideidandsource, so consumers can route on the kind of target converged, and so target references are shaped consistently across subjects.typeis a free string —cluster,namespace,environment— rather than a closed enum, so the subject is not coupled to a fixed list of convergeable things.driftDetectedandchangedare separate. Drift is observed at the start of a convergence; whether the convergence applied any change is known only at the end. Collapsing them would lose the case where drift was detected but the convergence made no change.No iteration or attempt identifier. Each convergence is its own occurrence with its own subject id. Numbering iterations asks the producer to carry correlation state it would have to compute. This is also not specific to
reconciliation; if attempt identity is wanted, it belongs in the context for every subject.Out of scope
Notes on this change
Event versions start at
0.1.0-draft.schemas/_defs/entries andx-cdevents-semanticsare not included here. The predicate definitions this subject would need (started,finished) overlap with those already being added by #319 and with further subjects we are proposing; 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:
0.1.0-draft; the spec version is unchanged