Skip to content

feat(landed): sweep a board for claims nobody is serving - #884

Merged
wenzowski merged 4 commits into
mainfrom
claude/test-run-phase-optimize-novz6s
Sep 6, 2026
Merged

feat(landed): sweep a board for claims nobody is serving#884
wenzowski merged 4 commits into
mainfrom
claude/test-run-phase-optimize-novz6s

Conversation

@wenzowski

Copy link
Copy Markdown
Contributor

batten landed check was ported in #859 and completed in #864, but the shell programs it replaced are still the ones that run: board-sweep.sh:279in-progress-drain.sh:207landed-check.sh. The tree carries two authorities over one question and the new one is the dead half.

The blocker to retiring them was not the port. Both suites were read in full: the unit is 2 ledger arms + 64 conserves arms — 31 cases in tests/landed-check.bats, 33 in tests/in-progress-drain.bats — and ~18 of the 33 belong to claimed-abandoned, a predicate with no successor anywhere in the tree. A pattern scan finds it in exactly two files: the dying program and its dying suite. Retiring without it would delete 18 cases into nothing, which is the conserves-files-not-logic defect CLOUD-908 records.

So this is a build, not a port, and it lands first — both shell programs untouched and still authoritative. That is the shape .claude/rules/toolchain.md names for gh-guard, and it is what makes the comparison below real rather than a claim about a program that no longer exists.

An arm, not a new verb

CLOUD-1182 measured 77 of 113 engine-source retirements landing a brand-new top-level noun and named that the scope creep. batten landed abandoned declares kind:verb on its future ledger arm without widening the command surface.

A policy module was not available: the predicate reads get_issue payloads from stdin, batten check is declared read and has no stdin channel, and RuleKind::scopes pairs every spawning kind with RuleScope::Tree.

Two decisions the shell made implicitly, made explicit here

The refs list is evidence, not a spawn. spec.rs:503-512 admits landed check to the read-only allowlist precisely because it "starts no program — the claimed-keys spawn its shell predecessor made is supplied AS evidence instead". A git ls-remote here would take the arm off that allowlist. So refs arrive as --refs <file>, gathered by the caller, which is the fetch side of agents-fetch-gates-decide where a credential belongs.

The instant is supplied, not read. House style §6 requires byte-stable output, which a value differing per invocation cannot give; and without it every fixture date drifts out of the bound as the calendar moves, so a suite rots on a date nobody edited. The boundary parses --instant and hands the predicate a day number, so the decision is pure.

The two-stage narrowing is behaviour

A projected-away key must be a named refusal rather than a silent scan — but demanded of the rows whose verdict needs it, never of the column. updatedAt is demanded of the candidates (In Progress ∧ ¬landed); attachments and gitBranchName only of the stale ones. So a fresh row is never demanded of a key a tracker's list projection cannot carry, a landed row is never demanded of anything, and an unresolved row in a mixed payload still refuses. Both directions are asserted in one case, so the exemption cannot hide the refusal.

The parity run, and the one divergence

Run with both programs present, same payloads, same evidence, same instant. Six of seven rows agree. The seventh does not, and it is a defect in the dying program:

in-progress-drain.sh:275 reads while IFS=$'\t' read -r id branch has_pr over a @tsv of [.id, .gitBranchName, <any pull URL>]. Tab is an IFS whitespace character, so bash collapses a run of them into one delimiter. An empty gitBranchName emits <id><TAB><TAB>true, which splits into two fields — branch takes true, has_pr is empty, and the pull-request rescue never fires. A row with no branch and an open pull request reports as an abandoned claim, which is the over-reporting direction.

Its own suite is green over this because both pull-request cases pass a non-empty branch, so the combination is never constructed. Filed as CLOUD-1516; per CLOUD-1176 the successor implements the corrected decision and the defect's own row records the change, because shell-retirement admits no edit to a governed program that is not a retirement.

The first parity attempt is also worth recording: it used CLOUD-9xx keys, five of which are real keys on main, so landed-check drained them against real history. Re-run with a prefix that cannot collide. A parity measurement over a live history has to choose keys the history does not contain.

Not in this PR

The retirement itself — deleting both programs, both suites, the 2 ledger arms and 64 conserves arms, repointing board-sweep.sh's three references to the drain, and dropping both names from $MUTANT_GATES. That is CLOUD-1513's second PR, and it is indivisible.

