Git Storage Format
Behavioral specification for Git Storage Format
Domain¶
Problem Domain: The atom protocol defines abstract behavioral contracts
(AtomSource, AtomRegistry, AtomStore) but explicitly defers anchor
derivation, storage format, and backend-specific machinery. This
specification defines the git backend's contract — how atoms are represented,
stored, discovered, and transferred in git repositories using the gitoxide
(gix) library. It fills the "Anchor derivation" scope gap declared in
atom-transactions.md § Implications.
Model Reference: publishing-stack-layers.md — §1 (olog: Anchor, Atom-set, Revision), §2.1–2.3 (coalgebras: AtomSource, AtomRegistry, AtomStore), §3.1 (PublishSession), §3.3 (PopulateSession).
Parent Specification: atom-transactions.md — this spec is a refinement, not a replacement. All constraints in atom-transactions.md apply unconditionally. This specification adds git-specific constraints and MUST NOT contradict the parent.
Backend Contract:
atom-backend-contract.md — this
specification is the git reference instantiation of the abstract
backend contract. Appendix A there maps every [backend-*] obligation
to its discharge site in this document (or honestly names the gap);
the [backend-*] discharge notes on constraints below are the
receiving ends of that mapping.
Criticality Tier: Medium — the git backend is the reference storage implementation. Correctness failures affect supply chain integrity, but the protocol-level cryptographic guarantees (in atom-transactions.md) are the primary defense layer. This spec constrains the storage layer beneath those guarantees.
Overview¶
Registry vs Store¶
The git backend serves two distinct roles with different addressing schemes, ref layouts, and invariants:
| Property | Registry | Store |
| Purpose | Source-side: claims + publishes | Consumer-side: aggregation + consumption |
| Addressing | Human-readable by label | Machine-addressed by blake3(publish_czd) |
| Scope | Single source repository | Many sources, many registries |
| Collision-free | Labels unique per registry | publish_czd cryptographically unique globally |
| Claim per atom | Exactly one active | Multiple (from different sources/forks) |
| Anchor | One anchor (czd(charter₀)) |
Many anchors (one per ingested source) |
| Ref prefix | refs/atom/{pub,claims/pub}/.. |
refs/atom/{d,dev,claims/d}/... |
A repository MAY serve as both a registry and a store simultaneously (e.g., a project that publishes its own atoms and ingests dependencies).
Git Object Architecture¶
The git backend uses four categories of git objects:
Charter transactions — the anchor is
czd(charter₀), the coz digest of the atom-set's founding charter transaction (atom-transactions.md[charter-anchor]), not a property of any git object. The genesis commit is no longer the anchor; it remains transitively pinned by the founding charter'ssrc(a revision hash commits to its entire ancestry), but nothing selects or derives it. The git object encoding and ref layout for storing and retrieving charter transactions is the charter commit and therefs/atom/charter/d/{charter_czd}ref family, specified under Constraints below ([charter-commit-format],[charter-succession-via-prior],[charter-ref-by-czd]).Claim commits — commits with an empty tree whose
messagefield contains a claimCozMessage(JSON). The first claim for an atom is parentless; subsequent claims (key rotation) chain to the previous claim commit via theparentheader. This forms an auditable ownership history. Their identity is the claimczd.Atom commits — parentless commits whose tree contains the atom's content subtree and whose
srcextra header records the source revision. These are the atom snapshots referenced bydigin publish transactions. Their hash is reproducible given the same (tree, src) pair.Publish tags — annotated tag objects pointing at atom commits (initial publish) or at a previous publish tag (updates). The tag's
messagefield contains the publishCozMessage(JSON). Theclaimczd in the publish payload identifies the authorizing claim. Update tags form a chain: new → old → ... → atom commit. Git's tag-peeling resolves the chain to the underlying atom commit.
Filesystem Source Ingestion¶
The abstract AtomSource contract and FsSource implementation are
protocol-level concerns defined in atom-transactions.md. This section
specifies only the git-specific ingestion behavior when a
filesystem source is consumed by a git AtomStore.
When a filesystem source is ingested into a git store, the store
MUST create an atom commit from the directory content. Because no
claim or publish transactions exist, the ingested atoms are
unsigned — they are available for build consumption but do not
carry provenance guarantees. The store SHOULD mark such atoms as
unsigned to distinguish them from atoms ingested from registries.
No publish tags or claim refs are created for unsigned atoms.
Unsigned dev atoms are stored under refs/atom/dev/{anchor}/{label}
(see Ref Layout).
Constraints¶
Type Declarations¶
TYPE Anchor = Vec<u8> -- opaque bytes: czd(charter₀), backend-agnostic
TYPE ClaimCommit = ObjectId -- commit with CozMessage in message
TYPE AtomCommit = ObjectId -- deterministic, parentless commit
TYPE PublishTag = ObjectId -- annotated tag → AtomCommit or prev tag
TYPE RefPath = String -- e.g. "refs/atom/pub/mylib/0.1.0"
TYPE AtomSnapshot = {
tree: TreeId, -- content subtree (the atom's files)
author: ATOM_AUTHOR, -- constant: blank identity
committer: ATOM_AUTHOR, -- constant: same as author
timestamp: 0, -- Unix epoch zero, UTC
message: "", -- empty
extra_headers: { "src": ObjectId }, -- source revision commit hash
}
-- The hash of this commit object is the `dig` in PublishPayload.
-- Including `src` cryptographically binds the atom to its source
-- revision, enabling a quick verification that the publish payload's
-- `src` field matches the atom commit's extra header.
TYPE CharterCommitFormat = {
tree: EMPTY_TREE, -- well-known empty tree hash
parent: ∅, -- ALWAYS parentless (founding and successor)
author: ATOM_AUTHOR, -- constant: blank identity
committer: ATOM_AUTHOR, -- constant: same as author
timestamp: ATOM_TIMESTAMP, -- epoch zero
message: CozMessage(CharterPayload), -- the signed charter, JSON (typ: atom/charter)
}
-- Identity is the charter czd (czd(charter)); the founding charter's
-- czd (the one with no `prior`) IS the atom-set Anchor. Unlike claims,
-- charter commits are ALWAYS parentless: succession is carried by the
-- signed `prior` czd in the payload, never a git parent link (see
-- [charter-commit-format], [charter-succession-via-prior]).
TYPE ClaimCommitFormat = {
tree: EMPTY_TREE, -- well-known empty tree hash
parent: ∅ | ClaimCommit, -- parentless (first) or prev claim (updates)
author: ATOM_AUTHOR, -- constant: blank identity
committer: ATOM_AUTHOR, -- constant: same as author
timestamp: ATOM_TIMESTAMP, -- epoch zero
message: CozMessage(ClaimPayload), -- the signed claim, JSON
}
-- Claims form a chain: new → old → ... → first (parentless).
-- All claims have empty trees, so the chain is lightweight —
-- fetching it pulls only the claim commit objects (CozMessages).
-- The `src` field in the signed ClaimPayload ties each claim to
-- its source revision at both the cryptographic AND object hash
-- level.
TYPE PublishTagFormat = {
target: AtomCommit | PublishTag, -- atom commit (initial) or prev tag (update)
tag: String, -- git tag object `tag` header (metadata)
tagger: <real tagger>, -- the publisher
timestamp: <real timestamp>, -- time of publish
message: CozMessage(PublishPayload), -- the signed publish, JSON
}
-- The `claim` czd in the publish payload provides the cryptographic
-- binding to the authorizing claim. Claims are looked up via
-- `refs/atom/claims/d/{czd}` or `refs/atom/claims/pub/{label}`.
--
-- Publish payloads MAY include additional user-defined fields beyond
-- the required set. For example, a reproducible-build artifact hash
-- can be signed into the payload to cryptographically tie the final
-- artifact to the source. This extensibility is a property of the
-- Coz message format and the protocol's payload structure.
Constants¶
ATOM_AUTHOR = "" <> 0 +0000 -- blank identity, epoch zero (matching POC)
ATOM_TIMESTAMP = 0 -- Unix epoch zero, UTC offset +0000
ATOM_MESSAGE = "" -- empty commit message
EMPTY_TREE = 4b825dc... -- well-known empty tree ObjectId
-- Note: ATOM_AUTHOR/ATOM_TIMESTAMP apply to both atom commits AND claim
-- commits. gix creates objects at the byte level, bypassing the git CLI's
-- identity validation. See Open Question #4 for alternatives.
Invariants¶
[anchor-is-genesis] (retired 2026-07-08 — superseded by the charter
amendment): The former rule (anchor = raw bytes of the repository's
genesis commit ObjectId, discovered by walking the claim's lineage to
its oldest parentless root) is retired. The anchor is backend-agnostic:
Anchor := czd(charter₀) per atom-transactions.md [charter-anchor].
No graph-walking anchor discovery exists; the backend resolves and
verifies the founding charter instead (storage encoding: Open
Questions #6). The genesis commit survives only transitively — the
charter's src commits to its entire ancestry.
[anchor-hash-agile]: The anchor is a coz digest (czd(charter₀)),
not a git ObjectId, so the anchor does not inherit the repository's
object hash algorithm at all. Git hash agility (SHA-1 → SHA-256) is
handled by the charter, not by the backend: a re-hash rewrites history,
so continuity across it is an explicit successor charter chaining to
the founding charter (atom-transactions.md [charter-succession]);
absent succession, the re-hashed repository is a distinct atom-set. The
backend MUST treat the anchor as opaque bytes and MUST NOT rehash,
truncate, or transform them.
VERIFIED: unverified
[snapshot-deterministic]: An atom commit MUST be deterministic given
the same inputs. Given the same content tree AND the same source revision
(src), any party MUST produce an identical commit object (and therefore
identical ObjectId). This is achieved by fixing: (1) author and committer
to ATOM_AUTHOR, (2) timestamps to ATOM_TIMESTAMP, (3) commit message
to ATOM_MESSAGE, (4) no parent commits, (5) exactly one extra header
(src) containing the source revision ObjectId, (6) no GPG signatures.
The commit hash is the dig field in the publish payload.
VERIFIED: unverified
[snapshot-parentless]: An atom commit MUST have zero parents. Atoms
are detached subtrees — they carry no history. Provenance is recorded
in the src extra header and the publish payload, not in git's parent
chain. (Satisfies atom-transactions.md [atom-detached].)
VERIFIED: unverified
[snapshot-src-header]: An atom commit MUST contain exactly one extra
header: src, whose value is the hex-encoded ObjectId of the source
revision commit from which the atom's content was extracted, in the
length-disambiguated encoding [src-hash-kind-disambiguated] defines.
This header cryptographically binds the atom snapshot to its source
revision.
The src value in the atom commit MUST match the src field in the
corresponding publish payload — semantically, as the same source
revision identity; the payload's src field is raw bytes
(atom-transactions.md TYPE PublishPayload) while the commit header is
hex text, so "match" means the header's hex-decoded bytes equal the
payload's raw bytes, not literal string equality. A consumer MAY
verify this as a quick integrity check before performing full Coz
signature verification.
VERIFIED: unverified
[temporal-vector]: The atom protocol's git backend enforces a
three-point temporal ordering — the authenticity vector
(atom-transactions.md [charter-ancestry]):
charter src → claim src → publish src
Specifically: (1) the claim's payload src MUST point to a commit that
is a descendant of (or equal to) the effective charter's src
(verifiable by walking the DAG from the claim's src), AND (2) the
source revision referenced by src in the publish payload MUST be at
or after the claim's src in the repository's history (i.e., publish
src is a descendant of, or equal to, the claim's src). History
prior to charter.src is visible but unowned by the set — orphaned
unless re-claimed after the chartering point.
An atom MAY be published from the claim's src commit itself (when no
code changes are needed), but MUST NOT be published from a commit that
precedes the claim's src. This ensures that a claim establishes a
temporal floor — only content at or after the claim is publishable.
Per-publish verification: To verify provenance of a published atom,
the verifier MUST check that the publish's src is genuinely in the
repository's history and is at or after the claim's src. This uses
treeless commit-only fetching (tree:0) for efficiency.
VERIFIED: unverified
[charter-commit-format]: A charter transaction MUST be stored as a
charter commit: a commit whose tree is the well-known empty tree
(EMPTY_TREE), whose author and committer are ATOM_AUTHOR at
ATOM_TIMESTAMP, and whose commit message is the charter CozMessage
(JSON, typ: "atom/charter"). A charter commit MUST have zero git
parents — every charter commit is parentless, founding or successor
alike. The charter's identity is its czd (czd(charter)); the founding
charter's czd is the atom-set Anchor ([charter-anchor]).
This adopts the claim commit's object shape (empty tree, CozMessage in
the message, lightweight object) but deliberately NOT its git-parent
chaining. Claims chain via git parents because that parent chain IS the
ownership-history audit trail, anchored by a single tip ref; charters
instead receive one protective ref per czd ([charter-ref-by-czd]), so
garbage-collection reachability needs no parent chain, and the
authoritative succession link is the signed prior czd in the payload
([charter-succession-via-prior]). Re-encoding succession as a git
parent would duplicate that link in an unsigned channel that could
diverge from the signed truth, and — collapsed onto a single mutable tip
ref — would hide the divergent-successor fork that
[charter-succession-linear] requires consumers to detect. Succession
therefore lives in the signed payload alone.
VERIFIED: unverified
[charter-message-is-coz]: The commit message of a charter commit
MUST be a valid, complete CozMessage JSON object whose payload has
typ: "atom/charter". The message MUST be parseable as JSON and MUST
pass Coz verification. No additional text, headers, or formatting
SHOULD be present outside the CozMessage JSON. (The charter analogue of
[claim-message-is-coz].)
VERIFIED: unverified
[charter-succession-via-prior]: A succession chain MUST be walked by
following the signed prior czd of each CharterPayload — each
prior resolved to its charter commit through
refs/atom/charter/d/{prior_czd} — never by a git parent link and never
by now (which is untrusted for authority ordering,
[charter-succession-linear]). The founding charter is the unique
charter carrying no prior; its czd is the anchor. The effective
charter is the head of the chain — the charter that is no valid
charter's prior. Two charters sharing one prior value are divergent
successors: the git encoding materializes both (each has its own
charter/d/ ref), so a resolver enumerating the family observes the
set-authority fork directly and MUST fail closed per
[charter-succession-linear] rather than let a mutable pointer silently
select a winner. This encoding satisfies [charter-succession],
[charter-succession-linear], and [charter-ancestry]; it does not
restate or alter them.
VERIFIED: unverified
[charter-src-reachable]: A resolver MUST be able to obtain the
effective charter's src for the [temporal-vector] ancestry check by
resolving the effective charter commit ([charter-succession-via-prior])
and reading the src field of its signed CharterPayload — one object
read. Locating the effective charter costs at most one pass over the
refs/atom/charter/d/* family, whose size is the succession-chain length
(the count of ownership transfers), bounded and independent of the
repository's commit history; in the common no-succession case there is
exactly one charter and the cost is O(1). This is the bounded replacement
for the retired genesis-graph walk ([anchor-oldest-root]), which walked
unbounded commit ancestry.
VERIFIED: unverified
[dev-atom-unchartered-local]: A development atom built from a git
source that carries no charter yet MUST be treated as local class
(atom-model.md §5): it acquires no set anchor, because the anchor is
czd(charter₀) and no founding charter exists to supply one. For the
refs/atom/dev/{anchor}/{label}/{dev_version} ref segment, such an atom
MUST use the same well-known constant sentinel anchor as a filesystem
source (atom-transactions.md [fs-source-contract]) — an unchartered git
source is, for identity purposes, exactly as anchorless as a filesystem
source. A local-class dev atom is available for local build consumption
only; it carries no protocol trust and cannot be claimed or published
until the set is chartered, after which atoms built from
charter-descended revisions anchor to czd(charter₀) normally
([fs-ingest-transition]).
VERIFIED: unverified
[claim-detached]: A claim commit MUST have the well-known empty
tree as its tree and no extra headers. The first claim for an
atom MUST be parentless. Subsequent claims (e.g., key rotation)
MUST have the previous claim commit as their parent, forming a chain.
The claim's src is carried in the signed ClaimPayload (the commit
message), which contributes to the commit hash. This design ensures
claims are lightweight (empty tree = no blob download), the full
ownership history is structurally auditable by walking the chain, and
claim objects are isolated from the main branch DAG.
VERIFIED: unverified
[claim-message-is-coz]: The commit message of a claim commit MUST
be a valid, complete CozMessage JSON object whose payload has
typ: "atom/claim". The message MUST be parseable as JSON and MUST
pass Coz verification. No additional text, headers, or formatting
SHOULD be present outside the CozMessage JSON.
VERIFIED: unverified
[publish-tag-targets-correct]: An initial publish tag MUST target
an atom commit (a deterministic, parentless commit with a src extra
header). An update publish tag MUST target the previous publish tag for
the same (label, version). A publish tag MUST NOT target a regular
commit, tree, or blob.
VERIFIED: unverified
[publish-tag-claim-binding]: A publish tag's CozMessage payload
MUST contain a claim field whose value is the czd of the authorizing
claim. The claim commit is looked up via refs/atom/claims/d/{czd}
(or refs/atom/claims/pub/{label} in the originating registry). No
extra header is needed — the signed payload is the sole binding.
VERIFIED: unverified
[publish-tag-message-is-coz]: The message body of a publish tag
MUST be a valid, complete CozMessage JSON object whose payload has
typ: "atom/publish". The message MUST be parseable as JSON and MUST
pass Coz verification. No additional text or formatting SHOULD be
present outside the CozMessage JSON.
VERIFIED: unverified
[tag-chain-immutable]: Publish tag updates MUST be implemented by creating a new tag object that targets the previous tag object (not the atom commit directly). Git's tag-peeling mechanism resolves the chain to the underlying atom commit. The ref is updated to point to the new tip of the chain. Old tag objects MUST NOT be deleted — they persist in the git object database as an immutable audit trail.
This ensures: (a) lock files referencing old tag ObjectIds remain
resolvable, (b) the full update history is structurally represented,
(c) consumers can reconstruct the complete chain of ownership/signing
by walking the tag chain.
VERIFIED: unverified
[coz-bit-perfect]: All CozMessage JSON stored in git objects
(commit messages, tag messages) MUST be preserved bit-for-bit. The
backend MUST NOT reformat, re-serialize, or alter the JSON in any
way. (Satisfies atom-transactions.md [backend-bit-perfect];
discharges atom-backend-contract.md [backend-carriage-bit-perfect].)
VERIFIED: unverified
[odb-immutable]: The backend MUST treat the object database as
append-only for protocol objects (charter/claim/atom commits, publish
tags): a protocol object, once written, MUST NOT be mutated in place,
and MUST NOT be deleted while protocol-reachable (the protective and
protocol refs define reachability). Deletion is permitted only through
garbage collection of protocol-unreachable objects. Git's
content-addressed object store provides this by construction; the
constraint makes the reliance explicit rather than implicit.
(Discharges atom-backend-contract.md [backend-store-immutable].)
VERIFIED: unverified
[refs-sole-mutable]: Refs are the backend's ONLY mutable protocol
state. Objects are immutable ([odb-immutable]) and ancestry is
derived from hash-committed object content
([ancestry-hash-committed]), so every protocol state transition is
exactly a refs transaction. Implementations MUST NOT keep protocol
state in any side store (index files, database tables, packed
metadata) that can diverge from the refs — such state MAY exist only
as a cache reconstructible from refs + objects. This is what makes
liveness-by-reachability, rollback-by-old-ref, and GC arguments
transfer to the intent store. (Discharges atom-backend-contract.md
[backend-refs-sole-mutability].)
VERIFIED: unverified
[ancestry-hash-committed]: A git ObjectId commits to its entire
ancestry: a commit's parent OIDs are part of the hashed commit
preimage, so asserting false ancestry for an existing OID requires a
hash collision. Every ancestry-dependent check in this spec
([temporal-vector], [no-backdated-src],
[anchor-vector-authenticity]) quantifies over THIS parent relation —
the hash-committed one — never over whatever a query layer happens to
answer. (Discharges atom-backend-contract.md
[backend-ancestry-sound].)
VERIFIED: unverified
[ancestry-query-path]: Protocol ancestry queries MUST resolve
parents from hash-committed object content with object replacement
disabled (--no-replace-objects semantics): refs/replace/* and
grafts MUST NOT influence [temporal-vector], [no-backdated-src],
or [anchor-vector-authenticity] checks — replacement rewrites the
operational parent function without changing the OID, which is
exactly the divergence [ancestry-hash-committed] exists to exclude.
Ancestry verification MUST NOT be attempted against a depth-limited
(shallow) clone: a shallow boundary is indistinguishable from a true
root to a naive walk, so the check MUST fail closed rather than pass;
treeless fetching (tree:0, [temporal-vector]) is the sanctioned
minimal mode — it limits blobs, never ancestry.
VERIFIED: unverified
[czd-oid-disjoint]: The seam law's git instance
(atom-backend-contract.md [backend-seam-typed]): Czd values and
git ObjectIds are disjoint types. The only OID-typed protocol
identities are the payload fields dig and src and the atom
snapshot's src extra header; every other protocol identity (anchor,
claim czd, publish czd) is a coz digest. Implementations MUST
represent the two as distinct types such that any czd↔OID comparison
is rejected at the type level. Ref paths are typed encodings, not free
strings: the identity-bearing segments come in three families —
czd-hex (refs/atom/claims/d/{claim_czd}, the {anchor} segment of
dev refs), blake3-hex (refs/atom/d/{blake3(publish_czd)}), and
oid-hex (refs/atom/src/{oid}) — each family renders exactly one
sort, parsing MUST rehydrate the segment to that sort, and comparison
of raw segments across families is ill-typed.
VERIFIED: unverified
[oid-hash-inheritance]: dig and src inherit the repository's
object hash algorithm; under git's SHA-1 default they are NOT
collision-resistant, unlike every czd-sorted identity
([anchor-hash-agile]). This inheritance MUST be documented wherever
dig is presented as an artifact identity. The closure for dig is
the protocol-level, publisher-signed content_hash field
(atom-transactions.md [content-hash-is-tree-digest]), not any
git-specific mechanism — the git instantiation carries it bit-perfectly
like any other payload field and does nothing further. src's
inheritance is NOT closed by content_hash or by any mechanism this
specification defines: src-anchored ancestry verification
([temporal-vector]) depends on this repository's own commit-parent
graph, whose links are this repository's own object hashes, and is
irreducible without migrating this repository's history to a stronger
hash (atom-transactions.md [content-hash-obligation]'s "Known
limit"). (Registers atom-backend-contract.md [backend-hash-strength]
for the git instantiation.)
VERIFIED: unverified
[src-hash-kind-disambiguated]: The atom commit's src extra
header ([snapshot-src-header]) is a hex-encoded git ObjectId whose
byte length MUST be read as the sole, authoritative indicator of which
hash algorithm produced it: 40 hex characters denotes SHA-1, 64
denotes SHA-256. A reader MUST NOT assume a fixed algorithm (e.g.
always SHA-1) regardless of length, and MUST NOT require any other
marker — no algorithm prefix or tag is added to the header's value.
This is a minimal fix, not a new mechanism: it makes an
already-structural property (git ObjectId hex length is
algorithm-determined; gix itself dispatches object decoding by OID
length) an explicit, binding reading rule instead of an unstated
assumption, consistent with [czd-oid-disjoint]'s "each family
renders exactly one sort" precedent for identity-bearing ref-path
segments applied here to a header value instead. No untagged
ambiguity exists to migrate away from: every src header written
under this project to date is 40 hex characters (git's SHA-1 default
is this project's only backend hash kind in current use), so this
constraint has no legacy population to reconcile — it binds future
readers as of this amendment, not a transition.
Caution — do not confuse with content_hash. BLAKE3 (the
content_hash algorithm, atom-transactions.md
[content-hash-algorithm]) also produces 32-byte digests that render
as 64 hex characters, identical in length to a SHA-256 src. Length-
based disambiguation under this constraint applies ONLY within the
src header's own git-OID field family ([czd-oid-disjoint]'s
oid-hex family); content_hash is a distinctly named field in a
different structure (the PublishPayload, not a git commit header)
and MUST NOT be inferred from, or cross-checked against, src's
length or position.
VERIFIED: unverified
[single-active-claim-registry]: In a registry (source repository),
at most one active claim MUST exist per AtomId. If a claim is
replaced (e.g., key compromise), the new claim commit's parent MUST
be the previous claim commit, forming a chain. The ref
(refs/atom/claims/pub/{label}) is updated to point to the new tip.
All historical claims are reachable by walking the chain from the tip.
VERIFIED: unverified
[store-claim-disambiguation]: In a store, multiple publishes for the
same AtomId MAY coexist (from different sources/forks). The store's
ref layout MUST disambiguate them by blake3(publish_czd). Distinct
publishes produce distinct publish czds and therefore distinct flat ref
keys — disambiguation is guaranteed by the cryptographic uniqueness of
the publish czd, without coordination.
VERIFIED: unverified
Ref Layout¶
Registry Refs (source repository)¶
refs/atom/claims/pub/{label} → claim commit (tip of claim chain)
refs/atom/pub/{label}/{version} → publish tag [→ chain] → atom commit
refs/atom/src/{oid} → src commit (provenance-protected)
The claim ref is the tip of a chain: subsequent claims (key rotation)
parent to the previous claim commit. All historical claims are
reachable by walking the chain — no separate claims/d/ refs are
needed in the registry. refs/atom/src/ protects source revision
commits from GC if the originating branch is deleted.
[registry-ref-label-unique]: Within a single registry, the {label}
segment MUST be unique. No two atoms in the same registry MAY share a
label. (Satisfies atom-transactions.md [atomid-per-source-unique].)
VERIFIED: unverified
[registry-ref-claim]: The ref refs/atom/claims/pub/{label} MUST
point to the currently active claim commit for that atom. When a claim
is replaced, this ref MUST be updated to point to the new claim commit.
The old claim commit remains in the repository's history. Claim refs
are separated from atom version refs to avoid polluting the version
subtree.
VERIFIED: unverified
[registry-ref-version]: The ref refs/atom/pub/{label}/{version}
MUST point to an annotated tag object (the publish tag tip), which
either directly targets the deterministic atom commit (initial publish)
or targets a previous tag in the update chain (updates). The
{version} segment is the RawVersion string from the publish payload.
VERIFIED: unverified
Store Refs (consumer repository)¶
# Published atoms (d = digest-addressed)
refs/atom/d/{blake3(publish_czd)} → publish tag [→ chain] → atom commit
refs/atom/claims/d/{claim_czd} → claim commit (shallow-fetched)
# Development atoms (unsigned, identified by the (anchor, label) pair, versioned)
refs/atom/dev/{anchor}/{label}/{dev_version} → atom commit (no tags, no claims)
Dev versions SHOULD include the tree object hash to avoid clobbering
and ensure new dev versions are only created when content genuinely
changes. For example: {manifest_version}.dev-{tree_hash_prefix}.
The version string is opaque to the protocol — tooling MAY adopt any
scheme that guarantees uniqueness per content snapshot.
The d/ sub-prefix under claims/ denotes digest-addressed claims
(store-side). This consolidates all claim refs under a single
refs/atom/claims/ namespace while preventing collision with
registry label-addressed claims.
[store-ref-by-publish-czd]: Store version refs MUST be keyed by
blake3(publish_czd), where publish_czd is the czd of the
specific publish tag CozMessage the ref addresses — the base tag
or any amendment tag in the same update chain
(atom-transactions.md [amendment-field-classification]) — never a
single identifier fixed for the whole chain. Each tag in a chain has
its own distinct czd and therefore its own store ref: landing a new
amendment tag creates a NEW refs/atom/d/{blake3(new_publish_czd)}
ref without invalidating or rewriting any earlier tag's ref
([publish-update-transition]'s POST states this for the amendment
case directly; [store-ownership-migration], below, states the
analogous claim-replacement case). This ensures global uniqueness —
distinct tags, base or amendment, produce distinct publish czds and
therefore distinct flat ref keys — and ensures a lock pinned to any
tag in the chain, not only the base, remains resolvable for as long as
that tag's ref persists.
VERIFIED: unverified
[store-claim-ref]: Each ingested claim MUST have a corresponding
ref at refs/atom/claims/d/{claim_czd} pointing to the claim commit.
The full claim chain (including historical claims) SHOULD be fetched
from the source — since all claims have empty trees, the chain is
lightweight (only commit objects). This ref serves two purposes:
(1) protecting the claim commit from garbage collection, and
(2) enabling efficient claim retrieval by czd for local verification.
The chain also makes the full ownership history locally auditable. If
a publish tag's payload references a claim czd but no corresponding
claim commit exists in the object store, this MUST be treated as an
error.
VERIFIED: unverified
[store-ownership-migration]: If the ownership of an atom changes
(new claim), new versions published under the new claim produce new
publish czds and therefore new flat ref keys
(refs/atom/d/{blake3(new_publish_czd)}). Versions published under
the old claim remain under their original blake3(publish_czd) keys —
they are still valid artifacts signed by the old claim.
VERIFIED: unverified
[store-claim-cleanup]: When a store ref
refs/atom/d/{blake3(publish_czd)} is deleted (e.g., cache eviction),
the backend SHOULD check whether any remaining store refs reference the
same claim czd (by inspecting the publish tag payloads reachable from
surviving refs/atom/d/ refs). If no remaining store ref references
that claim czd, the backend SHOULD also delete the corresponding
refs/atom/claims/d/{claim_czd} ref to prevent orphaned claim
accumulation. Git has no cross-namespace reference counting, so this
cleanup is the backend's responsibility.
VERIFIED: unverified
Charter Refs (source and store)¶
refs/atom/charter/d/{charter_czd} → charter commit (parentless, empty tree)
A charter has no label — its identity is its czd — so a single
czd-addressed family serves both a source (registry) and a store, with
no label-addressed sibling. The founding charter (prior absent) is the
anchor czd(charter₀); successors chain via the signed prior czd, not
via git parents ([charter-succession-via-prior]). Charter commits carry
the empty tree, so the whole succession chain is lightweight to enumerate
and fetch (commit objects only), exactly as claim chains are.
[charter-ref-by-czd]: Each charter commit MUST be referenced by
refs/atom/charter/d/{charter_czd}, a czd-hex ref family (conforming to
the typed ref-path discipline of [czd-oid-disjoint]) that both protects
the charter commit from garbage collection and addresses it by czd for
CharterStore::get_charter. Enumerating refs/atom/charter/d/* yields
the candidate charter set an [anchor-resolvable] consumer verifies
against — the git instance of atom-transactions.md [charter-transition]
POST ("stored in the source's atom refs, enumerable by consumers and
retrievable by its czd"). The family is identical in a source and a
store; ingesting a set fetches its charter chain into the same family.
VERIFIED: unverified
Transitions¶
[charter-transition-git]: An atom-set MAY be chartered on the git backend by creating a parentless charter commit.
- PRE: The repository MUST have at least one commit, and the
charter's
srcMUST reference a revision that exists in it. A founding charter's payload carries noprior; a successor'spriorczd MUST resolve to an existing charter commit viarefs/atom/charter/d/{prior_czd}whoseownerauthorizes the successor's signing key ([charter-succession]). The charterCozMessageMUST be valid ([charter-message-is-coz]). Authorization of a founding charter over a source that already carries pre-charter claims is governed protocol-side by atom-transactions.md[charter-transition](the bootstrap gate); the backend enforces storage, not that authorization decision. - POST: A parentless charter commit exists with the well-known empty
tree and the charter
CozMessageas its message ([charter-commit-format]), referenced byrefs/atom/charter/d/{charter_czd}([charter-ref-by-czd]). A founding charter's czd is thereby the atom-setAnchor. The charter is enumerable and retrievable by czd, discharging atom-transactions.md[charter-transition]POST for the git backend. Multi-ref writes follow[refs-atomic-multi].VERIFIED: unverified
[claim-transition-git]: An atom MAY be claimed by creating a detached claim commit.
- PRE: The repository MUST have at least one commit. The atom-set's
founding charter MUST be resolvable and verified — the claim's
anchorfield is its czd (atom-transactions.md[claim-chains-charter]). No active claim for this label MUST exist (or the existing claim is being explicitly replaced). The claimCozMessageMUST be valid and include akeyfield. A source revision (src) for the claim point MUST be provided. - POST: A claim commit exists with the well-known empty tree and
the CozMessage (containing
src) as the commit message. If this is the first claim, the commit is parentless. If replacing an existing claim, the commit's parent is the previous claim commit (forming a chain). The refrefs/atom/claims/pub/{label}points to the new tip. A protective refrefs/atom/src/{src_oid}is written to prevent GC of the claim's source revision.VERIFIED: unverified
[publish-transition-git]: A version MAY be published for a claimed atom by creating an atom commit and annotating it with a publish tag.
- PRE: An active claim for this label MUST exist in the registry
(
refs/atom/claims/pub/{label}is set). The source revision (src) from which the atom's content is extracted MUST be at or after the claim'ssrcin the repository's history ([temporal-vector]). The atom commit MUST be deterministic per[snapshot-deterministic]. The publishCozMessageMUST reference the active claim'sczd. - POST: An atom commit exists (parentless, deterministic, with
srcextra header). A publish tag points at it with the CozMessage as the message (containing theclaimczd binding). The refrefs/atom/pub/{label}/{version}points to the publish tag. A protective refrefs/atom/src/{src_oid}is written to prevent GC of the source revision. The ref write MUST use a compare-and-swap (CAS) onrefs/atom/claims/pub/{label}to ensure the claim has not been replaced between payload construction and tag creation. (Raised from SHOULD 2026-07-12: the non-CAS write is a TOCTOU window — a publish can land against a claim state that no longer matches what the signer verified against. Discharges the per-name register law, atom-backend-contract.md[backend-refs-linearizable].)VERIFIED: unverified
[ingest-transition]: Atoms MAY be ingested from a registry (or another store) into a store.
- PRE: The source has discoverable atoms (resolvable refs). The store is a valid git repository.
- POST: For each ingested atom: the atom commit exists in the
store, the publish tag (and its chain) exists in the store, the
claim commit (and its chain of previous claims) is fetched and
referenced by
refs/atom/claims/d/{claim_czd}. Claim chains are lightweight by design (empty trees) and require no special filtering. Version refs follow the store layout:refs/atom/d/{blake3(publish_czd)}. AtomId is preserved through ingestion. Refs MUST NOT be committed until all cryptographic verification ([verification-local]) passes — objects may exist in the ODB during verification, but are invisible to consumers until the refs transaction is committed. (Satisfies atom-transactions.md[ingest-preserves-identity].)VERIFIED: unverified
[claim-replacement-transition]: An atom's active claim MAY be replaced (e.g., after key compromise).
- PRE: An active claim for this label MUST exist. The new claim
CozMessageMUST be valid. The new claim MUST reference the same(anchor, label). - POST: A new claim commit exists with the previous claim as its
parent. The ref
refs/atom/claims/pub/{label}is updated to point to the new tip of the claim chain. Future publishes MUST reference the new claim'sczdand satisfy[temporal-vector]with respect to the new claim. Existing publish tags remain valid under the old claim. The full claim history is walkable from the new tip.VERIFIED: unverified
[publish-update-transition]: A publish tag MAY be updated (e.g.,
after key revocation or resigning, to carry a reproducibility-mode
transition — promotion or demotion per atom-model.md §6 — or to append
overwrite- or append-class fields per atom-transactions.md
[amendment-field-classification]) by appending a new tag to the
chain.
- PRE: An existing publish tag chain for this
(label, version)exists. The new tag'sCozMessageMUST carry an amendment payload: the same payload shape as a base publish, but with every identity-class field (atom-transactions.md[amendment-field- classification]) structurally absent — not present and checked equal, absent. The amendment payload MUST be valid per[sig-over- pay](atom-transactions.md) and MUST set at least one overwrite- or append-class field. Authorization is per amended-field class:- A tag that sets an overwrite-class field (
[amendment-field- classification]class 2 — signing identity,mode,meta) MUST be signed by a key authorized by the base tag's claim's single owner ([claim-owner-single], under[owner-authorization- delegated]'s per-value semantics — the same check[publish-transition]'s own PRE already applies to the base payload). This authorization check is against the SIGNING key, not the declaredtmbfield: a conforming verifier MUST first bind the declaredtmbto the actual key that produced the signature —tmb(verifying_key) == pay.tmbMUST hold, the same binding atom-transactions.md's Verification Pipeline step 6 already requires for charter/claim — and only THEN evaluate claim-owner authorization against that bound key. A tag whose declaredtmbdoes not equal the thumbprint of its actual signing key MUST be rejected before any claim-owner authorization check runs: without this binding, a declaredtmbnaming the claim owner proves nothing — the envelope's ownkeyfield can belong to an unrelated signer, and the signature still verifies validly against IT, letting an attacker declaretmb = <claim owner's thumbprint>while actually signing with their own key. This is a protocol-level validity requirement: a tag that sets an overwrite-class field without both the tmb-binding check and claim-owner authorization MUST be rejected — it is not a valid amendment. This closes the gap an unauthorizedtmb/key/alg/modechange would otherwise leave open ("no undeclared key signs anything"). - A tag that sets an append-class field (class 3, a fact entry)
carries NO protocol-level signer restriction: any validly-signed
tag MAY add a fact entry, from any signer, regardless of fact-kind
tier — this is a structural validity statement, not a trust one.
Which verdict (
fact/evidence/refused) a given consumer assigns a fact entry is governed entirely by the entry's fact-kind authorization tier (atom-transactions.md[fact-kind-table]) as evaluated through trust-model.md's consumer-local acceptance policy ([trust-role-authorization], atom-transactions.md[fact-claim-owner-gated]) — fact acceptance is deliberately signer-relative ([trust-signer-relative], trust-model.md), never a chain-level accept/reject gate. - A tag MAY set fields of both classes in one amendment; the overwrite-class authorization requirement above applies regardless of any append-class content also present.
- A tag that sets an overwrite-class field (
- POST: A new tag object is created targeting the previous tag
object (not the atom commit). The ref
refs/atom/pub/{label}/{version}(registry) is updated to point to the new tip. In the store, a new flat refrefs/atom/d/{blake3(new_publish_czd)}is created pointing to the new tag; the previous refrefs/atom/d/{blake3(old_publish_czd)}persists, keeping any lock pin on the old publish_czd resolvable (consistent with[tag-chain-immutable]). The old tag object persists in the object database. Git's tag-peeling resolves the chain to the underlying atom commit.VERIFIED: unverified
[tag-chain-semantic-immutable]: The identity-class fields of a
publish payload — label, version, dig, src, path,
content_hash, anchor, and claim (atom-transactions.md
[amendment-field-classification]) — MUST be set only by the base
publish payload: the first tag in a publish's update chain, the one
targeting the atom commit directly. Every later tag in the chain
carries an amendment payload ([publish-update-transition]) whose
shape has no slot for these fields — they are structurally absent, not
present and checked equal against the base tag's value. A backend or
consumer walking the chain MUST read identity-class field values from
the base tag only, and MUST NOT read them from, or expect them present
on, any later tag. (typ is identity-class in value — it never
changes across a chain — but, uniquely, IS required and present on
every tag, base or amendment alike: it is the CozMessage
payload-family discriminator, atom-transactions.md [publish-typ],
needed to parse the message at all, not per-chain data a resolver
accumulates from the base tag.) Overwrite-class fields (signing
metadata — tmb, alg, the envelope key, now — meta, the
reproducibility mode field — chain-VARIABLE by design,
promotable/demotable via signed chain appends per atom-model.md §6 and
atom-transactions.md [publish-mode]) and append-class fields (facts,
atom-transactions.md [fact-kind-table]) MAY be set by the base tag or
by any later tag in the chain. Altering the artifact identity (dig),
version string, or source revision requires a new atom commit and a
new publish ref — not an update to an existing tag chain.
VERIFIED: unverified
[refs-atomic-multi]: Multi-ref protocol transitions (claim +
protective ref; publish tag + version ref + protective ref; multi-atom
ingestion) MUST be committed atomically within the store — all named
refs move together or none do — so no consumer can observe a torn
intermediate state. This binds the authoritative store per-store;
cross-mirror consistency is convergence, not atomicity
(atom-backend-contract.md [backend-replica-reads]). (Promoted
2026-07-12 from the Atomicity implementation guidance below to a
tagged constraint; discharges the per-store half of
atom-backend-contract.md [backend-refs-atomic-multi].)
VERIFIED: unverified
[fs-ingest-transition]: Atoms from a filesystem AtomSource MAY
be ingested into a git AtomStore without claims or publishes.
- PRE: The filesystem source satisfies the
AtomSourcecontract (as defined in atom-transactions.md). The store is a valid git AtomStore. - POST: Atom commits exist in the store for each discovered atom,
referenced by
refs/atom/dev/{anchor}/{label}/{dev_version}. No publish tags or claim refs exist for dev atoms. The store MUST treat dev atoms as unsigned/unclaimed. The dev version string SHOULD incorporate the tree object hash to prevent clobbering across concurrent evaluations. Every atom has an AtomId — git-sourced dev atoms use the set's charter anchor (czd(charter₀)); filesystem-sourced atoms use a well-known constant sentinel anchor (see atom-transactions.md[fs-source-contract]). A git source that has no charter yet is identity-equivalent to a filesystem source: its dev atoms are local-class and carry the same sentinel anchor ([dev-atom-unchartered-local]). Note: the dev atom'sdigwill inherently differ from the publisheddigbecause published atoms include a realsrcextra header — this is by design.VERIFIED: unverified
[dev-atom-resolution]: Tooling consuming atoms from a store resolves through two namespaces in order of precedence:
refs/atom/dev/{anchor}/{label}/— local development atoms (unsigned, in-progress evaluations from filesystem or local git sources)refs/atom/d/— all ingested published atoms, regardless of origin (local registry, remote registry, or mirror); each keyed flat byblake3(publish_czd)
Published atoms from the local registry are ingested into d/ via
the same [ingest-transition] as remote atoms — there is no special
treatment. The pub/ namespace is registry-write-time only; the
resolver never queries it.
Clients MAY provide a release mode which skips step 1, resolving
only from d/. This ensures builds use exactly the ingested versions,
matching what downstream consumers would see.
Forbidden States¶
[no-non-empty-claim]: A claim commit MUST have the well-known
empty tree as its tree. If a claim commit has a non-empty tree,
the backend MUST reject it as malformed. A claim's parent (if present)
MUST be another claim commit (also with empty tree).
VERIFIED: unverified
[no-orphan-publish]: A publish tag MUST NOT exist in a registry
without a corresponding claim commit reachable via
refs/atom/claims/pub/{label} or refs/atom/claims/d/{claim_czd}.
(Satisfies atom-transactions.md [no-unclaimed-publish].)
VERIFIED: unverified
[no-backdated-src]: A publish MUST NOT reference a source revision
(src) that precedes the claim's src in the repository's history.
The publish's src MUST be at or after the claim's src. This is the
enforcement mechanism for [temporal-vector].
VERIFIED: unverified
[no-label-collision-registry]: Two atoms in the same registry
MUST NOT share a label. This is enforced by the ref layout — two
claim refs for the same label would conflict.
VERIFIED: unverified
[anchor-oldest-root] (retired 2026-07-08 — superseded by the
charter amendment): The oldest-parentless-commit selection rule
existed only to make genesis-based anchor discovery deterministic.
With Anchor := czd(charter₀) there is no anchor discovery to
disambiguate; the constraint is retired along with
[anchor-is-genesis].
[no-missing-store-claim]: In a store, if a publish tag's payload
references a claim czd, the corresponding claim commit MUST exist in
the store's object database and MUST be reachable via
refs/atom/claims/d/{claim_czd}. A missing claim MUST be treated as
ingestion corruption.
VERIFIED: unverified
Behavioral Properties¶
[anchor-vector-authenticity]: Given a publish tag in a registry,
the atom MUST be verifiable as authentic by checking the vector:
(1) the claim's anchor field equals czd(charter₀) of the effective,
verified charter (atom-transactions.md [charter-anchor],
[claim-charter-authorization]), (2) the publish payload's claim czd
resolves to a claim commit whose payload src is a descendant of (or
equal to) the effective charter's src ([charter-ancestry]),
(3) the claim CozMessage in that commit's message is valid, AND
(4) the publish's src is at or after the claim's src. This creates
a "vector of authenticity": charter src → claim src → publish src.
- Type: Safety
VERIFIED: unverified
[ingestion-portable]: Atom commits, publish tag objects (and their
chains), and claim commits MUST be transferable between git
repositories via standard git pack negotiation (git fetch). No
custom transport extensions are REQUIRED. Claim commits SHOULD be
shallow-fetched (without their ancestry) into stores to minimize
transfer cost.
- Type: Safety
VERIFIED: unverified
[update-chain-auditable]: The tag chain structure (new → old → ...
→ atom commit) MUST be traversable. A consumer MUST be able to
reconstruct the full update history by walking the tag chain from the
ref tip. Each tag object in the chain carries its own CozMessage,
enabling verification of the complete signing history.
- Type: Safety
VERIFIED: unverified
[peel-content-integrity]: When acquiring an atom via
refs/atom/d/{blake3(publish_czd)}, a consumer MUST walk the publish
tag chain from the tip (following tag-to-tag pointers until reaching
an atom commit), then verify that the peeled content-addressed sha
equals payload.dig in the publish CozMessage. A mismatch MUST
be treated as tampering — the fetched atom MUST be rejected (SAD
§8.3). Open failure mode: if a serving mirror has garbage-collected a
chain tip (e.g., for a superseded publish_czd no longer advertised),
the chain walk may fail to locate the expected tag; the backend SHOULD
surface this as a distinct "chain tip unavailable" error and MUST NOT
silently fall back to a different publish.
- Type: Safety
VERIFIED: unverified
[store-accumulates]: After ingestion, a store's resolve MUST
return at least what the source's resolve returns for every
ingested atom. (Model §2.3, ⊇ condition.)
- Type: Safety
VERIFIED: unverified
Verification¶
| Constraint | Method | Result | Detail |
| anchor-is-genesis | retired | n/a | Superseded by czd(charter₀); see Open Questions #6 |
| anchor-hash-agile | agent-check | pending | Anchor is a czd, not an ObjectId; re-hash = succession |
| snapshot-deterministic | unit-test | pending | Same (tree, src) → same commit hash |
| snapshot-parentless | unit-test | pending | Atom commit has zero parents |
| snapshot-src-header | unit-test | pending | Atom commit has exactly one extra header src |
| temporal-vector | integration-test | pending | charter src → claim src → publish src enforced |
| charter-commit-format | unit-test | pending | Charter: parentless, empty tree, CozMessage message |
| charter-message-is-coz | integration-test | pending | Parse charter from commit message, verify typ |
| charter-succession-via-prior | integration-test | pending | Walk signed prior; divergent successors fail closed |
| charter-src-reachable | integration-test | pending | Effective charter src via head, bounded cost |
| charter-ref-by-czd | integration-test | pending | charter/d/{czd} addresses, GC-protects, enumerates |
| charter-transition-git | integration-test | pending | Charter creates parentless commit + charter/d/ ref |
| dev-atom-unchartered-local | integration-test | pending | Unchartered dev atom = local, sentinel anchor |
| claim-detached | unit-test | pending | Claim: empty tree, chains to prev if exists |
| claim-message-is-coz | integration-test | pending | Parse claim from commit message, verify |
| publish-tag-targets-correct | integration-test | pending | Tag target is atom commit or previous tag |
| publish-tag-claim-binding | integration-test | pending | Payload claim czd resolves to valid claim |
| publish-tag-message-is-coz | integration-test | pending | Tag message is valid CozMessage JSON |
| tag-chain-immutable | integration-test | pending | Update creates chain, old tags persist |
| coz-bit-perfect | integration-test | pending | Store → retrieve → byte-compare |
| single-active-claim-registry | integration-test | pending | Second claim for same label replaces ref |
| store-claim-disambiguation | integration-test | pending | Two publishes, same AtomId, distinct blake3 keys |
| registry-ref-label-unique | integration-test | pending | Conflicting labels rejected |
| registry-ref-claim | integration-test | pending | Ref points to active claim commit |
| registry-ref-version | integration-test | pending | Ref points to publish tag tip |
| store-ref-by-publish-czd | integration-test | pending | Store refs keyed by blake3(publish_czd) |
| store-claim-ref | integration-test | pending | Claim commit ref exists, GC-protected |
| store-ownership-migration | integration-test | pending | New claim → new ref path |
| claim-transition-git | integration-test | pending | Claim creates commit + 2 refs, chains if needed |
| publish-transition-git | integration-test | pending | Publish creates atom commit + tag + src ref |
| ingest-transition | integration-test | pending | Full ingest cycle preserves identity |
| claim-replacement-transition | integration-test | pending | New claim replaces ref, old commit stays |
| publish-update-transition | integration-test | pending | New tag carries amendment payload; overwrite-class change without claim-owner signature rejected; append-class from any signer accepted; chains to old tag, ref updated |
| fs-ingest-transition | integration-test | pending | FS atoms ingested unsigned into store |
| no-non-empty-claim | unit-test | pending | Validation rejects non-empty-tree claim |
| no-orphan-publish | integration-test | pending | Publish without claim rejected |
| no-backdated-src | integration-test | pending | publish src before claim src rejected |
| no-label-collision-registry | integration-test | pending | Duplicate label rejected |
| anchor-oldest-root | retired | n/a | Retired with anchor-is-genesis (no anchor discovery) |
| no-missing-store-claim | integration-test | pending | Missing claim for payload czd detected |
| anchor-vector-authenticity | integration-test | pending | Full vector: charter src → claim src → publish src |
| ingestion-portable | integration-test | pending | git fetch transfers all objects correctly |
| update-chain-auditable | integration-test | pending | Tag chain walkable, all CozMessages retrievable |
| store-accumulates | integration-test | pending | Post-ingest resolve ⊇ source resolve |
| dev-atom-resolution | integration-test | pending | Local → dev/{anchor}/{label}/{ver}, remote → d/ |
| peel-content-integrity | integration-test | pending | Peeled sha == payload.dig; mismatch → reject |
| store-claim-cleanup | integration-test | pending | Orphaned claim ref cleaned on version eviction |
| tag-chain-semantic-immutable | unit-test | pending | Amendment payload has no identity-field slot; base tag is sole source |
| odb-immutable | agent-check | pending | Protocol objects append-only; GC only if unreachable |
| refs-sole-mutable | agent-check | pending | No protocol state outside refs + objects |
| ancestry-hash-committed | agent-check | pending | Parent OIDs in hashed preimage; checks quantify over it |
| ancestry-query-path | integration-test | pending | replace/grafts ignored; shallow ancestry fails closed |
| czd-oid-disjoint | agent-check | pending | Czd/OID distinct types; ref segments rehydrate to sort |
| oid-hash-inheritance | agent-check | pending | SHA-1 dig/src documented; dig closure is content_hash (atom-transactions.md), src closure irreducible |
| src-hash-kind-disambiguated | agent-check | pending | src header length (40/64) is the sole algorithm indicator |
| refs-atomic-multi | integration-test | pending | Multi-ref transitions commit atomically per store |
Implications¶
Implementation Guidance¶
atom-git: The reference implementation crate. MUST implement
AtomSource,AtomRegistry, andAtomStorefrom atom-core per this specification.atom-core FsSource: See atom-transactions.md for the abstract
AtomSourcecontract. The git backend's responsibility is to ingest atoms fromFsSourceper[fs-ingest-transition].Deterministic commit construction: Use gix's commit creation API with blank author/committer (
<> 0 +0000), epoch timestamp, empty message, and a singlesrcextra header. Thedigfield in PublishPayload is this commit's ObjectId.Claim commits: Use gix to create commits with the well-known empty tree, no extra headers, ATOM_AUTHOR/ATOM_TIMESTAMP, and the CozMessage as commit message. First claim is parentless; subsequent claims parent to the previous claim commit (forming a chain). The
srcin the payload ties the commit hash to the source revision. Write two refs:claims/pub/{label}andsrc/{src_oid}.Publish tags: Use gix's tag object creation API with the CozMessage as tag message. The
claimczd in the payload identifies the authorizing claim. For updates, the tag targets the previous tag object (not the atom commit).Publish tag metadata: Clients SHOULD leverage
[publish-payload-extensible]to provide programmatic overwrite- class metadata in the publishCozMessagepayload'smetaobject (atom-transactions.md[amendment-field-classification]—metais an overwrite-class field; the latest tag'smetavalue is current). One recommended field remains here:meta.min-compatible: "1.0.0"— minimum compatible version for semver-unaware ecosystems. This is a publisher-asserted compatibility declaration, not a post-hoc observation — the publisher states it deliberately, at or after publish time, rather than it being something building or independent inspection would discover the way a security advisory or a build-artifact hash is. It therefore stays an overwrite-classmetafield rather than moving to the fact-kind table below.
The five fields formerly recommended here —
meta.broken,meta.security,meta.superseded-by,meta.deprecated, andmeta.build-hash— are retired asmetafields. Each is a post-hoc, append-class fact under atom-model.md §4's partition law (an advisory or a lifecycle marker is exactly its "asserted fact" example; a build hash is exactly its "derived fact" example), and each is now a fact kind in atom-transactions.md's fact-kind table ([fact-kind-table]):meta.broken→yanked,meta.security→advisory,meta.superseded-by→superseded-by,meta.deprecated→deprecated,meta.build-hash→build-record. Allmetaand fact-kind fields alike are signed as part of theCozMessageand carry cryptographic assurance. Publish tag updates ([publish-update-transition]) enable both overwrite-classmetarevision and append-class fact accumulation, without altering the base publish.Claim metadata: Clients SHOULD leverage
[claim-payload-extensible]to provide programmatic claim chain transition metadata in replacement claims. When a new claim replaces a previous one (via[claim-replacement-transition]), the new claim'smetaobject communicates the intent to consumers who may hold the old claim from a stale mirror. Recommended fields for client implementors (all nested undermeta):meta.supersedes: "update" | "revoke"— why the previous claim was replaced. Two states with distinct trust implications:"update": the old key is no longer active but was valid at the time \u2014 covers routine key rotation, ownership transfer, or any benign transition; versions published under the old claim remain trustworthy."revoke": the old key is considered compromised; clients SHOULD warn users before consuming versions signed by the old claimmeta.announcement: "https://..."— link to an official communication about the claim transition (e.g., compromise disclosure, transfer announcement, rotation notice). Clients SHOULD surface this URL when prompting users about claim changesmeta.effective-after: <timestamp>— if set, only versions published under the old claim AFTER this timestamp are considered suspect; versions before it remain trusted. Limits blast radius for targeted compromise windows. Only meaningful whensupersedesis"revoke"All claim meta fields are signed as part of theCozMessageand carry cryptographic assurance. Because the new claim chains to the old one (parent commit), the meta is structurally bound to the specific transition it describes.
Tag peeling: When resolving a version ref to its content, peel the tag chain to the final atom commit. The atom commit is always the terminal object (parentless commit with
srcheader). gix providespeel_to_commit()for this.Store ingestion: Fetch atom commits, tag chains, and claim commits via gix pack negotiation. Create published atom refs under
refs/atom/d/, claim refs underrefs/atom/claims/d/. Claim commits can be fetched shallowly (no need for their ancestry).Dev atom ingestion: Create atom commits from filesystem content. Reference under
refs/atom/dev/{anchor}/{label}/{dev_version}. No tags, no claims, no verification ceremony. Dev version SHOULD include tree hash (e.g.,1.0.0.dev-{tree_hash_prefix}) to avoid clobbering across concurrent evaluations. AtomId is derivable for all atoms (git atoms use the set'sczd(charter₀)anchor, FS atoms use the sentinel anchor).Anchor verification: The anchor is not discovered from the commit graph — it is the founding charter's czd. The backend resolves the charter from the source's atom refs (storage encoding: Open Questions #6), verifies it, and checks
Anchor == czd(charter₀). Ancestry checks ([temporal-vector]) walk the DAG from the claim'ssrcto the effective charter'ssrc, not to a parentless root.Atomicity: The normative rule is
[refs-atomic-multi]; the gix mechanism isgix::refs::Transactionto batch all reference updates atomically. Remote pushes involving multiple refs MUST use atomic push semantics (equivalent ofgit push --atomic) to a single remote; cross-mirror consistency is convergence ([backend-replica-reads]), never coordinated atomicity.Tree construction: When building git tree objects from filesystem content (
FsSource), entries MUST follow Git's canonical byte-order sorting (directories sort as if their names end with/). gix's tree construction API handles this — implementations MUST NOT manually sort entries using OS-level alphabetical ordering.
Testing Strategy¶
- Unit tests: snapshot determinism (same tree+src → same hash), forbidden state validation, extra header round-trip
- Integration tests: full lifecycle (claim → publish → ingest →
verify), using
tempfilefor ephemeral git repos - Cross-reference: Phase 4 items in atom-transactions.md verification table (10 pending integration tests) are satisfied by this spec's verification plan
Model Gaps¶
- The formal model (publishing-stack-layers.md) explicitly marks "Git object internals" as out of scope. This spec fills that gap.
- The model's
PopulateSession(§3.3) maps to the[ingest-transition]defined here. - The model's coalgebraic
AtomSourceobserver maps to the git backend's ref-based discovery and object-based resolution. - The filesystem source (
FsSource) extends the model'sAtomSourcecoalgebra to non-git backends. The abstract contract is defined in atom-transactions.md; this spec covers only the git ingestion path.
Open Questions¶
Version string normalization:
RawVersionis opaque by protocol. Some values may contain characters invalid in git refnames (e.g.,~,^,:,..). A normalization scheme for version-to-refname mapping MAY be needed. gix provides ref normalization facilities that SHOULD be leveraged. Consider percent-encoding or a restricted character set for the ref segment. Slash (/) in version strings would cause directory/file conflicts in the refname filesystem (e.g., publishing1.0then1.0/beta).Tag extra header support in gix: The spec assumes gix supports extra headers on tag objects (analogous to commit extra headers). This
Tag extra headers in gix: Publish tags no longer require extra headers (the
claim-commitheader has been removed in favor of the signed payload'sclaimczd). This resolves the open question about whether gix supports tag extra headers.FsSource contract: The abstract
AtomSourcecontract for filesystem directories (manifest discovery strategy, path resolution, ingestion interface) needs formal specification in atom-transactions.md. The POC implementation provides a reference. This spec assumes that contract exists and specifies only the git ingestion side.ATOM_AUTHOR identity: The current blank identity (
"" <> 0 +0000) works with gix but may triggergit fsckwarnings. For good git citizenship, consider alternatives that preserve determinism: (a) a static sentinel like"atom" <atom-protocol> 0 +0000, (b) thesrccommit's author (deterministic sincesrchash is an input, but adds a read dependency), or (c) a protocol-derived constant like the CozMessagetypvalue (e.g.,"atom/publish"). The choice does not affect protocol correctness — only git tooling ergonomics.Charter storage representation — RESOLVED (2026-07-13): The git object encoding and ref layout for charter transactions is now specified. A charter is a charter commit — a parentless, empty-tree commit carrying the charter
CozMessage([charter-commit-format],[charter-message-is-coz]) — addressed and enumerated byrefs/atom/charter/d/{charter_czd}([charter-ref-by-czd]). Succession is walked by the signedpriorczd, never a git parent ornow([charter-succession-via-prior]), which is how a resolver reaches the effective charter'ssrcfor[temporal-vector]in bounded, chain-length-only cost ([charter-src-reachable]). A dev atom in a repository with no charter yet is local-class and carries the filesystem sentinel anchor ([dev-atom-unchartered-local]). The founding-charter authorization over a pre-claimed source (the bootstrap gate) remains protocol-side in atom-transactions.md[charter-transition], not a backend concern.