Bitspark constellation
accepted source ↗

ADR 0007 — Substrate releases are atomic and convergent: one logical version per component, every dependent on it, every ecosystem published together

  • Status: accepted
  • Date: 2026-06-04
  • Refines: 0004 (the BOM + per-repo check), 0006 (continuous channels)
  • Relates: logos ADR 0005 (cross-repo private dependency resolution — the mechanism this sits on); the differential-conformance vectors (thesmos/logos/stele ADR 0006)
  • Supersedes (in part): the version-lag stance of thesmos ADR 0010 — see Relationship to ADR 0010
  • Tracking: #65

One logical version per substrate component. Every dependent uses that version. Every ecosystem (Go, Rust, npm) and every package of a component is published together from one commit — or the release is not done. Version numbers may differ across ecosystems; version lag may not.

This is the plain statement of what the substrate effort is for. Earlier records (0004, 0006) describe the machinery; this one states the invariant the machinery exists to enforce, so no repo — and no agent — mistakes per-ecosystem lag for an acceptable state.

Why this exists — the failures it forbids

Two real incidents motivate this, and they are the same disease: uncoordinated, per-ecosystem publishing.

  1. Cross-lane lag. stele links ontos at Go v0.1.4 but Rust v0.1.2 — two different logical releases of ontos in one repo. (thesmos ADR 0010 documented this and declared it permanently intentional.)
  2. npm pair inconsistency. The logos kernel published from main needs a newer logos-contract than the stale @bitspark/logos-contract@0.1.0 on npm. Go/Rust pin a git rev (056b88b) and get a consistent (contract, kernel) pair from one commit; the @bitspark npm packages lag the git HEADs and aren't co-released, so a TS consumer cannot assemble a consistent pair from the registry at all.

Git-rev pinning (Go/Rust) accidentally hides the disease — one commit yields a consistent set. npm exposes it — each package is versioned and published independently. The cure is to make the property git-rev pinning gives for free — one commit ⇒ one consistent set — an explicit, enforced invariant for every ecosystem.

Decision

1. One logical release per component

Each substrate component (ontos, logos-contract, logos-kernel, thesmos, …) has, at any time, one current logical release = one upstream commit. A component is never simultaneously "at v0.1.4" in one place and "v0.1.2" in another.

2. Releases are atomic across ecosystems and packages

A release publishes every package of the component to every ecosystem (Go module, Rust crate, npm package) from that one commit, together. A release is not "done" until all ecosystems carry the consistent set. No ecosystem may lag — npm publication is part of the release, gated, never a later catch-up.

3. Dependents converge on the release

Every dependent pins that one logical release — each in its own ecosystem's coordinate. Because cargo permits only one version of a crate per graph (and Rust receives transitive substrate deps through logos-contract), convergence propagates in dependency-DAG order: bump the root, re-release each layer onto it, down to the leaves (ontos → logos → thesmos → stele). You cannot move a leaf's transitive pin without first moving the layer that owns it.

4. Per-ecosystem numbering is fine; lag is not

Version strings legitimately differ across ecosystems — Go semver git tags, Rust git tags/revs, npm per-package semver; there is no single number all three can hold. That is one release wearing three labels — not skew. A different logical version in one ecosystem or lane than another is the forbidden lag.

5. Consumers pin releases, never loose packages

A consumer asks for a release (or a channel that points at one) and receives the guaranteed-consistent set — e.g. (logos-contract@R, logos-kernel@R) from one commit. Pinning individual packages at "latest" per ecosystem is the anti-pattern that produced the inconsistent pair above; it is not how substrate consumers pin.

6. Conformance vectors are a transition/regression guard — not a license for lag

The byte-agreement vectors (thesmos/logos/stele ADR 0006) detect drift while a convergence is in flight and guard against regressions. They do not justify keeping cores on different logical releases indefinitely. Once everyone is on one logical release, byte-agreement is automatic (same source ⇒ same bytes) and the vectors are belt-and-suspenders — not the reason a skew is "fine."

7. Enforcement: un-adoptable AND un-producible

  • atlas gates make an inconsistent/lagging set un-adoptable: closure coherence (a component's declared deps must match the release's other rows), a registry-existence audit (a release may not cite an ecosystem coordinate that isn't actually published — this is what catches the npm lag), and the per-repo substrate check (a consumer's pins must form the release's consistent set).
  • publisher CI makes it un-producible: each component's release workflow publishes the full set to all ecosystems atomically from one commit and emits the release manifest (#109).

Both are required: atlas turns a silent break into a refused adoption; publisher CI stops the break from being produced in the first place.

Relationship to thesmos ADR 0010

thesmos ADR 0010 ("Per-core ontos version skew is intentional; the vectors enforce byte-agreement") is superseded in part:

  • Kept: version numbers differ across ecosystems (its irreducible point), and the conformance vectors enforce byte-agreement.
  • Superseded: the stance that per-core version lag (Go on a newer ontos than Rust) is permanently intentional. That lag is a deadlock artifact — thesmos accepted it only because it could not, alone, drive the cross-repo cascade (its own Context says reconciliation is "outside thesmos's control"). The substrate release train makes that cascade drivable, so the lag is reconciled, not enshrined. thesmos records this via a superseding ADR in its own log.

Consequences

  • The npm-pair and cross-lane-lag classes become impossible to adopt (gated now) and, once publisher CI lands, impossible to produce (atomic publish).
  • Convergence has a real, finite cost: a coordinated DAG-ordered cascade, and some ecosystems — npm especially — may need a catch-up publish from current source. The train sequences it.
  • The conformance vectors keep a clear, smaller job (transition + regression guard), no longer load-bearing for "is the skew OK".
  • substrate check must not over-claim: a green check proves coordinate-conformance + closure, not byte-agreement — the proof-level honesty of 0006.

Alternatives considered

  • Accept per-ecosystem lag permanently (status quo / thesmos 0010). Rejected: it is the very deadlock and the npm-pair failure this effort exists to stop; "the vectors will catch it" only covers inputs a vector happens to exercise.
  • Force one version string everywhere. Impossible — Go/Rust/npm share no version space. The invariant is one logical release, not one string.
  • Let consumers pin loose "latest" packages per ecosystem. Rejected: that is exactly how the inconsistent (contract, kernel) pair arose.

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

GitHub