Expand description
The refs co-tenant row table (substrate.md §refs, ADR-019 §2) — the
object-storage family’s third trait (GitRefs) in three shapes:
InMemoryRefTable— the CAS-semantics reference impl, engine-free and unconditional in the crate’s base (the completeness shape; nomemfeature exists — ADR-025’s placement rule).SqliteRefTable(thesqlitefeature) — the row-CAS twin over the co-tenantgit_refsrows (CoTenantSqlite): every CAS check and apply inside oneBEGIN IMMEDIATEtransaction — the one-transaction home that makesatomiccorrect (ADRs 013 §7 / 019 §2; POC-O verified stale/atomic/delete/force against realgit pushbehaviors, poco-findings DQ-2).PgRefTable(thepostgresfeature) — the pg row-CAS twin (crate::postgres::CoTenantPg): the same one transaction perapply_updatescall, the pg transaction class standing in for sqlite’sBEGIN IMMEDIATE— the call’s transaction holds the schema’s advisory CAS lock (the pg analog of the file write lock), so two clashing applies — cross-connection included — serialize and the loser sees the committed winner.
§The CAS semantics (the reference shapes both impls pin)
Per update, decided in request order against the row state with in-call visibility (each decision and write sees the updates already applied within the call); the whole call commits only under the non-atomic flag — atomic aborts whole on any denial. The wire-path input always carries distinct ref names per call (duplicate commands fail closed at the parse — ADR-013 §3), so in practice every update addresses its own row and the semantics below evaluate cleanly in the git-observed classes:
- Stale —
CAS_STALE_REASON: theexpectedCAS failed. An absent ref never matches anexpected: Some(..); a present row never matchesexpected: None(create-over-existing) or a mismatched oid; a symref row’s oid is unborn — it matches nothing (the seeded HEAD can be neither created-over nor CAS-deleted — git parity: a push never touches a repo’s HEAD row). - Create —
expected: Noneapplies onto an absent name only. - Delete —
new: Noneapplies onto an oid row whose oid matchesexpected. The degenerateexpected: None+new: Noneshape is a no-op success on an absent name (nothing to delete), stale on a present one. - Force — no fast-forward check exists (the v1 CAS-only policy,
ADR-013 §7): an update whose
expectedmatches applies with any new value, non-fast-forward included (the client’s forced-update shape is client-displayed; the table’s contract is exact-match CAS and nothing more). - Atomic — one transaction whole (ADR-013 §10): any denial
aborts — nothing applies; the denied refs keep their own reasons
and every other ref of the call reports
ATOMIC_PUSH_FAILURE_REASON. An all-passing call commits whole. - Non-atomic — per-update outcomes, each applying when its own CAS passes; denials never block the others.
Per-ref denials are RefOutcome values, never Err (a stale ref
is a business result of receive-pack — ADR-013 §7–8). Err is for
substrate failure only: an engine failure rolls the call’s
transaction back whole and rides StorageError::Io
(message-only — no engine source types on the surface, the family
posture).
§Name validation at the table layer (the pinned mapping)
Every update’s ref name runs alkgit::validate_ref_name — the
core’s gix_validate::reference composition plus the
RESERVED_REF_NAMESPACES deny-list and .lock rule, reused (no
second deny-list; ADR-013 §9’s validation homes at the CAS layer —
substrate.md §refs). The mapping this task pins: a rejected name is
a per-ref denial (RefOutcome with FUNNY_REFNAME_REASON),
never Err, whatever the row state; under atomic the
invalid-name refs keep funny refname while the other refs report
atomic push failure (the same upstream shape the wire layer’s
abort branch pins, ADR-013 §10). The wire layer already validated;
this is the CAS-home defense. list_refs does not re-validate:
rows can only arrive through the validated CAS path or the seeding
rule (substrate.md §refs), so read-back names are trusted — but a
structurally invalid row state is StorageError::Invalid
(mis-state, not a business denial).
§The row shapes
(repo_id, name) → oid, symref_target rows (POC-O-validated table,
DQ-2). An oid row lists as the plain RefLine; a symref row (oid
NULL, target set — the seeded HEAD → refs/heads/main) lists as
the unborn symref line RefLine { oid: None, symref_target: Some(..) } — the unborn-HEAD shape ls-refs=unborn serves (the
OQ-19 rider (a) fixture falls out of the seeding rule; the seeding
itself lands in substrate/w6-registry-sqlite’s put_repo, which
writes the HEAD symref row in the record-write transaction — this
task’s list_refs surfaces the shape on both twins). Listing is
deterministic: sorted by ref name (HEAD sorts before the refs/
subtree). peeled is never produced by a row table — peel needs
object access the refs surface does not have (ls-refs-arg data).
Ref-table writes ride their own durable surface — no stream events, no registry-seq coupling (ADR-019 §7: pushes anchor the event/ watermark legs with the composition-internal mutation marker, the receive composition’s business; ref-table writes are not registry-family writes).
Structs§
- InMemory
RefTable - The in-memory refs table — the CAS-semantics reference impl
(engine-free, single-process, stamp-free by construction: it builds
fresh every time and versions nothing durable — ADR-024 §6). Rows
are keyed on the resolved repo id (
repo_id → name → row), name-sorted maps (deterministic listing); the whole call runs under one short-held write lock — the in-memory “transaction” — with the atomic abort as simply not committing the call’s working copy (per-update decisions inside still see their in-call predecessors). The lock is the asynctokio::sync::RwLock: its guard is held across the (never-yielding) CAS futures’ awaits and isSend— theGitRefsSend-future surface holds (a blocking guard across an await would not). - Sqlite
RefTable - The sqlite row-CAS twin over the co-tenant
git_refsrows (substrate.md §refs, ADR-019 §2) — theatomicrealization as the one-transaction home: everyGitRefs::apply_updatescall runs all its CAS check-and-applies inside oneBEGIN IMMEDIATEtransaction on the co-tenant connection (the migration-checked schema fromsubstrate/w6-migrations), so two clashing applies — from this process or a co-tenant connection of another — serialize on the row write lock and the loser sees the committed winner (stale info), while an atomic denial rolls its transaction back whole, leaving the rows exactly as they were (POC-O’s demonstrated shapes, DQ-2). - Versioned
RefTable - The refs write counter — the liveness source’s in-process
invalidation half (crate::liveness “the arbitration window”):
bumped on every CAS call through the composition’s refs impl, read
by the source’s arbitration consult. See
VersionedRefTable.
Constants§
- CAS_
STALE_ REASON - The pinned per-ref CAS denial reason (ADR-013 §7–8: CAS-stale
failures carry a server-chosen reason — ours is upstream’s
stale info-style internal text, the same text the core’s contract doubles pin).