Skip to main content

Module refs

Module refs 

Source
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; no mem feature exists — ADR-025’s placement rule).
  • SqliteRefTable (the sqlite feature) — the row-CAS twin over the co-tenant git_refs rows (CoTenantSqlite): every CAS check and apply inside one BEGIN IMMEDIATE transaction — the one-transaction home that makes atomic correct (ADRs 013 §7 / 019 §2; POC-O verified stale/atomic/delete/force against real git push behaviors, poco-findings DQ-2).
  • PgRefTable (the postgres feature) — the pg row-CAS twin (crate::postgres::CoTenantPg): the same one transaction per apply_updates call, the pg transaction class standing in for sqlite’s BEGIN 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: the expected CAS failed. An absent ref never matches an expected: Some(..); a present row never matches expected: 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: None applies onto an absent name only.
  • Delete — new: None applies onto an oid row whose oid matches expected. The degenerate expected: None + new: None shape 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 expected matches 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§

InMemoryRefTable
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 async tokio::sync::RwLock: its guard is held across the (never-yielding) CAS futures’ awaits and is Send — the GitRefs Send-future surface holds (a blocking guard across an await would not).
SqliteRefTable
The sqlite row-CAS twin over the co-tenant git_refs rows (substrate.md §refs, ADR-019 §2) — the atomic realization as the one-transaction home: every GitRefs::apply_updates call runs all its CAS check-and-applies inside one BEGIN IMMEDIATE transaction on the co-tenant connection (the migration-checked schema from substrate/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).
VersionedRefTable
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).