All forge state MUST live under refs/meta/*.
A meta-ref MUST point at a commit whose tree is the entity itself, so the
ref is simultaneously the unit of storage (the commit’s tree),
synchronization (fetching or pushing the ref moves exactly that entity),
authorization (refname-keyed rules gate who may advance it), and history
(its commit chain is the audit trail).
Retention pins under refs/meta/pins/* (<<model.review-pin>>) are the
sole exception: a pin’s commits anchor other content’s reachability and
carry the empty tree, never an entity.
docs/spec/meta-ref.adoc
The Meta-Ref
A ref under refs/meta/* is simultaneously the unit of storage,
synchronization, authorization, and history for one piece of forge state.
The typed tree — a #[derive(Facet)] struct mapped directly onto a git
tree by facet-git-tree — is what lives behind it.
The entities that live on meta-refs (members, comments, effects, results,
toolchains, accounts) are specified in model.adoc; this file specifies
the ref and the tree, not what they hold.
''
Namespace and Granularity
A meta-ref MUST hold exactly one independently-authored entity:
refs/meta/member/*, refs/meta/issues/*, refs/meta/comments/*,
refs/meta/reviews/*, refs/meta/effects/*, and refs/meta/results/*
each decompose one ref per entity.
Repository-global state with a single writer-of-record MUST instead live
on one fixed ref, such as refs/meta/account or refs/meta/config.
Entities that different actors write concurrently MUST NOT share a ref.
Writes to a meta-ref MUST stay conflict-free — no two concurrent writers
ever race the same compare-and-swap — and reads aggregate multiple refs
into a view rather than depending on a shared, contended one.
refs/meta/inbox/<member>/<canonical-suffix> MUST hold one entity
authored by <member>, who is not authorized for the corresponding
canonical ref, awaiting adoption; <member> MUST be the leading segment,
symmetric with refs/meta/self/<member>/* below, so authorization
(<<gate.tip-signed>>) keys off the refname alone, matching the granularity
rule’s one-entity-per-ref shape (<<meta-ref.granularity>>) instead of
leaving <member> unencoded.
<canonical-suffix> MUST be the corresponding canonical ref’s entire path
below refs/meta/, not a bare entity id: refs/meta/issues/42 routes to
refs/meta/inbox/<member>/issues/42, and a multi-segment canonical ref
such as refs/meta/results/<effect>/<short-oid>
(<<effect.results-writeback>>) routes to
refs/meta/inbox/<member>/results/<effect>/<short-oid> in full — the
namespace segment MUST be preserved so two different entity kinds can
never collide under the same inbox id.
<member> MUST be authorized to create and update refs under only its
own refs/meta/inbox/<member>/* segment; no member, including an
admin-registered one, MAY write into another member’s inbox segment.
Adoption happens by an authorized member merging the inbox entity onto
the canonical ref (<<gate.adoption-merge>>, <<sync.adoption-machinery>>),
never by writing the contributor’s inbox ref directly, so admin write access
to another member’s segment is never needed.
An inbox ref MUST NOT be deleted on adoption or at any other time: it
remains the contributor’s own audit trail, exactly as a member entity is
never deleted on revocation (<<model.member-revocation>>).
refs/meta/self/<member>/<effect>/<short-oid> MUST hold results a
member produced on their own executor rather than a designated worker,
mirroring the canonical results pattern (<<effect.results-writeback>>)
under the member’s own namespace.
self MUST be its own top-level namespace under refs/meta/*, a fixed
segment from the spec’s own namespace table (<<meta-ref.namespace>>) rather
than a marker nested inside refs/meta/results/*: an effect name and a
member id are both otherwise-unconstrained ref-path segments
(<<effect.definition>>), so a marker sharing their position could collide
with one; a sibling top-level segment cannot, and it keeps the canonical
results glob (refs/meta/results/<effect>/*, <<effect.official>>) and the
self-run glob (refs/meta/self/<member>/*) disjoint by construction.
Both namespaces MUST hold the same typed trees as their canonical
counterparts (<<meta-ref.typed-tree>>); only the refname rule differs.
The Typed Tree
A Rust struct annotated #[derive(Facet)] MUST be the storage schema for
its entity: facet-git-tree MUST map the struct directly onto a git
tree, with no serialization format interposed that could version
independently of the struct itself.
A tree MUST stay a pure representation of its struct; it MUST NOT carry
a version-marker entry.
A meta-ref’s name MUST be a total function of its signed content, and the gate MUST recompute that function and refuse a mismatch (<<gate.identity-binding>>); no commit-message trailer or other side channel participates in the binding, and no reserved trailer exists. The function, by namespace:
-
Singleton state binds by its fixed name:
refs/meta/config,refs/meta/account. -
A natural-key entity binds by a designated tree field equal to the refname’s final segment: a member’s id, an effect’s name, a toolchain’s name (<<model.member-identity>>, <<model.effect-definition>>, <<model.toolchain>>).
-
A hash-identified entity binds by genesis: the refname’s final segment MUST equal the oid of the entity’s genesis commit, and every parentless commit reachable from the proposed tip MUST be that genesis — comments and issues (<<model.comment>>, <<model.issue>>). The reachability form, not a creation-time-only check, is what makes replaying a signed mutation commit as the genesis of a doppelgänger entity impossible, and it holds across the merge commits divergence resolution creates (<<gate.same-actor-divergence>>).
-
A composite-keyed entity binds by genesis fields and signer: a review’s
reviews/<target>/<member>segments MUST equal its genesis tree’s target field and its genesis signer’s member id (<<model.review>>); a result’sresults/<effect>/<short-oid>segments MUST derive from its own tree’s effect and target fields (<<model.result-identity>>), and a self-run’s member segment MUST additionally equal its signer (<<meta-ref.inbox>>). -
A retention pin binds by mirroring its entity’s segments, with parents that include the retained commit (<<model.review-pin>>). A pin’s ancestry deliberately reaches into code history, so the parentless-roots walk above MUST NOT be applied to pins.
-
An inbox ref binds by its owner segment equal to the signer (<<meta-ref.inbox>>), with the canonical suffix bound exactly as its canonical namespace binds.
Who authored a state, and when, MUST come from the commit itself — its header and its signature — never from duplicated tree fields: each datum has exactly one signed home, and a tree field participates in the binding only where the namespace’s function above names it. Within a meta-ref’s history, a commit parent means exactly one thing — the prior state of the same entity — with the pin’s retained-commit parents as the sole exception (<<meta-ref.namespace>>); a cross-entity relationship MUST be tree data (a reply’s parent field, a comment’s context field), never a parent edge. A genesis commit is frozen by the identity derived from it, so the struct of a hash-identified or composite-keyed entity MUST evolve additively only — new fields optional, required fields never added, renamed, or removed; <<meta-ref.migration>> governs the remaining namespaces.
Tip Invariant and Migration
The tip of a meta-ref MUST always be readable by the binary that owns
the entity type; a non-owning binary MUST treat the tree as opaque and
degrade to generic display (git store show), never an error
(<<model.extensibility>>).
Only history is archival.
A reader that encounters a withheld (redacted) object while reading a
meta-ref’s tip MUST surface a redaction marker, never an error
(<<receive.redaction-ingest>>) — the sole qualification to this
invariant.
Changing an entity’s struct MUST be performed as a storage migration: rewrite the tree under the new struct and commit it on top of the ref’s old tip, signed like any other mutation. History MUST keep the old encoding as archive; a struct change MUST NOT rewrite or delete a prior commit on the ref.