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 bytes — import 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