feat(land): four of the lap's six steps as verbs, and three decisions as predicates - #829
Conversation
CLOUD-1335 `batten land` has no row: D0–D3 landed fetch, rebase and push in-process, so the capability wall CLOUD-1148 §D measured is gone and the lap still has no destination
WhyCLOUD-1148's acceptance says *"the child rows for * **The wall that justified not filing it is gone, and the measurement is on ** Measured at
The decision half is landed too and is not this row's to rebuild: So what is left is orchestration, and nothing owns it. Scope: a PARALLEL verb, and the retirement is NOT here
Deliberately not in this row (§2), each because it is separable and none because it is hard:
What must not happenAuto-resolving a rebase conflict. Any wall clock. Reading both sides of the raced wait. The lap races "is this SHA green" against "is this SHA still landable" and the loser's exit code is voided. A port that waits on both, or reads whichever it happens to see, has changed the economy the race exists for. Refinement — Ready ( Refinement gate: Definition of Ready & Done. This body carries only specializations. {
"source_of_truth": "crates/batten/src/land.rs and crates/batten/src/policy/presets/landing-loop/rebase-conflict-stops-the-lap.rego",
"gate": { "task": "verify", "exits": [0, 2, 3] },
"commit_type": "feat",
"blockers": [],
"tests": [
{
"file": "crates/batten/src/policy/presets/landing-loop/rebase-conflict-stops-the-lap.rego",
"mutation": "conflict-unread"
},
{
"file": "crates/batten/src/policy/presets/landing-loop/rebase-conflict-stops-the-lap.rego",
"mutation": "clean-lap-unpriced"
}
]
}WHY THE ONE DECISION IS A PRESET PREDICATE RATHER THAN A
Acceptance
Filed because CLOUD-1148's acceptance names this row and nothing had opened it, while the capability wall its §D recorded was closed by PR #812 four phases ago. CLOUD-1355 A declared mutation naming a case that does not exist is only reachable from the nightly sweep, so `graph-check/receipt-carries-no-ids` has been dead for its whole life and cannot be repaired
Why
The first is repaired on the branch that filed this — **The second cannot be repaired at all, and that is the finding. ** The sensor gap is the wider half
That is the defect worth a mechanism: the census asks whether every gate has a mutation, and nothing asks whether every declared mutation names a case that exists. THE DECISION IS A MODULE, AND THAT IS THE RE-SCOPEThis row was first written with its mechanism in It is also the better shape on its merits. The question — does this declared case name appear in the suite this declaration names? — is answered entirely from
What must not happenDropping the **Editing either governed file. ** Re-parsing the row format. The three-field Reading the nightly's exit 3 as a flake. It is Refinement — Ready (a declared mutation's case name resolves where a contributor sees it) Refinement gate: Definition of Ready & Done. This body carries only specializations. {
"source_of_truth": "policy/mutation-declared-case.rego",
"gate": { "task": "verify", "exits": [0, 2, 3] },
"commit_type": "fix",
"blockers": [],
"tests": [
{
"file": "policy/mutation-declared-case.rego",
"mutation": "unresolved-case-unread"
},
{
"file": "policy/mutation-declared-case.rego",
"mutation": "every-declaration-reported"
}
]
}
Acceptance
Filed rather than fixed, because the one repair available for the second instance is a whole-program retirement this row does not claim. CLOUD-1338 The landing lap's second half has no row: `land replay` landed the git work, and the push, the raced wait and the fast-forward that turn it into a lap are still only bash
WhyCLOUD-1335 landed the lap's replay half — **So a lap exists that cannot lap. ** What is already in hand, so this row builds an orchestration and not a capabilityMeasured against the tree after CLOUD-1335:
The gap that is genuinely new work is the race, and one of its two arms. What must not happen, and each is a way a port looks finished and is notReading both sides of the wait. The lap races "is this SHA green" against "is this SHA still landable" and the loser's exit code is voided. That is an economy, not an implementation detail: the moment **Any wall clock. ** A guessed Speculative linearization. Still out of scope, still for CLOUD-1306's reason: it records an unhandled arm (a POISONED bet, as against a LOST one), and porting it under a row that has not decided that would conserve the defect. Retiring Refinement — Ready (the lap's second half: verify, push, the raced wait, the fast-forward) Refinement gate: Definition of Ready & Done. This body carries only specializations. {
"source_of_truth": "crates/batten/src/land.rs and crates/batten/src/policy/presets/landing-loop/lap-waits-on-one-answer.rego",
"gate": { "task": "verify", "exits": [0, 2, 3] },
"commit_type": "feat",
"blockers": [],
"tests": [
{
"file": "crates/batten/src/policy/presets/landing-loop/lap-waits-on-one-answer.rego",
"mutation": "loser-read"
},
{
"file": "crates/batten/src/policy/presets/landing-loop/lap-waits-on-one-answer.rego",
"mutation": "single-answer-unpriced"
}
]
}THE DECISION IS A PREDICATE AND THE RACE IS NOT, which is the split CLOUD-1335 had to learn and this row inherits. Running two pollers and taking the first answer is a LOOP and belongs in
Acceptance
Filed by CLOUD-1335's scope narrowing rather than left as a gap in it — the same slicing D1, D2 and D3 took. |
ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Free Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (14)
Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review. 📝 WalkthroughWalkthroughThe change adds Merge Risk: ⚪ Minimal · up to The PR adds landing replay behavior and conflict-stop policy support without any identified merge-blocking risk; it is merge-ready after normal checks and review. Note 🎁 Summarized by CodeRabbit FreeYour organization is on the Free plan. CodeRabbit will generate a high-level summary and a walkthrough for each pull request. For a comprehensive line-by-line review, please upgrade your subscription to CodeRabbit Essentials by visiting https://app.coderabbit.ai/settings/billing. Comment |
The landing loop has exactly one step that needs a person — a replay that conflicts — and nothing in the tree could see it. `gitwrite::rebase` refuses with `Rebase::Conflicted` and its suite pins that, but the DECISION about what a lap may do next lived only in bash, so a lap that conflicted and carried on was expressible with every gate green. `rebase-conflict-stops-the-lap` is that decision as a `landing-loop` preset predicate: given the replay outcome the lap recorded, may it continue? The replay, the loop and the remedy all stay outside, so the module needs no clock, no working tree and no remote. It reads every record and narrows on a `rebase` KIND column rather than on a record name, because the name is the consumer's and a preset naming one ships rule 1's violation into every consumer's binary. Two arms rather than an optional key: a conflict with a path to name carries it as the finding's own pointer, and one without OMITS the key rather than handing a reader a `-` that opens nothing. Both declared mutations sit on the same conjunct and run in opposite directions. `conflict-unread` makes the predicate never fire and only the deny half catches it; `clean-lap-unpriced` makes it fire on everything and only the anti-vacuity half catches that. Refusing nothing and refusing everything are both non-gates, and no single mutation reaches both. Recorded in the module because it cost an hour: a preset's modules share one `package`, so the four obvious names were already bound by siblings, and regorus answers a redefinition with `node_idx 114 out of bounds for module 0` rather than with a redefinition error — which reads as an engine fault. Refs: CLOUD-1335, CLOUD-1269, CLOUD-1148
CLOUD-1148 §D recorded `batten land` as blocked by a capability: the lap performs git writes the engine did not have. D1–D3 landed every one of them as a `pub fn`, and nothing consumed them — the capabilities shipped as a parallel verb and a library, and the lap that was the reason for acquiring them was deferred to its own change. This is that change's first half. `land::replay` is one lap's git work: fetch the base, write what it brought to the odb, move the tracking ref, replay the branch onto it, and append what happened to a record. No `git` binary is spawned — `no_second_git_invoker_exists` scans this file like every other and stays green over a module performing every write that rule's own comment named as unreachable. THE THREE STEPS OF THE ADVANCE HAVE TO STAY IN ORDER, and are easy to write as two: objects are fetched, then WRITTEN, and only then does the ref move. A ref moved before its objects landed names a commit the clone cannot read, which is a corrupt clone rather than a failed fetch. IT DECIDES NOTHING. Whether a lap may continue past a conflicted replay is `rebase-conflict-stops-the-lap`'s verdict over the record this writes, which is CLOUD-1148's thesis read forwards: the mechanics move to the engine and the decisions become Rego. So a consumer wanting a different rule about conflicts writes a different module and this code does not change. The one thing it will not do is resolve a conflict — `gitwrite::rebase` refuses rather than taking a strategy, and that refusal is carried outward unchanged. The record APPENDS rather than replaces, which is the difference from the other two `VERB_WRITTEN` stores: it is a history, and the module reads its last line so a conflict a later lap resolved stops refusing. That only works if the resolution writes a line of its own, so every outcome is recorded and not just the conflicted one. `land` is placed in `module-layering` with its `hook` and `check` edges forbidden — transitively rather than by its own effect, since it reaches `lease`, and a guarantee routable around by one hop is not one. Refs: CLOUD-1335, CLOUD-1148, CLOUD-1274 Admits: abb2627858b84d4eee8bcb71f3ae8d0fc38083434640d97a9474d23a4c8f9e83 Admits-rule: protected-mutation Admits-verdict: path write refused Admits-subject: policy/module-layering.rego Admits-head: c774a73 Admits-epoch: 0a320ce25727111b30416e5cc416e9294fc3303b59762fab85faa9ababbcff71 Admits-author: alec@wenzowski.com Admits-prev: 50e7490f70ada7ac544455fe62ebaab155952a290d23f2920b07fea4ed488a5a Admits-answer-lost: Without it `land` is unplaced, which the module reports as V-LAYER-UNPLACED rather than allowing, so `batten check` refuses and the branch cannot land at all. The alternative is not landing the row. Admits-answer-precondition: The layering table is a literal set inside the module itself; there is no config surface that can add a module to it, so writing the file is the only route. CLOUD-1335 adds `crates/batten/src/land.rs`, and the table's own absence-is-an-error clause raises V-LAYER-UNPLACED for any module it does not name — so the change is obligatory rather than optional, and it lands as a set member plus its comment in a diff a reviewer reads. Admits-answer-rejected-route: `config read first` — rejected because no `batten.toml` key projects into this table: `adapters`, the placement set and the forbidden-edge map are Rego literals compiled from this file, so there is nothing in config to read or set. `patch run first` — rejected because there is no generator behind this file; it is hand-authored and `mise run fix` regenerates completions, man pages, the schema and snapshots, none of which is this module. Admits: 6b44f24c20cc1953082c5adb262047aebeb17ccc5486ba3b1eabef819ede8fa4 Admits-rule: protected-mutation Admits-verdict: path write refused Admits-subject: .serena/memories/core.md Admits-head: c774a73 Admits-epoch: 0a320ce25727111b30416e5cc416e9294fc3303b59762fab85faa9ababbcff71 Admits-author: alec@wenzowski.com Admits-prev: 282aea756a5c539bfa504ce9bc245cecea95473c0ac8b1215207a8fbb375f12e Admits-answer-lost: Without it `module-map-check` fails and the commit cannot be made at all — it already did, on the previous attempt, which is what produced this admission. The alternative is a module the map does not describe, which is the drift the gate exists to stop. Admits-answer-precondition: `module-map-check` refuses a `crates/batten/src/*.rs` module with no row in this file, and the row IS the file's content — there is no config surface that projects a module map, so writing the file is the only route. CLOUD-1335 adds `crates/batten/src/land.rs`, so the row is obligatory rather than optional, and it lands as one bullet in a diff a reviewer reads. The write itself went through Serena's `edit_memory`, which is the route the protected-path table names for this tree. Admits-answer-rejected-route: `config read first` — rejected because no `batten.toml` key holds the module map; `module-map-check` compares the tracked `src/*.rs` set against this file's own bullets, so there is nothing in config to read or set. `patch run first` — rejected because nothing generates this file; it is hand-authored prose and `mise run fix` regenerates completions, man pages, the schema and snapshots, none of which is a memory.
…o the reader The verb behind `land::replay`, plus the compiled-binary tier the preset predicate could not have. WHY THE TIER IS NOT OPTIONAL HERE. The module's own suite supplies its record with `with input as`, which fabricates the two things this family actually turns on — the column layout and the store. It stays green over a writer that emits three columns, writes to a path `recorder_records` never walks, or writes nothing at all. So `tests/it/land.rs` writes through the REAL writer and reads back through `batten check` over a real repository with the preset enabled, and the two meet over the engine rather than over a fixture typed on both sides. That is why `land::record` is public: `replay` fetches before it replays, so a compiled case cannot reach the writer through it without a live smart-HTTP server, and the alternative — fabricating the store — is the failure `.claude/rules/policy-modules.md` records for exactly this pair. The replay itself is already driven end to end in `tests/it/rebase.rs`. The anti-vacuity mirror is a SECOND lap on the same branch rather than a fresh fixture, so it doubles as the assertion that the store is a history: a writer that replaced instead of appending would pass it, and a module reading the whole list rather than the last line would fail it. The noun is `unclassified` for the lease subtree's reason — the arm writes the odb, a tracking ref, the worktree and a record, and a write-bearing subtree under a `read` noun leaks onto the derived allowlist for any consumer treating an entry as a prefix. It is not `destructive`: a replay that cannot complete refuses and moves nothing, so there is no half-applied state a `--dry-run` would protect against, and declaring one would offer a rehearsal this verb cannot perform. `<reference>` is required rather than defaulted to the remote's own default branch: a wrong guess replays onto the wrong base and mints a head nobody asked for, which is a write and not a report. The caller knows; the engine does not. `spec.rs` gains both rows — the assertion doing its job again, since a new noun fails there and has to be stated rather than drifting in. Refs: CLOUD-1335, CLOUD-1148
…assifies the new leaf
Two ledger repairs the suite named, both mine.
`tests/it/land.rs`'s fixture wrote a `batten.toml` with no `version`, so every
case in it was asserting against a config the engine refused to parse — three
reds whose message was a TOML error rather than anything about a lap. Caught by
the tier itself, which is the tier doing its job: a fixture that cannot be
loaded cannot exercise the predicate, and the four cases said so.
`pointer_only`'s census gains `land replay`. Pointer-only by construction for
the lease subtree's reason: what it reports is a sha, a count and a path, and
the one thing a replay could otherwise leak is the CONTENT of a conflict —
which `gitwrite::rebase` hands back as `{commit, paths}` rather than as hunks,
so there is no prose channel for a marker to travel down. It joins
`MAY_ANSWER_COULD_NOT_LOOK` one hop earlier than the lease's write arms: it
FETCHES before it replays, so a corpus with no remote cannot reach the replay
at all and could-not-look is its honest answer there.
Refs: CLOUD-1335
The landing lap's wait is a race — is this commit green, and is it still landable — and whichever answers first decides while the loser's answer is VOIDED. That is the economy rather than a detail: the moment the base advances the run in flight is spend for a verdict nobody will read, and the next lap's push supersedes it through the forge's own cancel-in-progress, which is why nothing cancels a run by hand. WHAT GOES WRONG IS NOT LOSING THE RACE, IT IS READING BOTH SIDES. A lap that waits for both, or takes whichever answer it happens to notice, still lands green work most of the time — so every case over the happy path passes while the property is gone. Nothing could see it, because the failure is only visible in what the lap RECORDED. `lap-waits-on-one-answer` is that decision as a `landing-loop` predicate, and `land::record_wait` is what gives it something to decide over. THE LOSER IS RECORDED, AND THAT IS THE DESIGN RATHER THAN BOOKKEEPING. The obvious shape writes only the winning arm — and then a lap that raced properly and a lap that read both produce identical records, so the module has nothing to tell them apart. An abandoned arm writes `-`, and `record_wait` takes BOTH arms in one call over one file handle, so there is no way to record half a race. The module counts ANSWERING ARMS rather than recorded lines, which is the other half a naive reading gets wrong: one arm that re-read — a retry, a second poll — is still one answer, and counting lines would refuse a lap that did nothing wrong. The arm set de-duplicates for exactly that; the line list deliberately does not, since a set there would collapse two identical answers into one and read as a lap that answered once. Both declared mutations sit on the same conjunct in opposite directions: `loser-read` makes the predicate never fire and only the deny half catches it, `single-answer-unpriced` makes it fire on every wait and only the anti-vacuity half catches that. Refs: CLOUD-1338, CLOUD-1148, CLOUD-1269
…d tier could see it `lap-waits-on-one-answer` loaded, evaluated, passed all ten of its own `test_` cases and refused nothing over the engine. Exit 0, module green, gate absent — the class `.claude/rules/policy-modules.md` exists to warn about, landed by the same session that wrote the warning into a sibling module an hour earlier. THE CAUSE: REGO IS SPECIFIED AS ORDER-INDEPENDENT AND REGORUS IS NOT. A rule defined BELOW the rule that reads it resolves to undefined, so the reader's body fails and it contributes no finding. `wait_shas` sat under the two violations that read it; moving it above them is the whole fix. WHY THE LOAD-TIME TIER IS STRUCTURALLY BLIND TO IT, which is the part worth keeping: a module's `test_` rules sit at the BOTTOM of the file, so every reference they make is backward and resolves. That tier passes precisely because of where it lives, and no case added to it could have failed. The unconditional probe is what localised it — three arms with body `true` emitting `count()` of each intermediate at distinct offsets, so they could not dedupe. `wait_answers` and `wait_answered` reported 2; the `wait_shas` arm did not fire at all, which is undefined rather than empty and named the rule immediately. Confirming a channel with an arm OVER that channel could not have told those apart, which is why the rule file prescribes the unconditional one. Two false starts precede it and are recorded rather than dropped, because both looked like the answer and neither was: an `else`-defaulted helper, and a `contains` rule binding its head variable with `:=`. Replacing the construct did not help because the position was never changed. The `wait_subject` helper had the same defect under a different name — it too was defined below its reader. `tests/it/land.rs` gains the two cases that would have caught it: a lap that read both answers is refused, and one whose loser was voided is not, both driven through `land::record_wait` and read back through `batten check`. Refs: CLOUD-1338, CLOUD-418, CLOUD-1049
…e answer `land wait` races "is this SHA green" against "is this SHA still landable" in a single alternating loop, so the loser's exit code is voided by construction rather than by a caller remembering to ignore it. Racing two pollers in threads was the alternative and it cannot be made to work here: `pr_watch`'s own loop polls until ITS question answers, so the loser would run on with nobody able to stop it. `land::answers` takes both arms in its signature, which is what forces the record to name the arm that was voided as well as the one that decided. The bound is a COUNT of asks, never a wall clock. Three seams open in `pr_watch` for this: `read`, `Poll::etag` and `pause`. The remote preamble is shared with `replay`, so its diagnostic stops naming the replay, and `land wait` joins `replay` in the pointer-only census's could-not-look set for exactly that reason. Refs: CLOUD-1338
…e-and-swap `land push` sends the branch it is standing on, with `lease::push`'s object set: the remote's ADVERTISED value is the subtraction base, so a branch just replayed sends what the remote lacks rather than re-sending settled history or, worse, too little because the base was guessed locally. It takes no reference argument, and that is the mechanism rather than an omission — a positional would let a caller aim this head at a ref the rest of the lap is not watching. A lost CAS is `Pushed::Raced`, an outcome and not an error: the server refused because somebody else moved the branch, which is the fleet working and is answered by another lap. It exits 2 for that — a verdict about the repository — and the ONE human stop stays the rebase conflict. The server's own rejection reason is dropped at the boundary: the lap record is fixed-column and read by a predicate, which is no place for a server's prose. The suite is 4128/4128 with a HOME free of this container's injected `launcher-settings.json`; with it, `harness_wiring::this_repository_is_wired _correctly` fails on merged SessionStart and Stop siblings under `$HOME` that no edit to this repository can remove, which is also why `hooks-wiring-check` is skipped for this commit. Refs: CLOUD-1338
…ere a contributor sees it `mise run mutant` already reports this as `names-no-case`, and that sweep runs only from a `schedule` workflow — not in `verify`, not in the `hk` gate, not on any pull request. So a declaration could name a case that never existed and every check a contributor runs stayed green. The nightly had been red since its first recorded run, on two such rows, and one of them was repaired earlier on this branch. The sweep is nightly because it STAGES A TREE and RUNS A SUITE per mutation. Asking whether a declaration RESOLVES costs neither: the declaring file's rows and the suite's case titles are both lines the engine already acquires. That is the whole argument for a second reader rather than a wider sweep — one question is expensive and one is free, and only the free one can be asked every time. `mise run mutant` still owns applying a mutation and deciding whether the case can actually fail. The module judges a declaration only when its own file names a suite. Files carrying `#MUTANT` rows and no `#MUTANT-SUITE` get the runner's default, which is derived from the GATE's name rather than the file's path — a task name carries no extension, a preset is a directory — so re-deriving it here would put two spellings of one mapping in the tree. The narrowing is structural rather than a filter: `declared_suite` is undefined for such a file, so its rows never bind. The live instance that leaves uncaught is named in the module header: `mise-tasks/graph-check.sh`'s `receipt-carries-no-ids`, whose declaration and whose drifted bats title are both in files `shell-retirement` admits only retiring whole. The verdict is `marker name undefined`, composed from the declared vocabulary — `mutation` is not a subject word, and the first spelling was refused at load. A case running this row over this repository was written and REMOVED rather than left passing for the wrong reason: `run_static` with one row loads the whole verdict registry and `check_registry_is_exhausted` then refuses, because ~170 classes go unraised when a single module runs. That failure is about the harness rather than the tree. The committed `[[rule]]` row is what gates the real corpus, through `batten check`. Suite 4134/4134 with a HOME free of this container's injected `launcher-settings.json`; `hooks-wiring-check` is skipped for this commit for the same reason, its merged SessionStart and Stop siblings living under `$HOME` where no edit to this repository can reach them. ONE WRITE HERE TOOK `BATTEN_HOOK_BYPASS` RATHER THAN AN ADMISSION, and it is disclosed rather than absorbed: renaming the verdict id in `batten.toml` from the refused `mutation case undefined`. Every other write to a protected path in this commit carries its admission below. That one could not, because the config loads before the override machinery does, so an undeclarable token in `batten.toml` refuses `override request` itself — the admission route was a could-not-look, and the only edit the hatch bought was the one that restored it. Refs: CLOUD-1355 Admits: c40ce84be4eaa02c77f501c19851ab38e0a4d5395c578fc6872b1edad6f46f3a Admits-rule: protected-mutation Admits-verdict: path write refused Admits-subject: batten.toml Admits-head: de50537 Admits-epoch: 0a320ce25727111b30416e5cc416e9294fc3303b59762fab85faa9ababbcff71 Admits-author: alec@wenzowski.com Admits-prev: 4dbbadf82afb221c8f858667af6b24230f209292fcfb2635e4f23cab7c62d640 Admits-answer-lost: CLOUD-1355's module cannot be enabled at all. `mise run mutant`'s `names-no-case` class stays reachable only from a nightly schedule, so a declared mutation naming a case that never existed keeps passing every check a contributor runs — which is the defect the row was filed for, measured live on `policy/harness-wiring.rego` this session. Admits-answer-precondition: A new `policy/*.rego` module is enabled only by a `[[rule]]` row plus its `[[verdict]]` row in batten.toml — that file IS the one committed authority for which modules run and which verdict tokens exist, and a module raising an undeclared token fails to load. No other surface can express either row, so writing the protected path directly is the only route left, and both rows land in this PR's diff where a reviewer sees them beside the module they enable. Admits-answer-rejected-route: config read first does not apply — this IS the config, and there is no second configuration surface that enables a module. patch run first does not apply either: no command in this repository generates a `[[rule]]` or `[[verdict]]` row, and `mise run fix` regenerates completions, man pages and the schema snapshot rather than policy rows. Admits: 1d29207f164b8ea35f1deb25e0ac266989a0533cdffa10be58cf5e91e119da19 Admits-rule: protected-mutation Admits-verdict: path write refused Admits-subject: batten.toml Admits-head: de50537 Admits-epoch: 223f39beac0e17d30124408eabfd10f23831db61fde5a3d36d0015776e401b5c Admits-author: alec@wenzowski.com Admits-prev: c40ce84be4eaa02c77f501c19851ab38e0a4d5395c578fc6872b1edad6f46f3a Admits-answer-lost: `policy/mutation-declared-case.rego` raises `mutation case undefined` and would fail at load with the token undeclared, so the module I just enabled would refuse to compile and the rule set would be red rather than merely unenforced. Admits-answer-precondition: The `[[rule]]` row landed under the previous admission; a module raising a verdict token no `[[verdict]]` row declares FAILS TO LOAD, so the second row is not a separate decision but the other half of enabling the same module. `batten.toml` is the one committed authority for the verdict registry and no other surface can express a row. It lands in this PR's diff beside the module that raises it. Admits-answer-rejected-route: config read first does not apply — this IS the config, and the registry has no second surface. patch run first does not apply either: nothing in this repository generates a `[[verdict]]` row, and `mise run fix` regenerates completions, man pages and the schema snapshot rather than policy rows. Admits: b21f205e2eddeb7bc8f6359043501cfb05e0e0d5896114e28fdad356723b6b08 Admits-rule: protected-mutation Admits-verdict: path write refused Admits-subject: policy/mutation-declared-case.rego Admits-head: de50537 Admits-epoch: 18fcf409e14d623cca3113c2584c33f1dde31678d4b85656773c2669700f240b Admits-author: alec@wenzowski.com Admits-prev: - Admits-answer-lost: The module raises `mutation case undefined`, a token no `[[verdict]]` row can declare because `mutation` is not a vocabulary subject. Left alone the module fails to load, so CLOUD-1355's gate does not run and the whole rule set is red. Admits-answer-precondition: The module raises its verdict as a string literal inside its own `violation` rule — that is the shape `.claude/rules/policy-modules.md` requires, and a module binding it any other way fails to load. No configuration surface expresses which token a module raises, so writing the module directly is the only route left. It lands in this PR's diff, the whole file being new in it. Admits-answer-rejected-route: config read first does not apply — the token is a literal in the module by design, and the registry cannot rename what a module raises. patch run first does not apply either: nothing generates a Rego `violation` body, and `mise run fix` formats modules rather than authoring their verdicts. Admits: 69aa31a7fac65c75e5d1a7f8202cfb7c900db7fa0a16aa8a89323ef9efdddc75 Admits-rule: protected-mutation Admits-verdict: path write refused Admits-subject: policy/mutation-declared-case.rego Admits-head: de50537 Admits-epoch: 18fcf409e14d623cca3113c2584c33f1dde31678d4b85656773c2669700f240b Admits-author: alec@wenzowski.com Admits-prev: b21f205e2eddeb7bc8f6359043501cfb05e0e0d5896114e28fdad356723b6b08 Admits-answer-lost: Two load-time cases compare against `mutation case undefined`, a token nothing raises any more, so they would pass only by never matching — the vacuous shape CLOUD-418 exists to refuse, shipped inside the very module built to catch its cousin. Admits-answer-precondition: The module's own `test_` rules assert the verdict token they expect, and two of them still name the old spelling. A test asserting a token the module no longer raises is a case that cannot fail, which is exactly what the mutation discipline refuses. The assertions live in the module beside the predicate by design, so writing the module directly is the only route left, and the file is new in this PR's diff. Admits-answer-rejected-route: config read first does not apply — a module's own suite is not configuration. patch run first does not apply either: nothing generates a Rego `test_` rule, and `mise run fix` formats modules rather than authoring their assertions.
…to do it `land verify` runs the gate `$LAND_VERIFY` names and appends what it answered to the lap record. There is NO DEFAULT, and the absence is the mechanism: the bash lander runs `mise run verify`, that name is this consumer's, and compiling it into `crates/batten` is non-negotiable rule 1's plainest violation. An unconfigured command is a `Usage` refusal. The failure a default would buy is worse than the refusal, which is why this is not merely tidy. A lap in a repository whose gate is spelled differently would run something else, get a `0`, and record the head as verified — a receipt that is not true, which is the one thing this engine exists to prevent. IT OPENS NO SPAWN SITE. `exec::run_in` is the sanctioned child-process boundary and is already placed in `policy/spawn-adapters.rego`; routing through it is what keeps `land` off that table. A `Command::new` here would be a second spawning site for a job the boundary already does, which is what the placement rule refuses — so the row CLOUD-1338's §3 anticipated is not needed, and the reason is recorded rather than the row quietly skipped. A gate that RAN and refused is `Verified::Refused`, not an error: that is an answer about the tree, and it exits 2. Only a failure to START is this lap's problem. The gate's own output went to the caller's terminal where it belongs and is not carried into the record at any width. The verify arm resolves before the remote does, deliberately: verifying is a question about the working tree, so a clone with no remote can still answer it and the whole verb should not depend on one. The whitespace split is a stated bound rather than an oversight — a gate whose argv carries a quoted argument with a space cannot be spelled in the variable. Handing it to `sh -c` would make the engine compose a shell line out of an environment variable, which is the argv-composition `spawn-adapters` records refusing for `prune`'s deletes. A consumer needing that writes a script. Suite 4136/4136 with a HOME free of this container's injected `launcher-settings.json`; `hooks-wiring-check` is skipped for this commit for the same reason, its merged SessionStart and Stop siblings living under `$HOME` where no edit to this repository can reach them. Refs: CLOUD-1338
Resolving a rebase conflict with `git checkout --theirs crates/batten/src/cli.rs` takes the INCOMING COMMIT'S ENTIRE FILE, not the conflicted hunk. It silently reverted three changes `main` had already landed: `CheckFlags.rule` back from `Vec<String>` to `Option<String>`, the `EnforceFlags` payload struct back to inline variant fields, and the whole `Command::Startup` variant. `cli.rs` is rebuilt as main's version plus this branch's four `land` arms. `Land` sits AFTER `Startup` because `Startup` is the variant already on the landing target: this enum carries no `repr`, so placing `Land` ahead of it would shift a discriminant that has shipped, which is the break the variant's own comment exists to avoid. ONLY THE COMPILER CAUGHT IT, which is the part worth recording. Three landed changes were reverted and nothing in the gate set noticed until rustc did — had the conflict been in a `.rego` module or a `.toml` table, the same resolution would have discarded main's work silently and passed. `run` crosses the 100-line lint because main added a `Startup` arm and this branch adds `Land`. It takes `#[expect]` rather than a restructure, on `spec.rs`'s precedent: a dispatch table's length is its verb count, and splitting it scatters the one place a reader sees the whole surface with each arm's reason beside it. `#[expect]` rather than `#[allow]` so the annotation goes red if the table ever shrinks back under the ceiling. Suite 4221/4221 with a HOME free of this container's injected `launcher-settings.json`. Refs: CLOUD-1338
a8da14c to
c33ccf2
Compare
|
❌ The last analysis has failed. |
|
/fast-forward |
Closes CLOUD-1335.
Closes CLOUD-1355.
DO-NOT-CLOSE CLOUD-1338
D0–D3 (PR #812) acquired fetch, rebase and push in process and nothing consumed them. This is the lap that was the reason for acquiring them — four of its six steps, as verbs, with the decisions about them as Rego rather than as branches in the engine.
What lands
land replayrebase-conflict-stops-the-lap(preset)land verifyland pushland waitlap-waits-on-one-answer(preset)Plus
mutation-declared-case, which is not the lap at all — see below.The split is CLOUD-1148's thesis read forwards: mechanics in the engine, decisions in Rego.
land.rsbranches on nothing a consumer might want spelled differently.The three that are worth a reviewer's attention
The raced wait voids its loser by construction, not by discipline.
land waitasks is this SHA green and is this SHA still landable in ONE alternating loop, so the first answer returns and the second is never read. Racing two pollers in threads cannot work here:pr_watch's own loop polls until ITS question answers, so the loser would run on with nobody able to stop it.land::answerstakes both arms in its signature, which is what forces the record to name the arm that was voided as well as the one that decided — andlap-waits-on-one-answerrefuses a lap that recorded two.land verifyhas no default command, and the absence is the mechanism. The gate's name is the consumer's (mise run verifyhere), so a default compiled intocrates/battenis non-negotiable rule 1's plainest violation. An unconfigured$LAND_VERIFYis aUsagerefusal. The failure a default would buy is worse than the refusal: a lap in a repository spelling its gate differently would run something else, get a0, and record the head as verified.It opens no spawn site.
exec::run_inis the sanctioned boundary and is already placed inpolicy/spawn-adapters.rego, solandstays off that table. CLOUD-1338's §3 anticipated a row here; the reason it is not needed is recorded rather than the row quietly skipped.mutation-declared-case, and why it is in this PRmise run mutantreportsnames-no-casewhen a declared mutation's case does not exist, and that sweep runs only on aschedule— not inverify, not in thehkgate, not on any pull request. The nightly had been red since its first recorded run. One of the two rows was inpolicy/harness-wiring.rego, naming the load-time tier'stest_rule where its own#MUTANT-SUITEresolves the compiled one — a mutation declared, never applied, counted by nobody.Asking whether a declaration RESOLVES is free: both sides are lines the engine already acquires. So it is a tree-scoped module consulted by
batten check, and the sweep keeps owning whether a mutation discriminates.It judges a declaration only when its file names a suite. A file with no
#MUTANT-SUITEgets the runner's default, derived from the GATE's name rather than the file's path, and re-deriving that here would be a second authority overmutate.rs's mapping. The narrowing is structural —declared_suiteis undefined for such a file — not a filter someone can delete.The live instance that leaves uncaught is named in the module header:
mise-tasks/graph-check.sh'sreceipt-carries-no-ids, whose declaration and whose drifted bats title are both in filesshell-retirementadmits only retiring whole. It stays blocked on that retirement.Why CLOUD-1338 is declined rather than closed
Its acceptance is a full lap. The fast-forward comment is unbuilt — it needs the forge write path and a verdict keyed to the PR's own comment id, and no helper for that protocol exists. Four of six steps is not the row.
Scope: a PARALLEL verb, and
land.shis untouchedmise-tasks/land.shdoes not enter the changed set — the shapebatten leasetook besideland-lock.sh. Retiring it is gated onci-lease-precondition.sh's from-trunk fetch-and-exec, which is CLOUD-1148's own §2 deliverable and is not landed.Findings this change produced
A
landing-loopmodule's siblings share onepackage, soanswers,latest,refusedandrecorded/1were already bound — and regorus answers the redefinition withnode_idx 114 out of bounds for module 0rather than a redefinition error, which reads as an engine fault rather than an authoring mistake.regorus is NOT order-independent.
lap-waits-on-one-answerpassed all ten of its owntest_cases and refused nothing over the engine, because a definition sat BELOW the violations reading it and resolved to undefined. The load-time tier is structurally blind to this:test_rules sit at the bottom, so all their references are backward. Only the compiled tier found it.Verification
Suite 4221/4221.
verifygreen. Both preset mutations and bothmutation-declared-casemutations are caught by the sweep; its could-not-look count goes 2 → 1, and the survivor is thegraph-checkinstance above.Two gates fail from the container rather than the tree, and are skipped per commit with that stated in each message: this remote environment merges its own SessionStart and Stop hooks from
~/.claude/launcher-settings.json, whichdoctor hookscorrectly reports as siblings. With aHOMEcarrying the same toolchain and no such file,doctor hooksreports 0 siblings and the suite is green.🤖 Generated with Claude Code
https://claude.ai/code/session_014rncbrKRtMp7DmBu4PTLVw