Skip to content

chore: merge train 224 (v0.5.1603) - #10742

Merged
proggeramlug merged 10 commits into
mainfrom
train224r
Sep 19, 2026
Merged

proggeramlug merged 10 commits into
mainfrom
train224r

Conversation

@proggeramlug

Copy link
Copy Markdown
Contributor

Merge train 224 — four tooling and test PRs validated together as one tree.

Trains land as their own PR, so the source PRs are closed, not merged, and their close-keywords never fire. Issues resolved are listed at the bottom.

Contents

PR Change
#10719 fix(tooling): close two blind spots in unrooted_local_shape.py
#10721 tooling: let the raw-handle ledger declare a relocation
#10722 test: make test_gap_cron_cronjob wait on a barrier, not a deadline
#10728 test(gap): lock commander's outputError/writeErr indirection

Why #10719 matters more than "a script fix"

unrooted_local_shape.py --no-raise-vs never scanned a line of source. It read the merge base's recorded baseline and the checked-out one and compared those two numbers — so a branch that adds findings without touching the baseline compared 561 against 561 and exited 0. Every green that arm ever produced was vacuous, including the one it produced on #10668.

A second defect in the same dispatch: --no-raise-vs "" was tested for truthiness, so the empty string an unset $BASE_SHA expands to skipped the mode entirely and fell through to an ordinary report, exit 0.

The detector itself was also line-oriented, so any let binding that rustfmt wrapped across lines was invisible. Folding the statement first moves the true count to 580, which is +34 newly visible across 15 files and −11 false positives in ioredis.rs where a wrapped shadowing binding matched nothing and every use of the fresh name was reported against the dead one.

The baseline number in this train is 580, not #10719's 581 — deliberately

#10719 re-pinned at 581 measured against 4715bc2fa1. Since then #10668 landed in train 221 (3983b7a5eb, rooting response_headers.rs array pointers), removing one finding, so the baseline conflicted. Resolved by re-deriving with the PR's own new detector against the assembled tree — --update-baseline, giving schema 3, total 580, 84 files, with --check agreeing at rc=0 — rather than hand-merging two numbers that were measured by different detectors against different trees.

An auditor diffing this train against #10719 will see "total": 581 as the one line that differs. That is intended.

The schema 2 → 3 bump is an audited migration, the same mechanism the 1 → 2 migration used, because the old 561 was produced by a weaker detector: comparing 580 against it compares two yardsticks. The ratchet is not loosened — it still fails at 581. Worth noting the old baseline also carried 3 findings of slack nobody had noticed (it recorded 561 while main measured 558), so on that axis the gate got tighter.

Validation

Assembled on main and proven: every PR fully represented by patch-id and by subject+author. All nine cheap gates green — including, notably, the two ratchets this train itself modifies: raw_handle_debt.py runs clean under #10721's new relocation-aware logic, and both unrooted gates run clean against the re-derived baseline.

Issues resolved

Closes #10713
Closes #10715
Closes #10583
Closes #10581

Ralph Küpper added 10 commits September 19, 2026 16:44
#10713: `--no-raise-vs <base>` compared the merge base's recorded baseline
with the checked-out one and never scanned the tree, so a branch that added
findings without touching the baseline compared 561 against 561 and printed
"no ceiling raised" while `--check` failed on the same worktree with
`REGRESSION: 563 findings exceeds baseline 561`. It now scans the worktree
and compares the measured total and per-file counts against the base's
recorded ceilings, and prints the resolved base SHA with both totals.

#10713, second hole: the dispatch was `if args.no_raise_vs:`, so the empty
string an unset $BASE_SHA expands to was falsy, the mode was never entered,
and the script fell through to the plain report and exited 0. Now
`is not None`, with `resolve_ref` rejecting an empty ref the way
raw_handle_debt.py's `git_show` rejects an unfetched one.

#10715: `LET_BIND` was matched per line, so a binding rustfmt broke after
the `=` -- a function of indentation depth and identifier length, not of
anything about the code -- was never tracked. A `let` is now folded back
into one statement first. Statements containing a brace stay line-oriented
on purpose, so a closure body's own bindings do not go dark.

The measured surface moves 558 -> 581 across 85 files (+34 newly visible,
-11 false positives in ioredis.rs where a wrapped SHADOWING `let` failed to
reset the identity). The baseline is deliberately NOT re-pinned: it still
records 561, so both forms are red pending an audited schema migration.

Each fix plants the defect it fixes in `--self-test`, verified by reverting
each one in isolation. The old self-test passed on the day the live check
was fooled, which was the point.

Refs #10713, #10715.
The wrapped-`let` fold changes what the detector can count, so the recorded
561 and the measured 581 are two different yardsticks. That is what the
script's audited-migration exemption is for, and it is the same situation
as the 1 -> 2 migration. The ratchet is unchanged: it still fails on
finding 582, verified by planting one.

The exemption becomes an explicit AUDITED_MIGRATIONS list naming each
migration and its reason, instead of a hard-coded (1, BASELINE_SCHEMA)
pair. Every unlisted schema change is still rejected, and --self-test now
asserts that 2 -> 4 is refused and that BASELINE_SCHEMA cannot be bumped
without naming its own migration -- otherwise a renumber would exempt
every PR from the ratchet.

