Bitspark constellation
proposed source ↗

ADR 0011 — the constellation is a generated evidence ledger: collect / normalize / compare / report, never invent

  • Status: proposed
  • Date: 2026-06-07
  • Refines: 0006 §4 (derive the participant set; the participation/conformance dashboard; generated onboarding docs) and 0008 (the version-side ledger — the constellation release + the signed channel pointer)
  • Relates: 0005 (the generated views the topology/constellation.json structure graph already drives), 0009 (the per-repo lock the version axis reads), 0010 (the safe-vs-current split this ledger renders per member)
  • Tracking: epic #353; leaves #355 (the collectstate.json + dashboard.md), #358 (the severity-aware violations ledger); builds on the shipped #356 (generated cross-repo prose snippets) and #357/#359 (the owned/expiring onboarding-exception ledger); folds in #170 (repo→catalog completeness) as Phase 2

The file is named constellation but it is not the constellation. topology/constellation.json is a small, hand-authored structure graph. The family's actual current state — who is a member, what version each runs, whether declared edges match real manifests, whether docs are fresh, what is in violation — lives nowhere as data: it is scattered across prose that rots. This session an architecture review plus a drift hunt found five hand-authored cross-repo assertions already stale (member rosters, stack order, schema docs, a half-onboarded horos). The fix is not "write the prose more carefully." It is to make every cross-repo list, matrix, status page, and onboarding doc exactly one of: a generated fact, a checked invariant, or an explicit owned-and-expiring exception — and to forbid the collector from ever authoring a fact that has a natural owner elsewhere.

Context

0005 made topology/constellation.json the source of truth for the family's structure — the systems, their layers, their declared edges — and drives topology/family.md, the diagram, and the propagation fan-out from it. 0006 and 0008 built the version axis as its own ledger — the immutable BOM, the constellation release, the signed channel pointer, the verdict. 0009 gave each member a lock that records the refs it runs against; 0010 split "safe" from "current" so a member can be green on substrate yet behind on scaffolds. All of that is load-bearing and not in question here.

What is missing is the surface that answers, in one place, the family's current state — and answers it from evidence, not memory. Today that state is reconstructed by hand whenever someone needs it: a roster typed into a README, a stack order written into prose, a "who has onboarded" note in an issue. Each such assertion is a fact with a real owner elsewhere — the member's atlas.json, its real manifests and locks, its substrate/bom.json entry, its workflow scaffold, its docs manifest — copied into prose, where the copy and the owner drift apart silently. That drift is exactly the rotting-convention failure 0001/0004 exist to kill, displaced from conventions onto the description of the family itself.

The drift hunt made the cost concrete. Five assertions were stale, but the horos case is the one that proves the model: horos was not broken. Its build was green; its pins were internally consistent. What was wrong was invisible from any prose — the substrate/bom.json entry lagged ontos's real releases (#352). No human reading any status page would have caught it, because no status page was derived from the gap between the declared BOM and the published reality. A collector that compares the BOM against real releases would have surfaced it immediately as an owned violation with a rule id and an owner — not as a mystery. The lesson is that the valuable surfaces are precisely the ones a human cannot keep correct by hand: the comparisons between what one repo declares and what another repo actually published.

0006 §4 already called for this in outline — derive the participant set, render a participation/conformance dashboard, generate the onboarding docs. 0008 already built the version-side half of the ledger. The seeds exist: atlas substrate status (#328) is the Phase-1 probe, and the catalog-completeness work (#170) is the repo→catalog comparison that surfaces #352 as a standing check. The membership test holds — this spans every member with no single-repo owner — so the model belongs here, written down as a durable contract before the collector is built, so the collector is built coherent rather than re-deriving "what may this thing invent?" each leaf. This is that record.

Decision

Make the constellation a generated evidence ledger: a collector that reads already-owned facts across the family, normalizes and compares them, and reports the result as committed data plus a rendered view. Five decisions fix its shape.

1. Keep the structure graph pure; add a generated state mirror beside it

topology/constellation.json stays the pure, small, stable structure graph it is today — systems, layers, declared edges, hand-authored and human-reviewed. We do not overload it with versions and live state. Instead, add a generated whole-family mirror at constellation/state.json: the collected current state across five axes, regenerated from evidence, never hand-edited.

  • structure — the participant set and wiring, derived from the registry + membership (not re-typed);
  • dependency-evidence — declared edges (structure graph + each member's atlas.json) compared against the real manifests/locks (go.mod / Cargo.toml / package.json);
  • substrate-version — each member's channel, coordinates, and proof level, read from substrate/bom.json + the channel pointer (0008);
  • workflow-participation — scaffold/receiver presence and freshness from the vendored workflow scaffold (0010's Plane C);
  • docs-surfaces — docs-manifest presence and freshness per member.

This honours the structure-vs-version separation 0006 drew (and 0008 deepened) rather than collapsing two ledgers back into one file. The structure graph is authored; the state mirror is derived. They sit side by side because they change on different cadences and carry different trust: one is a reviewed human decision, the other is a snapshot of reality.

2. Collect, never invent

The collector may collect / normalize / compare / report. It must never author a fact that has a natural owner elsewhere. Ownership stays exactly where it is: members own their atlas.json, their manifests and locks, their substrate/bom.json entries, their workflow scaffold, their docs manifests, their publisher facts; atlas owns the collector, the schemas, the checks, the views, and the violation policy — and nothing else.

This is the governing guardrail, and it is enforced in the data shape, not just the prose: every status carries an evidence source (the repo + path + content hash it was read from), and every failure carries a rule id + an owner + a severity. A status with no source is a bug in the collector — it means a fact was invented. A violation with no owner is unactionable by construction. The schema makes "where did this come from?" and "whose problem is this?" un-skippable.

3. Severity model: error / warning / exception

Three classes, mapped to what the discrepancy means:

  • structural-truth violation = error. A declared edge with no matching real dependency; a BOM entry that lags a published release (the horos case); a duplicate type-identity-critical instance. These are wrong, not merely stale.
  • editorial / status lag = warning. A docs manifest behind its sources; a scaffold a train behind. Real, worth surfacing, but not a structural lie.
  • intentional transition = an explicit owned-and-expiring exception. A member mid-onboarding, a deliberately-held skew. These are not violations — they are declared, owned, and dated, recorded in the skew-exception ledger (0008) and the new onboarding-exception ledger (#359).

This mirrors the split topology sync --check --lenient already draws between a hard structural mismatch and a tolerable lag, generalized from topology to the whole ledger. An exception is the family's way of saying "yes, this looks like a violation, and it is allowed until this date because this owner is doing this transition" — which is precisely how a half-onboarded horos becomes a legible, time-boxed state instead of a recurring "is this broken?" question.

4. A committed deterministic snapshot and a separate live artifact

Two surfaces, deliberately distinct:

  • constellation/state.json — committed, deterministic, gated. It contains no volatile fields — no collectedAt, no CI run URLs, no freshness-age clocks. It carries only source refs and content hashes, so the same inputs always produce the same bytes. A --check mode regenerates and diffs it; staleness is a failed check, exactly like topology gen --check and the snippet gate. This is the diffable, reviewable, PR-gated history of the family's state.
  • A live dashboard artifact — NOT committed (CI / Pages). It carries the volatile view humans want: collectedAt, CI run URLs, freshness ages, the latest live read. It is regenerated on every CI run and published, never committed, so the things that legitimately change every minute never churn a PR.

Splitting them resolves the tension directly: a committed snapshot gives reviewable, gated history; a live artifact gives a current view — and neither contaminates the other. A timestamp in the committed file would make every regeneration a diff; a git-tracked dashboard would red --check on every transient CI blip. Keeping the deterministic core timestamp-free and pushing all volatility to the uncommitted artifact is what lets both be true at once.

5. The governing rule

Every cross-repo list, matrix, status page, and onboarding doc must be exactly one of: (1) a generated fact, (2) a checked invariant, or (3) an explicit owned-and-expiring exception. A hand-typed cross-repo assertion is, by this rule, a bug to be replaced by one of the three. The shipped snippet gate (#356) is the first instance — the prose rosters are now generated-or-checked — and the onboarding-exception ledger (#359) is the second. This ADR is the contract that says every such surface eventually joins them.

Consequences

  • Drift becomes a failed check, not a memory problem. The five stale assertions this session found by hand become, under this model, things CI catches: a generated surface out of date reds --check; a structural mismatch reds as an error; an intentional transition is an exception that simply expires. The class of failure that 0001 named — conventions that rot because a human must remember to update prose — is removed for cross-repo state, not merely tolerated.
  • Ownership stays distributed while visibility becomes central. This is the load- bearing tension the "collect, never invent" guardrail resolves. The dashboard is one place to look, but it owns nothing: every fact still belongs to the repo that produces it, and the collector only reads, compares, and reports. So centralizing the view does not centralize authority — the failure mode of "the dashboard became a second source of truth that drifts from the real one" is outlawed by construction.
  • The dashboard answers the family's standing questions in one read: who exists, where each system sits, its layer, its declared-vs-real dependencies, its channel + coordinates, its proof level, its conformance, and its current violations — each with the evidence it was derived from. The horos-class problem (a BOM lagging reality) stops being invisible and becomes an owned, rule-tagged error.
  • A real, owned build cost, phased. The collector, the state.json schema with per-status source and per-failure rule/owner/severity, the --check gate, the live artifact, and the repo→catalog comparison are genuine work (#355/#358/#170). Writing the model first is what keeps each leaf from re-deriving the guardrail and drifting toward a collector that quietly invents.
  • It is a read-only collector, by design — low risk, high leverage. It mutates nothing in any member; it reads owned facts and renders. That is why it can be built incrementally behind --check without a fleet wave, and why its risk is low even as its reach is the whole family.

Alternatives considered

  • Overload topology/constellation.json with versions + live state. Put everything in the file already named "constellation." Rejected: it conflates the structure graph (authored, reviewed, stable) with the version/state ledger (derived, volatile) — exactly the separation 0006 drew and 0008 warns against. A reviewed human decision and a machine snapshot of reality have different cadences, trust classes, and edit rules; fusing them means every collector run churns the hand-authored graph and every hand edit risks the snapshot. Two ledgers, side by side.
  • Keep hand-maintained dashboards and prose. Write the rosters, matrices, and status pages by hand, more carefully. Rejected: this is precisely the drift the session just fixed — five stale assertions, including a horos gap no careful reader could have caught because it lived in the comparison between two repos, not on any one page. "Be more careful" is the failure 0001 was written to stop trusting.
  • A live-only artifact, no committed snapshot. Skip state.json; publish only the CI/Pages dashboard. Rejected: it loses the diffable, reviewable, PR-gated history of how the family's state changed. Without a committed deterministic snapshot there is nothing to --check against, so staleness stops being a gate-able failure and slides back into "someone has to notice." The live artifact is additive, not a replacement.
  • A brand-new exception mechanism. Invent a fresh format for onboarding and transition exceptions. Rejected in favour of reusing the skew-exception ledger shape (0008) — scoped, owner, reason, expiry — which is already proven, already understood, and already enforced. The onboarding-exception ledger (#359) is the same shape applied to a second transition class, not a new vocabulary to learn and lint.

The work that realizes this ADR

Lands as epic #353, phased so the read-only collector ships before any new authority is claimed:

  • Phase 1 — a dashboard with no new authority.
    • the collector#355: collect → the deterministic constellation/state.json (sourced, hashed, no timestamps) + the rendered dashboard.md, gated by --check.
    • generated prose snippets (done)#356: the cross-repo rosters become generated-or-checked, killing the hand-typed lists.
    • owned/expiring onboarding exceptions (done)#357/#359: the onboarding-exception ledger, modeled on the skew-exception ledger.
    • the violations ledger#358: the severity-aware (error/warning/exception) violation policy with rule + owner.
  • Phase 2 — repo inventory + catalog completeness#170: repo→catalog completeness (the check that surfaces the horos/#352 BOM-lag as an owned error), declared-edge vs real-manifest evidence, and duplicate type-identity detection.
  • Phase 3 — channel/verdict proof status + fan-in + live view. Proof status (resolved-graph / canary / conformance) folded into the state; a member.changed fan-in plus a scheduled refresh that closes the token-gated skip; and the live, non-committed dashboard artifact (Pages) distinct from the committed deterministic snapshot.

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

GitHub