Skip to content

Commit f3f27be

Browse files
fix(pm): check-expected-skips refuses to judge a live head with a roster that is not the base branch's (#24892)
Fixes #24849 Clause-②: no ## What changed `scripts/pm/check-expected-skips.mjs` is the only file in the diff. A live run (`--pr N` / `--head SHA`) now compares the roster in hand with the base branch's roster **before** it judges anything. When they differ it answers **exit 3 NOT MEASURED**, naming each differing path and the re-pin recipe. When they are identical it judges as before, and the verdict line names whose roster judged it (`... every one in main's roster (exit 0)`). `--check-runs-json` and `--roster` are unchanged: no network, no comparison, and the offline verdict line is byte-identical (pinned). **What is compared.** The roster is a function of exactly two inputs in the checkout the command runs from: this file (the listed rows and the derivation code) and every `.github/workflows/*.yml` (the derived rows). Their working-tree bytes are hashed in-process as git blob ids, then compared with the blob ids the contents API lists for `scripts/pm` and `.github/workflows` at the base branch's **live tip**. That takes two directory listings and needs no history, no fetch and no git binary. A refused live run, measured from this branch (its copy of the script is not main's): ```text check-expected-skips: NOT MEASURED — the roster in hand is not objectstack-ai/objectstack@main's: 1 of its source file(s) differ from that branch's tip. A stale roster turns a newly rostered skip into a false ⛔ and a retired row's skip into a false OK, so it judges this head neither way scripts/pm/check-expected-skips.mjs — here d47f540, on main acb32de re-pin to main's tip, then re-run: git -C /home/user/objectstack-issue-24849 fetch origin main && git -C /home/user/objectstack-issue-24849 checkout --detach origin/main ``` ## Refuse or print the gap: the card left this to the script's maintainer The choice is to refuse, under the existing exit 3, with no new exit code. A roster that is not the base's belongs in the same class as an unreadable head, because it gives no confident verdict in either direction. A stale roster produces a false ⛔ for a newly rostered job **and** a false OK for a retired row's skip. Printing the gap beside a 0 or 4 would keep the confident exit code, which is the defect itself. A distinct exit code would give callers a third NOT MEASURED spelling to learn. The header's exit register is updated in place. ## Which ref can say whether the roster is current (measured 2026-10-20) | candidate | verdict | |---|---| | checkout `HEAD` behind `origin/main` (ancestry or behind-count) | **Rejected.** It would refuse on about 98% of merges, none of which can move the roster: main had 792 first-parent commits in the 7 days to `b1676441b`, only 14 of them touching `.github/workflows` and 1 touching this file (`scripts/pm/git-history.mjs count`; the history floor of 2026-09-06 predates the window). `merge-base` and `rev-list` also answer green from a shallow clone. And a local `origin/main` is only as fresh as the last fetch. | | the PR's `base.sha` | **Rejected.** It does not follow the base. PR #24771 reads `base.sha` = `580fd4ed3` (committed 04:15Z). Its head `7a9bc58c6` was pushed at 08:05:59Z, and its 08:06Z runs were built on a main that already carried `ab9890169`. Measured against that `base.sha`, the stale `6b222464c` would have read as ahead of its base: a false green on the instance itself. | | **the base branch's live tip, by blob identity of the roster's sources** | **Chosen.** Equal sources give an equal roster by construction, and no history is read, so a shallow clone has nothing to get wrong. | `--head` without a PR compares with the board's default branch, at the cost of one extra `GET /repos/{repo}` read. A different board, whose base carries neither this file nor these workflows, is now refused instead of being judged against objectstack's roster. That matches the pm-dispatch enqueue bar, which already says the roster judges objectstack only (`名单 check-expected-skips.mjs 只判 objectstack`). The header paragraph that described cross-board judging is rewritten to match. ## Evidence - **The instance, reproduced offline** with `--check-runs-json` on PR #24771's head `7a9bc58c6` (all 37 of its check-runs, read once into a fixture file): - the script as of `6b222464c` exits 4 with `⛔ 1 skipped check-run(s) outside the roster`, naming `18.x upgrade harness (opt-in)`; - the script as of `c56115bf9` exits 0 with `10 skipped check-run(s), every one in the roster`. - **The comparison, live against main's tip `b1676441b`** (42 roster sources on main), through the exported functions: - a snapshot of main's own roster sources: gap 0, at tip; - the instance's checkout `6b222464c`: gap 2, exactly `.github/workflows/upgrade-harness-optin.yml — on main (921469d), absent here` and `scripts/pm/check-expected-skips.mjs — here 6df58d8, on main acb32de`; - this branch: gap 1, the script itself. - **CLI, live.** From this branch, `--pr 24771` and `--head 7a9bc58...` both exit 3 with the gap and the re-pin shown above, at 3 REST reads each. - NOT MEASURED: the live CLI run where the comparison passes and the judgement follows. Reason: that needs main to carry these bytes, so it can only be read after landing. The at-tip half is covered by the gap-0 measurement above and by the self-test. - **Blob ids agree.** For `scripts/pm/check-expected-skips.mjs` at main, the contents-API `sha` and `git rev-parse origin/main:PATH` both read `acb32de67...`. ## Tests `node scripts/pm/check-expected-skips.mjs --self-test` stays fully offline. It now runs 169 cases, up from 154, and exits 0 at `fbd5fa30b`. The new cases: - git blob ids against two known git values; - the repo-relative self path; - this checkout's sources are this file plus every scanned workflow; - **at tip**: equal sources leave no gap; - **behind** (the PR #24771 shape): the base's new workflow reads `absent-here` and this file `differs`, nothing else; the reverse reads `here-only`; - the refusal lines end on the re-pin recipe, and a wide gap is capped and counted; - the false ⛔ a behind roster gives (exit 4) beside the base roster's exit 0; - the live verdict line names `main's roster`, while the offline line is byte-for-byte the old one; - the offline CLI prints no roster line; - a structural pin that the live path calls `rosterGap(` before `readCheckRuns(`. ## Gates (local, at `fbd5fa30b`) - **Derived set.** `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack`, run with no paths (change set taken from merge base `b1676441b`), derived 33 commands. All 33 ran and exited 0, with exit codes captured before any pipe. `--ran` printed `✓ dispatch-gates --ran: 33 derived famil(ies) accounted for — 33 run, 0 NOT-MEASURED`. - **Battery.** `pnpm check:pm-dispatch-gates`, run detached as its header prescribes: `✓ dispatch-gates self-test (fast tier): 1913 cases pass; 6 slow section(s) deferred`, exit 0. - **Suites that name this file.** `pnpm check:pm-half-states` exits 0 (4916 cases). The `dispatch-gates.data.mjs` and `dispatch-gates.self-test.mjs` pins run inside the battery above. The header edit adds no lines above the `self-test-reads` marker, so it stays on line 133, where that self-test pins it. - **Lint.** `eslint --no-inline-config` on this one file exits 0. Repo-wide scans are left to CI. ## Acceptance notes - `node scripts/measure-self-test-floor.mjs` refuses its census (exit 1: `2 file(s) carry a landed handshake spelling the recogniser reads as none: scripts/pm/check-prior-rulings.mjs, scripts/pm/dispatch-gates.mjs`). It refuses identically on base `3fbef41b4`, and this diff touches none of those three files. The tool is a measurement with no CI wiring; carrier: none. - Boundary, documented in the docblock: the comparison is with the base's tip **as of the run**. A head whose last run predates a roster change on its base is judged by the newer roster. An added row costs nothing. A retired row's skip still reads exit 4, which is also the news that the head's runs are older than its base. - No changeset: `scripts/pm/**` publishes nothing, so this PR carries `skip-changeset`. --- _Generated by [Claude Code](https://claude.ai/code/session_01M82feuetLzrCiBz41BJb7L)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 7e40e8a commit f3f27be

1 file changed

Lines changed: 247 additions & 19 deletions

File tree

0 commit comments

Comments
 (0)