docs: chapter 40, composition - #62
Closed
ExtraToast wants to merge 1 commit into
Closed
Conversation
Composition turns many independently-published declarations into the single global view layer 2 needs. Seven properties in this specification cannot be evaluated against one repository: Service Id uniqueness, hostname label uniqueness, the reconcile DAG, the inbound derivations, the reader set of a secret path, reachability completeness, and co-test membership. No service knows its own consumers, which is the whole argument. REFINES ADR-0015 and resolves open item 5. The unit of publication is a repository, not a domain, and a fragment declares which domains it contributes to. So homelab-collections may stay one repository publishing one fragment for five domains, or split into five publishing one each -- composition behaves identically. The split becomes a convenience rather than a prerequisite, and no decision is needed. Found the concrete justification for making the lock an output, in the tree rather than by argument: an artefact cannot contain its own digest. homelab-inventory's published context-manifest.yml ships with packageDigest: "" because the digest does not exist until the push completes, and the workflow calls oras resolve after oras push to learn it. A design where each repository pinned its peers would require every fragment to know digests unknowable at authoring time. Also copies that repository's two-hash discipline. sourceSha is the git commit, for provenance. inputsSha covers authored inputs only and never generated outputs -- homelab-inventory derives inventorySourceSha from inventory, catalog, vault and providers and never from the generated context tree, and the value is identical in both published manifests. A hash including generated output would change on every publish and detect nothing. Composition is order-independent, and that forces a design consequence rather than being a nicety: every merge must be commutative, so every collision is an error rather than a last-write-wins merge. There is no precedence between fragments and no fragment can override another. The normative content is 22 estate-wide invariants across four groups -- identity, references, secrets, completeness -- each with an error code. Composition fails on any and produces no ComposedIntent, so nothing renders. Two are worth calling out. Optional edges are excluded from the cycle check deliberately: required: false means a workload starts without its peer, so a cycle through optional edges cannot deadlock a rollout. And E_ROLL_AFFECTS_OTHER_READERS is the check nothing in the estate has today, with a live case motivating it -- secret/platform/observability holds the Prometheus token, the Discord webhook and the Grafana client secret, and one CronJob rolls one of those keys. E_PARTICIPANT_MISSING is not pedantry. Flux prunes, so a domain that fails to publish is not merely absent from the render, it is deleted from the cluster by a render that looks entirely valid. The composition lock generalises cluster-composition-lock from two pinned contexts to N pinned fragments, inheriting lockChain so "when did this fragment's digest change, and which render did that produce" is answerable without diffing published artefacts. Four open items, the first of which matters soonest: "on release" is underspecified, because a service repository releases when its image does, so an intent-only change could sit unpublished behind a staleness window.
Contributor
Author
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Chapter 40. Stacked on #61 → #60 → #59 → #58; each base retargets as the one below merges.
Composition turns many independently-published declarations into the single global view layer 2 needs. Seven properties cannot be evaluated against one repository — Service Id uniqueness, hostname label uniqueness, the reconcile DAG, the inbound derivations, the reader set of a secret path, reachability completeness, and co-test membership. No Service knows its own consumers; that is the whole argument.
Resolves open item 5, and refines ADR-0015
The unit of publication is a repository, not a domain, and a fragment declares which domains it contributes to. So
homelab-collectionsmay stay one repository publishing one fragment for five domains, or split into five — composition behaves identically. The split becomes a convenience rather than a prerequisite, and no decision is needed.The justification for "the lock is an output" was in the tree
I'd argued this from first principles before. The actual evidence is better: an artefact cannot contain its own digest.
homelab-inventory's publishedcontext/public/context-manifest.ymlships withpackageDigest: "", and the workflow callsoras resolveafteroras pushto learn the digest. A design where each repository pinned its peers would require every fragment to know digests that cannot exist at authoring time.That repository's two-hash discipline is copied for the same reason it works there:
sourceShainputsShainventorySourceShais derived frominventory/ + catalog/ + vault/ + providers/and never from the generatedcontext/tree — and is identical in both published manifests. A hash including generated output would change on every publish and detect nothing.Order-independence forces a design consequence
Composition must yield the same result regardless of pull order. That is not a nicety — it means every merge must be commutative, so every collision is an error rather than a last-write-wins merge. There is no precedence between fragments, and no fragment can override another.
The normative content: 22 invariants
Four groups — identity, references, secrets, completeness — each with an error code. Composition fails on any one and produces no
ComposedIntent, so nothing renders.Two worth calling out:
required: falsemeans a Workload starts without its peer, so a cycle through optional edges cannot deadlock a rollout.E_ROLL_AFFECTS_OTHER_READERSis the check nothing in the estate has today, and the motivating case is live:secret/platform/observabilityholds the Prometheus token, the Discord webhook and the Grafana client secret, and one CronJob rolls one of those keys.And
E_PARTICIPANT_MISSINGis not pedantry. Flux prunes — a domain that fails to publish is not merely absent from the render, it is deleted from the cluster by a render that looks entirely valid.The lock
Generalises
cluster-composition-lockfrom two pinned contexts to N pinned fragments, inheritinglockChainso "when did this fragment's digest change, and which render did that produce" is answerable without diffing published artefacts.Four open items, one of which matters soonest
"On release" is underspecified. A service repository releases when its image releases, so a change to
service.ymlalone may not cut a release — meaning an intent change could sit unpublished behind amaxAgewindow. Either intent publishes on merge independently of the image release, ormaxAgeis doing work it should not.The others: who runs composition and how often; whether the union may span clusters (the lock is cluster-keyed but Service Id uniqueness is estate-wide); and fragment signing, since composition verifies
MANIFEST.sha256per file but not provenance whiledeploy-artifact.ymlalready carriesid-token: writeandattestations: write.