Bitspark constellation
proposed source ↗

ADR 0010 — the three coupling planes: live release metadata / a pinned verifier protocol / actuators on a release train

  • Status: proposed
  • Date: 2026-06-07
  • Refines: 0006 §5 (the wiring-SHA pin + conformance gate) and 0009 (the atlas axis of the pin trinity)
  • Relates: 0008 (the BOM / channels / verdict the gate reads), 0005 §4 (the repository_dispatch fan-out the actuators ride)
  • Tracking: epic #306; leaves #310 (gate split), #312 (verifier-protocol bundle), #322 (receiver/scaffold release-train); supersedes the "pin the whole atlas commit" reading of the #147 trust base

A consumer pins "the atlas commit," but that one commit is four unrelated things at once — the verifier code that decides substrate check, the live release data it checks against, the PR-generating actuators that re-pin members, and the workflow scaffolds they vendor. So any atlas change a consumer needs ripples a fleet-wide "bump the SHA + re-adopt everyone" wave: in one working session we did that wave three times — once for a gate fix, then again because adding a new receiver to the scaffold set made the conformance gate demand every member carry it. The pin granularity is wrong. The fix is to stop pinning the repo and start pinning the smallest stable contract, splitting the four things into three planes that each move on their own cadence.

Context

0006 made substrate co-resolution continuous: a member tags → the producer emits facts → the atlas listener imports + advances edge → a fan-out wakes each subscriber's receiver to re-pin its lanes. 0008 gave that loop its data model (the immutable BOM, the moving channel pointers, the six-leg verdict). 0009 named the three version/toolchain pins a repo runs against (design / atlas / substrate). All of that is the load-bearing, now-running core and is not in question here.

What is unsettled is the granularity and cadence of what a consumer pins to the coordinator itself. Today a member's wiring pins atlas by an exact commit SHA — correctly, for the checker code (#147: fetching a moving atlas ref would let a later atlas commit, or a branch-tip compromise, silently change what passes under every member). The "pin the executor, stream the data" split we landed this week (0008; the live-BOM gate) already pried the data loose from that pin so a member can adopt a release newer than its wiring. But the SHA still pins, in one undifferentiated commit, everything else atlas is:

  • the verifier code — the small logic that decides substrate check pass/fail;
  • the live release databom.json, the channel pointers, the release objects, the exception ledger, the proof digests (already streamed live, per the split above);
  • the PR-generating actuators — the wiring/pin/scaffold/channel receivers (0006 §3.4/§5) that open mechanical re-pin PRs;
  • the workflow scaffolds — the vendored caller/receiver files the conformance gate (0006 §5) lints byte-for-byte.

Because these share one pin, they share one cadence: whichever one changes, the whole pin moves, and every consumer must re-adopt. That is the churn cost the as-built assessment (0004 §4) singled out — three fleet-wide re-adopt waves in one session — and it is structurally identical to the rotting-convention failure 0001/0004 exist to kill, displaced one level up: the contract a consumer holds is not "protocol v1 plus capabilities" but "whatever atlas main currently is," which drifts on every commit.

The second symptom is more pointed: adding a new receiver to the scaffold set made the substrate-safety gate demand fleet-wide scaffold adoption. That is a category error. Whether a member has the newest atlas-managed workflow file has nothing to do with whether its current lane pins are safe against its channel's current release. Two unrelated control loops were fused into one blocking check, so maintenance drift could red a member that was, on the only question that matters, correct.

The mature-system pattern (TUF, GitHub Actions pinning guidance, update systems generally; 0004 §1/§4) is to separate execution trust, protocol compatibility, data freshness, and actuator rollout into distinct layers, each pinned at its own granularity and moving on its own cadence. The membership test holds (this spans every member and atlas, with no single-repo owner), so the model belongs here — as a durable ADR, not as the implicit shape of whatever the gate currently does. This is that record.

Decision

