Expand description
The GC liveness sources over alkgit’s own durable rows (waves 7
and 9): the ref-table closure — every ref row’s full reachable
graph walk over the substrate object front — and, under the lfs
feature, the media rows source (LfsMedia, substrate.md §GC
sources 1 and 2; ADR-019 §6) — both registered as in-process
alkblobs::LivenessSources on the composition’s store at the
composition entry (crate::compose).
§The walk (the owning discipline)
The live set is the union over all repos’ oid rows of the full
graph closure of the row’s tip oid: commits (parents included —
the owning walk; gix-fsck’s Connectivity::check_commit does not
walk parents, so it is not the walk here), trees (sub-trees and
blobs), blobs, and tags (peeled to their target and walked on).
Gitlink tree entries (submodule modes) neither mark nor walk: the
pinned commit belongs to its other repo, whose own refs cover it.
Symref rows (the seeded unborn HEAD) carry no oid — unborn
contributes nothing; when the symref’s target is a real row, that
row’s own closure covers the object. Keys are assembled map-free
(crate::odb::oid_key): [0x03] + oid bytes, the git-object
family’s physical encoding.
The walk is best-effort by the seam’s shape
(alkblobs::LivenessSource::live_keys returns keys, not
errors): a missing or unreadable object stops that branch (the key
is absent from the pool or unread — both safe) and the walk
continues; the next rebuild retries. A corrupt entry surfaces the
front’s verification error at the walk read — the same
StorageError::Invalid the serving reads carry.
§The LFS media source (the second alkgit-owned liveness source,
the lfs feature)
LfsMedia (wave 9, ADR-022 §4; substrate.md §GC source 2) serves
the LFS media family’s liveness from the pointer-lifecycle rows
(crate::lfs::LfsRows). The mark consult is a row read, not a
graph walk — media is unstructured content, there is nothing
reachable through it — and the keys form map-free: the row’s
oid is the digest, so the key is [0x04] + oid bytes
(lfs_media_key, the odb front’s oid_key analog for the
32-byte sha256 form; the oid is the raw sha256 of the content,
byte-exact the GitObjectSha256 derivation — lfs.md §2, ADR-022
§1). One key with N rows is one live key (rows dedup through the
key set — the cross-reference refcount’s mark-side reading; any
row re-protects the key, the refcount column’s contract realized).
The invalidation posture is the pinned simple-honest shape: the
mark consult rebuilds per mark (the row read is cheap; the
refs closure’s fingerprint machinery buy the walk’s cost — nothing
to skip here, so nothing to key a memo on). What the arbitration
discipline does need is the refs closure’s write-counter
half: RefClosure‘s contains consult cannot answer exactly without an
epoch signal, and the LFS rows’ dangerous window is a row
landing between a sweep’s mark and a staged candidate’s
arbitration (the stale index says unreferenced; deleting it eats
referenced media). crate::lfs::VersionedLfsRows bumps the
shared counter on every assert — the coverage-increasing event,
and the only one whose staleness is dangerous: a removal’s stale
answers are the safe side (a stale “live” holds a candidate; a
stale “dead” is exact for a candidate whose rows left). The
same-window answer pattern as the refs source: index hit exactly,
cold memo live (the safe default), miss after a moved counter live
until the next mark’s rebuild resolves it.
The mark consult bridges with the front’s
block_in_place
posture (the sqlite/pg rows twins are async — spawn_blocking /
native I/O inside); the arbitration consult never bridges. Same
floor: the multi-thread runtime (test-pinned). The failed mark
consult answers empty with the memo untouched — the same residual
class the refs closure documents (the stale memo answers at
arbitration; the next mark corrects).
The cross-process posture is the refs closure’s, stated honestly:
the in-process source is instance-local (alkblobs ADR-012 §6.6’s
allow-and-document reading); the fleet-wide answer is the
materialized liveness table
(alkblobs::LivenessRegistration::FleetTable — wave-10 fleet
assembly’s wiring, not this module’s). The in-flight LFS uploads
are protected fleet-wide by the DB-backed pins (alkblobs ADR-008)
— the put side is pins’, not this source’s.
§The sync verb / async store bridge
alkblobs::LivenessSource’s verbs are sync (the sweep consults
live_keys from its async body, contains under the arbitration
lock). The walk bridges with
crate::odb::ObjectFront::block_in_place — the odb-front task’s
async-context posture (the POC-O panic shape; the blocking half is
the pack pipelines’). Consequence: the multi-thread runtime is the
floor — a current-thread runtime panics (test-pinned).
contains never bridges: it answers from the memo’s in-memory
index only (RefClosure::keys_builds proves no consult derives).
The walk consumes the front’s async read legs directly (the same
verified reads the sync Find legs bridge to) — the walk runs
inside the bridge’s future, where the sync legs’ own block_on
is illegal (the POC-O panic shape).
§Memoization and the invalidation decision (the honest posture)
POC-O’s shape was the closure memoized by the refs table’s version counter. The wave-6 refs rows carry no version counter, so this task pins the mechanism; the three candidates resolved as two composable half-decisions:
- The memo’s rebuild key is a durable refs fingerprint (the
durable-marker option, realized read-side — no schema change).
RefRows::snapshotreturns the rows plus a fingerprint over their canonical form; a sweep’s mark consult reads it (one cheap refs-rows read, ordered by repo/name) and rebuilds the walk only when it moved. Because the fingerprint is computed from the durable rows, cross-process ref writes are seen at mark time by construction — the fleet-shape invalidation is the snapshot read, not process-local state. - The arbitration window is covered by an in-process write
counter (the POC-O shape carried). A push landing between this
sweep’s mark and the next sweep’s execution-time arbitration
cannot move the fingerprint consult (arbitration runs under the
store’s short-held lock — no bridging, no reads). The
composition’s refs writes therefore bump a shared in-process
counter (
crate::refs::VersionedRefTableon the sqlite assembly), andalkblobs::LivenessSource::containsanswers conservatively — “live” (unknown) — whenever the counter moved past the memo’s build: the affected candidates cancel and resolve on the next sweep’s mark, which rebuilds against the new rows. Over-invalidation costs one delayed sweep; the alternative deletes a pushed object. A cold (never-built) memo answers “live” too — the safe default until the first mark; direct deletes under it refuse (alkblobs::Error::GcRefuse), the ADR-012 §6.4 deregister-delete-re-register correction flow is the escape hatch, and an index hit always answers exactly. - The cross-process posture, stated honestly: the in-process
source is instance-local (alkblobs ADR-012 §6.6’s allow-and-
document reading — the SQL protocol sees tables, not callbacks).
Single-node shapes (the default sqlite assembly) are exact.
Fleet deployments carry one residual window: a completed push
from another instance between mark and execution-time
arbitration (this instance’s counter does not move and the mark
has not come yet). The in-flight push itself is protected
fleet-wide by the DB-backed pins (alkblobs ADR-008), and the
fleet-wide answer is the materialized liveness table
(
alkblobs::LivenessRegistration::FleetTable— the embedder’s own table the store core reads at SQL arbitration); that materialization is the fleet assembly’s wiring — the wave-8 composition entry is the unifier and the wave-10 pg assembly exercises it (the pgGitRefstwin landed with the wave-7 pg refs task), not this module’s.
§Registration
RefClosure::register registers the source on the store
(Store::register_liveness_source, the live-shared seam —
consulted live every sweep, never a snapshot at registration).
The composition’s construction wires RefClosure::new over the
object front, the refs rows seam, and the assembly’s write
counter, then registers: zero registered liveness sources make
every sweep abort with
alkblobs::GcAbortCause::NoLivenessSources — this source is
what the wave-7 maintenance chain’s sweep dispatch requires. The
wave-8 composition entry unifies the construction; until then the
pieces + this wiring are the proof surface (the tests run the full
chain, sqlite co-tenancy included).
Structs§
- LfsMedia
- The LFS-media
alkblobs::LivenessSource— the second alkgit-owned liveness source (substrate.md §GC source 2; ADR-022 §4): the pointer-lifecycle rows as the media family’s live set, a mark consult that reads the rows (no graph walk) and an arbitration consult that answers from the mark’s index. See the module docs’ LFS section for the walk-not-walk posture, the invalidation decision, and the cross-process residual. - RefClosure
- The ref-table-closure
alkblobs::LivenessSource— the first alkgit-owned liveness source (substrate.md §GC; ADR-019 §6). See the module docs for the walk, the bridge, and the invalidation decision. - RefRow
- One refs row as the liveness seam reads it: the resolved repo id,
the validated ref name, and the row’s state — an oid tip or the
symref (unborn) shape. The rows cross
RefRows::snapshotas they are, not asalkgit::RefLinelistings: liveness walks tips, it does not advertise. - RefRows
Snapshot - A refs-rows snapshot: every row of every repo, in the canonical order (repo id, then name — sorted), plus the fingerprint the memo keys on.
Enums§
- RefRow
State - A refs row’s state as the CAS tables store it (the refs module’s row shapes, surfaced for the liveness seam).
Traits§
- RefRows
- The refs rows’ read seam — the durable state the closure walks and
fingerprints. Implemented by the refs tables
(
crate::refs::InMemoryRefTable, thesqlitetwin) and forwarded by the composition’s write-counting wrapper (crate::refs::VersionedRefTable). Engine twins carry their own: snapshotting is a plain ordered read of the rows the CAS writes.
Functions§
- lfs_
media_ key - The media family’s key formation from a row’s oid — the map-free
half the walk’s
oid_keyis, sha256-swapped (lfs.md §2; ADR-022 §1: the oid is the digest, byte-exact theGitObjectSha256derivation): the key is[Algorithm::GitObjectSha256] + oid bytes, decoded throughKey::from_bytesover the physical encoding — family-explicit, rejects-never-guesses. A non-64-lowercase-hex oid isStorageError::Invalid(the rows store validates the same form on write; the read-side check is the front’s OQ-05 posture).