Changes touching this path
- 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… - loot gates --run now exits with the verdict its rows state: 0 when every gate in the phase cleared, 2 when a gate ran and refused, and 1 when loot could not run one, a program that never started or a malformed declaration, which outranks a refusal. a listing still runs nothing, and the porcelain and json bytes are unchanged, while the human sentence that said the verb exits 0 either way now names the code. the code rides the shape through a new Emit exit_code hook read at the same output boundary as bytes and notice, so gates stays in the dispatch table and no third early dispatch was added, and ADR 0076 gains a dated amendment recording the codes, superseding the #1538 exit 0 call, and noting that buoy could now move into the table while bisect dispatches early for another reason. CONTEXT.md follows. a new spawned-binary test pins both arms in each output format plus the listing, the malformed file and the codes the help states; with the dispatcher ignoring the shape code it went red (4 passed, 2 failed) while the unit pin stayed green (1 passed), with the precedence swapped both went red (5 passed, 1 failed; 0 passed, 1 failed), and with the refusal code moved the help pin went red (5 passed, 1 failed). the workspace suite is green (#1764)
b1126183 · 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 - loot pipeline (#2127, ADR 0091, map #2099 ticket 3): the tracked .lootpipeline at the repo root, in the .lootattributes grain with no dependency column, one step per line as name, trigger, kind and what, where the trigger names the tree a step judges, change or main, never the moment, and the kind is run, argv through the spawn seam with {message} and {paths} as gates reads them through one shared builder gates::spawnable, or check, a loot-native gate deferred naming #2128 until the checks live where the verb can reach them, and a deferred step is a refusal exiting 1, never a pass. loot pipeline lists and spawns nothing; run judges the working tree by default or, under --version, a scratch tree of a held version written by Workspace::scratch_tree from readable_tree_at so the readability decision precedes any file, holding the version own .lootpipeline, and removed on drop, with the hand-off from Workspace::version_handoff naming the paths whose readable content differs from the first parent; --only narrows to one step and an unknown name is refused naming the declared set; status reads the pipeline/TRIGGER/KIND passes and pipeline/approve admissions trusted keys signed on a version through attester_trust, passes only. A tracked file never runs implicitly: loot new with a run step declared and no .loot/gates line reaches no spawner, pinned. Frozen shapes in ADR 0023 amendment, exit code on the shape. Measured on the release binary: a two-step run 74 ms, run --version 47 ms and a listing 18 ms on a three-file repo; on this repo of 1452 tracked files, run --version HEAD materializes the whole readable tree in 1.2 s against a 0.15 s listing. Found by that measurement, not by thought: under load a Windows scanner held a freshly written scratch file, the one-shot remove_dir_all failed and its swallowed error left plaintext in the temp dir, so ScratchTree drop now retries over half a second and names the path on stderr if it still cannot remove it. Seven censuses moved, each read off its own refusal: the verb tier method count to 387, MACHINE_OUTPUT gains pipeline in sorted position, the usage spells the list leaf as an invocation line, README block and its all 84 verbs sentence, 133 verb lines in three places, the revset placeholder lists take <change|main>, <step> and <id> and SELECTOR_ONLY takes pipeline status, and the site CLI page names the verb; ADR 0066 counts 84 dispatched, 82 in the table, 32 emit machine output with an amendment. CONTEXT.md Pipeline entry says what is built. Nine unit pins plus two in change.rs, five broken once by a named mutation and confirmed red at 0 passed 1 failed then green at 1 passed: a check made a pass, the trigger filter widened, the scratch removal removed, the approval role made a job, and the tracked file run at a finalize. loot-cli and loot-first green across 64 binaries, 2162 passed; the site verb census green. No wire, format or store byte moves, no migration (#2127)
fe693773 · dbf3dbe6…diff - a gate row whose program could not be started now refuses as not run rather than as a finding, the cargo re-run after a first run that reached diagnostics excepted as #2071 decided: impl From<String> for Refusal is gone, so each GATES row names the kind of each refusal where it builds it. spawn::require takes its refusal type from the caller, so the npm gates build a NotRun in the spawn-failure closure and a Finding in the non-zero one, and the lockfile and perf gates carry their spawn failures the same way; gates::guard_with now answers gates::Refused, the CliError plus unstarted, read off the runner outcomes (true when each gate that did not clear never started), which loot new and loot push convert with the same ? and the user-gates row maps to NotRun. an npm step that could not be spawned refuses in its own words, naming the gate, the step, npm as this platform spawns it and the operating system reason, in place of the step failure advice; the offline degrade does not cover it, since it classifies the output of an npm ci that ran. the merged pass gives any NotRun regate_not_run_remedy, which names no program and no longer claims that nothing was built or run, that no gate judged the tree, or that the cause is the machine rather than the tree, each false for some row that now reaches it; a program that never started is re-run nowhere. pinned by a census over GATES that drives each row with every spawn failing (a row that reached the spawner refuses NotRun naming the program and the reason, spawns nothing more, and on the merged pass gets a remedy with neither the ADR 0055 story nor the program) and by its positive control with every step failing (still a finding, still the story on the merged pass); the finding prose of each spawning row was captured before the change and is byte-identical after it. red first against the unchanged code (1 passed and 1 failed). red under mutation, counts read each time, each restored to green: the user-gates row always a finding (1 and 1), always not run (1 and 1, the control), the npm install spawn failure a finding (1 and 1), that spawn retried once (1 and 1), the npm local step spawn failure a finding (0 and 1), the lockfile spawn failure a finding (1 and 1), the perf build spawn failure a finding (1 and 1), the merged NotRun given the cargo clean remedy (1 and 1), given the ADR 0055 story (1 and 1), the npm refusal not naming its program (1 and 1), unstarted read as any rather than each (0 and 1), unstarted forced false (0 and 1), a malformed declaration read as unstarted (0 and 1). ADR 0055 gains a 2193 amendment and the land-change skill no longer names the From default. no migration, no format byte and no published wording moves, so this owes no deploy, but a land runs the primary release loot-first, so this is live only once those binaries are rebuilt. the workspace suite is green (4385 passed over 138 binaries, 12 ignored) (#2193)
20ef1484 · dbf3dbe6…diff
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.