Changes touching this path
- Spawn DevX (#232): loot lanes observability, --ticket lane spawn, wayfinder claim-to-lane
f0a885c2 · dbf3dbe6… - #244: loot adopt <version> — settle a dock onto a landed change
The re-baseline primitive #243 needs and could not do safely. A dock can
end up on a divergent local line that must be *discarded* in favour of a
landed change (the #243 state: the primary main dock on a stale fork while
origin/main moved on). apply/converge MERGES the lines — against a stale
fork that resurrects files deleted upstream; abandon --head drops a signed
fork tip but not the working-change head. adopt <version> fills the gap:
take the target wholesale, abandon every competing head, no merge.
Composition of shipped parts (no new engine machinery), per the committed
spec docs/specs/loot-adopt-target.md:
- Workspace::adopt: resolve the target among live finalized changes, fence
it to the harbor/main lineage (reachable from the change the mirror's
main projects, read from the local ferry spine — no network, no git
process), WIP-gate (refuse a dirty dock unless --discard-wip), then
abandon every competing head to a fixpoint (dropping a transient ferry
merge resurfaces its parents, so the whole divergent line is walked into
the abandoned set), settle tip on the target, and materialize its tree
via the existing resurface checkout — one undoable op (ADR 0031).
- loot adopt <version-id> [--discard-wip] CLI wiring (COMMANDS + USAGE).
- Amend ADR 0034 (the <version> take-wholesale arm + discard-vs-merge
table) and the CONTEXT.md Adopt entry.
Seven tests: settle+abandon+undo, dirty-refuse, discard-wip drop, off-
lineage refuse, unsigned-target refuse, a merge-over-stale-fork fixpoint
walk, and an end-to-end adopt-then-ferry asserting nothing projects (§6.5).
Verified live through the CLI: rolled a drifted dock back onto a landed
change, undo restored it, and the lineage guard refused an ahead-of-main
target.
Deliverable 3 (the §6 live reconcile that finishes #243) is an operator-run
repair of live .loot state and stays a follow-up per execution order §9
(it runs after this branch lands).
01bae608 · dbf3dbe6…diff - feat(loot-cli): no-arg `loot adopt` — harbor catch-up merge (#250, ADR 0034)
22524137 · dbf3dbe6…diff - Catch up to git main 809ddfe: #260 smoke default, #271 arm64 build, loot diff (#1), ADR 0034 dock retirement (#253)
587684dd · dbf3dbe6…diff - Fix pr-map ledger lost-update: serialize writes under pr-map.lock (#336)
review and land both did read -> mutate -> whole-file rewrite of
.loot/git-mirror/pr-map with no lock, and land's read happens minutes before
its write (tests, ferry, push) - a land clobbered all three rows sibling
reviews recorded mid-flight (live, 2026-07-18).
Every ledger write now funnels through one door, update_pr_map: take
git-mirror/pr-map.lock, re-read fresh, apply only this operation's own row
add/remove, replace atomically. Early reads in review/land are lookups only,
never written back. The ledger lock is deliberately separate from the harbor
lock: reviews must not queue behind a land's git-main section, and land's
ledger close-out runs after harbor release anyway.
The write is store::atomic_write like every replaced git-mirror spine file
(#307), and readers (orchestrator + loot lanes) use store::read_replaced so
the Windows rename-replace window (#293 tail) cannot read as an empty
ledger. HarborLock::acquire_contending carries a caller-supplied contended
message so a wedged ledger never claims to be a wedged harbor. ADR 0034
amended; concurrent.md ownership note refined.
11addd8f · dbf3dbe6…diff - 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…diff - a lane carries its own hold, and custody crosses back whole at finalize (#811)
941cafb9 · dbf3dbe6…diff - a verb that could only publish to main unreviewed is retired: lane merge goes, its fold machinery stays where adopt needs it, and the mirror-free fold that genuinely leaves with it is named rather than glossed (#1048)
3f7635a9 · dbf3dbe6…diff - the docs gate themselves: a land refuses a link that resolves to nothing, five rotted ADR cross-links are fixed, every amended ADR warns in its status, and the artifact sweep retires with its producer
586e3260 · dbf3dbe6…diff - the store stops threading a dock nothing can ever produce through 43 signatures, because a store instance is already the position ADR 0034 retired named docks in favor of, and the sixteen path getters no caller outside loot-core reaches stop being public while the one main reservation that guards a git ref name stays (#1413)
23ab3abd · dbf3dbe6…diff - a locked pull stops leaving a stale copy for the next verb to capture: the ingest parks the claim it cannot check, and the first open holding the key finishes it. #1256 recorded nothing when it could not open the pre-ingest content, on the ground that loot rehome materializes and moots the question, which it does not do when it refuses, and in that state it refuses: the untouched pre-pull copy read as an edit no change had recorded, so rehome declined it and loot converge, the next step the pull note names, captured it silently, one change below the version that had just arrived. the claim, being the path, the pre-ingest oid and the digest of the bytes on disk, now goes to .loot/stale-disk-unverified, a position-owned artifact of the same shape and codec that nothing in force ever reads, and Workspace::settle_unverified_stale_disk makes the same comparison at the first unlocked open that was given a graph, beside heal_hold and under its gate: bytes that match move into the record in force, bytes that do not are the operator own work and the claim dies, an open that cannot answer leaves it parked, and a write that disposes of the bytes a claim is about retires it, whether it wrote over them or pruned them. rehome refusal stops asserting the arrival guard would refuse a capture where no guard stands: it asks Workspace::arrived_unmaterialized, keeps the circle where the guard does stand, and elsewhere says what recording those bytes actually does and that a verb capturing on entry records them whatever rehome refused. the locked pull note says to stop if rehome refuses. review fixes: the pin for the printed order now runs the three printed commands and nothing else, since any extra verb opens the repo and an open is what settles the claim, with the record own story moved to a second test; and the three malformed-record refusals stop offering a first remedy the refusal itself prevents, because they refuse at the open, so the file has to go before any verb can run. the Known Issues entry stays, wearing FIXED_IN_MAIN, because v0.4.20 is the binary a reader can install, and pin 5 comes back over it as its own retirement note instructed. red first: the note recipe, run in order, left the arrived version on disk only after the fix (0 passed, 1 failed). red under mutation, each 0 passed and 1 failed unless noted: the claim dropped rather than parked, the open never settling, the settle promoting without the check, dropping rather than keeping what it could not check, leaving a checked claim parked, and running on an open given no history; the surface, the whole-tree write and the one-path write each keeping a refuted claim (that last two 6 passed and 1 or 2 failed); the disposed set naming only what a surface wrote and not what it pruned; rehome claiming the guard everywhere and rehome never claiming it at all; the note without its stop clause; the page deleting the held entry, the page dropping the marker, and the held row witnessing a fix the tree does not carry. no migration, no wire or format byte moves, since the new file is local to a position and never bundled, and no forge or relay byte moves, so this owes no deploy; the CLI change owes a release, and the Known Issues entry leaves with it. the workspace suite is green (3962 passed over 122 binaries, 7 ignored) (#1963)
f7b7e533 · dbf3dbe6…diff
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.