Files
alkblobs/tasks/gc-protocol.md
T
glm-5.3-flash 6bce301c55 docs(tasks): decompose wave 2 — mem + local engines, facade, in-process GC, first suite columns
Nine tasks against the wave-1 contract surface as reviewed (no
amendments from the gate):

- engine-mem / engine-local (parallel, dependency-free): the two
  engines; local owns the stage-hygiene exact-count tests and the
  Drop-cancellation story the wave-1 review recorded
- store-construction: the Store type, constructor modes, size-threshold
  dispatch; binds the conformance factory's Store associated type
- store-read-paths ∥ gc-protocol: read ops with the ADR-012 §6.1
  fall-through rule; the ADR-005 in-process protocol (pins, liveness,
  delete windows, abort-as-data, GcRefuse with the full protection set)
- store-put-paths: the two put paths behind one signature, ADR-003's
  three numbered rules, pin-before-publish, Batch, kv-only rejection
- suite-column-mem → suite-column-local: the reference column, then the
  dual-tier dispatch-coverage shape mirroring it
- review-wave-2: the gate (facade/dispatch/GC vs ADR-003/005/012)

Verification: dependency graph validated (all ids resolve, no cycles,
topological order clean); docs-only change.

Refs: docs/plans/implementation.md §Wave 2
2026-10-11 21:39:28 +00:00

6.1 KiB

id, name, status, depends_on, scope, risk, impact, level, tags
id name status depends_on scope risk impact level tags
gc-protocol In-process GC protocol — pins, liveness, delete windows, sweep, direct-delete refusal pending
store-construction
broad high phase implementation
wave-2
gc
facade

Description

Implement the in-process GC protocol (ADR-005; abort-as-data per ADR-012 §1; non-SQL window staging per ADR-012 §5) on the Store type — the architecture's most intricate state machine, in its single-node form. The fleet extension (DB-backed pins, sweep lease, SQL arbitration) is wave 6; this task is the protocol the fleet form extends without changing its invariants.

  • Pin registry (ADR-005 §Marks 2): per-key refcounts under the arbitration lock the sweep consults. Pin guards minted by the put path (store-put-paths wires the call sites; the registry and minting land here); a dedup re-put refcounts the existing entry — a dedup put's guard protects exactly as a fresh put's does. Pin drop decrements. Named design point: the guard's back-reference shape (how the public Pin/Batch types reach the registry — the wave-1 types carry only the key today) and the lock granularity (parking_lot short-held per AGENTS convention 3, poisoning via unwrap_or_else(|e| e.into_inner())).
  • Batch machinery: a batch's pins drop together on batch commit or batch drop, in one ordered step under the arbitration lock; no early single-pin release. The conversion ordering rule (ADR-005, pinned in facade.rs docs): a pin's drop and its replacement's registration into a liveness source are one ordered step under the same lock — the registry needs the API shape that makes the ordered step expressible.
  • Registered liveness sources (register_liveness_source, the live-shared seam — ADR-005 §Decision): named design point — the source trait/callback shape. Live-shared means consulted at mark time and at delete-time arbitration, never snapshotted at registration (POC #1 finding 7's install-implies-copy failure is the pinning reason).
  • Protect callback: consulted pre-sweep; may add externally-known hashes or abort the run — abort is report data (GcAbortCause::ProtectFailed), never an error (ADR-012 §1).
  • sweep() → Result<SweepReport, Error> — the delete-window protocol (ADR-005 §Sweep):
    1. mark: enumerate the whole pool (both tiers' complete list() unioned); compute the live set from all three sources (registered sources, pins, protect callback); stage the candidate-dead set.
    2. delete window: per candidate, arbitrate under the pin/ liveness lock at delete time (not once upfront) — pinned or re-observed-live cancels the candidate; otherwise proceed.
    3. commit: remaining candidates deleted in batches (~100/batch, iroh's proven shape); window closed.
    • Ok(SweepReport) always — sweeps are never errors; every abort is data in aborted: Option<GcAbortCause> (exactly three causes; NoLivenessSources is the safe default when zero sources are registered — nothing deleted). Exact counts in the report fields.
    • Non-SQL window staging (ADR-012 §5): local/mem stage candidates in-process and execute the window protocol inline in the same sweep — there is no durable window table on non-SQL engines, and the "executor is the next sweep" clause (ADR-012 §6.7) does not apply to them.
    • The test-asserted property (ADR-005): a visible pool entry is never deleted while liveness protects it, and an in-flight put is never deleted by a sweep started before it committed — concurrency tests (sweep racing a pinned put) assert it.
  • Direct delete(key) (wired through the fall-through dispatch — arbitration-then-delete where the entry lives): refuses any protected key — pinned, registered-source-live, or protect-callback-named (the full protection set, ADR-012 §6.4) — with the typed Error::GcRefuse, nothing deleted; unprotected keys delete; absent keys carry Err(Missing). The refusal is decided at the facade (the altitude discipline: GcRefuse never fires below the facade).
  • has/get during a window observe no torn state — existence flips atomically per key (immutable entries); document the posture the engines' CAS semantics already give.

Acceptance Criteria

  • Pin registry with per-key refcounts under the arbitration lock; dedup re-puts refcount; Pin/Batch drops release through it (batch drops in one ordered step)
  • register_liveness_source live-shared (consulted at mark and at delete-time arbitration, never snapshotted); source shape decided and recorded
  • Protect callback with abort-as-data (ProtectFailed); NoLivenessSources safe default; sweep() returns Ok(SweepReport) on every abort — never an abort error
  • Delete-window protocol: mark → arbitrate-at-delete-time → batched commit (~100/batch), with exact-count SweepReport fields (candidates_staged/deleted/cancelled_by_arbitration)
  • Direct delete refuses the full protection set with GcRefuse and deletes unprotected keys through the fall-through dispatch
  • Concurrency tests: sweep vs in-flight pinned put (the pin-before-publish property from the sweep side), exact-count outcomes, abort shapes
  • cargo test green; cargo test --features mem and cargo test --all-features green; clippy/fmt gates green

References

  • docs/architecture/decisions/005-pooled-cas-and-gc-mechanism.md (the protocol + invariants)
  • docs/architecture/gc-and-namespaces.md (§Mark-and-sweep GC; §Delete windows)
  • docs/architecture/decisions/012-pre-decomposition-consistency-rulings.md §1 (abort-as-data), §5 (non-SQL staging), §6.4 (full protection set)
  • docs/architecture/store-api.md (§Lifecycle — sweep shape; §Invariants 4/5)
  • src/facade.rs (the pinned Pin/Batch/SweepReport lifecycle contracts this wires)
  • docs/research/poc-trait-dispatch-findings.md (findings 3/7/8)

Notes

To be filled by implementation agent

Summary

To be filled on completion