Changes touching this path
- the ledger reap gains an authoritative half, so a row whose change id stopped being the work's id can still be cleared (#793)
#624 made the pr-map self-heal on discovery, and it does — but its proof is
"a version carrying this row's durable change id is published on origin/main",
and that question can become unanswerable for work that plainly landed.
Ask 1 was to pin why, and the answer is neither candidate the ticket named. It
is not gc and it is not the resolutions minting their own ids: the reviewed
change landed as ff89d2c, its version IS in the graph, and it carries durable
change id f7d93995 while its row still named aec9021d. The row is written once,
in review(), and thereafter only ever removed. Nothing re-keys it. A durable
change id is stable across re-SNAPSHOTS, but a bounce recovery that re-creates
the working change rather than amending it hands the same work a fresh one --
and the ledger never hears about it. That row was then unreapable forever.
The obvious repair is unavailable, and change_published_on_origin's own doc
already says why: a live sibling lane's unsigned WIP never enters the shared
graph either, so "absent from the graph" is the normal state of a row that is
genuinely in flight, and reaping on it would eat every concurrent agent's row
at once. Nothing local separates a stranded row from a live one.
So the second half is authoritative and online: closed_rows asks the forge
whether the row's PR is still open. Merged counts as over -- it is what GitHub
calls the zero-diff collapse that IS the landing signal here. It is spent in
review and land, which already hold a forge, rather than in status, the
constantly-run verb #624 deliberately kept offline.
Both sweeps run BEFORE their verb's own work. The operator most likely to be
looking at a phantom row is the one with nothing to project, and a sweep behind
review's `nothing to review.` return would be unreachable for exactly them.
land sweeps too, because land is what strands rows in the first place -- it
clears its own row as its last act, so a land that dies after publishing leaves
one behind -- which makes the re-run the pass most likely to meet one. land
excludes its own PR: a land that died after the collapse leaves the row with the
PR already reading Merged, which is precisely what this reaps, so without the
exclusion the retry would delete the row it needs and then fail to find it.
A row nothing can be proven about is still always kept, so a forge that will not
answer changes nothing. Rows are not aged out on a guess (ask 3): over-reaping
costs a land that cannot find its own PR, under-reaping costs a line of output.
Ask 4 is answered as far as it honestly can be: status cannot say which of its
surviving rows is stranded (it cannot see the difference), so instead of
guessing it says where the answer lives.
Both reap bodies now share one door (reap_rows), so the local and authoritative
passes cannot drift apart on the two things that matter -- going through the
ledger's locked write door, and being best-effort.
ADR 0034's #336 amendment and docs/agents/concurrent.md said every ledger write
"applies only its own row"; the reap passes have removed other positions' rows
since #624, so both now record the exception and why it is safe.
5cdfa18c · dbf3dbe6…
Renames are not followed. loot's tree maps a path to an address, so a rename is a delete and an add. This list is the history of the name, not of the bytes.