Closes CLOUD-1516

DO-NOT-CLOSE CLOUD-1513

CLOUD-1513 is served and stays open: this is the first of its two PRs, and the row is complete only when the retirement lands. Closing it here would mark a unit done while both programs it exists to retire are still tracked and still the ones that run — the state-claim-the-tree-refutes shape CLOUD-1253 records for exactly this campaign.

CLOUD-1516 closes here rather than on the retirement, because its whole deliverable is the corrected decision plus the case that pins it, and both land in this diff. No code was ever going to land under that row itself.


Generated by Claude Code

@coderabbitai

coderabbitai Bot commented Sep 6, 2026

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 4 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Team

Run ID: ddc5e0af-8e76-4b94-963c-ac4a44fc7534

📥 Commits

Reviewing files that changed from the base of the PR and between 25f34ab and 92ef45c.

⛔ Files ignored due to path filters (1)
  • crates/batten/tests/it/snapshots/it__snapshots__golden_json_schema.snap is excluded by !**/*.snap
📒 Files selected for processing (13)
  • completions/batten.bash
  • completions/batten.fish
  • completions/batten.zsh
  • crates/batten/src/cli.rs
  • crates/batten/src/landed.rs
  • crates/batten/src/lib.rs
  • crates/batten/src/spec.rs
  • crates/batten/src/surface.rs
  • crates/batten/tests/it/landed_check.rs
  • crates/batten/tests/it/pointer_only.rs
  • man/batten-landed-abandoned.1
  • man/batten-landed.1
  • mise.toml

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

`batten landed check` was ported in #859 and completed in #864, but the
shell programs it replaced are still the ones that run. The blocker to
retiring them is that `in-progress-drain.sh` carries a SECOND predicate,
`claimed-abandoned`, with no successor anywhere in the tree — a pattern
scan finds it in exactly two files, the dying program and its dying
suite. Retiring without it would delete 18 cases into nothing, which is
the conserves-files-not-logic defect CLOUD-908 records.

So the successor lands first, with both programs untouched and still
authoritative. That is the shape `.claude/rules/toolchain.md` names for
`gh-guard`, and it is what makes the parity comparison real.

An arm rather than a new verb: CLOUD-1182 measured 77 of 113
engine-source retirements landing a brand-new top-level noun and named
that the scope creep.

Two decisions the shell made implicitly and this makes explicit.

The refs list arrives as `--refs` EVIDENCE rather than through a
`git ls-remote`. `spec.rs` admits `landed check` to the read-only
allowlist precisely because it "starts no program", so spawning here
would take the arm off it. Gathering is the caller's half of
agents-fetch-gates-decide, where a credential belongs.

The instant is supplied rather than read. House style §6 requires
byte-stable output, which a value differing per invocation cannot give,
and without it every fixture date drifts out of the bound as the
calendar moves. The boundary parses it and the predicate is handed a day
number, so the decision stays pure.

The two-stage narrowing is behaviour, not an optimisation: `updatedAt`
is demanded of the candidates, `attachments` and `gitBranchName` only of
the stale ones. A fresh row is never demanded of a key a tracker's list
projection cannot carry, and an unresolved row in a mixed payload still
refuses — both directions in one case, so the exemption cannot hide the
refusal.

Refs: CLOUD-1513
…e derived artifacts

ABSENCE IS A READING, AND ON THIS ARM IT LEANS THE UNSAFE WAY. The
sibling `landed check` already says so when `--claimed` is absent, where
the cost is an under-report. Here it is the opposite: landedness is what
RESCUES a claim, so a key whose only landing is a closing keyword on
`main` reads as unlanded, and an unlanded idle row with no pull request
and no branch reads as ABANDONED.

The predecessor could not reach that state — `landed-check.sh` read
`main`'s log itself, so arm one was always live behind the drain. Taking
it as a file makes it omittable, which is the silently-halved
disjunction CLOUD-1458 fixed displaced one level out into the caller.

Paired, so neither half is vacuous: the notice must fire on absence and
stay quiet when the arm is supplied. Either alone passes over a line
that is always printed or never is.

Renamed from the names the first draft used, which collided with the
`check` arm's own pair — the compiler caught it, which is the tier
working.

Derived artifacts regenerated for the new leaf: completions, the man
page, and the golden JSON schema snapshot. A stale snapshot is what
failed a landing lap on this branch yesterday.

Refs: CLOUD-1513
…s readable

`mutant-census` refused the tree: a module that DECLARES `#MUTANT` rows
and is not in the enforced set reads `uncovered`, because rows nobody
sweeps are the coverage-shaped nothing the verb exists to refuse.

Two corrections, both found by running the gate rather than by reading.

THE NAME IS `engine-landed`, NOT `landed`. `mutate::sources_for`
resolves a gate as a shell task, then a policy module, then — behind an
`engine-` prefix — an engine module. A bare `landed` resolved to no
subject at all and the census said so (`names-no-subject`), which is the
same finding pointed the other way. `engine-config` and `engine-doctor`
were already enrolled in exactly that shape.

THE SUITE IS DECLARED, because the default cannot exist here. An
undeclared gate's suite resolves as `tests/<gate>.bats`, this module has
none, and `V-SHELL-RULE-ADDED` refuses adding one. The named cases are
this file's own unit tier, so the declared path is the file.

A ROW'S SCRIPT MAY CARRY NO `|` OF ITS OWN. The first draft mutated both
rescues at once — `has_pull_request() || has_live_branch()` — and the
two pipes made it a five-field row the sweep refused to read
(`malformed-row (5 fields, want 3)`). Mutating the single conjunct the
case is about is both well-formed and the sharper declaration: it
discriminates the pull-request arm rather than the disjunction.

SHOWN ABLE TO FAIL, each applied to a copy and its named case run:

  abandoned-ignores-bound -> the_idle_bound_is_exclusive_at_exactly_the_threshold  FAILED
  abandoned-ignores-pr    -> only_a_pull_request_attachment_rescues_a_claim        FAILED

The sweep's other 13 survivors are pre-existing and belong to other
gates, each already carrying a `#MUTANT-OWNER` naming CLOUD-845 or
CLOUD-989; none is this branch's and none is touched here.

Refs: CLOUD-1513
`main` moved to v0.0.144 under the branch and the lap refused. Four
findings, all this branch's.

TWO CLIPPY ERRORS, AND THE FIRST WAS FORESEEN AND THEN NOT DONE.
`used_underscore_binding`: `run_landed` took `_out` because its only arm
wrote to stderr, and the new arm writes a summary to stdout — the plan
for this change said "un-prefix `_out`" and the edit did not happen, so
a `cargo check` passed and clippy did not. `too_many_lines`: the new arm
resolved six inputs and then rendered three blocks and a summary, and
the render is now its own function. That is the seam that already
existed — everything above it resolves inputs, everything below says
what was found, and they share nothing but the report.

THE READ-ONLY ALLOWLIST IS COMPARED SORTED, NOT AS A SET.
`landed abandoned` went in after `landed check`, where alphabetically it
belongs before. Every other entry in the list is in order, so this was
the one place the list's own invariant was not read before appending.

THE PLAN WAS RECORDED AS THE SESSION'S TASK LIST RATHER THAN THIS
BRANCH'S. `plan-complete` refuses a non-empty diff with no recorded
plan, and then refused again with `plan-unfinished` over two entries:
one is this PR, which is finished, and one is the RETIREMENT — the
second PR of CLOUD-1513, which this branch deliberately does not carry.
A plan naming another PR's work is not this branch's plan. The
retirement's durable home is the row, whose body already carries its
full scope: two ledger arms, sixty-four conserves arms, `board-sweep`'s
three references, and both `$MUTANT_GATES` names.

Refs: CLOUD-1513
@wenzowski
wenzowski marked this pull request as ready for review September 6, 2026 04:25
@wenzowski
wenzowski force-pushed the claude/test-run-phase-optimize-novz6s branch from 92d27f5 to 92ef45c Compare September 6, 2026 04:25
@sonarqubecloud

sonarqubecloud Bot commented Sep 6, 2026

Copy link
Copy Markdown

❌ The last analysis has failed.

See analysis details on SonarQube Cloud

@wenzowski

Copy link
Copy Markdown
Contributor Author

/fast-forward

@wenzowski
wenzowski merged commit 92ef45c into main Sep 6, 2026
11 of 12 checks passed
@wenzowski
wenzowski deleted the claude/test-run-phase-optimize-novz6s branch September 6, 2026 04:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant