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.
- Cross-lane lag. stele links ontos at Go
v0.1.4but Rustv0.1.2— two different logical releases of ontos in one repo. (thesmos ADR 0010 documented this and declared it permanently intentional.) - npm pair inconsistency. The logos kernel published from
mainneeds a newerlogos-contractthan the stale@bitspark/logos-contract@0.1.0on npm. Go/Rust pin a git rev (056b88b) and get a consistent(contract, kernel)pair from one commit; the@bitsparknpm 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 checkmust 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.