Changes touching this path
- Docs: loot-first workflow design (map #148 tickets #153, #154) (#158)
The design half of wayfinder map #148 "flip the agentic workflow
loot-first, git downstream": loot leads, git main is a downstream
projection, and the GitHub PR is a review view rather than the merge
target.
- docs/agents/workflow.md (#154): the authoritative loot-first workflow
doc, beside identity.md. Covers the daily loop, review-projection
mechanic, cross-identity clone integration review (the ADR 0018
asymmetry), ferry authority (git main == projection(loot tip)),
harbor serialization, abandonment, and the guard rails. Carries a
status banner: the new surface (ferry --with-wip, loot-day) ships in
#155; the repo runs git-first until then.
- docs/research/loot-first-workflow-prototype.md (#153): the throwaway
transcript the doc was written against; surfaced six build-only seams
for #155 (PR<->change_id/dock ledger, per-agent inbox dock, land
targets the PR's dock, approval loop-vs-signal, ...).
Design only; the tooling itself is #155.
Git-Author: Connor Miller <53197564+Connor-Miller@users.noreply.github.com>
3a7797de - loot-first land: treat GitHub auto-close as the landing signal; amend workflow + evidence
b3cfb0f9 · dbf3dbe6…diff - Merge branch 'main' into s2-verb-surface
c35a0626 · dbf3dbe6…diff - docs+tools: pre-land cargo test gate; second loot-first day findings
f09a9d68 · dbf3dbe6…diff - 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 - docs: concurrent-agents playbook, lane/harbor vocab, docks->lanes migration (#234)
12aa8264 · 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 - merges refuse to sign an un-described change too, closing the #174 residual (#275)
#174 stopped the *deliberate* finalize from signing a nameless change, but three
verbs still sealed the operator's own work in passing, to make a signed merge
parent: `dock merge` and the `adopt` catch-up (via `fold_line_in`), and `ferry`
over a git main that moved (via `reconcile_capture`). Only the *trigger* was
mechanical — the content was authored — so the placeholder still reached main.
Reproduced against the pre-fix binary: a ferry over unnamed disk work landed the
literal subject `(working change)` on git main in one silent pass.
Both paths now refuse an un-described merge parent, below their capture (edits
held, only the signature waits) and below a redundant-capture drop (a pass with
no real work to sign stays a no-op, never a nag). Two reasons the ADR gave for
NOT doing this were checked and dropped: a land never reaches these paths dirty
(it finalizes before it ferries), and #219's refuse-on-dirt rejection was about
being forced to capture, not about being asked for a name.
The merge *nodes* are untouched — they are machine-authored and already carry an
honest mechanical subject. That is the line: mechanical content may be named
mechanically, authored content may not.
- `refuse_if_undescribed` (one rule, two messages) + `drop_capture_if_redundant`
(the empty/duplicate drop, previously copied at two sites and missing at a
third — which is what would have made `dock merge` nag).
- Cost, documented: uncaptured dirt takes two passes, since naming *is*
capturing. `loot-first review` asks for a name only when it must merge.
ADR 0030 amendment (correcting the two wrong claims), CONTEXT, workflow, and
concurrent.md updated. Closes #275.
aced27c1 · dbf3dbe6…diff - review refs carry the position, not the dock (#281)
Every lane's home dock is main, so dock-named review refs made N
concurrent lanes share one review/main branch: the second lane's
ferry --with-wip force-pushed over the first's in-flight PR head,
and either position's reap pass could misjudge (and retire) the
other's live entry, since liveness reads the positional working
pointer a foreign position cannot see.
The review lane is now keyed by its owner position end to end:
- ferry projects review/<lane-id> from a lane, review/<dock> on the
primary; the wip and pr-map ledgers gain an owner column (- =
primary; pre-#281 short rows parse as primary-owned) and the
review line carries owner=.
- Reap is owner-scoped: only the owner judges liveness; a foreign
pass reaps exactly the entries whose owner lane is gone from the
registry, so an abandoned lane's review ref dies with it instead
of leaking.
- land derives the collapse branch from the pr-map lane's owner and
refuses to run from any other position: it finalizes the current
position's working change, and the dock guard cannot catch that
mismatch when every lane's dock is main.
Docs: ADR 0033 amendment, concurrent.md (review projection is now
genuinely parallel-safe), workflow.md, CONTEXT.md review-lane entry.
aa68437a · dbf3dbe6…diff - Docs: post-break-glass ferry is mandatory even for loot-originated content (#297) (#299)
The #291 site work originated as a loot change, went to GitHub via a
break-glass squash-merge, and the resulting squash commit was never
ingested into the remoteless mirror -- so every subsequent land
hard-stopped on DIVERGED. The break-glass ferry rule read as skippable
because loot already held the content; the lesson is that it is the
*commit*, not the content, that must be ingested.
- concurrent.md: expand the break-glass recovery section -- ferry is
mandatory even for loot content (mirror is remoteless), GitHub
merge/squash-merge buttons are themselves break-glass, plus the
divergence signature and the #297 recovery recipe (force-fetch
checkout main into the mirror with rollback sha recorded, primary
ferry, land the parked PR through the harbor bounce).
- workflow.md: flag the GitHub merge button as break-glass in the
git->loot residual list and the guard rails, linking the recovery.
Closes #297.
Git-Author: Connor Miller <53197564+Connor-Miller@users.noreply.github.com>
271581ebdiff - Catch up to git main 809ddfe: #260 smoke default, #271 arm64 build, loot diff (#1), ADR 0034 dock retirement (#253)
587684dd · dbf3dbe6…diff - Land proceeds when the signed line is already projected but unpushed (#349)
08a05020 · 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 - land-change: make the skill a thin pointer to workflow.md (#511)
b4d26432 · dbf3dbe6…diff - loot diff --content: unified content diffs from inside a lane (#536)
7b01b038 · dbf3dbe6…diff - Say where a code review fits, and why not before step 3 (#525)
4a06bbbc · dbf3dbe6…diff - Gate loot push on the relay's format major; carry a failed relay push in the land verdict (#519)
6c5cb6e0 · dbf3dbe6…diff - loot diff --content: unified content diffs from inside a lane (#536) (conflict resolution: docs\agents\workflow.md)
125f254f · dbf3dbe6…diff - Say that reaping a lane needs the session that spawned it (#564)
d9813a9b · dbf3dbe6…diff - loot-first land: report the provisional-branch reap instead of swallowing it (#625)
ed9ad0c2 · 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 - loot-first: land folds a pre-finalized change so the perf point rides it (#725)
d98f26f6 · dbf3dbe6…diff - a ferry pass refuses to project over an open conflict, so a catch-up after a resolve can no longer land the side nobody chose (#653)
a9b5fd64 · 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: docs/agents/workflow.md)
9e0902a0 · 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 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 - the agent runbooks stop naming one machine: the primary is derived, not spelled, and every hazard says which platform it is about (#1006)
130b15a6 · 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 - the primary stops authoring: describe -m and new -m refuse on a shared floor and print the exact lane take that carries the work onward - ADR 0058's loot lane take built, --allow-primary now a triple (#985)
67df9345 · dbf3dbe6…diff - 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 verb that could only publish to main unreviewed is retired: lane merge goes, its fold machinery stays where adopt needs it, and the mirror-free fold that genuinely leaves with it is named rather than glossed (#1048)
3f7635a9 · dbf3dbe6…diff - 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 - loot 0.4.17: the Known Issues page is re-reviewed against the binary it now names, four entries move because their fixes shipped, and the land runbook learns that a skip drops three gates and not one
44de9890 · 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 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 perf half added tomorrow cannot go undocumented, because one table now drives the gate's parse, its usage line, its module doc and both operator docs — and the figures that had drifted from whatever owns them, including a timing resolution quoted ten times better than the shipped tolerance, point at the owner instead of restating it (#1436)
4568d998 · 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 - the everyday `loot diff` stops answering about a change instead of about your work, because the endpoint you did not type is now the disk — one rule replacing two defaults, so `status` and `diff` read one DeltaSpec rather than two spellings of it, and seeing your own uncaptured edits no longer costs you a subject line you have not thought of yet (#1491)
f07d74e7 · 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 aspect a half refuses under stops being a const in the measure module and becomes a field of the row itself, built by a macro so a row names its fixture and never the sentence, because that was edit site 9 and the census that called it fictional had missed three module-level consts and five of the eight function-local ones while #1601 went on to pay it twice more; the five defaults each Workload constructor re-listed collapse into one plain base; and the two-edit promise is restated with the scope a throwaway half on a new axis measured, seventeen structural sites down to twelve, the last silent one now failing a test because a dimension added without an arm in Workload::id makes two fixtures report one workload_id (#1633)
c2a454dc · dbf3dbe6…diff - ancestor_closure stops re-walking the whole ancestry once per seed, and unlike its two siblings from the same hunt this ticket's numbers REPRODUCE in release - 94.97 ms against a claimed 101.1 and 383.9x against a claimed 424x, both within ten percent - so the debug-build correction #1704 and #1702 each needed does not apply here and the ranking that placed this finding stands. What does repeat is the other family trait: this ticket also said the fix already exists one file away, and that was wrong again, though for a narrower reason than #1702's. GraphView::ancestor_closure is NOT set-identical, proved by running rather than assumed - it seeds its out set with whatever it is handed, so an unrecognized seed comes back inside its own closure where the per-seed loop skipped it through graph.get(h).is_some(). A probe printed DagRepo 0 against GraphView 1 for a stranger alone and 6 against 7 mixed, differing by exactly the stranger, and this is not a corner because sync.rs hands it a PEER's declared heads. A dangling parent reached mid-walk is kept by both, which is why the fix is the shared walk plus a SEED filter and not either one alone. The instrument was blind for a reason worth naming precisely: --push-shape passed vec![parent], which is the shape of a /haves REPLY, where sync.rs passes that id's whole closure - so the half was already not describing the code, making this #1576's extend-in-place case rather than #1425's honest-but-narrow one, and #1702 had re-based the same readings one commit earlier so there was no unbroken series to protect. Widening it moved the instrument 4.9x against the UNCHANGED engine, 273 microseconds to 1.33 ms at 200 by 128, which is the non-vacuity proof taken before any fix was trusted. After, the quadratic term is gone: per doubling of depth the cost grew 2.8x, 4.0x and 4.3x before and grows 1.2x, 1.4x and 1.6x after, and at depth 512 the half reads 22.713 ms against 0.608. The evidence is therefore a THREE-point chain and not a pair - the landing position's 273 microseconds, the widened fixture on the old engine at 1.33 ms, and the widened fixture on the new engine at 283 - and only the last two are the A/B, because the first is a different fixture; reporting the outer pair alone reads as a regression, so that rule is written into HUNT-PERF.md rather than left for the next reader to rediscover. The control on the widened have is the part most worth recording: the first version was a count-nonzero floor, and a two-element have of parent and root clears a floor of two, passes coverage, and still seeds a 127-deep walk twice - about ninety-nine percent of the blindness back with every control green. It is now set equality against the have's own closure, the fixed point sync.rs actually passes, with a negative arm proving that closure is a fixed point and a red proof that reverting to the floor fails on the two-seed case (#1700)
04859438 · dbf3dbe6…diff - ADR 0079 settles what a visibility tier MEANS for a live delta row, and the answer is the shipped one for a reason narrower than the ticket argued: a live side is read at the tier the disk assigns it, because visibility describes SEALED content and loot gates on a key rather than on a tier - reading a live row at its recorded tier would make a declared tier withhold something all by itself for the first time anywhere in loot, and the first bytes it would withhold are the author's own from the author. The ticket's framing overstates what actually ships, and that was measured rather than repeated: with the embargoed path FINALIZED so the anchor holds it, a bare loot diff --content REFUSES at 0 paths rendered in the clear with the plaintext sitting unsealed on disk, so the real rule is that the record wins wherever it holds a version and the disk answers only for the rest - and the rest is a path added since the anchor. The two positions were far closer than the ticket says. The losing position is recorded in its own terms rather than strawmanned, because #536's discipline is about what leaves in a transcript and not about what the operator could otherwise reach, and an agent piping into a PR body does capture what the verb used to refuse; it lost because it has no well-formed answer for the row this ticket is actually about - a path with no recorded tier must fall back to .lootattributes, which is the disk's tier the long way round - and because the refusal is a BAD signal for that job, firing on paths the operator wrote thirty seconds ago while staying silent on the restricted paths #536 always meant to print. The residual is named with no mitigation claimed, since nothing stops the PR-body case and no mechanism at this seam could, the same bytes being one cat away. Its concern nevertheless landed as two real fixes that nothing covered. Unreadable::of_row asked the OLD side about prev_visibility, which is always absent over a live endpoint, so it was really asking about the disk: one path at one instant answered embargoed from cat, content not readable here from --content and sealed from --stat, which is verbatim the #1551 defect, and two of those three pointed the reader at loot grant for something ADR 0007 lets nobody grant. Reverting that term fails the new agreement pin while #1491's own pin stays GREEN, which is the demonstration that the old pin never covered it. And the in-the-clear tally under-reported: Tier::disclosed_by read one side, so dropping a restricted rule and editing the path - the ordinary declarative un-restrict, and the case the function's own doc names - printed recorded restricted plaintext under 2 paths rendered in the clear naming no tier. It now reads 1 restricted, filled through a REQUIRED constructor argument rather than by relaxing prev_visibility, which would print a was-to-now transition for a policy edit nothing recorded and reopen the #1005 phantom; over a both-recorded delta the tiers are equal and max is idempotent, so the tally can only gain a note and never lose one. A pin that closed on embargoed greater-or-equal to one is tightened to full set equality on the disclosure, because a nonzero check is not a guard. One label changed and no gate did: content not readable here becomes the embargo sentence, which AC2 cannot be satisfied without, and no byte of content moves in either direction. A row may now read internal in its header and embargoed in its body, which is correct under this ADR and asserted deliberately - the header is the disk's tier and the body is why the record's side would not open, and moving the header would be the losing position arriving one field at a time (#1574)
daaf69d3 · dbf3dbe6…diff - the one flake four runbooks name as the thing to re-run CANNOT FAIL A LAND, and has not been able to for the whole life of the advice: all three describe_contention tests have carried the ignore attribute since #476, whose own subject is move contention stress tests out of the land gate - the gate is cargo test --locked and never passes --ignored, so run directly the file reads 0 passed, 0 failed, 3 ignored. the advice naming it was written by #406 and #404, both ANCESTORS of #476 as merge-base confirms, and nothing updated it when the ground moved. that is the strongest possible argument against writing a list, so what replaces it is the PROPERTY with the fixed instances as examples: the two recurring shapes, a control failure and a Windows sharing error out of a rename or a remove; a four-step triage that captures to a FILE before re-running, reads the COUNT rather than the word, and records that a green re-run is NOT evidence; and an explicit what-would-make-this-section-wrong clause. the concurrency framing this ticket was filed under does not survive anywhere, because the fifth occurrence fired with NO second session running and a process check to prove it - so running alone is not evidence against a flake, and no runbook now says otherwise. the root cause is confirmed FIXED rather than assumed: a statement-scoped census over the WHOLE workspace, not just the crate its own census test covers, reads ten temp_dir occurrences and zero shared roots, each read individually - two are pure string tests, one is a read_dir gated on a prefix that cannot reach the testkit dirs, two are children carrying their own pid, two are false positives whose pid arrives in the next statement, and one derives its stem from a fixture dir both constructors give a pid. the two originally named tests split, and the split is the point. the settled ledger one is COVERED: at the fix parent it read the bare shared root, so resolved() was the single file under TEMP that all 161 fixture sites shared, and a sibling saving an empty ledger between this test save and its load reads back exactly the reported empty map - it is also the test the fix own perturbation experiment PREDICTED and then hit. the persist codec one is NOT covered and never was: it has never used the shared root, its directory has carried a pid and a counter since #252, an ANCESTOR of the fix, so #1667 neither caused nor cures it - its race is its own eight threads renaming onto one destination, and per this ticket explicit non-goal NO RETRY was added, only the two facts recorded where the next reader meets them. and the control-failure signature is corrected before being written down, because tonight refuted its narrowing: #1864 also failed at a control and its medium was a process-global static rather than a directory, so the rule is that a control failure is the signature of SHARED STATE something outside the test wrote, with both media named. it is deliberately not written as therefore it is a flake, because a control can also fail when the change broke the setup path - it says where to look FIRST, not what to conclude (#1606)
c87a13f6 · dbf3dbe6…diff - the removal wait stops reading a FAILED SCAN as an absent file, and the hole was that ONE fallible answer served two callers needing opposite failure behaviour: the precondition, where false-on-failure makes the assert FIRE and is safe, and the exit, where it makes the wait STOP and is not. the scan now answers three ways rather than two - named, not named, or the scan did not run. NotFound stays not-named, because an absent directory naming nothing is a statement rather than a failure; every other error is an Err; and each caller decides explicitly, the precondition panicking with its own message about failing to establish its own precondition, and the exit leaving ONLY through a scan that RAN and did not name the address, waiting a transient error out on the same store budget the removal already followed one level up. flatten is GONE, and it matters at the exit for the same reason, one entry wide: the entry whose read failed may be the very address being waited on, so flatten reports not-named for a name the scan never reached. the proof is a REAL failing scan rather than a simulated one - a regular file standing where the objects directory goes is a genuine OS refusal, error 267, reachable with no second process - and the two arrangements are DISJOINT on one fixture: with the fixed exit it is 0 passed 4 failed naming that error, and with the pre-fix exit restored it is 4 passed 0 failed, which IS the quiet success, reproduced rather than argued. #1596 is otherwise untouched, same helper and same budget. the projection neither surface derived is settled by naming WHICH QUANTITY SCALES: the honest half, being the only arm a design satisfying the never-authoritative rule can reach - so 22.3 becomes about 223 at ten times the paths, on BOTH surfaces, with measured now separated from extrapolated, since the read COUNT is linear and pinned at three sizes while the TIME was measured at one. 223 is therefore the order of magnitude at which to re-open the question rather than a reading, and the other arm about 439 is named as explicitly not the number to quote. the pin the ADR claimed is now the pin the test asserts, strengthened rather than narrowed because the numbers had already been observed: the two-per-path-plus-one relation holds EXACTLY at all three sizes, run rather than trusted, 101 against 50, 401 against 200 and 1601 against 800 - with the per-path multiplier and the fixed overhead kept as SEPARATE constants, since two-N-plus-one and three-N agree only at one, and with the old greater-than line deliberately NOT kept beside it, because over the constants this file writes it is green whatever the code does. four prose corrections ride along: a step that stated the conclusion its own section refuses, a caveat a commit message claimed and no file carried, two runbook short forms stronger than the long form they point at, and a count of three defects that lists two - which STOPS COUNTING rather than inventing a third (#1899)
a9018dad · dbf3dbe6…diff - the open two biggest arms become COUNTABLE, so a regression in them can fail a land instead of passing all three checks: store.rs held no instrumentation whatsoever, no Work, no tally, no bump - and that NEGATIVE was re-verified before anything was built on it, because a negative that has quietly become false is exactly how this class of blindness starts. every line number had moved and every one is re-derived: the graph read at 2200 rather than 1993, the keyring read at 2228 rather than 2019, the two call sites at 4467 and 4510 rather than 3468 and 3502. the unit is READS, one counter rather than a pair, and the reasoning is recorded rather than assumed. a byte count is NOT 0%-gatable here, being a function of fixture contents and encoding, so every format change would fail a land for a reason its author cannot act on - and tolerancing it instead re-creates precisely the load-sensitive alarm ADR 0073 rejected the timings for. at this seam a byte count would also be blind to its own stated reason, since the lazy half of #1536 already landed and read_graph_required still reads the file WHOLE while deferring the per-change manifests, so bytes READ did not move for #1536 and will not, while bytes DECODED are a different counter at a different seam whose value depends on which manifests a verb happens to touch. the object pair precedent does not transfer either: those are two because a MEMO sits between them, and nothing analogous splits a metadata read. disjointness is BUILT rather than assumed, because object files are whole files in the store too - the object store now reads through its own door, same body, same retry budget, no tally - and the two of that door four sites which tally NOTHING today are named in the code and deliberately left alone, since closing that gap would move an already-gated number. the tally sits OUTSIDE the retry loop, because inside it would count attempts and read 1 idle against 17 busy, and that placement is pinned DETERMINISTICALLY rather than by timing luck, since an absent path walks the whole budget by construction. the structural-zero bar is met by SHAPE rather than by not-zero: across a tenfold fixture the object counters go 60 to 600 and 20 to 200 while the store counter STANDS STILL AT 24 - per-open and not per-object - and no tally left on the object door, nor one that had picked up a per-path read, can produce that pair of lines. four mutations with counts read, and the discrimination one is a discrimination rather than a constant because it ALSO fails the excluded-counters pin, catching it from both directions. the gate was then run for real in a release build: exit 0, the workload id UNMOVED so no baseline is orphaned, and the new metric absent from the baseline, so the first armed land records it and the land after that is armed. and ADR 0073 consequence clause is amended exactly where it read as full coverage, with the correction stated in as many words, alongside its table row, its status header, and six other surfaces carrying the same claim (#1662)
904f6996 · dbf3dbe6…diff - three sweeps running, the commit that DELETES an asserted-complete list writes a new one in the sentence that replaces it - so the recurrence itself is recorded as a named shape rather than fixed a fourth time and forgotten: the replacement is where the next list gets written, because the author is mid-sentence explaining what the true set IS. and this change caught itself committing the class TWICE while writing, which is the evidence rather than an embarrassment - an amendment draft saying the page said twelve in FOUR places where it was five, and a draft of the replacement paragraph writing six of the NINETEEN artifacts. both numbers are gone. the six-item metadata list becomes the PROPERTY: the set is the StoreArtifact trait, RepoStore::read routes every implementor through one door, and what keeps the conclusion true as the set GROWS is each impl path resolving to a named file under the store root - verified across all nineteen, none into the object directory. the conclusion that carries the 0% gate is kept verbatim, because it was never the wrong part. a THIRD copy of that enumeration, which the ticket did not name, was found and folded in. the twelves take a RULE rather than a sweep: drop the numeral where the sentence is a present-tense claim about the current table, KEEP it where the sentence narrates what ADR 0073 decided or refused as an EVENT. fifteen sites fixed, six deliberately LEFT - including the ADR title, which is the name the decision was accepted under, and a dated amendment recording what one mechanical edit touched, since correcting THAT is precisely the defect this ticket warns about. one of the twelves was already off by one and now states the property instead. the durable guard refuses a cardinal standing before opt-in halves across derived directories, and its LIMITS are in its own header rather than discovered later. the mutation that matters most is M2: writing todays CORRECT number, fourteen, is still RED, which is what stops the next reader fixing it to a value that expires. and the control that mattered is M3: the first draft tokenized raw text, so a count split by an 80-column comment wrap read clean - it PASSED OVER ITS OWN DOC COMMENT, which was quoting the offending phrase - and with the comment-marker strip removed and the defect still in place the pin goes GREEN, which is what proves the strip load-bearing rather than decorative. the fourth count of the roster population is deleted and all three of its escape routes closed, and the ADR stops citing a miscounting line as its authority for the run-versus-night correction, making the landing times the authority and citing that line only for the NAME. the negative-control heading is narrowed to what was actually measured, on the fixture rather than on the metric, and the two unreconciled readings of one measurement are reconciled once - where the ticket own framing was itself slightly off, since the two are the same commit pair measured in two sessions (#1907)
b781aefd · dbf3dbe6…diff - the forge live-database suites now run in CI: test-main.yml stands up a postgres:18 service, provisions it the way setup-forge.js does including its default privileges, migrates it with the commit own loot-forge --migrate, sets every LOOT_FORGE_TEST database url the Rust and site suites read, and runs cargo test and then the site pg tests against it. LOOT_FORGE_TEST_REQUIRE_DATABASE turns a database test skip into a failure through pg::live_opt_in and the site skippingLive helper, and with it set and no url 91 Rust tests and all six site pg files fail, while two half-suites that used to skip silently now say so. The default-privilege tripwires run in CI by decision, and fail on a cluster without provisioning. A new migration that neutralises the roster policy passed the bare suite and failed rls.rs and membership.pg.test.ts against the cluster. It is a canary on landed main and not a pre-land gate, and the prose that said no workflow sets these urls now says that instead (#1735)
c0931f78 · 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 - loot resolve no longer refuses a delete/modify conflict that ours deleted: the #1988 guard reads a conflict record whose ours is its base as a deletion, so a disk with no file there matches it and a file written back there is still refused over, and on a shared primary its refusal names loot describe --allow-primary, the capture that works there. spawned-binary tests on a ferry-bounced two-head home position went red before the change for the ours-deleted resolve and the restored file (1 passed, 3 failed with the primary hint test), and with the deleted-ours arm removed (3 passed, 1 failed), with ours read as bytes whatever the record says (2 passed, 2 failed) and with a position-blind hint (3 passed, 1 failed). the implement brief now takes the job cap as a placeholder, keeps deploys inside the warrant and names SKILL.md, cites only what AGENTS.md says, says which commands need the lane position and gives the CR count and describe in PowerShell too; AGENTS.md gains the cargo fmt ban, ADR 0062 names the per-ticket safeguard that replaced the implement review, and the redeploy commands in workflow.md and diagnose-push-rejected name the new tag, scope the relay override to a forward bump and say the forge takes none, as the scripts repo reads them. ADR 0082 and apply-patch say the text deletion arm checks no preimage either (#1992) and pin that --check refuses a binary deletion the same way, the line-ending docs say CRLF and NUL, nul_offenders is private, and the sweep 10 wrap leftovers are fixed. the workspace suite is green (#1993)
7ecaa467 · dbf3dbe6…diff - loot apply-patch no longer records a conflict base equal to its ours: the header-disagreement stop took the parent content as the base, which is the ours object when the path is unchanged since the parent, and the loot resolve guard from #1993 reads a record whose ours is its base as a deletion, so a file on disk holding those bytes could be refused over. the stop now leaves such a base out. a new verb-tier test went red before the change (0 passed, 1 failed, the recorded base equal to ours) and with the filter disabled (12 passed, 1 failed), and the resolve guard doc and ADR 0080 now say the deletion reading is only as true as the records it is handed. the sweep 11 prose fixes ride along: hygiene.rs states that tests already check docs and skills files instead of naming them, workflow.md uses <jobs> for the build job cap and gives the relay and forge redeploy commands a Set-Location <scripts> prefix with the placeholder defined, afk-loop points at the brief for its placeholders instead of listing them, the rewrap leftovers in afk-loop and diagnose-push-rejected are fixed, and the README apply-patch line no longer says CRLF on either side is refused. runbook_portability and the workspace suite are green (#1999)
f221956f · dbf3dbe6…diff - every bare remove_file of a loose object in loot-core fixtures now waits for the absence it asserts, and the set that does is a census rather than a sentence: #1596 measured that a handle held without FILE_SHARE_DELETE makes remove_file return os error 32 and leave the file, fixed the sites in negotiation.rs and swept no further because its aggressor had reddened nothing else, and #1897 asked whether the rest were safe or merely unexposed. they were unexposed. the aggressor was rebuilt and lives in the tree now as testkit::hold_without_share_delete, and with it holding one handle across one removal the whole family went red site by site rather than statistically: the selection reads 1 passed, 10 failed, every panic os error 32, with the already-fixed negotiation site under the identical hold as the green control, and the second removal inside accept_loss measured on its own with the first hold lifted (0 passed, 1 failed). with the helper at all of them the same selection under the same hold is 11 passed. the helper moved from negotiation.rs into testkit keyed on the OBJECT DIRECTORY rather than a store directory, because the object_store.rs fixtures are an object directory with no store around them, and #1899 exit rule and the three-way scan answer came with it unchanged. the ticket list was wrong in BOTH directions, which is the finding: it named sites a realistic scan does not reach and MISSED two of the most exposed, the live-repo removals in engine.rs and custody.rs that are #1596 own shape; under an emulated indexer scanning the fixture roots, 50 paired interleaved rounds, the bare tree is red at a removal in 36 rounds over four sites, two of them the ones the list omitted, and the converted tree is red at a removal in ZERO. what is bare and why is now derived: tests/loose_object_removal_census.rs reads every removal whose statement or whose binding names a hex-encoded address out of src and tests, and names the one home, the aggressor pin own deliberate bare arm and the two PRODUCTION removals, which return their error rather than panicking and are right to. run against the pre-change files the census names exactly the nine test functions that were converted, the tenth site being the one it states it is blind to, a removal by directory entry, which was given its address so it could take the helper and so the census could see it. mutations: a bare removal put back single-line, multi-line and through a let binding reddens the census each time naming that function (2 passed, 1 failed each); a name dropped from the expected set reddens it (2 passed, 1 failed); blinding the address needle reddens the classifier fixture and the not-gone-blind guard too (0 passed, 3 failed); giving the aggressor FILE_SHARE_DELETE reddens the new pin because the bare removal then succeeds (4 passed, 1 failed); dropping the named-first precondition reddens the should-panic pin (4 passed, 1 failed); and the pre-#1899 scan spelling reddens the moved scan pin (4 passed, 1 failed). docs/agents/workflow.md flake section carries the rule, the aggressor and the census, and stops saying the fix ends at one file. a latent write-side exposure was found on the way and is NOT fixed here: save_objects_loose renames its staging file without store.rs retry, so a scan holding the stage makes an ordinary save fail with os error 32, which is the create side #1596 explicitly left alone. every edit is inside a cfg(test) item or a doc comment, so no production byte moves and no perf gate is owed. the workspace suite is green (3843 passed over 120 binaries, 7 ignored) (#1897)
b8eb322d · dbf3dbe6…diff - the live-database checks test-main.yml runs can run locally, from the same script CI runs, because the account is out of Actions minutes and a land never ran them: the provisioning, the database keys and the suites move out of the workflow into ci/test-main.sh, which builds every connection URL from a host and a port it requires rather than defaulting to the one a real cluster listens on, and the workflow keeps only what prepares a fresh runner and calls it. ci/local.sh starts a throwaway Postgres on its own port, refuses if anything already answers there, runs that script against it and deletes the cluster on exit, so it runs beside a real cluster and from a lane before a land. run in a lane against a portable Postgres 18.6 it applied forge migrations 0001 to 0017 and the three site migrations, and 122 cargo test results and all seven site live files passed with LOOT_FORGE_TEST_REQUIRE_DATABASE set, so none skipped, before and after the review fixes. with the returning alias put back into read.ts it failed at the site step on syntax error at or near returning, and pointed at the port a real cluster owns it refused with exit 2. the CI-shape pin reads through the script and its STEPS list, because the job is still named cargo test --workspace and a check on the workflow text alone would pass on the name after the command had gone; it went red when the workflow stopped calling the script and when the script dropped --locked. the comments and living docs that said the workflow sets the keys now say the script does, the dated ADR and evidence entries are left as history, and AGENTS.md, docs/agents/workflow.md and the afk-loop security hunt point at the script. the Actions wiring itself is unexercised until minutes return (#2061)
8ff6117c · 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 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 permissive deposit arm gets the pin its defence was resting on, the standing-lane assertion that could not fail gets a comparison, and the declined cross-shape census weighs the instrument it never considered: #2194 carries the sweep 14 fix-ups plus the five a focused review of #2188 found, each premise re-verified on the tree first. item 8 is judged kept rather than closed to match its siblings, because where the seal cannot be produced embargoed_paths keeps the tree entry claim and the tree entry instant, which an unsigned tree can spell 0, and the recorded defence is real, grant_sealed reading the same object through the same door before it seals anything; so the arm stays and both halves are asserted now, the row surviving with the number the entry chose and the grant that row plans refusing at the same address, under a control that the same call succeeds while the seal reads. item 9 is made to bite: a second path over the same Restricted seal is entered honestly, so the standing-lane arm is a comparison rather than an absence, and the comment names the mutation that reddens it. item 10 narrows a pin message that reached past what it asserted, onto the deposit-plan pins that do assert the fan-out. item 1 corrects the ADR 0023 cost paragraph, which named the spool leaf where the endpoint moved too, off an unescaped format string onto the shared escaping, so an endpoint holding a quote or a backslash had been emitting JSON a parser rejects; the matching commit correction is already a comment on #1971. item 3 weighs the text census and records a measurement instead of an assumption: it needs no visibility widening and spawns nothing, so no census can was the wrong shape of decline, but what a text census reads is a spelling and section 3 is a rule about rendered bytes, and measured, the comma-join needle finds its own home and sites that are not columns while the contract field is not spelled one way, so a needle on either spelling reads a subset and says nothing about a shape carrying no such field at all, which is the defect #1971 came here to fix. item 2 replaces a false inference in the land-change skill: those three headings belong to the cargo test gate, and every other gate arrives as a finding under prose of its own, the line-ending gate among them, so an unfamiliar heading is another gate before it is a new kind; workflow.md scopes the same sentence. item 11 takes the completeness claim off the deposit accessor doc, where privacy is what bounds the callers, and off the census block that asserted a key guard over a membership rule which does not check it. item 12 names the two readers still deciding off the entry, the mirror projection and the forge manifest fold, with what bounds each, and records that neither belongs to #2187. items 4 to 7 are the smells: the forwarding closure, the clone taken and then borrowed, the exemption pin that read the real machine config and now builds its report from values, and the reversed import. red under mutation, counts read each time: the unreadable-seal arm dropped (68 passed and 1 failed, the row reading empty), that arm handing on a substituted instant (68 passed and 1 failed, 9999 where 0 belongs), restricted_paths made to ask the seal (68 passed and 1 failed, secret.txt joining owned.txt in the standing lane), a contract field added to the report the sender posts (6 passed and 2 failed) and joined_col joining with a semicolon (loot-cli 1335 passed and 8 failed), each restored to 69 passed and 0 failed. no migration, no wire or format byte moves and no host behaviour moves, and no output byte moves either, every code edit here being a refactor or a test, so this owes no deploy. the workspace suite is green (4110 passed over 132 binaries, 8 ignored) (#2194)
609afd9f · 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 - the second failure mode in the tutorial interrupt test stops being uncharacterized, because a message for it already existed in main CI and what it shows is a handler reporting a CALL rather than a PATH: run 35373927304 on 5e66658 went red on ubuntu-latest in 0.24 seconds with the interrupted - removed line on stdout and that path still on disk, at the agreement assertion, on a head that already carried the #2037 fix - so the change read as closing this did not, and the two sightings the ticket comment carries are the pre-#2037 assertion and are the defect #2037 named. what is established is the disagreement and nothing past it: why remove_dir_all answered success is NOT known and is not written down as if it were. so sweep_interrupted_tree asks the filesystem after a removal reports success and prints the refusal when the path is still there or cannot be read, which is the property #2037 asserted and did not reach, and the interrupt test says what a surviving tree held instead of printing true. THE SATURATION HALF IS A NEGATIVE RESULT STATED WITH ITS COUNT: 150 runs with capture on from the first, under up to four concurrent workspace builds at -j 6 over a scratch copy, against 12 idle, none failed, plus 300 more under a load generator discarded for measuring 7.9 percent busy while it spent its wall clock deleting a 16 GB target directory. the load that did run was bursty rather than sustained and the file says so, a median of 3.6 percent against a peak of 99.8, while the spread moved from 0.38 to 0.44 seconds idle to 0.48 to 126.4 loaded - and none in 450 does not retire one in 534, which is recorded too. two smaller corrections ride along, both read off runs: the fixture behind a tree already gone reports as removed answered NotFound over a directory it had left standing, so it pinned the mapping and never the filesystem, and the sentence saying the swept sandbox holds one heartbeat file missed the empty alice directory that walk mints ahead of its first prompt. workflow.md gains the shape its own falsifier asked to be told about, a test that fails at a closing assertion because the process it drove reported the state that assertion checks. red under mutation, counts read each time: the filesystem confirmation deleted failed the new pin (10 passed and 1 failed, the removal line over a tree still there), the old fixture restored failed the already-gone pin (10 passed and 1 failed, the refusal naming the path), and through the integration test a handler whose removal does nothing failed it naming the remnant as still there holding alice and heartbeat (2 passed and 1 failed), each restored to 11 passed and to 3 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) (#1916)
2c05fdc7 · 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 - the tutorial interrupt red #1916 read as a removal that lied is a tree made again after the sweep: on the linux runner std remove_dir_all answers success only once the root is gone, the captured transcript prints the first prompt ahead of the handler line, and walk, still running on the main thread beside the handler, opened with a create_dir_all over alice that minted the root back before ask beat the heartbeat into it. two delays injected into that sequencing reproduced the red and its alice, heartbeat remnant on this desktop with confirm_gone in place, so that check alone would not have closed it; Sandbox::create now makes alice before the handler is armed, the same two delays pass, and a unit pin drives the sweep, the walk and the beat in that order. what stays open is said at the fix: other writers, the relay thread and the loot subprocesses among them, can still put a path back mid-step. the CI run recorded the two readings and not the sequencing, so the mechanism is established by reading and by the reproduction rather than observed there, and workflow.md stops calling the report and the look one moment. the loot-s3 client keeps its 60 s budget for want of any measured rate to its bucket, but a request that runs out now names it and what it covers, where reqwest said error sending request or error decoding response body with no word of a deadline. the sdk doc stops claiming the native policy: the formula is copied, native allows the send budget between answer chunks where the sdk allows idleMs, the rounding differs, wire.rs now points back at the copy, and the unit test reads the floor from its constant. smaller: the engine.rs verdict compares the band against the band, the measure.rs refusals state a property instead of counting callers, the fixture generator import note sits at the import, and transportFailure takes a role rather than a string. red under mutation, counts read each time: create_dir_all put back in walk (11 passed and 1 failed, holding alice and heartbeat), the s3 timeout naming removed (12 passed and 2 failed), the sdk upload rounded down (6 passed and 1 failed), each restored to 12, 14 and 7 passed. no migration and no wire or format byte moves; the forge and relay deployables word a timed out object store request differently once next deployed, and no code in the tree matches on that text, so this owes no deploy. the workspace suite is green (4192 passed over 133 binaries, 9 ignored) and the sdk suite is green (137 passed over 13 files) (#2254)
bdeb6a0c · 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 - a network budget refusal now says how long the wait lasted beside the limit, and a relay loot-net serves writes a received line and an answered line per request under LOOT_NET_TIMING=1 on the client clock, because the #2079 stall is unreproduced and nothing on record could say where its time went. the wait is timed around the whole send while the reqwest limit starts inside it, so time well past the limit was spent in this process rather than waiting for the host. the relay lines landed in the failing test captured output beside the client lines when a panic was put after a push and removed, and the land-shell use of the flag is written into workflow.md. no retry is built and no cause is claimed. a workspace run went red once on both format_skew_gate tests under load, one printing the wait lasted 5.5 s on its 5 s probe, and both passed alone: that file now prints what its stub took. the seek smoke test drops the flag from the loot it spawns, since it pins their stderr and failed with the flag set. red under mutation, counts read each time: the wait printed as the limit (1 passed and 1 failed), the wait stored as the limit (0 passed and 1 failed), the relay layer not attached (0 passed and 1 failed), no line on arrival (0 passed and 1 failed), each restored to 2, 1, 1 and 1 passed. no migration and no wire or format byte moves; a relay prints lines only with the flag set, so this owes no deploy. the workspace suite is green (4196 passed over 133 binaries, 9 ignored) (#2079)
ac8afc4b · dbf3dbe6…diff - Manifest::addresses reads the frames only after a step over them shows the stored keys strictly ascend in Path order, so a manifest whose frames are not its map, two keys naming one path or keys out of the map order, answers from the map, and a pull over a corrupt local graph file no longer asks for an address the map dropped. tier_counts keeps its backslash test, now documented as not a proof, and the a.txt and a/b ordering it cites is corrected to component order in both places. what the check costs, paired --missing readings, one loot-perf-gate --features count per arm, eight interleaved rounds, load idle at 2 to 10 percent: 0.875 to 0.924 ms without it against 2.09 to 2.18 ms with it at width 200, and 5.76 to 5.88 against 10.50 to 10.63 ms at width 800, where a decode arm read 18.75 to 19.13 and 100.0 to 100.8 ms, so about 9x against the decode where it was about 21x and 17x; the Path comparison is most of it. red under mutation, counts read each time: the check removed, compared by byte, not strict, byte equality only and path equality only (1 passed and 1 failed each), restored to 2 passed. riding along: store.rs and ADR 0075 say the ingest transaction still reads for checks of its own and that the landing decision is what reads nothing inside it, that a push with no proposal open pays 0.28 to 0.43 ms where #2177 asked for nothing measurable, and that the fallback decision rests on one repo size; finish_stage says why any refusal is answered by the address on disk; the dated test counts in format_skew_gate.rs and workflow.md say at the time; two rewraps; the calls.rs runner stubs use the file imports. no migration and no format byte moves, so this owes no deploy. the workspace suite is green (4274 passed over 135 binaries, 10 ignored) (#2273)
0a64d280 · 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 - 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 - 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 browser SDK budget refusal prints how long the wait lasted beside the limit, as the native refusal has since #2079, and names it as read off the page clock: the wait runs from the moment its timer was armed, the whole request for the answer and the time since the last bytes for a silence mid-answer, so a frozen or throttled tab shows as a wait well past the limit. budgetRanOut and transport_text_from point at each other. a forge loot-forge serves writes the received and answered lines a relay writes under LOOT_NET_TIMING=1, opening loot-net: forge, through loot_net::timing_layer, which the relay now attaches through too; with the flag unset serve_until passes no sink and the forge serves the router app builds with no layer added. pinned by a stalling loopback peer across a blocked page loop and across a silence after a streamed answer, and by a forge with a timing sink. red first (7 passed and 2 failed). red under mutation, counts read each time, each restored to green: the wait printed as the limit (8 and 1), the wait timed from the first arm (8 and 1), the clock label dropped (7 and 2), the forge passing no sink (0 and 1), the forge named relay (0 and 1), the arrival line naming relay whatever the server (0 and 1), the layer ignoring its sink (0 and 1 on the forge, 2 and 1 on the relay). CONTEXT.md and workflow.md record it. no migration, no format byte and no wire byte moves; the SDK refusal text changes and the forge writes lines only under a flag the scripts repo never sets, so this owes no deploy. the workspace suite is green (4398 passed over 139 binaries, 13 ignored), the sdk suite 141 passed over 13 files, tsc clean (#2313)
1b9a9597 · 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 - revset_everywhere names each scratch directory by a per-process counter rather than the clock, so two tests asking for the same row no longer share one copy: on Windows SystemTime ticks in 100 ns steps and the tests here reach scratch(archive) together, so a second caller could empty the first caller copy and fill it with another fixture store, which answers no change matching word for word. reproduced by overwriting one copy with another fixture by hand, and by naming scratch by process id alone (5 passed and 5 failed, the historic test failing at loot archive with the #2258 message). pinned by concurrent_scratch_callers_never_share_a_directory, red under the old clock naming in 18 runs of 18 (0 passed and 1 failed, 9 to 14 of 400 directories handed twice), restored green. ci/test-main.sh runs the workspace suite with --no-fail-fast and runs the site live suites after a red workspace suite, recording a failed suite and exiting non-zero naming it, while a failed provisioning step still stops the run; the bare step-or-record form was measured exiting 0 over a failed step, so a suite step runs in a subshell with set -e. the published_surfaces pin reads the --no-fail-fast line (5 passed and 1 failed with the flag removed). no migration, format byte or wire byte moves. the workspace suite is green under bash ci/local.sh against Postgres 18 (4431 passed over 138 binaries, 13 ignored; site live suites 7 files passed) (#2258)
e39a6465 · 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
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.