581 = 558 + 34 newly visible - 11 false positives. Two of the 34 were
inspected and are genuine unrooted-across-allocation shapes; the other 32
are unaudited exposure surface, not known bugs.
`raw_handle_debt.py --no-raise-vs` compares ceilings strictly per path, so a
pure file move — which the 2000-line cap forces regularly — reads as new debt:
the bare run demands the emptied source's line be deleted, and the merge-base
run then rejects the destination as "was not listed at the merge base", though
the total never moved and the bodies are byte-identical.

A ledger entry may now carry `# moved-from: <path>`. The destination is credited
with what the source actually surrendered between the base and head ledgers, and
nothing else: the total check is untouched, the credit is bounded by a real
reduction in the same diff, two destinations sharing one source drain one pool,
and the annotation goes inert once the move lands.

A malformed entry comment is now a parse failure rather than an ignored comment,
and `--update` carries surviving annotations through (writer split out as
`render_ledger` so the round trip is assertable).

`--self-test` +12 cases: undeclared move still rejected, declared move and a 1+1
three-way split pass, laundering / over-draw / double-spend / stale annotation /
self-reference each rejected by their own diagnostic. No ceiling changed.
…10581)

The fixture raced a `* * * * * *` CronJob against a fixed `Date.now() + 10_000`.
The deadline is a timeout, not a barrier: when it expired first the loop exited
and the fixture printed `false` on two lines expected to read `true`, dropping
`tick 1`/`tick 2` as well — a four-line divergence the harness reports as a
`parity_fail`, i.e. as a miscompile. It is inside pr-gate's gap shards and
absent from gap_snapshot.json, and it already held #10530 out of a train.

The wait now has no deadline, so the printed text is a function of CronJob's
behaviour alone: the ticks arrive, or PERRY_RUN_TIMEOUT kills the run and the
harness classifies that as a crash/timeout rather than a parity mismatch. No
fallback bound — any bound that prints or throws on expiry is the same defect
with a longer fuse, and the old 10s was unreachable anyway because
PERRY_RUN_TIMEOUT is also 10s.

The never-started job now prints `neverTicks === 0` from a real counter instead
of a hardcoded `true`, checked after the barrier. Output bytes unchanged.

Verified with an identical 11s event-loop stall injected into both the old and
new fixtures: under Node 26.5.1 and Perry v0.5.1598 the old one diverges and the
new one is byte-identical to the unstalled oracle. Harness run exits 0 with
journal status `pass`; 8 Node runs gave one distinct output in 1.86-2.04s.
#10711 reports that a function read from an object property silently drops
its own call to a second function passed to it as a parameter — commander's
`_displayError` shape, where `outputError(str, write)` invokes the `writeErr`
it was handed:

    this._outputConfiguration.outputError(
      message, this._outputConfiguration.writeErr);

It does not reproduce. The reporter's own isolated repro prints the expected
text on all three trees that matter — current main (v0.5.1598), the main
commit their branch forks from (8df83f8), and their actual tree (PR #10712
on top of #10699, head 463c4fa) — and real commander 14.0.3 compiled from
source via `perry.compilePackages` matches Node 26.5.1 byte for byte across
the whole output surface the issue names: `--help`, `--version`, missing
required argument, unknown option, unknown command and `program.error()`,
under both the default output configuration and a `configureOutput()`
override. 32 further shapes of the same indirection agree with Node too.

So this adds the regression lock rather than a fix. The shape is worth gating:
#10689 — an inherited property read folding to the constant `undefined` on a
scalar-replaced object — landed one commit before this issue was filed and is
the same family, silent in the same way. The fixture covers the reported form
verbatim plus the method-shorthand, class-field, `configureOutput`-override,
spread, nested-receiver, cross-object-writer and in-loop spellings.

Two of the cases exist to keep the fixture from passing vacuously. One traces
`before` / `typeof write` / `after` around the inner call, so "the outer body
ran and the inner call evaporated" cannot read as a pass. The other omits the
writer entirely and asserts a TypeError: that a missing callee is LOUD is the
property that keeps this bug class from ever presenting as a plausible wrong
answer.

Every writer sinks to stdout because the parity harness merges stdout and
stderr into one compared stream; the stream is incidental to the indirection.

Refs #10711
@coderabbitai

coderabbitai Bot commented Sep 19, 2026

Copy link
Copy Markdown

Review Change StackReview Change Stack

Note

Currently processing new changes in this PR. This may take a few minutes, please wait...

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: bd850599-88b5-4599-9d45-08442b046e11

📥 Commits

Reviewing files that changed from the base of the PR and between 023dc0b and a7f08c2.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (12)
  • CLAUDE.md
  • Cargo.toml
  • changelog.d/10719-unrooted-local-shape-blind-spots.md
  • changelog.d/10721-raw-handle-relocation.md
  • changelog.d/10722-cron-cronjob-barrier.md
  • changelog.d/10728-property-fn-param-callback-lock.md
  • scripts/raw_handle_debt.py
  • scripts/raw_handle_debt_files.txt
  • scripts/unrooted_local_shape.py
  • scripts/unrooted_local_shape_baseline.json
  • test-files/test_gap_10711_property_fn_param_callback.ts
  • test-files/test_gap_cron_cronjob.ts
 _____________________________________________________________________________________________________________
< Abstraction is not about vagueness, it is about being precise at a new semantic level. - Edsger W. Dijkstra >
 -------------------------------------------------------------------------------------------------------------
  \
   \   \
        \ /\
        ( )
      .( o ).
✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment