Changes touching this path
- loot-first in Rust: land policy behind a Forge seam (#218) — new loot-first bin crate (ledger/forge/policy/orchestrator), in-process Workspace reads via loot-cli lib split, typed pr-map owner + shared ferry::WipState; every policy decide-tested against a fake forge. Build+test only; shadow-run, real land, and ps1 deletion remain operator-gated.
58c28888 · dbf3dbe6… - Run the pre-land gate in the landing position's tree, not the shared root (#287)
orchestrator::paths() derived its one `root` from ws.dot() -- always the
SHARED store's .loot -- so a land from a lane ran `cargo test` (and the
relay push) in the primary checkout's possibly-stale tree. The #284 land
was validated only because the operator had run the suite in the lane by
hand. Same shared-vs-position confusion as the #229 mirror-gitdir and
gh-cwd dogfood fixes.
Fix: split Paths.root into `position` (Workspace::root(), new accessor:
the lane dir from a lane, the checkout on the primary) and `checkout`
(the shared .loot's parent). The pre-land cargo test now runs in
`position` -- the tree about to be signed.
relay_push decision: it now also runs in `position`, deliberately. loot
push discovers .loot from its cwd and a lane's .loot points at the shared
store, so the push ships the same store from either dir -- but from the
position it reads the tip this land just signed (has_unsigned_tip) and
records its push op in the landing position's own oplog instead of
writing the primary's (ADR 0034 single-writer). The spawned binary is
now resolved beside the running loot-first (one cargo build produces the
pair) with the old target/release fallback, because a lane tree carries
no release build of its own.
The drift guard's git reads and init-hook stay on `checkout` on purpose:
only the primary has a .git; run from a lane dir every probe would fail
and silently mute the guard.
Tests: loot-first regression pair (lane position vs checkout, primary
degenerate case) built on a real spawned lane via temp-dir helpers;
loot-cli locks root()-vs-dot() semantics at the Workspace seam.
46f1b598 · dbf3dbe6…diff - primary catch-up: adopt landed main (post #287)
7e3bbe42 · dbf3dbe6…diff - 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 - 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 - user-declared gates arrive as data rather than as hooks, and the decision the ticket owed splits rather than picking one of its three options, because the gate vocabulary is two halves with different properties. The mechanism moves down into loot-cli, since its value is concentrated in the fake - a FakeSpawner that panics on an unstubbed step is what turns no process spawned from a claim into a failure, and two fakes drift with a fake that has quietly stopped intercepting being indistinguishable from one that works; loot-first keeps a re-export shim so no orchestrator caller changed. The phase axis is duplicated deliberately because it is not the same axis: loot-first asks about two trees, one of which only a land produces, while the CLI asks about two commitments, and a shared enum would carry four variants of which each consumer must refuse two - a rule nothing checks, where two small enums are checked by the type system. A new crate was rejected on arithmetic rather than taste, since loot-first already depends on loot-cli and there is no pair needing a bridge; loot-core was rejected because only half of #1682's criterion transfers, and that half is now a census asserting the engine spawns nothing rather than a sentence. A gate runs after the capture and can never prevent it: it does not decide whether the tree is recorded, only whether it is signed or sent, so a refusal costs the signature and nothing else, which is the ordering #174 already chose. On the confidentiality boundary the absolute form of the criterion is unachievable by any mechanism that runs a command in the working tree, and saying so is better than a strong claim that will not hold - loot surface writes and never deletes, so a path whose grant lapsed stays on disk as stale plaintext readable by cat, grep, the editor and every process running as the operator. The guarantee is therefore the narrow checkable one, that a gate is granted no read the operator does not already have, held up by four facts each of which a regression would have to break first: the runner never sees a Workspace, nothing is materialized, a Step carries program args and cwd with no environment channel, and there is no shell. A fifth fact answers a different attack - the declaration is untracked, so a pull cannot write one, which is git's own reason for never transporting hooks and matters more here since ADR 0075 has the forge holding proposals. This ticket also understated the surface that already exists: loot resolve --tool hands a user command decrypted plaintext today via LOOT_BASE and its siblings, so gates are not the first such door but they are the one that opens none (#1538)
44d4869a · 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 - loot-hygiene (#2128, map #2099 ticket 4): the land text gates move from loot-first into a crate with no dependency that builds for wasm32, re-exported as loot_first::hygiene so every gate row, checkout test and AGENTS.md path reads unchanged; a new crate rather than loot-codec because three consumers now read it, loot-first, loot-cli and loot-wasm, and the wasm one is the fact ADR 0076 §2 no-new-crate reasoning did not have. Corpus::from_entries files an in-memory tree under exactly the allowlist the directory walk applies, top-level files plus SOURCE_ROOTS filtered by TEXT_EXTENSIONS minus SKIP_DIRS and SKIP_PATHS, through one filing rule both constructors share, and a link resolves against the entry set with dot and dotdot folded, so a Worker with no directory answers what the land answers. CORPUS_CHECKS is the table a .lootpipeline check step names, keyed by the land gate names and pinned to them, holding the floor-free halves: the land refusals still refuse a vacuous walk first because a mis-rooted land must not read as clean, but a three-file repo is not mis-rooted, so a check answers about the files it was handed. loot pipeline now runs check steps in-process over one corpus walk per run, a finding is failed and exits 2, an unknown name cannot start and names the set and exits 1, and the ? deferral #2127 shipped this morning is retired the same day with the ADR 0023 amendment saying so. loot-wasm links the crate and a wasm-bindgen test runs the three checks from entries under node. Measured: the site wasm is 475667 bytes before and after, unchanged, because nothing exported calls the crate; native the three checks over a 1120-file synthetic corpus take 0.8 ms in release and 28.0 ms under wasm in node; through the release binary on this repo, roughly 1355 covered files and 20 MB by an approximate count, the three checks cost 160 ms warm against a 73 ms listing and 6.5 s on a cold page cache. Two lands worth of censuses read off their refusals: the destination allowlist row for the moved file, and no other. Five pins broken once by a named mutation, red at 0 passed 1 failed then green at 1 passed: from_entries skipping widened, link resolution made true, the table key misspelled, a finding made a pass, an unknown check made a pass; the fifth sweep needed a second pass because its first restore string matched twice and left the mutation applied, caught by the full run that followed. 66 test binaries green across loot-hygiene, loot-cli and loot-first. No wire, format or store byte moves, no migration (#2128)
858575e1 · 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.