Stop coupling a consumer to the atlas repository. Couple it to three planes, each with its own pin granularity, change cadence, and trust class. The blocking substrate gate answers one question and only that one; everything else is data the gate streams, or maintenance that runs as a separate loop.

Plane A — live release metadata (streamed, not pinned)

The BOM, the release objects, the channel pointers, and the proof digests (0008). The gate reads these live from atlas's default branch; a consumer pins nothing here except the trusted-metadata root / policy epoch (the catalogEpoch trust root, 0008). This is the split we already landed — the gate fetches the checker at a SHA but the data live — generalized into a named plane.

  • Cadence: continuous. A new release or a channel advance is visible to every member's next gate run without any pin moving.
  • Trust class: authenticated, versioned, append-only metadata — not casual data. The correction the as-built assessment made to our own framing (0004 §1) is load-bearing here: the live metadata can decide pass/fail (it names the target release, catalog identities, exception policy, deprecation/yank state, eventually proof requirements), so it is not "trusted code over inert data" — it is pinned verifier over authenticated live metadata. The data therefore carries update-system properties (schema version, freshness/validUntil, monotonic channel sequence, the immutability gate) and the gate logs the concrete data commit it evaluated, so "live" stays reproducible and auditable after the fact.
  • Anything that changes "what the checker is allowed to believe" — catalog identity, typeIdentityCritical, skew-exception rules, epoch transitions, leg-required-ness — is epoch-scoped, not casual data. It moves on a stricter cadence than release facts and is governed by the catalogEpoch discipline, extending that pattern from bootstrap to a standing trust-class boundary. (Implementation of the compatibility declaration — minGateProtocol + requiredCapabilities — is leaf #312.)

Plane B — the pinned verifier protocol (SHA-pinned, rarely)

The smallest code bundle that decides substrate check pass/fail. This is the only thing a consumer pins by exact SHA, and it pins a gate bundle, not the whole atlas repo. It is backward-compatible across many Plane-A data changes, so it changes rarely.

  • Cadence: monthly/quarterly, or an emergency security release. A Plane-A data change must not require a Plane-B pin move; that decoupling is the whole point.

  • Trust class: execution trust — the highest. It decides admission, so it stays SHA-pinned (#147's requirement is retained, narrowed from "the atlas commit" to "the verifier bundle"). We do not make the admission checker a live/moving ref by default: a compromised moving ref executes new code inside every consumer. (A moving ref is reserved for Plane C, below, where the output is a PR, not the final admission decision.)

  • The pin contract is protocol:N + capabilities, not a bare SHA. The member's wiring declares the protocol it speaks and the capabilities it implements:

    atlas:
      substrateGate:
        protocol: 1
        ref: 2f4c…            # exact commit / bundle digest — execution trust
        capabilities:
          - bom.v1
          - channel.v1
          - typeIdentityCriticalSkew.v1
          - publisherClosure.v1
    

    and the live Plane-A data declares the minimum it requires:

    "compatibility": {
      "minGateProtocol": 1,
      "requiredCapabilities": ["bom.v1", "channel.v1", "publisherClosure.v1"]
    }
    

    A newer checker accepts older data. An older checker accepts newer data only if the data requires no new critical capability it lacks — new optional fields are ignored (forward compatibility), new critical fields are declared in requiredCapabilities and an old checker fails closed against them (no silent weakening). The pin a consumer holds is thus a small, stable ABI, not a snapshot of everything atlas happens to be. (Design-level here; built in leaf #312.)

Plane C — PR-generating actuators & scaffolds (a release train)

The wiring/pin/scaffold/channel receivers and the vendored workflow scaffolds (0006 §3.4/§5). They are categorically different from the verifier: they produce PRs; they are never the final admission decision. That decision is always Plane B, gated on the consumer's own check. So they move on a slower release train with a deprecation window, not a per-commit fleet bump.

  • Cadence: release train. A worked example:

    Week 1: atlas publishes receiver bundle 2026.06; a drift report opens per-member PRs.
    Week 2–3: members auto-adopt the new bundle when their own gate is green.
    Week 4: atlas marks the older bundle deprecated.
    Week 6: the gate begins failing ONLY IF the old bundle lacks a required SAFETY capability.
    

    So a member that lags the newest scaffold is not red — it is merely "self-heals less well for now," surfaced as a drift-report PR, never a blocked build, until a deprecation window closes on a genuinely safety-relevant capability.

  • Pin granularity & the credential/trust tradeoff. Two acceptable shapes, traded off explicitly:

    • exact SHA with scheduled fleet waves — same execution-trust guarantee as Plane B, more fan-out toil; or
    • a protected moving ref (receiver/v1, scaffold/2026.06) — far less toil, but a moving ref means a compromised coordinator branch/tag executes new code in every consumer. This is tolerable for PR-generating workflows iff they run with least-privilege tokens (a reusable workflow can only receive downgraded GITHUB_TOKEN permissions from the caller, so narrow caller permissions: blocks bound the blast radius) and the final state is always gated by the SHA-pinned Plane-B verifier on the consumer's own check.

    For a small private org the protected-ref shape is reasonable for Plane C; it is not acceptable for Plane B. The rule: pin a tiny verifier ABI by SHA (Plane B); make almost everything else data (Plane A), policy, or an actuator release train (Plane C).

The invariant: scaffold/maintenance drift must NOT block substrate safety

The blocking substrate gate answers exactly one question:

"Is this repo safe relative to its declared channel and the current release metadata?" — i.e. do its real lane pins (go.mod / Cargo.toml / package.json) co-resolve to its channel's current release, with no forbidden skew?

It does not answer "does this repo carry every newest atlas-managed workflow file?" Those are separate control loops and must be checked separately (leaf #310):

  • blocking substrate gate: lane pins, resolver truth, release metadata, skew rules (Plane A data + Plane B verifier);
  • non-blocking maintenance drift: scaffold version, receiver-bundle version, wiring freshness (Plane C) — reported, PR-opened, never a hard red on its own;
  • blocking only after a deprecation window: a Plane-B gate protocol below the supported minimum, or a Plane-C bundle missing a required safety capability past its window.

A missing new receiver means a member self-heals less well; it does not mean its current lane pins are unsafe. Fusing the two — which is the bug that forced the second re-adopt wave — is the coupling smell this invariant outlaws.

Relationship to ADR 0009 and the #147 trust base

0009 names the three version/toolchain axes a repo pins (design / atlas / substrate) and unifies their declaration in one lock. This ADR is orthogonal and complementary: it does not add a fourth axis — it decomposes 0009's "atlas" axis into the three coupling planes, so "which atlas does repo X run?" stops meaning "which whole atlas commit" and starts meaning "which verifier protocol (Plane B, pinned), reading which live data (Plane A, streamed), with which actuator bundle (Plane C, on a train)." 0009's lock is where a member's Plane-B protocol:N/ref and Plane-C bundle ref are recorded and drift-gated; this ADR is the model that lock encodes.

#147's requirement — never check against a moving checker ref — is retained in full for Plane B. What changes is its scope: it pinned "the atlas commit," which over-coupled the data, actuators, and scaffolds along with the checker. Here it pins only the verifier bundle, which is the thing whose moving would actually be a supply-chain hole. Planes A and C are deliberately not SHA-pinned to the consumer (A is authenticated live metadata; C is a gated, least-privilege actuator train), because their trust is established by other means (immutability/epoch for A; final Plane-B gating + scoped tokens for C).

Consequences

  • Control-plane evolution stops rippling a version bump into every consumer. A Plane-A data change (the common case — a release, a channel move) needs no consumer pin move at all. A Plane-C scaffold/receiver change rolls on a train, not a same-day fleet wave. Only a Plane-B protocol change moves the SHA every consumer holds, and that is rare by construction. The three-waves-in-a-session churn is structurally removed, not merely tolerated.
  • "Safe" and "current" become different questions with different answers. A member can be safe (Plane A/B green) while behind on scaffolds (Plane C drift) — and that is now a legal, visible, non-blocking state instead of a red build. This also disentangles "release blocked" from "adoption blocked" from "scaffold stale" in any dashboard.
  • A real, owned migration cost. Splitting one pin into three planes means defining a gate protocol + capability vocabulary (Plane B), a compatibility block in the live data (Plane A), and a receiver/scaffold bundle with a deprecation policy (Plane C) — the three leaves #312/#310/#322. The value of writing the model down first is that those three are built coherent — pinning a small ABI, streaming authenticated data, training actuators — instead of each re-deriving "what should a consumer pin?" and drifting back toward "whatever atlas main wants."
  • The trust boundary gets more explicit, not looser. Streaming Plane A is only safe because the data carries update-system properties and the policy-bearing subset is epoch-scoped; moving Plane C to a protected ref is only safe because the final decision stays on the SHA-pinned Plane-B verifier and the actuators run least-privilege. The ADR records why each plane's looser pin is sound, so a later "just make it all live" shortcut has to argue against a stated boundary rather than slide past an unstated one.
  • It depends on the unbuilt leaves, by design. This ADR is the design-of-record under epic #306; it sequences #310/#312/#322 against the three-plane model rather than pre-empting them. Its value is the durable contract that keeps the three implementations from decaying back into a single over-loaded pin.

Alternatives considered

  • Accept the churn as the price of pinning. Keep pinning the whole atlas commit and just run the re-adopt wave whenever atlas changes. Rejected: the wave fired three times in one session for changes (a gate fix, a new receiver) that had nothing to do with substrate safety. The cost scales with atlas's commit rate × the member count; for a small team "make it not rot with minimal babysitting," that is exactly the toil the constellation exists to avoid.
  • Make the checker code live too (a moving v1 tag / protected branch for Plane B). Tempting for symmetry with the live-data split. Rejected for the admission gate: a moving checker ref means a compromised coordinator branch/tag executes new pass/fail logic inside every consumer — the precise #147 hole. A moving ref is acceptable only where the output is a PR gated by something else (Plane C), never where it is the admission decision.
  • Capability negotiation without any pin (pure protocol handshake). Let the live data and the checker negotiate purely via requiredCapabilities, dropping the SHA entirely. Rejected as the whole answer: capabilities govern compatibility, but execution trust still needs an immutable identity for the code that runs — so Plane B is protocol:N + capabilities over a SHA-pinned bundle, not capabilities instead of a pin.
  • One coarser channel-style pin for all of atlas (a single atlas/stable ref). A single moving pointer for the whole coordinator. Rejected: it re-fuses the four things into one cadence (now a moving one), so a data change still drags the actuators and a scaffold change still drags the verifier — the original coupling, with a worse trust model. The point is more seams, not one blurrier one.
  • Fold scaffold freshness into the substrate gate (the status quo). Rejected explicitly: it is the bug that forced the second re-adopt wave. "Has the newest workflow file" and "are the lane pins safe" are different questions; conflating them lets maintenance drift red a member that is safe on the only axis the gate exists to guard.

The work that realizes this ADR

Lands as the leaves of epic #306, built against the three-plane model from the start (cross-linked here so the model and its implementation stay coherent):

  • Split the member gate#310: separate the blocking substrate-safety check (lane pins co-resolve to the channel's current release) from non-blocking maintenance drift (scaffold/receiver/wiring freshness); enforce the "safety vs drift" invariant above.
  • Pin a verifier-protocol bundle, not the atlas commit#312: the Plane-B protocol:N + capabilities pin contract and the Plane-A compatibility (minGateProtocol + requiredCapabilities) declaration, with fail-closed handling of unknown critical capabilities; record the resolved Plane-B pin in the 0009 lock.
  • Receiver/scaffold release-train#322: the Plane-C bundle, its deprecation window, drift-report PRs, auto-adopt-when-green, and the protected-receiver/vN-ref vs SHA-with-waves credential/trust tradeoff.

The Bitspark constellation — how the systems are built and relate.

GitHub