0027 — release-identity vocabulary: the component release is the atomic unit; a repo tag is only a batch label
Status: accepted Date: 2026-06-17
Ratifies the release-identity question left open by atlas#507
and refines — without yet changing the machinery — the identity assumptions in
0004, 0007,
0008, 0023,
and 0025. Grounded in
research-docs/0002-polyglot-cross-repo-dependency-coherence.md and the external-expert
advice recorded beside it.
This ADR establishes vocabulary and a boundary. It deliberately does not remove the substrate's global closure; that is later, sequenced work (see Consequences → Roadmap). The closure stays in force until its replacements exist.
Context
The substrate (0008) holds the whole multi-repo, multi-language fleet to one coherent cross-repo version closure at every release. docs/ATLAS_FRICTION.md traces six recurring failure classes to a single root: an independently-released, polyglot, multi-repo system forced through one global closure, with the release-identity question never ratified — "is a release identified by its git tag, or by its per-lane manifest versions?" (atlas#507).
The acute symptom: an additive release of a multi-component repo deadlocks. When
only one component of ontos changed (ontos-data), the unchanged components
(ontos-core, ontos-codec) legitimately did not re-publish their byte-identical npm
packages — so their TS lane still recorded the old commit while the new repo tag
pointed at the new one. The one-commit rule (a per-component anti-skew check) then
refused the release: "a release's lanes must not mix two commits." The only way through
was to force-republish byte-identical code at new version numbers — exactly the wrong
long-term discipline.
The root error is a conflation of three different things under one word ("release"):
the repo tag, the per-component identity, and the per-lane coordinate. External-expert
review (research-docs/0002…) and our own analysis converge on the fix: name them
distinctly, and make the component release — not the repo tag — the atomic identity.
Decision
1. The vocabulary (ratified terms)
- Lane — one language output of a component: its Rust crate, its Go module, or its npm package. ("tri-code" components have three lanes; most components have one.)
- Lane coordinate — the native, per-ecosystem locator of a lane: a Cargo git
tag → resolvedCommit, a Gomodule@version+go.sum, an npmversion+integrity+gitHead. Lane coordinates are evidence, not identity. - Logical component version — the single human-facing version of a component (e.g.
ontos-data 0.5.0), shared across its lanes where the ecosystems permit and supplied by the component release where they do not. This is the component's identity. - Component release — the atomic identity object: one component, built from one source commit for that component release, with one logical version, all its supported lanes, and one verification verdict (for tri-code components, one byte-parity vector-suite pass). A component release is immutable and content-addressable.
- Repo batch — a repo-level git tag (e.g.
ontos v0.4.0). It is a human communication label for a set of component releases cut together. It is NOT the identity of the components inside it, and it is NOT a source identity for an unchanged component. - Wire epoch — the byte-format-compatibility identity of a serialization format: the
byte language a version emits/accepts, decoupled from the logical version. A format
carries a
writerEpoch(what it emits) and a set ofreaderEpochs(what it promises to read). Compatibility is asserted between epochs, never inferred from a semver major. - Deployed service revision — what a running service reports about itself: its git revision, the component versions it links, and the wire epochs it writes and reads.
- Environment compatibility verdict — a per-
(environment, runtime-edge)judgment that two co-deployed services exchanging a format are wire-compatible (producer.writerEpoch ∈ consumer.readerEpochs). Owned by the runtime plane, scoped to one environment.
2. The principles these terms ratify
- The component release is the atomic unit of identity and admission. A repo batch is a label over a set of component releases. Unchanged components are referenced by their prior component-release id — they are never force-republished to satisfy a repo-level tag or a one-commit rule.
- Lane coordinates are evidence; the logical component version is identity. When the ecosystems can share one version string, they should; when a lane carries an independent version line, the component release still supplies the shared identity.
- Two component categories, both legitimate:
- Lockstep component — its lanes are one artifact surface that must be published together from one commit (e.g. a contract/kernel pair; a known runtime break occurred when two such npm packages were built from different commits). The one-commit rule is correct here, scoped to the single component.
- Independent component — independently versioned within a multi-component repo
(e.g.
ontos-core/ontos-codec/ontos-data). A new release of one does not force the others to republish.
- Three gates, nested, with a strict boundary — no gate may masquerade as another:
- Gate A — build correctness (per repo): native package managers + committed
lockfiles resolve; tests pass; no type-identity-critical package is duplicated in
the resolved graph (
cargo tree -d,go list -m all, the npm lockfile/npm ls). - Gate B — component coherence (per producing repo): a component release is internally coherent — one spec, one commit, all lanes, one byte-parity verdict.
- Gate C — runtime compatibility (per environment): co-deployed services that exchange a format are wire-compatible on each declared runtime edge. The anti-patterns to reject: letting "the fleet is current" stand in for "this repo is build-safe"; letting "this repo is build-safe" stand in for "this deployment is wire-compatible"; or letting "this deployment is wire-compatible" force unrelated repos to republish.
- Gate A — build correctness (per repo): native package managers + committed
lockfiles resolve; tests pass; no type-identity-critical package is duplicated in
the resolved graph (
- Type-identity-critical dependencies use exact pins, never semver ranges (already the intent of 0008; this ratifies it as a checked rule and flags the caret-range examples still in some member docs as drift to fix).
- Wire compatibility is declared, not inferred from semver — especially pre-1.0.
At
0.x, a minor bump may break the byte format. So a format-owning repo publishes explicit compatibility declarations (writerEpoch,readerEpochs, and the vector/fuzz proof that justifies them); the runtime plane enforces them per edge.
3. Scope of this ADR
This ADR ratifies the vocabulary, the identity unit, and the three-gate boundary. It does not itself remove the global closure, rewrite the BOM, or change any workflow — those follow as sequenced steps (below), and the closure remains the live release gate until its per-component / per-environment replacements are built and proven.
Consequences
What it enables. Additive releases stop deadlocking — a changed component cuts a new component release; unchanged siblings are referenced by their prior ids. Independent repo cadence is legitimate at the model level, not a drift to be policed. The unratified identity question (atlas#507) is answered: identity is the component release; the repo tag is a batch label; lane coordinates are evidence.
What it supersedes / refines (identity only, not mechanism). The "one logical version per component, every dependent on it, every ecosystem published together, no lag" intent of 0007 and the single-closure admission model of 0004/0008 are narrowed: atomicity binds a component release, not a fleet release. The content-addressed identity of 0023 and the event model of 0025 remain, re-anchored on the component release as the event subject. The one-commit rule (0008 §3) is correct but mis-scoped today — it is a per-(lockstep-)component rule, not a per-repo or cross-repo rule.
What stays. atlas remains the generated-evidence, topology, package-identity, lint, compatibility-schema, and desired-runtime-intent layer — it is demoted from being the global build-time admission gate, not deleted. The substrate machinery keeps running as the live gate until replaced.
Roadmap (this ADR is Step 1). Sequenced so the closure is shrunk last, never
ripped out first: (1) this ADR — ratify the vocabulary; (2) component-release
manifests (changed components emit; unchanged reused by reference); (3) exact-pin +
no-duplicate lints in every consumer (Gate A); (4) a per-service runtime-metadata
endpoint (versions + wire epochs written/read); (5) a runtime-plane (pharos)
environment-compatibility view — surface-only, per declared edge (Gate C); (6) ontos
wire-epoch compatibility declarations; (7) pre-deploy admission verdict; (8) an
ordered breaking-roll planner (dual-read → switch-write → drain → retire); (9) only
then shrink the atlas global closure to a compatibility/projection tool.
Risks / things to hold. (a) The runtime plane must check only declared runtime
edges within one environment — a fleet-wide "all services on one ontos" check would
silently recreate the global closure at deploy time. (b) Pre-1.0 churn means wire-epoch
declarations must be cheap and constant, not occasional. (c) TypeScript duplicate
detection is the weakest natively (types erase at runtime) and must be a hard lockfile
check, not best-effort. (d) Concentrating runtime authority in pharos makes RBAC, audit,
dry-run, break-glass, and rollback load-bearing, not optional. (e) Because docs already
drift from policy, these rules should be machine-checked or generated, not prose.
Alternatives considered
A full menu (monorepo; git-SHA pinning without packages; a hermetic build system
(Nix/Bazel); collapsing the three language implementations to one canonical) is evaluated
in research-docs/0002-polyglot-cross-repo-dependency-coherence.md. All are ruled out by
the fixed constraints: separate repos, real packages, tri-code as an optional per-repo
property, independent release cadence. This ADR takes the remaining direction —
native package resolution per repo, a per-component coherence gate, and a per-environment
runtime-compatibility plane — and ratifies the vocabulary it requires.