Bitspark constellation
accepted source ↗

ADR 0023 — release identity is the content digest; the epoch date is an atlas-owned label

  • Status: accepted
  • Date: 2026-06-14
  • Relates: 0004 (the BOM this keys), 0006 (channels resolve a release id), 0008 (the release model), 0020 (the BOM is ground truth for tags)
  • Motivating incidents: the horos v0.1.0 registration (#559) and the stele admission (in flight) — see below
  • Source: research-docs/0001 + its external-expert advice

One thing about what a release is was true but never written down, so adding a new component to a release demanded a hand-edit of every other component's date field. It is now stated, and the accidental check that enforced the unwritten assumption is removed:

  1. A release's IDENTITY is the full SHA-256 over its canonical coordinate set. That digest is the release; it is already the verified trust boundary (verifyReleaseId).
  2. The epoch date is an ATLAS-OWNED label, the human-readable prefix of the BOM key <datePrefix>-<digest> — minted by atlas, never a fact about any published artifact.
  3. Therefore a producer does not own the epoch. A component announced with its own publish date is normalized into the active epoch at assembly, not rejected.

Why this ADR exists

ADR 0004 makes the BOM the content-addressed record of a release; ADR 0008 is the release model. What neither wrote down is the relationship between a release's identity (the content digest) and the epoch date that labels it. Because that was left implicit, the importer grew a check that treated the date as a fact every component had to agree on — and that check turned "add one new component" into "re-stamp the date on all the others."

  • The horos v0.1.0 incident (#559). horos published with its own publish date, 2026.06.13. The active release was 2026.06.06. There was no way to make horos join in place: keep the header at 2026.06.06 and import threw “horos.json declares release "2026.06.13", expected "2026.06.06"”; advance the header to 2026.06.13 and import threw the same on the first existing, unchanged component. Registration required a governance PR that hand-normalized horos's date field to 2026.06.06, leaving every coordinate exactly as emitted. It worked — but the manual edit was pure ceremony, and stele (the 8th component) hit the identical wall right after.

  • What the incident proved. No check ever matches a published artifact against the epoch. releaseFullDigest hashes only the canonical coordinate set; mintReleaseId concatenates the date as a prefix, never hashing it. The online audit probes go.version / rs.ref / ts.version / source.commit against the live registries and never sends the epoch. The only reader that compared the manifest's epoch to anything (manifestAgreement's publisher-release rule) compared it solely to release.json's header — atlas-owned metadata vs atlas-owned metadata. The date guards nothing about the artifacts. (An external expert engaged on the research-0001 question reached the same conclusion independently: manifest.release is coordinator-owned placement metadata masquerading as a producer-owned artifact fact.”)

The check that was removed

Two sites enforced the unwritten "uniform date" assumption:

  • src/commands/substrate/import.mjs loadManifests — a hard throw when m.release !== header.release. Removed. The assembled row never carries release (toComponentRow), and the minted key's date prefix already comes solely from the header, so dropping the check changes no assembled bytesimport reproduces the committed BOM byte-for-byte.
  • src/commands/substrate/verify-publisher.mjs manifestAgreement — the publisher-release rule, a blocking error. Downgraded to a non-blocking info note: a manifest whose epoch differs from the active header is expected (the producer stamped its publish date; atlas owns the active epoch), so it is surfaced for visibility but never reds the gate.

Decision

  1. The full SHA-256 over the canonical coordinate set is the release identity (already true: releaseFullDigest / verifyReleaseId; the 12-hex key suffix is a documented human label, never the trust boundary).
  2. The epoch date prefix is an atlas-owned label, minted at assembly. Producers do not own it; it is never compared against any published artifact.
  3. Drop the uniform-date equality check in import and downgrade the publisher-release re-assertion to a non-blocking note.
  4. Migration is ignore-and-normalize, NOT forbid. The publisher-manifest schema keeps release as an accepted, optional property (moved out of required) so every existing producer manifest still validates and a producer may keep stamping its publish date harmlessly. Forbidding the field (the cleaner end state) is a producer-breaking schema migration reserved for a later, separately-greenlit event-sourcing change — explicitly not authorized here.
  5. The cross-file mis-slot hygiene signal (a manifest physically in the wrong release directory) is preserved as the downgraded info note, not silently removed.

Consequences

  • + The horos/stele manual-edit class disappears. A newcomer's stale- or future-dated manifest validates and assembles with no hand-normalization; atlas stamps the active epoch.
  • + It unblocks a one-command "add a component." With the epoch equality gone, a thin atlas substrate assemble verb (a follow-up) can auto-stamp the epoch and reuse the existing re-cut machinery — "add one component" becomes a single command, not a governance hand-edit.
  • + No immutable BOM bytes change; fully reversible (re-add the throw / re-require the field). Backward-compatible: every committed manifest still carrying release: "2026.06.06" still validates and verifies green.
  • − The epoch is now decorative, which can mislead if read as a compatibility concept. It is a calendar label on an atlas assembly, nothing more; a semantic compatibility epoch (an ABI/universe number) is a separate idea, not this date.
  • This ADR sets the identity model the follow-on work builds on (the assemble verb, folding catalog identity into the digest, and the larger event-sourced-announcements decision). It does not authorize re-keying existing rows to the full 64-hex digest — that is a declined, history-rewriting change blocked by the immutability gate.

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

GitHub