Skip to content

fix(pm): check-expected-skips refuses to judge a live head with a roster that is not the base branch's - #24892

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-24849-expected-skips-stale-roster
Oct 20, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-24849-expected-skips-stale-roster

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

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):

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 d47f54046, on main acb32de67
  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 docs(readme): say "ontology-driven" beside "the ontology is the software" #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 (921469dc9), absent here and scripts/pm/check-expected-skips.mjs — here 6df58d83c, on main acb32de67;
    • this branch: gap 1, the script itself.
  • CLI, live. From this branch, --pr 24771 and --head 7a9bc58c6... 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 docs(readme): say "ontology-driven" beside "the ontology is the software" #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

…ter that is not the base branch's

The roster a live run judges with is this file plus every workflow file in
the checkout it runs from; a head's check-runs come from the workflows of
the merge they were built on. A checkout behind its base therefore read a
job rostered on the base since as an unexpected skip (exit 4). A live run
now compares the roster's sources (this file and every workflow, by git
blob id, working tree against the base branch's live tip via the contents
API: no history, no fetch) before it judges, and answers exit 3 NOT
MEASURED naming each differing path and the re-pin when they differ. The
offline paths (--check-runs-json, --roster) are unchanged.

Claude-Session: https://claude.ai/code/session_01M82feuetLzrCiBz41BJb7L
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对

ACCEPT · domain:skills seat 1 (seat post #7623) · session_01M82feuetLzrCiBz41BJb7L · head fbd5fa30bbb63352cb74c508da1116db6968b27a · 2026-10-20T13:36Z

  1. Form. The PR is a draft against main. The body's first line is Fixes #24849, with no other open card number beside a closing keyword (full body read). Assignee huangyiirene equals the card's. Files, read with the PR's file list: scripts/pm/check-expected-skips.mjs only (+247/−19), the declared surface. It is not a governed path and touches none of the contract-review faces, so this seat's review at TIER_DEFAULT is the review of record (contract-review.md: CI plus the seat's own read). skip-changeset is right: no package is touched.
  2. Premises measured.
    • The seat read the whole diff. The live path (--pr / --head) compares the git blob ids of the roster's sources with the contents-API listing at the base branch's live tip, before any check-run is read. The sources are this file plus every .github/workflows/*.yml, hashed from working-tree bytes. A pin asserts that rosterGap( precedes readCheckRuns( in run().
    • A gap answers the existing exit 3 (NOT MEASURED) with each differing path and the re-pin recipe; no new exit code is added. The offline paths (--check-runs-json, --roster) are untouched, and their verdict line is pinned byte-identical.
    • Blob-id equality with stored blobs, checked on this tree: .gitattributes carries only merge=os-regen drivers (no text, eol or filter attributes), core.autocrlf is unset, and all 41 workflow entries are mode 100644 (no symlinks). Working-tree bytes therefore hash to the stored blob.
    • The dev's measured instance: PR docs(readme): say "ontology-driven" beside "the ontology is the software" #24771's head, judged offline from 6b222464c, gives exit 4; from c56115bf9 it gives exit 0. The live at-tip path is NOT MEASURED until main carries these bytes. The seat's next landing pre-check after this merges is that measurement.
  3. Gate check-runs, pinned to this head (2026-10-20T13:36Z): 45 runs, 0 pending, 0 failures; every Lint · and Type Check · lane, Lint & Repo Gates, TypeScript Type Check, Test Core and the claim guards are success. check-expected-skips.mjs --pr 24892, run on a checkout re-pinned to origin/main fcb40ad67: OK — 13 skipped check-run(s), every one in the roster (exit 0). The dev's local derivation: --ran reconciles 33 derived, 33 run, 0 NOT MEASURED. check:pm-dispatch-gates (1913 cases) and check:pm-half-states (4916 cases) both exit 0.
  4. Accepted deviations.
    • The comparison is blob identity against the base's live tip, not checkout HEAD against local origin/main or the PR's base.sha, which are the card's literal candidates. Accepted on measurement: a behind-count refuses on about 98% of merges and answers green on a shallow clone, and PR docs(readme): say "ontology-driven" beside "the ontology is the software" #24771's base.sha did not follow its base. The card's intent holds: behind ⇒ refuse with the gap and the re-pin, offline paths unchanged, no workflow edit, no roster row.
    • Refusal was chosen over printing the gap, folded into exit 3. The card left this choice to the script's maintainer.
    • Another board is now refused rather than judged against objectstack's roster, which matches SKILL.md's 「名单 check-expected-skips.mjs 只判 objectstack」.
    • Size: +247/−19 in one file. About 90 lines are code, about 60 are the docblock recording the measured reasons, and about 65 are self-test. This is in proportion for a live comparison with offline-pinned behind and at-tip cases, and the comment density matches the file's existing style.
  5. Out-of-scope findings. measure-self-test-floor.mjs's census refusal (identical on base 3fbef41b4, no CI wiring, outside classes a/b/c): Acceptance notes, as the PR body has it.
  6. Landing. Not governed: once every check on this head is green, the seat flips it ready and arms auto-merge through the relay, follows it to MERGED, then closes out check-expected-skips.mjs judges a head against the roster in the checkout it runs from, so a shared checkout one merge behind origin/main reads a freshly rostered opt-in job as an unexpected skip (exit 4) — the PR #24771 arming #24849.

Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 20, 2026 13:38
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 20, 2026
Merged via the queue into main with commit f3f27be Oct 20, 2026
46 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-24849-expected-skips-stale-roster branch October 20, 2026 14:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/m skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants