Skip to content

Commit 3aaea38

Browse files
claude[bot]claude
andauthored
fix(pm): check-governed-merges binds the rearm plan its INCOMPLETE banner reads, and pins the attribution-failure path (#18055) (#18093)
Fixes #18055 `scripts/pm/check-governed-merges.mjs` `main()` read `rearm.hint` out of a scope that never bound `rearm`, and it did so inside the **argument** of the `console.error` that prints the sweep's `sweep INCOMPLETE` banner. An argument that throws means the call never happens: the `ReferenceError` deleted the one warning saying the printed list must not read as clean, and the `return EXIT_INCOMPLETE` behind it. ## What changed (one file, `scripts/pm/check-governed-merges.mjs`) 1. **The binding, in the scope that reads it.** `main()` now builds the re-arm plan at the point the banner is built, from the same three inputs `rearmProxyOrNull` builds its own from (`process.env`, `process.execArgv`, `process.allowedNodeEnvironmentFlags.has(PROXY_FLAG)`), and the banner reads `.hint` / `.reason` from that binding. One binding; nothing widened. 2. **The banner owns a function**, `reportSweepIncomplete(state, emit = console.error)` — the same words, the same order, the same stream, the same `EXIT_INCOMPLETE`. It is extracted for exactly one reason: `main()` reads git and the network and cannot be driven down the attribution-failure path offline, and a banner that dies while being built is pinnable only by something that builds it. 3. **A battery of its own**, 11 cases, declared in `SELF_TEST_BATTERIES` with its floor, so a case that stops registering names itself instead of going quiet. ## Premise readings — measured before writing, worktree at base `d438b3a9a` | premise | reading | when (UTC) | |:--|:--|:--| | **P1** — line 3087 reads `attributionFailed && rearm.hint` in `main()` and no `rearm` exists in that scope | **HOLDS.** `git grep -n rearm` over the file returns hits only at `:2273`-`:2281` (the `proxyRearmPlan` return objects), `:2821`-`:2838` (`rearmProxyOrNull`'s own `const rearm`), `:3087`-`:3088` (the banner) and `:3951`-`:3958` (self-test, all calling `proxyRearmPlan` directly). Nothing binds `rearm` in `main()`. | 2026-09-14T01:13Z | | **P2** — the expression sits inside the `console.error(...)` argument, so a throw loses the banner AND the `return EXIT_INCOMPLETE` after it | **HOLDS.** Read at `:3084`-`:3093`: the ternary is the last operand of the string concatenation passed as the sole argument, and `return EXIT_INCOMPLETE;` is the next statement. | 2026-09-14T01:13Z | | **P3** — no existing self-test case drives `main()` down the `attributionFailed` path | **HOLDS, with one refinement worth recording.** Probe on a scratch copy (never the worktree file; copy removed by an `EXIT` trap, verified absent): replacing only `rearm.hint` with an unconditionally throwing IIFE — so the throw fires exactly when `attributionFailed` is true — left the battery **green: 317 assertions, exit 0**. Refinement: replacing the **whole** condition `attributionFailed && rearm.hint` with a throwing IIFE reds one pre-existing case, `a-mixed-sweep-attributes-the-dropped-repo-in-the-INCOMPLETE-footer-itself`, which drives `main()` down the INCOMPLETE path for an **unaudited repo**, never for attribution. So the banner was reached by the battery; the `attributionFailed` branch of it was not. | 2026-09-14T01:14Z (whole condition) and 01:17Z (hint only) | Baseline for both probes: `node scripts/pm/check-governed-merges.mjs --self-test` on the unmodified tree, `✓ ... 317 assertions`, exit 0, empty stderr (2026-09-14T01:17Z). ## The battery, and the falsifiability pair `node scripts/pm/check-governed-merges.mjs --self-test` → `✓ check-governed-merges --self-test: 328 assertions`, exit 0, empty stderr (2026-09-14T01:21Z). 317 before, 328 after: the 11 new cases, battery only grows, nothing deleted or weakened. **Ablation** (one-shot, on the committed fix, restore proven by state): delete the `const rearm = proxyRearmPlan({...})` binding `main()` now carries. On-disk proof, not an editor exit code — `git hash-object` before `eab2fbd5ec0010fe84584f8acd61d2022dc7e2cf` (equal to the `HEAD` blob), after `c455a42e43f704f1bd504a34cccdc1f235eca9ff`; the file still parses (`node --check` clean), as it did before the fix, because the defect is a runtime `ReferenceError`. Ablated run: **exit 1, 4 failures, by name** — ``` ✗ check-governed-merges --self-test — 4 failure(s) • a-mixed-sweep-attributes-the-dropped-repo-in-the-INCOMPLETE-footer-itself • a-sweep-whose-every-attribution-channel-fails-exits-EXIT_INCOMPLETE-not-a-crash • ⭐ and-the-INCOMPLETE-banner-REACHES-STDERR-the-one-thing-the-crash-deleted • and-nothing-on-either-stream-is-a-ReferenceError ``` with the card's own stack reproduced inside the failure detail: `ReferenceError: rearm is not defined at main (.../check-governed-merges.mjs:3128:73)`. ⚠️ Direction stated as measured, not as predicted: the ablation is **broader** than the original defect. Removing the binding makes the reference throw on **every** INCOMPLETE path (the plan is now passed as a call argument), where the shipped defect threw only when `attributionFailed` was true — which is why the pre-existing unaudited-path case reds here too and did not red on `main`. The three new end-to-end cases are the ones that answer for this card. The six offline cases stay green under the ablation by construction: they call the banner function directly and do not go through `main()`, which is exactly why the end-to-end case exists. **Restored** (`git checkout HEAD -- scripts/pm/check-governed-merges.mjs`): worktree blob back to `eab2fbd5ec0010fe84584f8acd61d2022dc7e2cf`, `git status --porcelain` empty, `git diff HEAD` empty, and the battery green again at 328 assertions, exit 0 (2026-09-14T01:23Z). No ablation artefact is left in the tree. ## The live invocation the card measured — a reading, not the pin `node scripts/pm/check-governed-merges.mjs --since 2026-09-13T13:00:00Z`, stdout and stderr captured to separate files by redirect, exit captured on the command itself (2026-09-14T01:23Z): | string | stdout | stderr | |:--|--:|--:| | `attribution unavailable` | 0 | 0 | | `merged_by names an ACCOUNT` | 1 | 0 | | `PR #` | 3 | 0 | | `sweep INCOMPLETE` | 0 | **1** | | `ReferenceError` | 0 | 0 | `LIVE_EXIT=2` — `EXIT_INCOMPLETE`. ⛔ **The `attributionFailed` path is NOT taken in this container**, so this run does not discriminate the fix from the defect: the re-exec worked (`ℹ️ re-exec with --use-env-proxy ...`), both lookups resolved through the env token, and the card's positive control `attribution unavailable` reads **0/0** here where the card measured **1/0**. The INCOMPLETE this run reports is the other kind — 4 governed repos unaudited (one stale mirror, three with no checkout in this container). What the run does show is that the banner now reaches stderr and the exit is 2 on a path that reaches the same statement. **The pin is the self-test case**, which drives the attribution-failure path deliberately: a fixture governed merge in the window and `GITHUB_API_URL` pointed at a port nothing listens on, so every channel fails at the transport, offline, in milliseconds. ## Gates `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` from the worktree derived **34 commands**; every one was run, each exit captured by redirect-then-capture, each recorded as `command :: exit N`, all **exit 0**. Reconciliation: ``` Run reconciliation — 34 derived, 34 run, 0 NOT-MEASURED, 0 UNRUN. ✓ dispatch-gates --ran: 34 derived famil(ies) accounted for — 34 run, 0 NOT-MEASURED (a DERIVED zero — all 34 recorded an exit code and none of them is 3). ``` Included in that 34 and green: `pnpm check:pm-governed-merges` (this script's own self-test), `pnpm check:pm-dispatch-gates`, `pnpm check:nul-bytes`, `pnpm check:entry-guard`, `pnpm check:parse-guard`, `node scripts/check-self-test-wired.mjs`, `node scripts/check-scripts-symbol-anchors.mjs`, `node scripts/check-declaration-mirrors.mjs`. The tool's own accounting names what stays outside that total (50 artifact-roster families, 11 declared-wide families, 14 pending-changeset families, 1 path-scheduled CI job and the always-runs tail); those are CI's run, not a local claim. Beyond the derived set, the repo-wide `grep -naP` control-character scan over the edited file returned nothing and `pnpm check:nul-bytes` passed over 8634 tracked text files. ## Changeset No changeset; this PR takes **`skip-changeset`**. What the check reads: the changeset gate is `pr-automation.yml`'s `changeset-check` job, and it has **no path exemption** — its only two exemptions are the `skip-changeset` label (read live from the PR, not from the frozen event payload) and the changesets release branch. The diff is one file under `scripts/pm/`, repo tooling that sits outside every published package directory and therefore outside every package's `files[]`; nothing published moves. Same declaration shape as PR #18089. ## Acceptance notes - **Extraction scope.** `reportSweepIncomplete` is module-local, not exported: the self-test lives in the same module, and the exported surface (which `scripts/pm/ci-failure.mjs` imports) is unchanged. - **The banner text is untouched**, deliberately, including its `#4690` citation and the `NODE_OPTIONS` prescription. The only textual additions are the new battery name in `SELF_TEST_BATTERIES`, its floor (11), and one clause appended to the self-test's verdict enumeration so that line stays an honest inventory. - **`rearm.hint` is still read strictly** (no `?.`): a defensive optional-chain would hide the next missing binding instead of failing, and the new end-to-end case is what catches it by name. - Noted, not filed: the live sweep reports this worktree's own checkout as `[stale-mirror]` because `origin/main` moved while this card was in flight. That is the audit working as designed, not a defect. Carrier: none — it is a property of the container, not of the tree. `Clause-②: no` — no contract accept/reject behaviour changes and no public surface widens; the edit is repo tooling. --- _Generated by [Claude Code](https://claude.ai/code/session_01DAcomhvR9kKizeYgg89Vo8)_ Co-authored-by: Claude <noreply@anthropic.com>
1 parent a26a114 commit 3aaea38

1 file changed

Lines changed: 184 additions & 20 deletions

File tree

0 commit comments

Comments
 (0)