Changes touching this path

  • docs: concurrent-agents playbook, lane/harbor vocab, docks->lanes migration (#234) 12aa8264 · dbf3dbe6…
  • feat(loot-cli): no-arg `loot adopt` — harbor catch-up merge (#250, ADR 0034) 22524137 · dbf3dbe6…diff
  • gc roots the whole shared store and catch-up verbs ingest the harbor lineage, so a lane-landed change the primary never adopted fast-forwards instead of merging, duplicating, or being pruned (#265, #263 prevention) 4c03b44c · dbf3dbe6…diff
  • drift guard: a mirror merely AHEAD of a stale origin/main is quiet, not DIVERGED (#273) The guard fired the loudest warning it has — "DIVERGED — do NOT land" — on the single most common healthy path: between a land and the checkout's next `git fetch`. A guard that cries wolf on the normal path is one the operator learns to scroll past, which is how the real #243 case (a lane projecting backward over landed work) would eventually slip through. Its value is its rarity. The pure policy half was already correct and unit-tested; the bug lived entirely in the impure oracle that computed `Ancestry`, which had no tests at all. - `Ancestry::MirrorAhead`, probed by asking BOTH directions. Each direction is asked of the repo guaranteed to hold both commits when that answer is true: "ahead" of the mirror (which always holds its own lineage — the checkout may never have fetched the tip, which is why "ahead" collapsed into "diverged" by construction), "behind" of the checkout. Ahead is quiet — it is the normal post-land state. - `land`/`tag` refresh `main` from the remote before judging; `status`/`review` stay local and cheap. This pays for `MirrorAhead` being quiet: a stale tracking ref could otherwise hide a `main` that moved under us at exactly the two verbs that must not miss it. Falls back to the tracking ref, with a note rather than in silence, when origin is unreachable. - `is_ancestor` captures output, so a missing object no longer sprays a raw `fatal:` that reads like a real failure. The refresh FETCHES rather than `ls-remote`s, deviating from the ticket's suggested fix 2: ancestry needs operands git can walk, and a bare sha is not one — the checkout may never have seen that commit, so the behind-probe would fail into a false Diverged on precisely the fresh break-glass push the refresh exists to catch. Caught in review; regression-tested. Behind and diverged still warn exactly as loudly. `mirror_ancestry` and the refresh now have direct tests over real temp repos — the previously untested half — covering same/ahead/behind/diverged/missing-object, plus the case the old doc claimed was undecidable (ahead when the checkout lacks the mirror's tip). Verified live against the ticket's repro: the old binary says DIVERGED, the new one is quiet on identical state, while forged diverged/behind states still warn. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> 0c808aa8 · dbf3dbe6…diff
  • merges refuse to sign an un-described change too, closing the #174 residual (#275) #174 stopped the *deliberate* finalize from signing a nameless change, but three verbs still sealed the operator's own work in passing, to make a signed merge parent: `dock merge` and the `adopt` catch-up (via `fold_line_in`), and `ferry` over a git main that moved (via `reconcile_capture`). Only the *trigger* was mechanical — the content was authored — so the placeholder still reached main. Reproduced against the pre-fix binary: a ferry over unnamed disk work landed the literal subject `(working change)` on git main in one silent pass. Both paths now refuse an un-described merge parent, below their capture (edits held, only the signature waits) and below a redundant-capture drop (a pass with no real work to sign stays a no-op, never a nag). Two reasons the ADR gave for NOT doing this were checked and dropped: a land never reaches these paths dirty (it finalizes before it ferries), and #219's refuse-on-dirt rejection was about being forced to capture, not about being asked for a name. The merge *nodes* are untouched — they are machine-authored and already carry an honest mechanical subject. That is the line: mechanical content may be named mechanically, authored content may not. - `refuse_if_undescribed` (one rule, two messages) + `drop_capture_if_redundant` (the empty/duplicate drop, previously copied at two sites and missing at a third — which is what would have made `dock merge` nag). - Cost, documented: uncaptured dirt takes two passes, since naming *is* capturing. `loot-first review` asks for a name only when it must merge. ADR 0030 amendment (correcting the two wrong claims), CONTEXT, workflow, and concurrent.md updated. Closes #275. aced27c1 · dbf3dbe6…diff
  • review refs carry the position, not the dock (#281) Every lane's home dock is main, so dock-named review refs made N concurrent lanes share one review/main branch: the second lane's ferry --with-wip force-pushed over the first's in-flight PR head, and either position's reap pass could misjudge (and retire) the other's live entry, since liveness reads the positional working pointer a foreign position cannot see. The review lane is now keyed by its owner position end to end: - ferry projects review/<lane-id> from a lane, review/<dock> on the primary; the wip and pr-map ledgers gain an owner column (- = primary; pre-#281 short rows parse as primary-owned) and the review line carries owner=. - Reap is owner-scoped: only the owner judges liveness; a foreign pass reaps exactly the entries whose owner lane is gone from the registry, so an abandoned lane's review ref dies with it instead of leaking. - land derives the collapse branch from the pr-map lane's owner and refuses to run from any other position: it finalizes the current position's working change, and the dock guard cannot catch that mismatch when every lane's dock is main. Docs: ADR 0033 amendment, concurrent.md (review projection is now genuinely parallel-safe), workflow.md, CONTEXT.md review-lane entry. aa68437a · dbf3dbe6…diff
  • docs: review-before-sibling-lands gotcha in concurrent.md (#289 land rider) Discovered landing #289: if git main moves between a lane's describe and its first loot-first review, the review's own ferry seals the described WIP as a signed merge parent (#275) and then finds nothing unsigned to project - the change is signed, merged, and PR-less, and land refuses without a pr-map entry. Document the ordering rule (review right after describe) and the recovery (plain ferry, then a follow-up review round carries the sealed work into the PR diff). b745b6c4 · dbf3dbe6…diff
  • converge: three-way deletion-vs-base rule (#295) (#300) merge_trees had no deletion-vs-base case: a path in theirs but not ours was always AdoptTheirs, and a path in ours but not theirs was never visited (the walk only iterated theirs). Either way a one-side deletion since the fork was silently undone -- it fired twice while landing #288, re-adopting freshly deleted files from the sibling line. Apply the standard 3-way rule against the base merge_tips already computes: - one side deleted + other unchanged-from-base -> the deletion wins (both directions; a symmetric ours-only pass covers the path theirs dropped); - one side deleted + other edited-since-base -> a delete/edit Conflict, recorded so it surfaces through the harbor bounce (ADR 0036) rather than silently resurrecting or deleting (base content stands in for the deleted side's missing oid); - base lacks the path -> a genuine add, adopted as before. Unchanged-ness: address equality first, then plaintext via the key oracle (re-seals mint fresh addresses, #65/#98); an unopenable side keeps the conservative pre-#295 keep/adopt. New same_content helper. Tests: converge unit tests for both delete-vs-untouched directions (incl. a reseal-address case), both delete-vs-modified conflict directions, delete-vs- delete clean, base-lacks-path add, unopenable conservative, and a classify theirs-side case; engine merge_tips tests proving the one-side deletion is honored (both orders, clean) and a delete/edit collision surfaces a conflict. Amends ADR 0028 (new #295 amendment) + CONTEXT.md Convergence-classifier and Adopt entries + concurrent.md, retiring the "the merge is what resurrects files deleted upstream" caveat for the reconcile/adopt merge. Git-Author: Connor Miller <53197564+Connor-Miller@users.noreply.github.com> 9fed0444diff
  • Docs: post-break-glass ferry is mandatory even for loot-originated content (#297) (#299) The #291 site work originated as a loot change, went to GitHub via a break-glass squash-merge, and the resulting squash commit was never ingested into the remoteless mirror -- so every subsequent land hard-stopped on DIVERGED. The break-glass ferry rule read as skippable because loot already held the content; the lesson is that it is the *commit*, not the content, that must be ingested. - concurrent.md: expand the break-glass recovery section -- ferry is mandatory even for loot content (mirror is remoteless), GitHub merge/squash-merge buttons are themselves break-glass, plus the divergence signature and the #297 recovery recipe (force-fetch checkout main into the mirror with rollback sha recorded, primary ferry, land the parked PR through the harbor bounce). - workflow.md: flag the GitHub merge button as break-glass in the git->loot residual list and the guard rails, linking the recovery. Closes #297. Git-Author: Connor Miller <53197564+Connor-Miller@users.noreply.github.com> 271581ebdiff
  • 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
  • Bounced-land resolutions inherit the described ours-line subject (#337) fbcb501d · dbf3dbe6…diff
  • ADR 0039 build: pure-projection review + carry-at-land (#362) Review mode (`ferry --with-wip` / `loot-first review`) is now a pure projection: no ingest, no dock reconcile, no mirror-main advance, no spine rewrite - it mints the provisional commit from the lane's own anchor marks and pushes only review/<position>. A lane behind git main reviews normally; REFUSE_REVIEW_STALE_ANCHOR is deleted with the fold it guarded (#292/#302), and the #349 review-mode trigger is structurally gone. Reconcile lives only at the signing verbs, and its diverged-line shape is now the carry (DagRepo::carry_line): a self-authored suffix replays onto landed main as superseding versions - same change id, same subject, single parent, stale original kept as predecessor - so landed history stays exactly one commit per change with no ferry: 1412f811 · dbf3dbe6…diff
  • Build the seal-WIP guard (#418): refuse a bare sync verb that seals live described WIP Graduated from #356's "Prevent + hint" resolution (map #354). After ADR 0039 (#362) made review a pure projection, the only way to strand a described working change as a PR-less signed line is a *deliberate* bare sync verb. Guard that path at its source. - New typed refusal RepoError::SealWip { subject, verb } in loot-core, a sibling of MisSeal/Demotion, on the ADR 0030/0038 guard+override pattern. Overridable with --seal-wip on both verbs. - ferry seal path: thread seal_wip through ferry::run -> reconcile_onto; the Merge-with-wip arm (the ONLY place a bare ferry seals — land pre-finalizes, adopt folds through fold_line_in) refuses unless overridden. reconcile_onto now returns the sealed subject so the caller reports the seal from the seam that decided it, not a post-state heuristic. - adopt seal path: thread seal_wip through adopt_harbor; refuse before fold_line_in when a described line diverged. The clean fast-forward and redundant-capture drop return earlier, so a no-op catch-up never trips. - Fires ONLY on a described line the sync would actually fold: an un-described one stays the #275 refusal; a break-glass ferry / no-op sync (no live described WIP) is untouched. - On override the verb prints the follow-up-round recovery recipe (SEAL_WIP_RECOVERY, one shared seam); the review "nothing to review" and land "not in the pr-map" paths print it too when sealed_unlanded_anchor() detects a signed line ahead of mirror main with no PR. - tag's ferry passes seal_wip=false — it is not a finalizer, so it refuses rather than silently seal live WIP while cutting a release. - docs/agents/concurrent.md §"One seal path remains" rewritten to describe the guard; CONTEXT.md gains the "Seal-WIP guard" vocabulary entry. Tests: guard at both seams (refuse without / seal with --seal-wip), un-described stays #275, no-wip catch-up untripped, full ferry::run break-glass + no-op pass untripped, sealed_unlanded_anchor signal. Full suite green (loot-core 337, loot-cli 243, loot-first 69); the describe_contention CPU-load flake passes in isolation. 42409371 · dbf3dbe6…diff
  • Evidence (#359): a three-lane wave lands with zero orchestrator surgery The terminal ticket of map #354. Prove the destination live: a real wave of lanes over one shared store reviews and reconciles with no respawn+copy, no hand-merge, no folklore. A hermetic, re-runnable PowerShell script drives the wave through real `loot` verbs; its captured run is committed beside it. - docs/evidence/scripts/wave-proof-lanes-demo.ps1 + runs/wave-proof-lanes-demo.txt (run 2026-07-21, 23 checks, all pass). Seven acts on three lanes forked from one base: (1) three reviews open as pure projections, three review/<lane> refs, main unmoved; (2) an interleaved out-of-wave land moves main mid-wave; (3) a stale-anchor review refreshes byte-identically (op=up-to-date, same sha, WIP intact) — the REFUSE_REVIEW_STALE_ANCHOR respawn family is structurally gone; (4) the #418 seal-WIP guard: bare `loot ferry` and no-arg `loot adopt` both refuse to seal described WIP, and --seal-wip seals on purpose + prints the recovery round; (5) the colliding lane catches up over the moved tip, bounces on the shared path, and reconciles IN-LANE with the resolution folded into its subject (#337); (6) a disjoint lane catches up with no merge cost. - The harbor land-bounce and the #349 "already-projected -> proceed" path live inside `loot-first land` (shells to gh), so they cannot run hermetically. ACT 7 runs `cargo test -p loot-first --lib` live (69 passed) — harbor_guard_*, already_projected_line_ahead_of_origin_reads_as_landable, the gate suite — as the cited proof of the GitHub-facing layer. docs/evidence/wave-proof-lanes.md is candid about what is proven live vs cited. - docs/agents/concurrent.md gains the explicit "Running a wave" framing (#358's "Nothing new": N lanes fire land, queue on the harbor, carry handles the moved tip — no wave verb, no orchestrator), pointing at the evidence run. The script gates on the release build's exit code, not just the binary's presence, so a failed rebuild can't run a stale binary and claim the guard. ac4b6814 · dbf3dbe6…diff
  • land-change: make the skill a thin pointer to workflow.md (#511) b4d26432 · dbf3dbe6…diff
  • Give a lane a session owner, so gc and rm refuse someone else's (#532) 47af2987 · dbf3dbe6…diff
  • Landing moves the shared primary: correct the catch-up command, warn about readers (#618, #616) 62a99602 · dbf3dbe6…diff
  • Guard against committing a path under .claude/worktrees/ (#623) 6bf804b5 · dbf3dbe6…diff
  • concurrent.md: slicing a wave inside one crate - shared vocabulary is the collision surface (#693) 23d33843 · dbf3dbe6…diff
  • a view step refuses over work no change records, and a primary finalize says so on stderr (#436) a3248ede · dbf3dbe6…diff
  • a land from the primary refuses when the git checkout is parked on another branch, so a projection can no longer write across someone else's work (#618) Level A of #618, first half. `reconcile_onto` materializes the reconciled tree over the running position's disk; from the primary that disk is the shared checkout, the one position that also carries a .git. Parked on another session's branch it takes landed content across their working copy — observed live, the primary ended up holding main's content against research/tracker-prior-art, and only luck (their work was committed and pushed) made it harmless. `projection_target` (policy.rs) decides from two facts: the position running the land, and the branch its checkout is on. A lane's projection writes its own tree and leaves the primary for its own `loot adopt` (#265), so the guard is silent there rather than crying wolf on the normal way to work. Off-branch from the primary refuses, naming the branch; --allow-off-main is the break-glass. An unreadable branch (detached HEAD, no .git, a git that failed) proceeds but says the guard did not run, so a broken git cannot mute it silently. The refuse/proceed decision is carried out on Gate::Proceed's new `note` rather than re-derived by the caller, so what gets reported cannot drift from what was decided. Level A's second half — refuse a primary tree dirty with content that is not this land's — is deliberately absent: from the primary the implicit snapshot (ADR 0030) has already captured that dirt into the landing change, and dirt arriving after the review round is already refused by review_currency (ADR 0033). The residual, recorded in policy.rs: a land with no reviewed_version takes Currency::Current unconditionally, so that path is still unguarded. Levels B and C remain open. c1148309 · 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
  • the doctrine catches up with the gate: primary-only splits into materializing and not, and the lander stops catching up someone else's tree (#935) a498c914 · dbf3dbe6…diff
  • the primary stops authoring: describe -m and new -m refuse on a shared floor and print the exact lane take that carries the work onward - ADR 0058's loot lane take built, --allow-primary now a triple (#985) 67df9345 · dbf3dbe6…diff
  • the Network sync entry says what a multi-batch pull actually declares, the measure rule records why its selectors stay direct-child, four restated stories become pointers to one telling each, and the last two hand-rolled repo walk-ups join tests/common (#1314) a906a965 · dbf3dbe6…diff
  • lane gc and lane rm name what holds a lane they cannot reap and give a way back for a lane that lost its lane-id. on Windows a held path that is a directory is now reported as held by a file open inside it or by a process whose current directory is inside it, which holds the directory with no file open, a held file is told a process has it open, and both say loot cannot name the holder, point at Resource Monitor, and say to leave the lane alone and re-run; POSIX, where a current directory does not block removal, is told the path is busy. the refusal for a directory without a matching lane-id now names loot lane rm <id> --deregister, a new flag on rm that drops the registry entry and leaves the directory untouched, keeps the ownership guard, and refuses while the directory still carries the matching lane-id, because there the entry is what lets a retried reap finish, which also answers the ticket question about deregistering a held landed lane: no. a new test spawns a real sleeper process whose current directory is a temp lane and went red with the old advice and with the sleeper removed; its POSIX branch asserts the sweep reaps and was not run here. a deregister test went red with the recovery hint, the still-a-lane refusal or the ownership guard removed and with the directory deleted, and a cli test went red with the flag not passed through. the verb tier width moves to the derived count, and CONTEXT.md and the concurrent and issue-tracker runbooks say to leave a held lane alone and name the recovery. the workspace suite is green (#1760) 16968085 · dbf3dbe6…diff
  • a plain loot ferry in a lane refuses to project anything no land has carried, where it projected the lane unlanded line onto the mirror main every position shares so the next land from any of them pushed it unreviewed: the pass asks what it would project before the ingest and again at the gate seam after the reconcile, since --seal-wip can seal into the line, and loot-first land marks its own pass with RunOpts landing; a lane catch-up that projects nothing runs as before, and the refusal names the land of the PR that carries the lane tip or else the fresh-lane route of #962. the review reap says landed only from a land pass, on main for a projected version otherwise, and signed, no land has carried it for an unprojected one, where it said landed for any signed version. the seal-wip recovery recipe is chosen by position: the primary keeps its follow-up round and a lane gets the fresh-lane route, one const shared with the #2314 refusal. reproduced through the 0.4.24 binaries in a scratch repo with its own lanes (the advised ferry projected 2 lane changes and printed landed) and checked through the lane-built binary (refused, mirror main unmoved, lane catch-up still runs, reap and recipe reworded). the lane-simulated lands in the ferry tests now run as land passes, and 2 primary reap pins read on main. pinned by six new tests; red with each piece undone, counts read each time, each restored green over the ferry tests: the guard off (69 passed and 3 failed), a land pass refused too (61 and 11), every lane pass refused (70 and 2), the seam ask dropped (71 and 1), the PR route dropped (71 and 1), the old reap word (69 and 3), the recipe always the primary one (71 and 1) or always the lane one (71 and 1). ADR 0039 amended, CONTEXT.md, concurrent.md and the land-change skill updated. no format constant, codec byte or migration moves. the workspace suite is green (4442 passed over 139 binaries, 13 ignored) (#2366) dac3385d · dbf3dbe6…diff
  • review sweep 12 over the lane projection guard: items 1 and 2 did not reproduce through the binaries in a scratch repo with its own lanes, since a position graph is its own lineage-filtered load (ADR 0022), so after a lane signed K a primary plain ferry projected nothing and minted no mark for K and the lane ferry still refused, and a lane with nothing of its own caught up while a sibling seal sat unmarked; the guard question stands and both scenarios are pinned. a lane ferry --seal-wip over described work refuses before the reconcile, where it sealed into the catch-up and then refused, leaving a sealed line no PR carries (reproduced on the landed binary). the reap never says landed, since the push that follows a land pass can still be refused, and says projected for a marked version, which a mark is and on main is not. the ferry verb landing false is pinned through the spawned binary. a refused pass carries its notes. the primary recovery recipe names --allow-primary for the verbs that need it there. the #2314 and #2366 lane refusals print one route rule, lane_landing_route, which sends a lane to its PR land only when the PR carries the tip with nothing captured on top. ADR 0039 amendment, ADR 0092 (--skip-gates), CONTEXT.md and concurrent.md corrected. red with each piece undone, counts read each time, each restored green: the verb flag flipped (0 passed and 1 failed), the seal-wip refusal dropped (75 and 1), the reap saying landed (73 and 3), the route ignoring captured work (75 and 1), the route ignoring the PR (75 and 1), notes dropped (75 and 1), the lane recipe on the primary (75 and 1), the guard off (71 and 5), every lane pass refused (73 and 3). the scripts MAINTAIN ask is its own scripts commit. no format constant, codec byte or migration moves. the workspace suite is green (4450 passed over 140 binaries, 13 ignored) (#2370) 18ea6db5 · 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.