Changes touching this path
- loot-first in Rust: land policy behind a Forge seam (#218) — new loot-first bin crate (ledger/forge/policy/orchestrator), in-process Workspace reads via loot-cli lib split, typed pr-map owner + shared ferry::WipState; every policy decide-tested against a fake forge. Build+test only; shadow-run, real land, and ps1 deletion remain operator-gated.
58c28888 · dbf3dbe6… - loot-first: retire the ps1 (#218 tail) — delete tools/loot-first.ps1; land now REFUSES a lane not on the git-main-tracked dock (the false-success gap that no-op'd the first land), tested; workflow.md + ADR 0033 point at the Rust binary and in-process reads
56c25bfb · dbf3dbe6…diff - Spawn DevX (#232): loot lanes observability, --ticket lane spawn, wayfinder claim-to-lane
f0a885c2 · dbf3dbe6…diff - The harbor: an on-demand lock serializes landing to git-main (#229, closes #195)
8adf34a2 · dbf3dbe6…diff - #243 D2: loud mirror-drift guard on status/review/land
Warn when the loot mirror's projected `main` has drifted from the
checkout's real `origin/main` — the signal that would have stopped PR
#241 before it projected a revert of landed work.
- policy.rs: pure, unit-tested classifier. `Ancestry {Same, MirrorBehind,
Diverged}` + `mirror_drift_warning(mirror, origin, ancestry)` rendering
the mandated wording ("loot mirror is behind origin/main (X vs Y) —
reconcile before landing"), with the loudest "DIVERGED — do NOT land"
for the #241 shape. No "ahead" variant: an unpushed-ahead mirror tip is
not an object the checkout holds, so it folds into Diverged.
- orchestrator.rs: best-effort, side-effect-free reader (`mirror_drift`
/ `warn_if_drifted`) — reads the local `refs/remotes/origin/main` only
(no network), classifies via `git merge-base --is-ancestor`, and prints
loudly to stderr in `status`, `review`, and `land`. A guard never fails
its host command.
Deliverable 1 (the one-time mirror/dock re-baseline) is not in this
commit — it needs a clean fork-drop/adopt path loot does not yet expose.
cadc5ebe · dbf3dbe6…diff - feat(loot-first): tag-push ferry verb — release tags reach GitHub-projected main (#256)
Add `loot-first tag <version> [-m msg]`: mint an annotated release tag on the
projected, sealed-free git-mirror `main` and push it to GitHub through the
Forge seam so cargo-dist's release.yml fires — no break-glass raw git.
- loot-cli ferry::tag_projected_main creates the tag object in the harbor
mirror at refs/heads/main (sealed paths are already omitted by projection,
so a release tag never widens the public boundary); refuses on an unbound
mirror or an existing tag.
- loot-first orchestrator::tag holds the harbor lock across project → main FF
push → tag push, matching land's serialization; execute_tag_push is split
out and tested against FakeForge (main first, then the tag; a diverged main
aborts before the tag is pushed).
Closes #256.
ba29acec · 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 - finalize refuses to sign an un-described change, and status's hint names describe -m (#174)
`status`'s hint on a dirty tree pointed at `loot new`, but `new` is
capture-*then*-finalize: following it signed the dirty tree in one stroke under
the `(working change)` placeholder, skipped the review lane, and rode to git
main as a permanent commit subject. The hint was the trigger; `new`'s
willingness to sign a nameless change was the foot-gun.
- The hint names `describe -m` — capture *without* finalize, the first verb on
dirty work.
- `finalize_capturing` refuses an un-described change (no message, or the
stored placeholder). It sits *after* the capture, so edits are held and only
the signature is withheld, and *below* the empty/duplicate drop, so a bare
`new` on a clean tree stays a no-op. Both callers inherit it — `loot new` and
`loot-first land` — so 'describe before landing' is enforced, not remembered.
- Deriving a subject from changed paths was rejected: it mints plausible history
nobody wrote, and loot has no changed-path concept to derive from.
- The placeholder now has one mint (`working_message_or_placeholder`) and one
test (`is_undescribed`); 10 stray literals collapsed onto them, so the guard
cannot silently stop firing on a drifted copy.
ADR 0030 amendment + CONTEXT/workflow/evidence updated. Residual (dock merge and
ferry sign the operator's authored work in passing) named in the ADR and filed
as #275.
a8fb4684 · 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 - Run the pre-land gate in the landing position's tree, not the shared root (#287)
orchestrator::paths() derived its one `root` from ws.dot() -- always the
SHARED store's .loot -- so a land from a lane ran `cargo test` (and the
relay push) in the primary checkout's possibly-stale tree. The #284 land
was validated only because the operator had run the suite in the lane by
hand. Same shared-vs-position confusion as the #229 mirror-gitdir and
gh-cwd dogfood fixes.
Fix: split Paths.root into `position` (Workspace::root(), new accessor:
the lane dir from a lane, the checkout on the primary) and `checkout`
(the shared .loot's parent). The pre-land cargo test now runs in
`position` -- the tree about to be signed.
relay_push decision: it now also runs in `position`, deliberately. loot
push discovers .loot from its cwd and a lane's .loot points at the shared
store, so the push ships the same store from either dir -- but from the
position it reads the tip this land just signed (has_unsigned_tip) and
records its push op in the landing position's own oplog instead of
writing the primary's (ADR 0034 single-writer). The spawned binary is
now resolved beside the running loot-first (one cargo build produces the
pair) with the old target/release fallback, because a lane tree carries
no release build of its own.
The drift guard's git reads and init-hook stay on `checkout` on purpose:
only the primary has a .git; run from a lane dir every probe would fail
and silently mute the guard.
Tests: loot-first regression pair (lane position vs checkout, primary
degenerate case) built on a real spawned lane via temp-dir helpers;
loot-cli locks root()-vs-dot() semantics at the Workspace seam.
46f1b598 · dbf3dbe6…diff - primary catch-up: adopt landed main (post #287)
7e3bbe42 · dbf3dbe6…diff - Refuse the review catch-up fold; never strand a described WIP (#292) (#302)
`loot-first review` (and `loot ferry --with-wip`) run a catch-up ferry
before projecting the WIP. When git `main` moved *under the lane* (a
concurrent land) while a live, described working change sat on the tree,
`reconcile_onto` captured and FINALIZED that WIP as a merge parent, folded
it into a "ferry: reconcile git main" change, and minted a fresh empty
working change. Review then projected the empty change and reported
"nothing to review" — the work signed-but-stranded: unreviewable,
unlandable (land finalizes a *working* change), and with no op-log entry
to undo. #257 had to land via break-glass git PR.
Fix: thread `preserve_wip` into `reconcile_onto`/`reconcile_capture`,
set only by the review projection (`with_wip`). Under it, a catch-up that
would fold real local work refuses (REFUSE_REVIEW_STALE_ANCHOR) BEFORE
finalizing, leaving the WIP a live, unfinalized working change (only the
signature withheld — the capture persisted). The no-op paths (main where
we left it) still project un-described WIP untouched, and a plain
ferry/land (with_wip=false) keeps the existing fold.
Defect 2: the review's in-process ferry never passed through cmd_ferry's
op record, so an honest catch-up (a clean fast-forward onto landed main)
left no op to undo. The orchestrator now records a "ferry" op after the
review pass, mirroring cmd_ferry.
Tests: a new reconcile_onto regression proves the described-WIP fold is
refused and the WIP survives; the #288 resurrection test moves to the
plain-ferry reconcile path (review no longer folds); the #275 review test
now asserts the #292 refusal instead of the old name-then-fold. Full
`cargo test --workspace` green.
Git-Author: Connor Miller <53197564+Connor-Miller@users.noreply.github.com>
3e83f40ediff - Catch up to git main 809ddfe: #260 smoke default, #271 arm64 build, loot diff (#1), ADR 0034 dock retirement (#253)
587684dd · dbf3dbe6…diff - #316: land refuses a resolve-placeholder subject, preserving git main's described subject
A same-path conflict bounce is resolved via `loot resolve`, which mints the
change's message as the placeholder `resolve conflict at <path>` (engine.rs).
Re-running land finalized that tip, so git main's commit subject became the
placeholder instead of the change's real described subject. Mirror the #174
`(working change)` precedent: land_gate now refuses a working change whose
subject carries the placeholder prefix and names the fix (`loot describe -m`).
15b58d08 · dbf3dbe6…diff - Workspace accepts its clock; lane flag-gate goes pure so cmd_* tests never touch a real .loot (#322)
20ac82e1 · 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 - Land proceeds when the signed line is already projected but unpushed (#349)
08a05020 · dbf3dbe6…diff - Mis-seal gate covers loot edit and audited signing chokepoints (#353)
e4e3a804 · dbf3dbe6…diff - Bound the already-projected trailer walk to origin/main..main (#367)
The #349 escape hatch's third condition grepped the Loot-Change-Id
trailer over ALL history reachable from mirror main. A tip already
pushed (trailer reachable from origin/main) plus a sibling's unpushed
projection ahead of it passed all three conditions - and the land would
push the sibling's projected-but-unlanded line: the green lie the
reachability condition exists to block. The walk is now bounded to the
unpushed span origin/main..main, per the spec's wording: projected and
reachable from mirror main but not pushed. Adds the missing test:
tip already pushed + sibling projection ahead -> refuse.
76b32dd8 · 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 - CLI: thread typed engine errors through Workspace so slugs travel (#430 follow-up)
#430 laid the CliError plumbing but the taxonomy stayed inert on live verb
paths: workspace.rs methods returned Result<_, String>, stringifying RepoError
before the verb's `?`, so every engine variant collapsed to code "error" at the
--json boundary. Convert that boundary: impl Workspace / Snapshotted / lanes
methods return Result<_, CliError>; engine (self.repo.*) sites use
.map_err(CliError::from) so code() is preserved; the MisSeal/SealWip guards
propagate the typed RepoError instead of .to_string(). Non-RepoError sources
(io, format!, bare strings) stay code "error", messages byte-for-byte intact.
Workspace::open/init stay Result<_, String> (open_repo() stamps the CLI-level
no_repo slug), keeping the loot-first ripple to two boundary conversions. New
emit_snapshot test proves a real RepoError::UnsupportedFormat reaches --json
stderr as {"code":"unsupported_format"} on `loot apply <garbage>`, with the
non-json `loot: <message>` line unchanged.
Also: share loot_core::verdict::json_string (drop error.rs's byte-identical
twin) and collapse the emit_snapshot run_with_code/run_stderr_with_code helpers
into one run_streams. Known residual: the ferry.rs bridge path still flattens
engine errors to "error" (deliberate scope boundary — git2 errors ride its
String contract). cargo test --workspace green.
627897cb · dbf3dbe6…diff - Lift the wip review-lane ledger out of ferry.rs into ledger.rs
WipState/WipEntry — the `wip` ledger of in-flight review-lane projections —
lived in ferry.rs (the git-bridge file) though they are a typed on-disk-format
owner with nothing to do with git mechanics, read cross-crate by the loot-first
orchestrator. Move them into ledger.rs, joining PrMap as the two typed owners of
the `.loot/git-mirror/` ledgers (both sharing the review_handle rule). The old
"it lives in ferry because ferry writes it" rationale never held — PrMap's writer
(the orchestrator) doesn't own it either; ledger.rs does, because loot lanes reads
it and the workspace dependency points this way.
Not a pure relocation: ferry reached straight into `WipState.entries` (a ~50-line
reap loop, an upsert, and a lookup), so the move designs a real interface. The
entries vector is now private, driven through:
- record(entry) — the upsert (replaces ferry's retain-then-push)
- retain(pred) — for ferry's reap loop, which stays in ferry because its
liveness predicate reads positional Workspace/git/report state
- entry_for(change, dock, owner) — the lookup, parallel to PrMap::lane_for
- reviewed_version — kept (loot-first's read), now delegating to entry_for
- parse / encode — the format, encode now pub (PrMap's caller-writes pattern)
WipEntry is a pub-field data carrier like the sibling PrLane. The wip unit tests
move with the type into ledger.rs's test module. loot-first imports WipState
from ledger now; ferry keeps only the projection that writes it.
ferry.rs: -140 lines. Full workspace suite green.
0579b5f4 · dbf3dbe6…diff - Gate loot push on the relay's format major; carry a failed relay push in the land verdict (#519)
6c5cb6e0 · dbf3dbe6…diff - Diagnose a premature finalize, and stop a stray ferry silencing the hint (#512)
b16d77f2 · dbf3dbe6…diff - Guard against committing a path under .claude/worktrees/ (#623)
6bf804b5 · dbf3dbe6…diff - loot-first land: report the provisional-branch reap instead of swallowing it (#625)
ed9ad0c2 · dbf3dbe6…diff - Guard against committing a path under .claude/worktrees/ (#623) (conflict resolution: crates/loot-first/src/orchestrator.rs)
132887df · dbf3dbe6…diff - GitMirror: the bridge owns its own layout (#665)
b0461c4c · dbf3dbe6…diff - edit guard agrees with status; premature-finalize remedies tell the truth (#682, #683)
c78e9a39 · dbf3dbe6…diff - Sweep the rust-1.96 clippy debt; document the land-holds-the-binary hazard (#667, #681)
26cfbaa9 · dbf3dbe6…diff - edit guard agrees with status; premature-finalize remedies tell the truth (#682, #683) (conflict resolution: crates\loot-cli\src\workspace.rs)
d8757f2c · dbf3dbe6…diff - the pre-land perf gate: counters block at 0 percent, timing warns, and a lane pins itself (#637)
52a2341e · dbf3dbe6…diff - loot-first: exec the perf gate from a binary built from the position (#712)
The gate measured in-process, exercising whatever loot-core was compiled into
the running loot-first.exe while PinnedCheckout stamped the point with the
position revision — and nothing rebuilds before the gate, so a stale binary
reported perf: no move for a revision that provably moved a counter (#638's
live find). The alarm failed OFF, silently.
Now loot-first land builds loot-perf-gate from the landing position's own tree
(cargo build --release --locked -p loot-perf --bin loot-perf-gate — cargo's
staleness check is the provenance proof) and execs it. A land mode on the bin
(--land <change> --stash <file>) keeps the split the record demands: the
verdict decides before cargo test, and the would-be record line is stashed
outside the tree for the land to append verbatim as its last act, so the
recorded bytes are produced end to end by the position's own code. loot-first
deliberately no longer links loot-perf: no compiled-in harness, no way to
measure the wrong code.
--locked keeps #698 held: a stale Cargo.lock is refused, never rewritten into
the signed change; target/ stays outside the seal (verified live — loot status
clean across the build). Measured cost: +0.16 s build on an already-built
position, 3.1 s when the gate's own crates changed, 12.6 s from a stone-cold
lane — against the unchanged 2.2-2.6 s measurement and the multi-minute
cargo test that follows.
0d9c6195 · dbf3dbe6…diff - land gate: never rewrite the tree being signed, never trust a stashless verdict (#698, #723)
132e06ba · dbf3dbe6…diff - loot-first: land folds a pre-finalized change so the perf point rides it (#725)
d98f26f6 · dbf3dbe6…diff - loot-first: land folds a pre-finalized change so the perf point rides it (#725) (conflict resolution: crates\loot-first\src\orchestrator.rs)
30eadcdc · dbf3dbe6…diff - the perf record becomes a directory of one-file points, so two lands no longer collide on the last line, and a point names its PR (#740)
The record was the repo's worst conflict surface: every land appended to the tail
of docs/benchmarks/series.jsonl, so two lands always collided on the last line —
6 of the 38 conflict-resolution commits on main name that one file. It is now
docs/benchmarks/series/<change>.json, one file per point, so lands touch disjoint
paths. The pre-#740 jsonl stays where it is and is read as the older head of the
same series; migrating it would have been one last whole-file rewrite of exactly
the file this was about.
A point also carries the PR it landed under. A bounce recovery mints a new change
id, so one shipped thing can leave four points, each measuring a genuinely
different tree — none may be suppressed, and the PR is what says they are one
thing. It is also the record's only join back to git log: landed_change is the
durable change id, while the projected trailer carries the version id, so no id
in the record has ever matched anything in git.
The land's write is still a verbatim copy of the gate binary's stash (#712); the
stash itself stays a single-line jsonl, since it is a temp handoff merged by
nobody. The file-name rule is duplicated in loot-first, which may not link
loot-perf, and pinned by a test on both sides.
c393beef · 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 perf record becomes a directory of one-file points, so two lands no longer collide on the last line, and a point names its PR (#740)
The record was the repo's worst conflict surface: every land appended to the tail
of docs/benchmarks/series.jsonl, so two lands always collided on the last line —
6 of the 38 conflict-resolution commits on main name that one file. It is now
docs/benchmarks/series/<change>.json, one file per point, so lands touch disjoint
paths. The pre-#740 jsonl stays where it is and is read as the older head of the
same series; migrating it would have been one last whole-file rewrite of exactly
the file this was about.
A point also carries the PR it landed under. A bounce recovery mints a new change
id, so one shipped thing can leave four points, each measuring a genuinely
different tree — none may be suppressed, and the PR is what says they are one
thing. It is also the record's only join back to git log: landed_change is the
durable change id, while the projected trailer carries the version id, so no id
in the record has ever matched anything in git.
The land's write is still a verbatim copy of the gate binary's stash (#712); the
stash itself stays a single-line jsonl, since it is a temp handoff merged by
nobody. The file-name rule is duplicated in loot-first, which may not link
loot-perf, and pinned by a test on both sides. (conflict resolution: crates/loot-first/src/orchestrator.rs)
8762bf36 · dbf3dbe6…diff - the repo publishes README and both licenses, and land learns to sign a publication (#750)
7ddc9629 · dbf3dbe6…diff - the pr-map ledger heals on discovery: status reaps a row only where origin/main proves its change published, and prints the durable id in letters (#624)
1c459083 · 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 - every loot-first failure reaches the exit status, and a land whose fast-forward is refused no longer reports a landing (#832)
f641cfe4 · dbf3dbe6…diff - a lineage catch-up brings the objects and keys its nodes reference, and a ferry that dies mid-projection names the oid in hex and the recovery (#778)
The filed hypothesis was that a change node can become visible before the
objects it references are durable. It is wrong: save_to already writes objects
before the graph that names them, and that order is deliberate and commented.
The real defect needs no race at all. The object store and the keyring are
loaded exactly once, at load_from, while the shared graph is deliberately
re-read mid-process by ingest_shared_lineage (the 265 catch-up primitive). So
any peer that finalizes after this process opened contributes a node whose
bytes and keys this process will never hold. A land guarantees a long window
there: it opens the workspace, runs the pre-land tests, and only then ferries,
so the ferry aborts mid-projection while holding the harbor lock.
ingest_shared_lineage now refreshes objects and custody after it re-reads the
graph, and load_from reads in the reverse of save_to write order, so a reader
never blends a newer graph with an older object scan.
Also: an Oid renders as hex instead of a 32-element decimal byte array (Debug
full width, error strings short), and a mid-ferry abort now names its recovery
with flags checked against the verbs that parse them. hex moved from loot-core
to loot-codec because RepoError is defined in loot-codec and the dependency
runs one way only; loot-core re-exports it, so every call site is unchanged.
0ba6d577 · dbf3dbe6…diff - a ferry refusal reaches a machine consumer as itself, not as a generic error (#867)
Every seam inside `ferry::run` returned `Result<_, String>`, so a pass — which
is mostly other people's failures — flattened all of them before the CLI could
emit them. A typed `RepoError` the engine had already coded, and the reconcile
executor's `Plan::Refuse`, both reached a `--json` consumer as the generic
`error` code. The prose was fine; the machine channel was the loss.
The nine phase signatures now carry `CliError` end to end, which restores the
engine's own slugs for free: engine calls propagate with a bare `?` instead of
`.map_err(|e| e.to_string())`, which is precisely what was flattening them.
Exactly three slugs join the frozen ADR 0023 contract (amendment recorded):
`undescribed_parent` and `foreign_paths` from a new `reconcile::Refusal::code`,
built alongside the unchanged wording by BOTH executors — `apply_plan` and
#866's adopt catch-up — so two verbs sharing one table cannot become two
taxonomies; and `git_mirror` where ferry lifts git2, deliberately coarse
because the bit a consumer acts on is retry-the-environment versus obey.
`git_mirror.rs` stays stringly inside: inventing a loot taxonomy for git2's
errors would be fiction.
`rollback_note` keeps the abort's code and grows only its message — a rollback
is context about that failure, not a different one.
Every refusal's wording is byte-identical; the human channel does not move.
Ferry's own handful of refusals stay honestly generic, and so do the tier's
remaining flatten sites, which graduate one at a time when a consumer needs
them.
The one loot-first line is the seam back out: its orchestrator keeps a String
channel, so `From<CliError> for String` makes that a `?` rather than a
hand-written `.to_string()` at every call.
Found while pinning the codes: the uncaptured-WIP ferry test refuses with
`foreign_paths`, not `undescribed_parent` as it reads. Both wordings offer
`describe -m`, which is why prose was never a taxonomy.
4fc73479 · dbf3dbe6…diff - a review with no PR is named for what it keeps: --no-pr replaces the dry-run that was never dry (#861)
ad590be9 · dbf3dbe6…diff - the gate proves the tree the position holds, and main receives the merge: a land refuses a lane behind landed main (#939)
main went red at 3597c9d with two lands that each passed their own gate: #920
added a caller (`Ignore::parse`) in one file, #921 deleted the callee in
another. Disjoint edits, no path in common, so loot per-path conflict detection
had nothing to look at — and each land compiled the tree its LANE held while
the ferry projected the MERGE, a tree nothing had ever compiled.
The repair is one line: #920 test takes `parse_recorded`, the sole parse since
#921, which is also what it means (it pins the shipped file behaviour, not a
live-tree strictness that no longer exists).
The guard is the rest. `Workspace::behind_landed_main` is the read-only half of
the `covered` question `adopt` already asks, and `land_gate` refuses on it,
naming what it is behind and pointing at `loot adopt` + a re-review. The land
does not adopt for you: a catch-up merges, a merge can conflict, and burying
that inside the verb whose promise is shipping the reviewed change is the wrong
place for it. The same question is asked again after the gate and before the
finalize, for the sibling land that completes inside a multi-minute cargo test
— there rather than inside the harbor lock, which would be airtight and would
serialize every concurrent land behind one full test suite (the ADR 0036 cost).
The residual window is the git-quiet finalize plus the lock acquisition;
workflow.md names it rather than papering over it.
510a5be7 · dbf3dbe6…diff - the refusal stops naming the command that refuses: #939 catch-up remedy is the one loot adopt declines (#944)
#939 shipped a guard whose remedy was `loot adopt`. Measured minutes later: in
the exact state the guard fires in, that command refuses — a catch-up finalizes
the working change (it becomes a parent of the merge, ADR 0034), and finalizing
the described change under review PR-less is the one thing adopt declines. The
guard named the #921 failure shape and then repeated it.
This is the honest repair, not the fix. The refusal and workflow.md now warn off
`loot adopt`, and name what works today: re-do a small change in a fresh lane
(one change id start to finish), or `adopt --seal-wip` + a follow-up round.
The clumsiness of the second is structural, not wording: every route out of the
post-catch-up state changes the change id the pr-map row is keyed on, so the PR
stops recognising its own work (#832 Currency::Rotated). #944 carries the three
measurements and the fix that removes the dance — gate AFTER the ferry converge,
whose carry_line keeps change ids by construction (ADR 0039) — with the land
throughput trade that makes it a decision rather than a patch.
a5e4d560 · dbf3dbe6…diff - a release tag verifies and mints: the ferry leaves the tag path, so cutting one materializes nothing and names what it leaves behind (#933)
4979e4fc · dbf3dbe6…diff - a land runs from a lane: the position gate replaces #618's checkout guard, and level A is deleted rather than kept beside it (#932, ADR 0050)
b629f752 · dbf3dbe6…diff - a skipped gate leaves a mark, and landed main gets a canary: the single test gate stops being silent (#642)
Three questions, decided.
1. A test/lint CI workflow — YES, but exactly one, and it runs on landed main
rather than on PRs. The pre-land gate IS the design real gate (loot is the merge
authority, git main is a projection), so a job re-running the same suite on every
review round duplicates it, adds latency, and covers only paths nobody takes.
What it covers instead is SILENCE. The ticket named three ways past the gate that
leave no test run behind; a fourth appeared today and cost a morning — #920 and
#921 each passed their own gate and together left main red, and nobody learned
until the NEXT land failed on a breakage it had not caused. #939 closed that
window; this says so in minutes if anything reopens it. It uploads no artifacts,
so it does not touch the quota blocking releases (#835, #909), and docs-only
pushes are skipped, which is what --skip-tests is documented for.
2. clippy — NO, not yet. ~30 pre-existing warnings mean -D warnings lands
permanently red and warn-only lands permanently yellow, which teaches everyone to
ignore the one signal the workflow exists to make trustworthy. Same argument that
kept an audit gate out of site/ (#916). It wants a cleanup pass and then its own
decision.
3. --skip-tests recording itself — YES, on the verdict line, as tests=ran or
tests=SKIPPED. Shouted like relay=FAILED and branch=FAILED (#519, #625), because
the field is saying the change above it was never proved to build, plus a block
below that cannot be scrolled past. The block also names the thing that is easy
to forget: the perf gate skips WITH it, so such a land writes no point and the
previous one stays the baseline. Before this the only trace of a skip was one
line minutes earlier, above everything anyone was watching for.
Not the commit trailer the ticket offered as the alternative: the projected
commit trailer set is part of the ferry ingest contract, and adding to it is a
wire-adjacent change that wants its own ticket rather than riding this one.
49e4554a · dbf3dbe6…diff - the second remedy was wrong too: the behind-main refusal names the one route that works, and says when to start it (#962)
41b04104 · dbf3dbe6…diff - ferry stops calling an unpulled change gc'd: where a relay could still produce it, the ingest refuses and names loot pull (#1007)
e896ee3b · dbf3dbe6…diff - the harbor lock measures silence, not duration: a long-running holder beats a heartbeat, so a land may hold it for minutes and a crashed one is reclaimed in seconds (#1013)
b9376a30 · dbf3dbe6…diff - a land gates the tree it publishes: the merged-tree gate runs inside the ferry pass, so a lane behind landed main queues and converges instead of being refused (#1013)
4d9d4c0f · dbf3dbe6…diff - a store knows which machine materialized it: an arrived copy refuses to record until loot rehome, so a bootstrap cannot silently capture every sealed path as deleted (#986)
4c15a2ca · dbf3dbe6…diff - a tag stops firing a workflow that can only fail: the GitHub release pipeline retires, and every live pointer to it says where publishing went (#1025)
f9a18c18 · dbf3dbe6…diff - an ADR number stops being picked from a stale position: a land reads landed main and refuses a number it already holds, before it signs the subject that would name it wrong (#1080)
aa99ef0a · dbf3dbe6…diff - a hygiene check runs at the moment that makes it true: the two silent-damage checks move into every land where --skip-tests cannot reach them, the CI net they were said to have turns out not to exist, and the ADR number is re-asked under the lock that makes the answer hold (#1082, #1105)
cd3021b8 · dbf3dbe6…diff - the ADR-number guard fetches before it answers: both asks read a fresh origin/main instead of two common-mode stale refs, and six citations the d47c498 sweep missed now say 0064 (#1099)
64d54660 · dbf3dbe6…diff - a hung fetch can no longer hold the harbor: the ADR guard and the drift check share one fetch_main_or_warn with a 30s process-level bound, and ADR 0065 stops undercounting what the under-lock ask costs (#1115)
d4804b0a · dbf3dbe6…diff - the peek stops telling a fresh second machine there is nothing to receive, the glossary catches up to #1114, one predicate decides standing self-custody, and the shared warn gets its provenance back (#1120)
e9544471 · dbf3dbe6…diff - the format-skew refusal tells the caller the host is behind and their repo is untouched, and scopes the redeploy to whoever operates the host (#1136)
74f719c6 · 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 - a land builds the site and weighs the eager bytes of every published surface, so a redesign cannot spend the byte lead in silence, and the four places that called site/ ungated now say what is true (#1241)
674361ad · dbf3dbe6…diff - a seal to a holder list that excludes its author refuses and names what nobody could ever open, --allow-lockout is the deliberate way through, and ADR 0038 records why the gate refuses rather than filing a key for the author (#1249)
bbb858b0 · dbf3dbe6…diff - an offline land skips the site gate loudly instead of refusing, the verdict grows a site= field read like relay=, and only a provable npm network failure ever earns the skip (#1251)
3326a228 · dbf3dbe6…diff - the sweep fix-up lands: a land translates a lockout refusal into the lane ceremony that actually works, the seal gate stops refusing over a stale copy the capture will never seal, and the surface prune stops deleting a copy of a path the line still holds (#1271)
68230ea5 · dbf3dbe6…diff - the consent slices become one Allowances struct a swap cannot compile against, the lockout gate calls the engine predicate it used to mirror, and the site gate steps destructure so a third step stops compiling (#1272)
ac6e2096 · dbf3dbe6…diff - the land verdict becomes a value: one Verdict struct renders the line and its operator blocks so every combination is a table test, and the ferry re-gate closure body gets a name without leaving the pass (#1293)
a89e5aca · dbf3dbe6…diff - the land walks its position once instead of three times, the three text gates read one corpus that keeps their root sets distinct rather than flattening them, and the vacuity rule two of them had quietly re-implemented inline becomes the one rule hygiene was always documented as holding
a499f9c7 · dbf3dbe6…diff - the land reads its position once and hands that one corpus to three gates that still run in ADR 0065's order, undoing the two-and-a-half-fold read the previous change caused by leaving a walk inside each gate while claiming there was only one
ec75201e · dbf3dbe6…diff - the perf gate stops reporting a run that measured nothing as a clean pass, and the load probe stops letting one bursty draw discard the measurement, because the two bracketing reads were folded by max and the second sat in the harness's own wake (#1389)
542d5442 · dbf3dbe6…diff - the three surfaces describing the perf gate stop disagreeing about what an absent metric means, because an explained absence is reported and exits 4 exactly as an unexplained one does, and each surface now points at the one that owns a fact rather than restating it — including the exit code itself, named once in loot-perf and read from that name by both the gate binary and the land (#1403)
dc3ccf75 · dbf3dbe6…diff - a --skip-tests land that converges stops publishing a merged tree no gate ever saw, because the eight gates are one table now and each declares when it runs, so the re-gate asks that table per gate instead of reading the perf gate's return value as if it spoke for all eight (#1408)
a998f52b · dbf3dbe6…diff - deleting a named read on Workspace stops being free, because the 377 places that reached past it into the engine and the store now go through those faces instead — repo() no longer exists, store() is module-private, and the three extracted children share their fixtures by name rather than through the parent's private test module (#1411)
c454db04 · dbf3dbe6…diff - the land's shelling gates stop building their own child processes, because every command they run is now a value handed to an injectable spawner and read back as one outcome — which is how #1251's offline-degrade posture, the one gate result that lets a land proceed, finally gets a pin that needs no npm (#1467)
8a773eb9 · dbf3dbe6…diff - the reader seam's own doc stops telling a loot-core author that content can only be read through it, because the sentence #1464 wrote into four places and #1471 corrected in three is now enumerated in ADR 0007 as one thing that changes together, and spawn.rs likewise stops claiming every command every gate runs while GATES row 0 shells out to git beside it (#1486)
b6657789 · dbf3dbe6…diff - the land gate stops watching only what a run produces and starts watching what it does, because two work counters now ride the untimed batch it was already measuring so they cost no fixture, no repetition and no workload_id move and orphan no baseline, while the twelve opt-in timings stay ungated on the argument that a timing is dropped by the load policy exactly on the busy machine a land runs on, and the count feature stays off by default because turning it on in a manifest was measured to change the loot.exe a plain workspace build produces (#1602)
f641ea1d · dbf3dbe6…diff - the skew this ticket reported never happened, and the trap that produced the report is the finding: a land pushes with the loot.exe sitting beside the running loot-first.exe, which is the primary's last cargo build --release, not the tree being landed - the only build a land performs is the perf gate binary in the position. So the land that RAISES FORMAT_MAJOR pushes with the major before it, and reading the constant out of main's source is not reading it out of the pusher. Verified by timeline rather than argued: both pushes ran at 23:26 and 00:08 while the primary's binaries date from 01:28 the next morning, so the pusher predated all three lands and wrote major 11 to a relay serving 11. Two further claims in the ticket were false and are refuted by the tree rather than reasoned away. stow DOES version-check, at the frame decoder before a single object is stored - handle_stow to RelayStore::stow to DagRepo::stow_from, whose first statement is Frame::decode and whose UnsupportedFormat maps through NetError::Engine to a 400 - and it was already pinned by a green test when the ticket was filed, so the acceptance criterion asking for it was asking for something that shipped. Which means the observed 200 and its byte count are themselves proof the marker was readable, since StowReport is constructed only after stow returns Ok and put_version writes the compile-time constant unconditionally. What survives is worth landing on its own merits and not as this incident's cause. The fail-open branch stops being silent: relay_can_read_our_writes returns Checked or Skipped carrying the probe's own failure string, push returns that beside the report, and cmd_push prints a warning to stderr where the refusal would have stood - a value on the result rather than a log line, because the broken pairing was a promise asserted by the path that fires and inverted by the path that does not, and a caller may decline to say a returned thing but cannot fail to be told it. The new test is the first that watches the wire at all: every prior skew pin simulated one by moving the CLIENT to FORMAT_MAJOR plus one, a value production never has, and none of them observed a byte - this one stubs a relay that reports a chosen major, asserts the request log is exactly the probe with zero bytes sent, and carries its positive control in the same function so that no bytes recorded and this harness cannot record bytes stop being the same observation. Two mutations were each verified present in the file before being run red. And the doc comment on relay_push now names the trap rather than the code, because the code was correct every time (#1768)
25a5a122 · dbf3dbe6…diff - the catch-up stops leaving its own consequence to a runbook the reader may never have loaded: adopt now prints the git step IT JUST CREATED, and only when it actually moved the tree and the checkout is measurably behind - naming the paths it wrote, which is exactly the set needing the proof. the ancestry is judged on the LOOT GRAPH through the mark map rather than by asking git, because git cannot answer it: the checkout has not fetched the landed commit. there are two arms by position, since which git command is correct depends on where HEAD is, and neither arm ever recommends git before a catch-up, because git-first WEDGES the primary and loot-side-first is and stays the order. and the land closing note is narrowed so its final word about the primary cannot read as being about the checkout: it now says which thing the fast-forward moves, says HEAD does not move with the dock, and names the verb that says the rest. the finding that changed the procedure is bigger than the ticket: the skill had the FETCH AFTER THE PROOFS, and a land pushes main FROM THE MIRROR, while the only fetches a land runs in the checkout are its pre-land drift and ADR guards - so at the exact moment an operator needs this procedure their origin/main is stale by that very land, every landed path reads as differing, and the freshly landed perf point does not resolve at all. followed literally the skill produced the precise wrong conclusion it exists to prevent, so the fetch is hoisted with the reason recorded, and the printed block fetches first by construction. twelve mutations with counts read, of which two are worth the reading. the tenth is a CONTROL: the skill quote wraps across two indented lines, so with the whitespace flattening removed the citation pin reddens over LAYOUT rather than over words, which is what makes the flattening load-bearing rather than decorative. and the fifth is the one to learn from - the lane guard test PASSED WITH THE GUARD DELETED, because the lane was silent for an unrelated reason, landed main having been recorded on the primary so the lane lineage-filtered graph had never seen it and the ancestry answered false. a pass that survives deleting the thing under test is not a pass, so the arrangement was rebuilt with landed main recorded THROUGH the lane and two explicit preconditions proving the lane reaches the checkout and can answer the ancestry at all - after which the same mutation reddens. what is NOT done is said rather than implied: a bare ferry still says nothing about git, its catch-up being a different path, so the note claims only the dock for it; the block deliberately stays silent on a repeat adopt, because its claim is that THIS run wrote over your tree and a no-op cannot make that claim; and none of this was reproduced live (#1679)
c8ebe232 · dbf3dbe6…diff - loot-first land now asks the loot binary it pushes with which FORMAT_MAJOR it writes, through a new loot --version --json that prints the contract and the crate version, and reads the major the landed commit declares out of the mirror. the relay push runs only when both were read and are equal; otherwise the verdict says relay=REFUSED, the land stands, and a final block gives the reason. the landed line gains a trailing pusher= field naming that binary as version/v<major>@path, with UNKNOWN in place of the version and major when it reported neither, and the bare loot --version line is unchanged. stub pushers staged as real child processes pin the refusal, the matched push, a pre-1776 binary and an unreadable tree: removing the equality went red (207 passed, 1 failed), pushing past the refusal went red (205 passed, 3 failed), trusting the linked constant instead of asking went red (206 passed, 2 failed) and dropping the field went red (202 passed, 6 failed), and the spawned --version --json pin was red before the shape existed (0 passed, 1 failed). the land-change and afk-loop skills, workflow.md, CONTEXT.md, ADR 0023 and ADR 0088 follow. the workspace suite is green (#1776)
d3e2f994 · dbf3dbe6…diff - review sweep 7 fix-up: loot-first now reads loot --version --json with a JSON parser, so the relay push is decided on a single JSON object whose top-level contract fits a u8 and whose top-level version is a string, in any key order and beside other fields, while a duplicated field, trailing text or a looser number refuses the push. the pusher= path now percent-encodes control characters as well as percent signs and whitespace, and workflow.md says so. land-change gains the relay=REFUSED recovery (catch the primary up, cargo build --release, check the contract loot --version --json prints against the major the landed commit declares, then loot push, and record the push output on the issue), says the refused block names both majors on a mismatch and a read failure otherwise, and stops counting the relay and branch states. afk-loop agrees that a FAILED or REFUSED land stands and owes a push, gains that push as a step, and counts the ticket done once the push is recorded on the issue. format.rs, ADR 0066 and ADR 0083 note that a land carrying a FORMAT_MAJOR bump normally comes out relay=REFUSED rather than FAILED, and the --version usage line, the print_out and dispatcher comments and a verdict test name follow. both new pins went red before the change (2 passed, 2 failed), the control-character pin went red with that arm removed (1 passed, 1 failed), and a serde_json Value reader went red on the duplicated field (1 passed, 1 failed). the workspace suite is green (#1979)
ce994e1d · dbf3dbe6…diff - review sweep 8 fix-up: the owed relay push recovery now lives once, in a new workflow.md section, paying an owed relay push, which covers relay=FAILED and relay=REFUSED in both shells with the position on every command, the build job cap and the in-flight guard, and states one done-when: the push succeeded, or its format skew is recorded on the issue as owed to the release relay redeploy. land-change and afk-loop point at it instead of carrying their drifted copies, afk-loop refers to that item and to its sections by name rather than by step number, as AGENTS.md now does too, and its when-it-goes-wrong list gains relay=FAILED. probe_pusher now says why a report was refused, with the parser message or the version check, and keeps the pre-1776 explanation for output that does not open a JSON object, and the pusher= docs say UNKNOWN means the report was not read. the new pin and the tightened pre-1776 pin went red before the change (0 passed, 2 failed), and red again with the pre-1776 arm removed (9 passed, 1 failed) and with the pre-1776 explanation given for the new pin reports (9 passed, 1 failed). LanePeek becomes a struct and a dead unwrap_or_default becomes an expect, the 4.2 s lanes figures in heads.rs and lanes.rs are labelled as history, the PositionHalf doc credits the reorder with matching the documented load order, the ObjectIndexScan doc no longer names its CLI consumer, ADR 0028 says merge_tips reads tree_at itself, and rewrap leftovers in land-change, format.rs and CONTEXT.md are joined. the workspace suite is green (#1983)
2ee76c18 · dbf3dbe6…diff - loot resolve now refuses, naming the path, when the bytes on disk at the path it resolves differ both from the bytes recorded for it, in the change the disk mirrors or as the conflicts ours side, and from the resolution, so the one-path write #1798 gave every position no longer destroys an uncaptured edit to that file, while an in-place resolve still proceeds. the ours side is there because a home position with more than one head can mirror a head the disk does not show, which a ferry bounce test hit. spawned-binary tests on the home position and in a lane went red with the guard call removed (2 passed, 2 failed), the in-place pair went red with the resolution check removed (2 passed, 2 failed), and dropping the recorded check turned six resume tests red (10 passed, 6 failed). the line-ending land refusal now also refuses a NUL byte in the files it walks, which held none: a planted NUL went red before the change and with the check emptied (13 passed, 1 failed each). ADR 0080 scopes its ledger reason to revert and cherry-pick and records the guard, workflow.md and two skills say a format skew owes a relay and a forge redeploy and that the relay run needs the ALLOW_FORMAT_MAJOR_CHANGE override, and the sweep 9 citation, rationale, doc, long-line and short-line leftovers are fixed. the workspace suite is green (#1988)
a1bfdaa2 · dbf3dbe6…diff - the pre-land cargo test gate tells a compiler that did not answer from code that failed. cargo test now tees its stderr through a new Spawner::tee_stderr, and hygiene::classify_cargo_test_failure reads it: inside a run where cargo said could not compile, an ICE banner (internal compiler error, query stack during panic, the I-ICE bug-report note) or a compile whose process line shows a 0xc exit code or a signal death is a compiler that did not answer, any other build failure is one a corrupt incremental cache can fake, and a failing test is neither. a build failure of either kind clears target/debug/incremental and runs cargo test once more, and the re-run is the result: a pass lands with tests=RETRIED on the verdict and a block naming each classified first run, a second crash refuses as a new Refusal::NoAnswer whose merged-tree prose never prints the ADR 0055 story or its loot edit procedure, and anything else refuses as a finding that says a re-run happened. a failing test still refuses at once with the old message, un-retried. the classifier is pinned against the t1561 crash tail, unwrapped and trimmed, the t1870 151-error tail, and a reconstructed ICE banner, since no ICE transcript survived. red under mutation: the could-not-compile requirement dropped (1 passed, 1 failed), the --crate-name check dropped (1 passed, 1 failed), a failing test re-run (0 passed, 1 failed), a double crash given the merged-tree remedy (0 passed, 1 failed), RETRIED dropped from the line (0 passed, 1 failed), and the cache left uncleared (0 passed, 1 failed). no migration, no wire or format byte moves, and no forge or relay byte moves, so this owes no deploy. the workspace suite is green (3936 passed over 123 binaries, 7 ignored) (#1873)
74728216 · dbf3dbe6…diff - a lander can no longer read a pre-land finding as the refusal whose recovery is to change nothing, and the gate claims no verdict only when neither run reached one. the no-verdict refusal opens on a heading of its own, pre-land cargo test: NO VERDICT, and a finding keeps the old heading and never says in words of its own that the compiler did not answer, so the crash it quotes from the first run cannot be mistaken for the other refusal; land-change keys its two recoveries on those headings. a build failure and then a crash is now a finding, not no verdict: the first diagnostics are the only verdict either run reached and the crash neither confirmed nor cleared them, so telling the lander there is nothing to fix would claim more than the gate knows. a re-run that cannot start cargo now says a re-run happened, leaving the merged-tree remedy to #2066. afk-loop reads the verdict line through land-change instead of keeping a second list of values, the --skip-tests block stops stating the CI net as a fact and names ci/local.sh for when the job does not run, and the claims that the job is only cargo test are narrowed to what ci/test-main.sh runs. red first: a finding carrying the no-verdict words, a build failure and a crash judged as no verdict, and a re-run spawn failure that never said a re-run happened (0 passed, 3 failed). red under mutation: the finding rendered with the retry evidence (2 passed, 1 failed), the no-verdict guard dropped from the first run (1 passed, 2 failed), the re-run note dropped from a spawn failure (2 passed, 1 failed), and the deadline dropped from the new wire pin (0 passed, 1 failed). the ticket premise about a deadline ceiling is corrected: in reqwest blocking a request timeout replaces the client per-wait timeout, so a deadline is not undercut by the transfer budget, and a local server pins that. the smells are taken: the fake spawner stdio choice is an enum, wire post takes a budget as get does, Endpoint get sends in one place, the transport remedy sentence is written once, add_attestation is add_attestations with one record, and CargoTestFailure Other is named Finding. no migration, no wire or format byte moves and no forge or relay byte moves, so this owes no deploy. the workspace suite is green (3946 passed over 122 binaries, 7 ignored) (#2071)
bb8acc52 · dbf3dbe6…diff - ADR 0076 amended by #2101: .loot/gates gains a land phase and two hand-offs, and git hook names are mapped or refused by name. Phase::ALL has three members and every sentence that spells the set derives from it (Phase::words, Phase::usage_alternatives), so the parser refusal, the loot gates usage and the --phase errors moved without a second edit. A land gate is the user-gates row of orchestrator::GATES, between the text gates and the expensive ones, PreLandAndMerged and Outside: it runs on the lane tree and again on the merged tree when the converge moved the position, --skip-tests never silences it, and loot-first land gains --skip-gates which honors honored and never required per declared posture and records the skip in the op log after each pass, because a gate holds no Workspace. A finalize or land gate may write {message}, which expands to the path of a temp file holding the message about to be signed, newline-terminated and unlinked the moment the child returns, and {paths}, the touched paths root-relative with forward slashes, sorted, deduplicated, one argument each, deletions included; a push gate that names either is malformed, since a push has no single change, by Phase::commits_one_change rather than a list. The hand-off is built by Workspace::gate_handoff only when a gate in the phase names a placeholder, so a declaration that never asks never pays the working-delta walk, and gates::run still takes no Workspace; that accessor is the 384th Workspace method and the verb tier module doc now says so. A {message} with no message is unstartable, never a pass. The spelling was decided by running the tools, not reading them: pre-commit 4.6.2 files stage hands a hook the paths as separate arguments and its commit-msg stage hands exactly one filename, and a bare script reads that file, so a file path and argv tokens are the compatible spellings; pre-commit itself needs a .git and so cannot run inside a lane, recorded in the amendment. git pre-commit and pre-push are refused naming finalize and push; any other word is refused by name with the one sentence that holds for every unmapped hook, no index, no checkout, no rebase, no merge commit, and GIT_HOOK_PHASES is the map, never a claim about git set; the optional init --from-git was not built. Measured through the real binary in a throwaway repo, since .loot/gates is shared over the store and a land line there would make every other session binary refuse its finalize phase as malformed, which the amendment and workflow.md now warn about: two Python gates over 13 paths and the message cost 77 ms and 64 ms wall for the whole phase against 23 ms for a listing, so a contended land pays about 140 ms; loot new through the binary handed the message file and 13 paths, and --skip-gates recorded skipped p in the op log. Five pins each broken once by a named mutation and confirmed red at 0 passed 1 failed, then green at 1 passed: the row demoted to PreLand, the message file cut to its first line, the sort dropped, pre-commit unmapped, and required skipped under the flag. CONTEXT.md Phase entry and workflow.md step 7 say the same. loot-cli gates 25 passed, change and sync 65 passed, loot-first lib 225 passed after the two table assertions learned the ninth row; two lands refused on censuses the targeted runs never selected, the module-doc method count and the help placeholder classification in revset_everywhere, and each fix is one line saying the new spelling. No wire, format or store byte moves, no migration (#2101)
5c4e3996 · dbf3dbe6…diff - a transfer waits for its answer on a budget read off the bytes it is sending, not on one constant: reqwest blocking holds a single wait over connecting, the whole upload and the peer working on it, so the number #2064 kept for the header wait was never a limit on silence but a floor under the uplink rate this client will tolerate, and below that rate a healthy push is refused while its bytes are still going out, which a push batch capped at an eighth of MAX_BODY_BYTES makes reachable rather than hypothetical; the silence half stays a constant, because a body read returns the moment any bytes arrive and there the number really does bound inactivity. wire::transfer_budget is the floor plus room for the upload at UPLINK_FLOOR, rounded up to a whole multiple of the floor because a budget is a client and a client is a connection pool, so an exact budget would mint a pool per body size, and the rounding costs a peer that says nothing at most one extra floor of waiting; bytes past what any loot host accepts buy none, so the wait stays bounded by the body. the budget now rides the response it produced, through Answered, so a body read is measured against the number reqwest held that request to rather than one the reader recomputes, which would otherwise repeat the #2064 error in the opposite direction. measured before choosing a number, with LOOT_NET_TIMING=1 on this desktop: the fault-injection pushes are hundreds of bytes and answer in tens of milliseconds, the eighty-change push at about 170 kB answers in under a second, and under two concurrent workspace builds, heavier than the load the refusing land ran under, the worst answer was 64 ms and thirty-three rounds of that binary went green; so 30 s was never marginal for a local relay, and the land #2079 escalated on was the process not running rather than the relay being slow, which is recorded beside TRANSFER_IDLE and leaves the ticket open for it. whether a wall-clock budget failure should be classified the way #1873 classified a compiler that did not answer is decided no, recorded beside Refusal in orchestrator.rs: that classifier reads stderr and a failing test panic is on stdout, widened to the whole run the text it would key on is minted by transport_text and asserted verbatim by the budget tests next door, so a genuine failure of one would read as not a finding, and the claim itself would be false because the compiler answered and the suite answered. red under mutation, counts read each time: the budget ignoring the bytes sent (9 passed and 3 failed), the body read recomputing the budget from the floor instead of carrying it (12 passed and 1 failed), the rounding dropped for exact seconds (12 passed and 1 failed), and the cap at MAX_BODY_BYTES removed (0 passed and 1 failed). no migration, no wire or format byte moves, and no host behaviour moves because the change is entirely on the client side of the transport, so this owes no deploy and rides the next release. the workspace suite is green (4011 passed over 125 binaries, 7 ignored) (#2079)
8e8a873d · dbf3dbe6…diff - a cargo that could not be started gets its own refusal and the merged pass stops sending that lander after a combination nothing compiled: #2066 asked where a failed spawn belongs and the answer is a kind of its own rather than the no-answer one #2071 sharpened, because NoAnswer needs a crash on both runs while a spawn failure is never re-run at all, an emptied incremental cache being unable to put a program on PATH, and because its remedy is cargo clean, a cargo command for an operator whose cargo would not start, which is the #944/#962 shape of naming a remedy the tool then declines. the premise verified on the tree, where cargo_test_once minted a Refusal::Finding for the one thing a Spawner Err can mean and merged_refusal handed that to regate_remedy, the ADR 0055 semantic-conflict story plus loot edit. cargo_test_once returns the operating system reason as text now and the gate, which knows what the first run reached, decides the kind: Refusal::NotRun under a PRE_LAND_NOT_RUN heading, whose merged remedy keeps the lane-state facts and drops both the loot edit procedure and the cargo clean, naming the machine instead. the #2071 rule is asked of the new kind too, so a re-run that could not start after a first run that reached diagnostics stays a finding, those diagnostics being the only verdict either run reached, and the retry is deliberately not widened to a missing program. red under mutation, counts read each time: the first-run spawn arm minting a Finding again (191 passed and 1 failed, the refusal reading Finding where NotRun belongs), NotRun routed to the no-answer remedy (191 passed and 1 failed, cargo clean back in the merged prose), the diagnostics-then-spawn case made a NotRun (191 passed and 1 failed), the first-run spawn failure sent on to the re-run (191 passed and 1 failed, the fake naming an unstubbed second cargo test) and the heading taken off the not-run prose (190 passed and 2 failed), each restored to 192 passed and 0 failed. workflow.md and the land-change skill carry the third heading and point at the Refusal enum as what defines the set, and the From impl records that the other spawning gates are still on the finding default. no migration, no wire or format byte moves and no host behaviour moves, but what a merged-tree gate prints moves on the client, so this rides the next release and owes no deploy. the workspace suite is green (4109 passed over 132 binaries, 8 ignored) (#2066)
510752c3 · dbf3dbe6…diff - the adr guard says on the verdict line that its fetch did not answer, and the test whose subject is that fetch asks whether it ran before it reads the refusal: #2140 was filed from a land refused by the_adr_guard_fetches_the_collision_the_local_refs_still_hide under the note that origin could not be reached, which is the documented degrade, so the guard did exactly what it promises and the test failed anyway with a message naming a property rather than a cause. the premise was narrowed on the tree first, and the ticket asks for that: the test never reaches a real origin at all, origin_and_clone builds a git repo under the temp dir and clones it, so pointing it at a local fixture remote was already done and what failed in the sighting was a local git fetch. severing that remote reproduces the sighting byte for byte, the same note above the same panic reading the fetch must surface the collision the local refs hide, so the condition was made rather than waited for. the ask carries its degrade out now as well as speaking it: refuse_adr_collision returns an AdrAsk of a refusal and a reason, git_fetch_main says which step did not answer instead of a bare none, and the note both fetch-before-read guards share quotes that reason, the sighting having captured nothing beyond the note itself. the test reads the fetch before the refusal, so under the same severed remote it says the fetch did not run and that it measured nothing about the collision, with the reason beside it. the loudness question is answered yes and recorded as an ADR 0065 amendment: a degraded guard reaches the verdict line as adr=STALE-REFS with a block under it, on the #1251 posture, because the note from the first ask lands minutes of cargo and npm above the verdict and what a stale answer can publish is a duplicate ADR number on main, which is #1080 itself. the field reports the last ask a land made, every ask putting the same question to the same landed main, so an ask that fetched supersedes one that did not. red under mutation, counts read each time: the ask dropping its reason again (190 passed and 3 failed), the pass no longer recording it (192 passed and 1 failed), the verdict word made unconditional (191 passed and 2 failed), the block taken off (191 passed and 2 failed), the reason no longer naming the step (191 passed and 2 failed) and the kill no longer naming the bound it happened at (192 passed and 1 failed), each restored to 193 passed and 0 failed. no migration, no wire or format byte moves and no host behaviour moves, but what a land prints on its verdict line moves, so this rides the next release and owes no deploy. the workspace suite is green (4111 passed over 132 binaries, 8 ignored) (#2140)
17277998 · dbf3dbe6…diff - the pre-land gate reads the stream a doctest reports on, and the four failures in the sighting turn out to be the four that link rather than the compile_fail probes: #2084 asked for the condition to be reproduced rather than guessed, and it reproduces in one command, a transitive dependency rlib under target/debug/deps held open with no sharing, after which cargo test --locked -p loot-cli --doc comes back 6 passed and 4 failed with rustc saying crate socket2 required to be available in rlib format, the four being the doctests that link, cargo not rebuilding because the rlib mtime is as readable as ever, and the handle released giving 10 passed again. two premises in the ticket body are corrected off its own land log: the four it names are the doctests without compile_fail, a compile_fail probe never reaching a link at all, and the feature-unification hypothesis is refuted by profile, since the perf gate builds loot-perf-gate with --features count under --release into target/release while the suite reads target/debug, and that land test build was already fresh at 0.66s, so nothing in the invocation built anything to collide with. the gate could not see any of it because the evidence is on stdout, under the libtest captured-output heading, while tee_stderr set Captured stdout to empty by construction, so classify_cargo_test_failure read a finding off a stream that could not hold it. tee_stderr becomes tee and drains both pipes, stdout on a thread of its own because two pipes read in turn deadlock; CargoTestFailure gains ArtifactForm, keyed on cargo naming the doctest step on stderr together with a line opening error: crate on stdout, which is the widening #2079 declined and which answers the objections of that ticket rather than stepping around them; a first run classified that way buys the re-run #1873 already gives, and the same condition on both runs refuses as a no-verdict rather than as a finding, so a merged pass owes cargo clean and never the ADR 0055 story. the NO VERDICT heading says neither run judged the tree now rather than naming a crash, the third question is answered no in the doc of the gate itself with what a split would cost, and the stale #2079 bullet saying this classifier reads stderr is corrected where it stands. red under mutation, counts read each time: the stdout half never asked (loot-hygiene 39 passed and 1 failed, loot-first 193 passed and 3 failed), the doctest-step anchor dropped (39 passed and 1 failed, a real compile failure reading ArtifactForm), the line-start anchor dropped (39 passed and 1 failed, a quoted source line classifying), the real adapter dropping stdout again (loot-cli 17 passed and 1 failed), a doctest link failure counted as a verdict on the tree (194 passed and 2 failed) and the no-verdict closing sentence removed (195 passed and 1 failed), each restored to 196 passed, 40 passed and 1344 passed. no migration, no wire or format byte moves and no host behaviour moves, but what the pre-land gate prints and whether it retries moves on the client, so this rides the next release and owes no deploy. the workspace suite is green (4117 passed over 132 binaries, 8 ignored) (#2084)
aa28e802 · dbf3dbe6…diff - the no-verdict refusal stops telling a lander there is nothing to fix when a doctest really did fail, and the two lists asserted as complete are replaced by the question that decides membership: #2199 asked for every premise to be re-verified on the tree first and each of them held — cargo test here carries no --no-fail-fast, so the step cargo names on stderr is the one that failed and every step it ran before that one passed, and doctest_artifact_form returns the first rlib line whatever else the stream holds, while the two pins that cover those posed the real error and the rlib line in separate fixtures, so nothing saw the combination. the fix is the prose and not the classifier, because deciding which error on a stream is the genuine one is the text guess #2079 declined, while what a run left the gate unable to judge is a fact the gate already holds: unjudged is an exhaustive match over CargoTestFailure that takes no wildcard, so a classification added there cannot inherit a sentence written for a different one, which is exactly how this defect arrived; each run's line in the refusal now carries its own clause, the shared sentence says only that neither run reached a verdict, the heading says neither run reached one rather than neither run judged the tree, and the merged-tree remedy stops saying there is nothing to fix in it. one stream holding a genuinely broken doctest and the held-rlib line is entered as a fixture in both crates, red-first. the verdict-line test stops naming three runbooks that quote it and says what makes something a reader of that line, and ADR 0088 section 4's V4 decline keeps its load-bearing claim while stating the membership question instead of four ways to miss, two of the hits fitting none of the four. Spawner::tee carries a dead drain thread's panic through instead of reading it as empty stdout, which is the pre-#2084 blindness reached by another route. red under mutation, counts read each time: the wide shared sentence restored (196 passed and 1 failed), the ArtifactForm arm inheriting the crash sentence (196 passed and 1 failed), the per-run clause dropped from the layout (196 passed and 1 failed), the heading's coverage claim restored (196 passed and 1 failed), the merged remedy's nothing-to-fix restored (196 passed and 1 failed), the drain panic swallowed again (loot-cli 1344 passed and 1 failed) and the classifier made to weigh the rest of the stream (loot-hygiene 40 passed and 1 failed), each restored to 197 passed, 1345 passed and 41 passed. no migration, no wire or format byte moves and no host behaviour moves, but what a pre-land refusal prints moves on the client, so this rides the next release and owes no deploy. the workspace suite is green (4120 passed over 132 binaries, 8 ignored) (#2199)
0bd49130 · dbf3dbe6…diff - the deposit cost test measures the shape its comment claimed and the ships-nothing push is measured rather than assumed: #2206 asked for every premise to be re-verified on the tree first and the leading one holds, the comment saying a peer that already holds the tip while the code passed have = &[], a peer that holds nothing, so what it measured was the shipping push twice — the promotion the zero rests on comes from ride_entry probing every entry of every sent change, and send nothing, promote nothing. the missing shape is on the same fixture now: have = [tip] with no want promotes no address at all, the send pass skipping a held change before it touches the manifest and the wants lane skipping on empty wants, so the deposit plan behind it pays the whole cold walk, 64 object gets and 64 disk reads against the zero disk reads behind a push that ships, which is the cold arm number and not the pushing one. it is recorded rather than repaired and the reason stands where a reader meets it: the waste is plan_standing already-deposited filter, a map lookup, running after each lane has built its list with a store read per row, and putting the ledger first needs the remote and the recipient the lanes take no argument for, which gives the dedupe rule two spellings whose failure direction is a standing self-grant silently not planned, so it is #2208 and ADR 0012 takes an eleventh amendment carrying the number, the measurement and the scope the tenth amendment zero really had. the third Visibility comparison keeps the discriminant and says why rather than leaving it to a later tidy, the value under a tier being unable to move a path between lanes so that reading it withholds a self-grant over a difference that files the key in the same place, with same_seal named as the strictly stricter call it must not become and the row that separates them pinned; the case that doc named and the function cannot see, an entry spelling an embargo instant the seal does not, is gone. the two grant-apply doors asserted as complete become the question that decides membership — Keyring::insert callers against the one filing keyed on obj.vis — at the code, in the ADR and in #2205 body, refresh_hold and the save/load unions being routes the count missed. the silent drop keeps its decline and stops resting on a state an honestly captured tree cannot enter, an ingested tree being the only kind the repair is for. the standing fail-open arm is asserted over both lanes in one comparison, a struct Run no longer collides with a test-local alias, unjudged residual arm stops claiming the suite answered, kept_or_unwind records that its resume orphans the child, and the ungrammatical no-verdict sentence is fixed at the gate doc and in workflow.md. red under mutation, counts read each time: the tier comparison made same_seal (73 passed and 1 failed), the fail-open arm made to drop (73 passed and 1 failed, both lanes empty), the seal question moved ahead of the key guard (73 passed and 1 failed, object_gets reading 65 where 64 belongs), the ships-nothing peer made to hold nothing (73 passed and 1 failed, the bundle opening 65 where 0 belongs), the new pin fixture made to agree (73 passed and 1 failed, the vacuity control firing) and the Internal lane made to stop asking (71 passed and 3 failed), each restored to 74 passed and 0 failed. no migration, no wire or format byte moves, no host behaviour moves and nothing an operator can observe moves, the one string that changed being a clause no refusal quotes today, so this rides the next release and owes no deploy. the workspace suite is green (4128 passed over 132 binaries, 8 ignored) (#2206)
723c7f1e · dbf3dbe6…diff - the sync ingest door weighs the seal this store holds rather than the one that arrived beside the key, so a tag-0 bundle spelling a weaker tier at an address this position already owns can no longer file a live embargo's key into the Keyring: #2212 asked for item 1 to be demonstrated before it was repaired and the reproduction read exactly as the ticket claimed, bob holding oid sealed Embargoed 9_000 and a Sync frame carrying a byte-identical object that spells vis Internal beside the real key leaving bob's Keyring holding that key at a clock of 0, which is the state #2205's own new pin forbids reached by changing the frame tag, no plaintext escaping because sealed::open's header gate still refuses. the same read carries grant_ids and that half was demonstrated too, a copy spelling the ANYONE marker over a held Restricted seal getting a key past the entitlement filter #864 built, so every question this door puts to a seal now goes to the copy that will stand at the address. it is asked before the put while the arriving object is still in hand, because first-write-wins makes the held copy the standing one only where the store holds the address at all, so the cost is zero store reads on a fresh address and one on a dedup, which is the read the key verification already owed and now answers the seal's questions with its own; the already-held guard consults both lanes, so the lane the first door chose is the one that stands. the prose is the harder half and the lesson is sharper than do not list members: the sentence that failed was in the correct derived shape, the set of them is Keyring::insert's callers which the compiler enumerates, with a hand-maintained count welded on in the same breath, and the count is the half that was wrong, so every replacement names what decides membership and stops there, at ADR 0007's amendment, escrow.rs, CONTEXT.md, custody.rs twice, ADR 0012's tenth and twelfth amendments, negotiation.rs and the grant-door pin, and escrow.rs's headline stops claiming that no route moves a key between lanes when flush is one and a grant is a new filing at the recipient rather than a carry. secondary items: the stale pin citation and the now-false claim around it, spawn.rs's three false statements about the orphaned child, the unproducible-seal fallback recorded as releasing nothing only at the instant it files, expires_at declined as a term of the staging max with the reason at the code, the demotion refusal naming which of the two recordings fired, the census group sentence that named its members, ADR 0012's push qualifier at the tip with no want, the ingest cost fixture given a publishes-nothing control, workflow.md's three refusals derived from CargoTestFailure and the PRE_LAND constants, a usize subtraction restated as a sum so the sentence beside it can print, and orchestrator.rs's tombstoned pin names declined with the reason. red under mutation, counts read each time: the vis term reverted to the arriving copy (659 passed and 1 failed), the grant_ids term reverted (659 passed and 1 failed), the already-held guard narrowed to the one lane it writes (659 passed and 1 failed), the seal question asked through a second store read (659 passed and 1 failed, object_gets reading 8 where 0 belongs), the lying sync copy made a different object (659 passed and 1 failed, the vacuity control firing), the lying grant ids made to agree (659 passed and 1 failed, the second vacuity control firing), the publishes-nothing control inverted (1353 passed and 1 failed), the refusal made to say both either way (1352 passed and 2 failed) and the ingest cost relation moved by one (1353 passed and 1 failed), each restored to 660 and 1354 passed with 0 failed. no migration, no wire or format byte moves and no host behaviour moves, but which lane a sync-borne key is filed into moves on the client, so this rides the next release and owes no deploy. the workspace suite is green (4140 passed over 132 binaries, 8 ignored) (#2212)
c643aadc · dbf3dbe6…diff - every land now states which versions it published that the lander did not write, and refuses nothing - ADR 0092's detection half, part 1 of 2 of this ticket. a version is foreign when its author is not in the lander's own key set, which is new: loot_identity::own_signing_keys reads the active id.pub plus every id.pub.rotated archive that loot id rotate writes, exposed as Workspace::own_signing_keys beside author_pubkey rather than instead of it, because the two answer different questions. carry_line's Foreign asks may I RE-AUTHOR this and only the current key can sign, while the gate asks did I WRITE this and authorship is historical, so a rotated operator's own change is foreign to the carry and theirs to the gate and both are right - each site now names its own question and points at the other, since unifying them breaks a caller in either direction. the judged set is the ancestor closure of the landed change minus the closure of the main it was judged against, read BEFORE the pass because by verdict time main already includes the landed change and the difference would be empty on every land, and it is the whole closure rather than carry_line's first-parent chain because a stack carrying a merge of its own would otherwise have one side waved through. an absent author is foreign, which is carry_line's answer too, so the two predicates disagree only about which keys are mine. the count rides the verdict line as foreign= and a block names the versions, since the count says that and the next question is which. foreign= sits immediately BEFORE pusher= and not after it, which I got wrong first: pusher= carries an arbitrary percent-encoded path and is designed as the tail, workflow.md says the path comes last, and five pusher tests read it with ends_with - appending after it broke exactly those, while inserting before it breaks only a fixed-index reader and the repo has none, and those five pass unmodified. the key set reads only id.pub.rotated and never the private id.rotated spelling, which holds a secret key and has no business answering a question about authorship. a machine that pulled rather than rotated holds no archive, so there your own pre-rotation work reads as foreign, which fails toward asking for an approval nobody owed and never toward waiving one, and the block and workflow.md both say so. red under mutation, counts read each time: the key set ignoring the archive (52 passed and 1 failed), the filter admitting the private archive spelling (52 passed and 1 failed), and an unauthored change read as the lander's own (202 passed and 1 failed), each restored to 53, 53 and 203. ADR 0092's status now says which sections are built, because section 5 - the gating column, review/approve and the refusals - is part 2 and until it lands detection reports and nothing refuses, which is that section's decided default anyway. no migration, no wire or format byte moves, and nothing on a host moves, so this owes no deploy. the workspace suite is green (4189 passed over 133 binaries) (#2178)
8cb77a56 · dbf3dbe6…diff - the offline site gate block stops stating its compensating detector as a fact, the shape #2071 fixed one block over: site-main.yml is still named as the net, but only if the land moved a path on its list and only when that job actually runs, which nothing in the land can see, and the fix line now says npm ci and npm run gate in site/ answers on this machine whether or not that job ran. the block does not hard-code whether Actions minutes exist, though the latest site-main.yml run was refused for account billing, the state #1916 found for test-main.yml. ci/local.sh is not named because what ci/test-main.sh runs of the site is its live-database pg tests and not the gate, and the comment there that said a land runs the gate only when it moves a site path and that site-main.yml runs it on main now says neither. land-change and workflow.md, which paraphrase the block, carry the same qualifier. the existing byte-for-byte pin over the block is moved to the new text and went red first (0 passed and 1 failed); red under mutation, counts read each time: the new qualifier dropped from the net line (0 passed and 1 failed) and the fix line put back to its old ending (0 passed and 1 failed), each restored to 1 passed. no migration, no wire or format byte moves and no host behaviour moves, so this owes no deploy. the workspace suite is green (4179 passed over 133 binaries, 9 ignored) (#2074)
c136bcb2 · dbf3dbe6…diff - a land that publishes work you did not write now refuses when the repo asks it to, ADR 0092's gating half and part 2 of 2 of this ticket: a require line in the .lootpipeline on main names a step whose pass or the approval every foreign tip must carry, and with no such line the land stays advisory as decided. the syntax is a directive rather than the column section 5 named, the operator's choice, because a step line's command swallows the rest of the line so a column would have re-laid every step line in a grammar map #2099 owns - a require line has two words, every two-word line was already refused as a malformed step, so it changes the meaning of no line that parsed before, and it can express the approval, which was never a step. only the owner's own keys satisfy require approval, as review/approve: a trusted runner's passes count and its approval does not, or the machine that ran the tests could also sign the second pair of eyes. a require line that names no step, names a step not on change, or repeats refuses the land rather than reading as nothing required, because a typo there is a gate the owner meant to declare. the requirement is read at the judged main and never from the stack being landed, a sealed or burned landed file refuses, the check runs before the break-glass branch so --skip-tests cannot reach it, and own work returns before reading the file at all. only the foreign tips must carry the roles, since a pass on a version certifies the tree at that version, and a role signed by a key this machine does not trust gets its own refusal and repair because re-running would change nothing. not proved end to end: no test drives a real land with a second identity and a projected main, so the decision logic is pinned as pure functions and which copy is read and where the check sits are pinned by censuses on the source. red under mutation, counts read each time: the approval accepting any trusted key (34 passed and 1 failed), untrusted read as missing (33 passed and 2 failed), the landed-copy census fed the working tree (0 passed and 1 failed), the check moved after the break-glass (0 passed and 1 failed), and a require naming no step dropped silently (21 passed and 1 failed), each restored to 35, 35, 1, 1 and 22. ADR 0092 records the directive and what building it found, and workflow.md says how to turn gating on. no migration, no wire or format byte moves and no host behaviour moves, so this owes no deploy, though the primary's release binaries must be rebuilt before a land enforces it. the workspace suite is green (4208 passed over 134 binaries, 9 ignored) (#2178)
946e7f14 · dbf3dbe6…diff - the land now type-checks sdk/: a new sdk gate, right after the site gate in orchestrator::GATES, runs npm ci, npm run build:wasm and npm run typecheck in the landing position sdk/ (hygiene::SDK_GATE_STEPS), refusing on a failed build or a type error, riding the merged-tree re-gate, skipping under --skip-tests as sdk=SKIPPED, and degrading a provably offline npm ci to sdk=SKIPPED-OFFLINE with a block of its own, so the verdict line gains sdk= after site=; the two npm gates now share run_npm_gate and supersede_offline. the land was chosen over ci/test-main.sh because only a land refuses before main moves and GitHub Actions had not run since 2026-09-19, and over the site gate step because site= would then speak for two packages. measured in a fresh lane: npm ci 1.4 s, build:wasm 17.8 s cold but 1.7 s once the site gate has built the same crate, tsc 1.0 s, the whole gate 5.05 s. proved live through the sdk row with real npm, wasm-pack and tsc: clean, red at npm run typecheck with TS2322 for a string assigned to a number in sdk/src/hex.ts, clean again once removed. ADR 0071 gains the #2080 amendment and CONTEXT.md an SDK gate entry, and workflow.md, the land-change skill and sdk/README.md say what runs and that npm test still does not. red under mutation, counts read each time: the typecheck step dropped (7 passed, 2 failed), the sdk row running the site gate (5 and 4), the sdk= field reading the site slot (7 and 2), the supersede rule reading one fixed gate (8 and 1), an offline install refused rather than degraded (7 and 2), each restored to green. a land runs the primary release loot-first, so the gate is live once those binaries are rebuilt; no migration and no format byte moves, so this owes no deploy. the workspace suite is green (4322 passed over 137 binaries, 12 ignored) (#2080)
072abcc1 · dbf3dbe6…diff - the review-sweep fix-up over #2291, #2043 and #2080. loot verify no longer walks past the deferred-bodies refusal: it finds its store by layout through resolve_store_dot and never met the record, so in a store carrying .loot/deferred-bodies it reported a body it had never fetched as missing, and verify --accept-loss wrote that body into .loot/lost as unrecoverable while the remote still held it. the refusal is now decided once, in BodyNeed::admit, which the open (Workspace::assemble) and the layout door both call, so the doors it guards are that function callers: resolve_store_dot takes a BodyNeed, verify declares Held and is refused naming the state, and doctor and config declare the new Unread, since they read no body. the relay doors take none, and loot_net::is_relay records why: each refuses a store without the relay role marker before reading an object, no code that writes the record makes a relay, and gc there removes only objects the store holds; gc --dir, count-objects --dir, relay reap-grants, relay abandon-head and serve --dir were each driven through the binary at a deferred store and each refused it. the refusal now says the verb did not declare it fetches first rather than that it reads a body, since whoami, status and log read none, and doctor, ADR 0093 section 3 and CONTEXT.md say the same; section 3 is restated as the property and records the #2295 correction. false sentences fixed: the counts beside GATES in spawn.rs and orchestrator.rs now point at the rows, the sdk gate doc and SDK_GATE_STEPS say build:wasm can reach the network and refuses rather than degrading there, the in_progress verb and operand docs state the property instead of listing verbs, HUNT-PERF stops counting the reads around the graph load, and the rewrap leftovers in the land-change skill and ADR 0071 are fixed; resolve_store_dot also gets back its own doc, which sat on registered_peer_keys. red first through the binary: verify answered 1 missing (0 passed, 1 failed). red under mutation, counts read each time: verify declaring Unread (1 passed and 1 failed), verify refused but --accept-loss let through, which accepted 1 missing object as lost (1 and 1), the refusal in admit keyed on the wrong need (0 and 2), each restored to green. no migration and no format byte moves, so this owes no deploy. the workspace suite is green (4323 passed over 137 binaries, 12 ignored) (#2295)
ded7dfa3 · dbf3dbe6…diff - a merged-tree re-gate that fails stops asserting a cause it cannot know: regate_remedy no longer says the change is green on its own and breaks combined, and says instead what the gate knows, that the merged tree failed it after the tree of the lane itself got through the pre-land pass, naming the combination and a run that failed for a reason outside the tree (a Windows file lock, a timeout under load) as causes it cannot tell apart; its recovery is ordered, re-run what failed alone and read the count, change nothing and re-land if it passes, and only if it fails again the loot edit procedure. the finding stays a finding: a test ran and failed, so the kind was right and the claim was wrong. the cargo test gate now names each failing test libtest reported under the unchanged pre-land cargo test failed heading, through hygiene::failed_tests, and beside a test whose own captured block carries a spelling in hygiene::SHARING_OS_ERRORS it says Windows returns that when another handle holds the file and that a lock and a defect both produce it (#2233 was a defect), deciding nothing on it: no retry and no skip. the plain pre-land finding and the merged finding are pinned whole over the #2079 shape, concurrent_stage_of_same_address_does_not_tear red on os error 5. red under mutation, counts read each time: the old ADR 0055 story restored (2 passed and 1 failed), cargo_test_once naming nothing (0 and 3), the first-run finding without its names (1 and 2), the re-run finding without its names (2 and 1), a lock-shaped failure re-run (1 and 2), the sharing error searched over the whole stdout (hygiene 1 and 1), the block not stopped at the next test header (1 and 1), the code matched without its closing paren (0 and 2), the FAILED line matched mid-line (1 and 1), each restored to green. ADR 0055 gains a #2310 amendment, and workflow.md and the land-change skill carry the ordered recovery and the failed lines. no migration, no format byte and no published wording moves, so this owes no deploy, but a land runs the primary release loot-first, so this is live only once those binaries are rebuilt. the workspace suite is green (4356 passed over 137 binaries, 12 ignored) (#2310)
157034cc · dbf3dbe6…diff - a gate row whose program could not be started now refuses as not run rather than as a finding, the cargo re-run after a first run that reached diagnostics excepted as #2071 decided: impl From<String> for Refusal is gone, so each GATES row names the kind of each refusal where it builds it. spawn::require takes its refusal type from the caller, so the npm gates build a NotRun in the spawn-failure closure and a Finding in the non-zero one, and the lockfile and perf gates carry their spawn failures the same way; gates::guard_with now answers gates::Refused, the CliError plus unstarted, read off the runner outcomes (true when each gate that did not clear never started), which loot new and loot push convert with the same ? and the user-gates row maps to NotRun. an npm step that could not be spawned refuses in its own words, naming the gate, the step, npm as this platform spawns it and the operating system reason, in place of the step failure advice; the offline degrade does not cover it, since it classifies the output of an npm ci that ran. the merged pass gives any NotRun regate_not_run_remedy, which names no program and no longer claims that nothing was built or run, that no gate judged the tree, or that the cause is the machine rather than the tree, each false for some row that now reaches it; a program that never started is re-run nowhere. pinned by a census over GATES that drives each row with every spawn failing (a row that reached the spawner refuses NotRun naming the program and the reason, spawns nothing more, and on the merged pass gets a remedy with neither the ADR 0055 story nor the program) and by its positive control with every step failing (still a finding, still the story on the merged pass); the finding prose of each spawning row was captured before the change and is byte-identical after it. red first against the unchanged code (1 passed and 1 failed). red under mutation, counts read each time, each restored to green: the user-gates row always a finding (1 and 1), always not run (1 and 1, the control), the npm install spawn failure a finding (1 and 1), that spawn retried once (1 and 1), the npm local step spawn failure a finding (0 and 1), the lockfile spawn failure a finding (1 and 1), the perf build spawn failure a finding (1 and 1), the merged NotRun given the cargo clean remedy (1 and 1), given the ADR 0055 story (1 and 1), the npm refusal not naming its program (1 and 1), unstarted read as any rather than each (0 and 1), unstarted forced false (0 and 1), a malformed declaration read as unstarted (0 and 1). ADR 0055 gains a 2193 amendment and the land-change skill no longer names the From default. no migration, no format byte and no published wording moves, so this owes no deploy, but a land runs the primary release loot-first, so this is live only once those binaries are rebuilt. the workspace suite is green (4385 passed over 138 binaries, 12 ignored) (#2193)
20ef1484 · dbf3dbe6…diff - the review-sweep fix-up over #2307, #2176 and #2193. a seek busy marker is believed while its clock is within the window of the reader clock on either side, so the round that claims first with the later clock is no longer taken over, and a marker that does not parse is dated by its mtime against that same now. loot propose --decline takes the version it declines, the full tip --show prints or a selector naming a version held here, following the grammar that already reads a version where a change id is expected; a change id is refused, the CLI finds the row standing at that tip and refuses when none visible does, so a revision made after the owner read the proposal is not declined. two act flags on loot propose refuse through ProposeAct::from_args, where the first one checked ran. the format-constant pin compares the marker of each trailer and act payload with the one a client from before #2159 wrote, recorded in the frozen codec copy, so a bump of either constant reddens it; a push declaring each head its own kinds is pinned through a two-head repo; the decline cost pin asserts the whole call sequence with the gate calls measured alone; the forge door tests ask every route in Route::ALL, now public, that the router mounts. each GATES row declares what its finding is read off (Judged): a finding off files gets a merged remedy that goes to loot edit at once, with no re-run and no file lock, and the pre-land pass rather than each finding says nothing has been signed or published, so the merged pass no longer prints it beside a signed merge; the #2193 census reaches each spawn after passing ones and holds every Judged declaration against the spawner. false sentences fixed in ADR 0055, 0075, 0090 and 0091, workflow.md, npm_not_started and the census doc, including a network-specific NAT64 prefix, which the wake-up rule cannot recognise; the forge refusals of withdraw and decline spell the tip as a version id; the cmd_propose doc sits on cmd_propose; the wake slot test waits for its places to come back. red under mutation, counts read each time, each restored to green: a marker ahead of the clock read as dead (2 passed and 1 failed), the unparseable marker dated by the system clock (2 and 1), the decline signing the tip the forge holds (4 and 1), a change id accepted (4 and 1), two acts not refused (4 and 1), FORMAT_MINOR at 1 (1 and 2), the push unioning kinds (0 and 1), a second read in the decline (0 and 1), /propose/read mounted without the door (0 and 2), the pre-land state line dropped (2 and 1), the files remedy replaced by the run remedy (2 and 1), the second perf spawn failure a finding (2 and 1), the site row declared Files (2 and 1). no migration, no format byte and no wire byte moves; the forge refusal wording changes, so that is live once the forge is redeployed, the decline argument once the CLI is released, and a land runs the primary release loot-first, so the gate changes are live only once those binaries are rebuilt. the workspace suite is green (4392 passed over 139 binaries, 12 ignored), and green under bash ci/local.sh against Postgres 18 (#2325)
ff3b789d · dbf3dbe6…diff - the review-sweep fix-up over #2325, #2207 and #2208. a proposal landed close is bound to the tip it was decided for, on both stores: ingest decided the landing outside the transaction and each store closed the row on state open alone, so a revision through record_proposal between the decision and the transaction, which leaves the row open and moves no ref and so is invisible to generation_expected, was closed landed at a tip nobody landed. the rows proposals_this_push_lands hands the store already carry the tip decided on, and the Postgres close now adds tip_version = $4 and the memory close compares the tip, the binding a decline has; no other writer closes a row this way. a new conformance case, a revision between the landing decision and the ingest stays open, runs on each store: red first on the memory store (1 passed and 1 failed, the pg half skipping without a database) and on Postgres 18 with the tip term defeated (106 passed and 1 failed), green with it (107 passed). CONTEXT.md, store.rs, pg/meta.rs, memory.rs, ingest.rs and ADR 0075 stop calling the CAS the whole of the soundness. false sentences fixed: ADR 0012 eleventh amendment now says the ledger ran after the lanes until #2208, and the fifteenth names the plan pin as the fixture where the timed walk reads one seal, measured by hand; the plan pin now deposits every row it plans at another forge as well as the Restricted row at a relay, so dropping the remote from the dedupe empties the plan rather than dropping pitch.md alone (20 passed and 3 failed, all six rows gone), restored green (23 passed); Route::ALL is written by a routes macro from the enum variant list, so a variant added to Route is in it with no second edit, checked by adding a probe variant, and the http.rs sentence states that. the --decline refusal says the handle reads as a change id, and the Judged census doc says it sees only the Spawner. no migration, format byte or wire byte moves; the landed close is live once the forge is redeployed. the workspace suite is green under bash ci/local.sh against Postgres 18 (4404 passed over 138 binaries, 13 ignored; site live suites 7 files passed) (#2347)
facefe81 · dbf3dbe6…diff - the review-sweep fix-up over #2308, #2310, #2311, #2312 and #2313. the lock note beside a failed test is said only on a Windows land: loot-hygiene failed_tests now takes whether the run was on Windows, since os error 5 and 32 are an I/O error and a broken pipe on macOS and Linux, and loot-first passes cfg!(windows); both answers are pinned on this host, red under mutation with counts read each time (recognised everywhere 2 passed and 1 failed, nowhere 0 and 3, loot-first passing false 1 and 4). the plain pre-land test finding now prints the re-run-first recovery the merged pass prints, for each gate row that runs programs and rides the merged pass, derived from the row and pinned whole (no triage 3 and 2, ignoring the merged pass or the row judgement 4 and 1 each, the order swapped 4 and 1). the timing line noun is a ServerKind enum whose relay and forge lines are pinned byte for byte (a misspelled noun 7 and 1), and the loot-s3 put refusal calls the idle duration the idle budget and keeps floor for the rate (the old word 18 and 1). tutorial.rs qualifies the #2311 halt as run on Windows and reasoned elsewhere, and a reap failure no longer prints could not run; ADR 0075 names the graph read by MetadataStore::repo_change_graph and its callers rather than a list that missed propose::admit; sync.rs states the store property the scoped forge offer rests on and what would break it, and its round-zero comments speak of the remote; land-change SKILL.md, workflow.md and ADR 0055 carry the re-run-first order and the Windows-only note. the (idle, uplink_floor) data clump in loot-s3 is left as it is. no migration, format byte or wire byte moves; the loot-first change is live once the primary release binaries are rebuilt. the workspace suite is green (4406 passed over 139 binaries, 13 ignored) (#2348)
77d9c94c · 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 - the require gate of the land is proved through a real land: three orchestrator tests run loot-first land under --skip-tests over a mirror main a plain ferry projected and a change signed under a second keyring, applied by bundle and merged in by the lander so it rides the second parent of the merge, with the fake forge standing in for GitHub. an unapproved land refuses naming that version and its loot attest, moves no main and pushes nothing, and lands once the owner attests it through that verb; a require line only on the stack copy of .lootpipeline gates nothing, and a stack that deletes the file main requires from is still gated. red with each piece undone, counts read each time over the five require pins, each restored green: the requirement read at the stack tip (2 passed and 3 failed, the read-from-main census among them), the check wrapped in a skip-tests condition (3 and 2, both censuses green), the owner attestations not consulted (4 and 1), the judged main taken from the lane anchor (3 and 2), the first-parent chain in place of the closure (3 and 2). the ADR 0092 amendment says the gap is closed and by what, and the break-glass census says what it cannot see. no format constant, codec byte or migration moves. the workspace suite is green (4445 passed over 138 binaries, 13 ignored) (#2317)
72d33080 · dbf3dbe6…diff - a land from a lane syncs origin from the primary: origin is now the forge (#2388) and loot push refuses to push a lane to a forge (ADR 0075, unchanged), so the post-land sync that pushed in the landing position was refused on every lane land. from a lane the sync now runs loot adopt then loot push in the primary checkout, after reading the primary in-process and refusing by name, before touching it, when it holds a non-empty working change or carries finalized changes landed main does not contain (so nothing unreviewed is published to origin), failing closed when that cannot be read; a primary land still pushes in position. the harbor is held until the sync finishes so two lands cannot write the primary at once, the #1776 format check still runs first, and the verdict keeps relay= and its tokens, a primary that holds work spelling REFUSED with a block that says what to clear; the block headings and fix text now say origin, and workflow.md and the land-change skill say what the land now does. nine new tests over the spawner and real primaries, each red under a named mutation and restored; loot-first 239 lib tests, workspace suite 4542 passed over 141 binaries, 13 ignored. live only once the primary release binaries are rebuilt (#2390)
b6484d1b · dbf3dbe6…diff - every compiled-in and current-tense loot host moves to loot.build (map #2412, ADR 0099): loot --help names security@loot.build and https://loot.build/trust, ALPHA_PROMISE_URL is https://loot.build/install and the telemetry DEFAULT_ENDPOINT https://loot.build/api/telemetry, so the next release carries them; the site install one-liners, R2_BASE and HOP_BASE (https://dl.loot.build, https://loot.build/dl), the metrics SITE_SCOPE and own-host referrer, the disclosure links on trust, terms, privacy and known-issues, and the install, quickstart and guides pages follow, with the privacy and terms markers moved to 25 September 2026 and re-pinned since readers see the new address; README, CONTEXT.md (the forge door now forge.loot.build), the release checklist, site CI and test fixtures follow, and the two specs carry a note instead of a partial rewrite. the destination census gains build in WEB_TLDS, without which security@loot.build would be invisible to its bare-host clause, and exempts the two shipped lines that tld makes read as hosts (state.build, r.build()) by their exact text; ADR 0074 section 9 says twelve-entry to match. 37 lines still name millerbyte.com outside ADR bodies, evidence, research and scratch, each kept on purpose: dated history, millerbyte.com the site and its @millerbyte/ui tokens, the two noted specs, and relay-era tooling with no successor host, filed as #2438. census red with build dropped from WEB_TLDS, restored; site gate 819 passed and within budget; workspace suite 4559 passed over 142 binaries, 13 ignored (#2418)
c5663115 · dbf3dbe6…diff - tickets never reach git main (ADR 0098 section 8, map #2422): the ferry leaves every path under tickets/ out of each projected tree at every tier, set aside before the tier is asked so a sealed tickets/security path is not reported as an omitted sealed path, and drops any the git parent carries. a change whose projection repeats its one git parent tree and which writes a ticket path against one of its loot parents is commitless: it is marked at that parent commit and makes none, after the git parents are reduced so a merge with a commitless ticket change stands at the code commit in either order, while a sealed-only change still commits as before. one sha can now stand for several changes and change_for answers with the latest mark; the refs phase marks the change main moves to last, so mirror_main_change, adopt and the ticket harbor tip read a ticket-only land as landed, and the marks file keeps its format by writing the earlier marks at a sha before the latest, which older readers parse as the same map. the ingest leaves a git-side ticket path out with a note rather than refusing it, a review round of tickets alone is still made with no diff and a note saying why, and loot-first land lets through a land whose tip stands at main with no commit of its own, which the #195 guard refused. ADR 0028 records how it is built and its limits (a trailer rebuild loses commitless marks, a second machine learns such a land late), CONTEXT.md gains Commitless change and workflow.md the land case. red under sixteen named mutations, each 0 passed and restored, 1 failed unless noted: the ticket set-aside removed, the parent strip removed, both at once (2 failed), never commitless (3 failed), a commitless change left unmarked, refs not marking the anchor latest, the ingest taking tickets in, the land predicate skipping its trailer check, no parent reduction, writes_tickets asking the first parent only, the review note dropped, a text-prefix ticket path, the encode dropping earlier marks (seen in loot-core and through a re-ferry), the latest written first, and the land guard branch removed. cargo test green, 4614 passed over 142 binaries with 13 ignored (#2425)
f305ece5 · 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.