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
This commit is contained in:
glm-5.3-flash committed 2026-10-11 21:39:28 +00:00
1 parent c1d9494c11
commit 6bce301c55
10 files changed
+834 -2

No files matched your search

+120
View File
@@ -0,0 +1,120 @@
---
id: gc-protocol
name: In-process GC protocol — pins, liveness, delete windows, sweep, direct-delete refusal
status: pending
depends_on: [store-construction]
scope: broad
risk: high
impact: phase
level: implementation
tags: [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