Skip to content

fix(pm): the queue guard's pull_request leg attributes to the payload PR and reads the net diff - #26568

Merged
os-zhuang merged 2 commits into
mainfrom
claude/project-thread-lewq7j
Oct 27, 2026
Merged

os-zhuang merged 2 commits into
mainfrom
claude/project-thread-lewq7j

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Warren · project thread
Fixes #26316
Clause-②: no
Metadata-protocol: none

维护者速读

  • 改了什么:scripts/pm/check-governed-queue-guard.mjs 的 pull_request 腿不再按提交拆行:整段 diff 读成一行「合并基点 → head 的净差异」,归到事件载荷里的 PR 号。merge_group 腿(按首父提交拆、按标题 (#N) 归属、各种拒绝)一字不改。自测加 8 例,两组电池下限按实测抬高。
  • 为什么改:提交标题写的是 issue 号时,预警去读那个 issue 的 reviews(404),报「review list could NOT be read」;分支合过 origin/main 时,合并提交的首父 diff 把 main 已落地的几十个路径算进预警,且每行都是「无归属」。预警本来就不会变红,但读的人会被误导。
  • 风险与回滚:只改 PR 上的预警输出,队列腿的输出在两个夹具上逐字节相同(下方附哈希);回滚即 revert 本 PR。
  • 先看:① enumerateRows(L2386 起)新的 pull_request 分支;② readEventDiff 那一行调用(L2794)把事件传进去;③ 自测 decomposition 电池里的 7 个新用例。
  • 两分钟验证:node scripts/pm/check-governed-queue-guard.mjs --self-test → 末行 ✓ check-governed-queue-guard self-test: 401 cases pass(改前 393)。
  • 需要你做的:无,席位落地。

What changes

Card #26316's 「Wanted」, and nothing wider. One file, scripts/pm/check-governed-queue-guard.mjs:

  1. enumerateRows reads by event. The merge_group reading is the old one, byte for byte: one row per first-parent commit, attributed by pullNumberFromSubject, with the head_ref fallback only for a one-commit group. On the pull_request leg it now returns ONE row: diff-tree baseSha headSha (the caller already hands in the merge-base), attributed to the payload's own pull request number, carrying commits = the range's first-parent count. Shape chosen: the single net row, because it makes both negatives provable with no intersection logic. The subject is not read for attribution on this leg at all; a subject naming an issue cannot redirect the review read, and a merge of origin/main cannot add a path the net diff does not hold.
  2. readEventDiff passes { event: context.event } to it. The merge-base call it already made (L2793) is unchanged; the premise reading in the claim held: the missing piece was the per-commit enumeration, not a merge-base.
  3. commitsInRange(rows) keeps the header line honest: pull request #N — 3 commit(s) in range still counts the range's commits. On merge_group rows carry no commits, so it equals rows.length, the old count.
  4. A runGit(args) seam on enumerateRows, default null (production reads the checkout as before), so the self-test drives the enumeration offline, as its header promises (no network, no git).
  5. A header paragraph under Multi-PR merge groups states that the pull_request leg is not decomposed, and why.

Not touched: .github/workflows/governed-surface-guard.yml, REQUIRED_CONTEXTS, the register, any refusal text, decomposeGovernedWork, queuedPullsInGroup, runSizeGuard.

Tests

Self-test 393 → 401 cases, exit 0. Eight new cases, all driven through the runGit seam:

  • Battery decomposition, and the multi-PR group trap (floor 6 → 13, measured 13):
    • ⭐ pull_request-leg: a-subject-naming-an-ISSUE-is-attributed-to-the-PAYLOAD-pr-never-the-issue: the PR ci(release): the hotcrm downstream smoke resolves hotcrm's latest release tag at run time #25965 shape (one commit, subject ends (#25770), payload PR 25965) gives one row with PR 25965, governed entry 25965, zero unattributed.
    • ⛔ CONTROL merge_group-leg: the-same-subject-still-attributes-by-its-trailing-number-unchanged: the same range on merge_group still reads 25770 from the subject.
    • ⭐ pull_request-leg: a-branch-that-merged-origin-main-carries-ONLY-its-net-rows-against-the-merge-base: the PR ci(governed-surface-guard): the pull_request leg checks out commits and trees only and installs the toolchain on demand #26311 shape (own commit, merge of main carrying .github/workflows/lint.yml, AGENTS.md, packages/core/src/x.ts, own commit) gives one governed entry, PR 26311, paths exactly .github/workflows/governed-surface-guard.yml.
    • and-it-reads-the-net-diff-base-to-head-never-a-per-commit-first-parent-diff: the recorded git reads hold the two-tree diff-tree and no --first-parent diff-tree.
    • and-the-header-still-counts-the-ranges-three-commits.
    • ⛔ CONTROL merge_group-leg: the-same-range-is-still-one-row-per-first-parent-commit-unattributed-without-a-subject-number: three rows, all PR null, the merge row still carries AGENTS.md.
    • and-an-empty-pull_request-range-is-no-rows.
  • Battery the pull_request leg is an EARLY WARNING and never reddens (floor 5 → 6, measured 6):
    • ⭐ the-early-warning-names-the-payloads-pull-request-and-never-the-issue-its-subject-names: the rendered warning carries #25965 — governed and no #25770.

Floor binding checked once: with the decomposition floor at 14 the run exits 1 naming registered 13 case(s), below its pinned floor of 14. The self-test's success line is otherwise identical before and after; only the count moves (393 → 401). SELF_TEST_BATTERY_FLOOR (29) is unchanged: no battery added.

Acceptance notes

The card's three acceptance lines:

  • A pull_request run on a PR whose commit subjects name an issue number prints the PR's own review state: yes, the fixture run below prints #25965 — governed where it printed #25770 — governed before.
  • A PR whose branch merged origin/main prints only the PR's net rows: yes, 3 unattributed rows (one of them the merge commit's 7 workflow paths) became 1 governed entry with 1 path.
  • --self-test green; the merge_group cases unchanged: 401 pass; every pre-existing case passes unedited; the live merge_group output on both fixtures is byte-identical before and after (below).

This PR's own pull_request run is a live case of the second line: its branch carries a merge of origin/main (25 commits of main), so the guard's check-run on this PR should read pull request #THIS — 2 commit(s) in range and name no governed path (the diff touches none).

Verification

Fixtures: a throwaway worktree off d2ca7079 with two branches built to the card's shapes; payloads point the guard at them; reviews unreachable on purpose (API URL at a closed local port) so the output is deterministic. "Before" = the guard at d2ca7079; "after" = this branch.

PR #25965 shape: one commit, subject names issue 25770

Before:

pull request #25965 — 1 commit(s) in range
Governed Surface Queue Guard — pull_request — 1 governed pull request(s), 0 unattributed governed commit(s), 1 review lookup(s).

  #25770 — governed:
        AGENTS.md ×1 — the repo-root agent instruction file

After:

pull request #25965 — 1 commit(s) in range
Governed Surface Queue Guard — pull_request — 1 governed pull request(s), 0 unattributed governed commit(s), 1 review lookup(s).

  #25965 — governed:
        AGENTS.md ×1 — the repo-root agent instruction file
PR #26311 shape: own commit, merge of main, own commit

Before:

pull request #26311 — 3 commit(s) in range
Governed Surface Queue Guard — pull_request — 0 governed pull request(s), 3 unattributed governed commit(s), 0 review lookup(s).

  ⛔ UNATTRIBUTED — commit 18743a82d8c2 … the workflow commit
          - .github/workflows/governed-surface-guard.yml
  ⛔ UNATTRIBUTED — commit d08da36b536b … Merge commit 'd2ca7079…' into fx-b
          - .github/workflows/board-snapshot.yml
          - .github/workflows/fleet-patrols.yml
          - .github/workflows/lint.yml
          - .github/workflows/platform-checklist-watchdog.yml
          - .github/workflows/release-coverage-patrol.yml
          - .github/workflows/required-set-patrol.yml
          - .github/workflows/governed-surface-guard.yml
  ⛔ UNATTRIBUTED — commit 57cfc2aac768 … pin the self-test step
          - .github/workflows/governed-surface-guard.yml

After:

pull request #26311 — 3 commit(s) in range
Governed Surface Queue Guard — pull_request — 1 governed pull request(s), 0 unattributed governed commit(s), 1 review lookup(s).

  #26311 — governed:
        .github/workflows/** ×1 — CI, deploy and release workflows: what runs on every push, what deploys, what gates the queue
          - .github/workflows/governed-surface-guard.yml
merge_group leg, same two ranges: byte-identical

cmp of before and after, each run with a merge_group payload over the same range (head_ref naming the PR):

merge_group fixture a: byte-identical (7529 bytes, sha256 04261d5a4b8e70cb)
merge_group fixture b: byte-identical (7666 bytes, sha256 dd83c48111ca9ab5)

Fixture b on the queue leg still reads 3 commit(s) in range and 3 unattributed governed commit(s): the queue's decomposition and its refusal are untouched.

--toolchain-needed on the pull_request leg
Governed Surface Queue Guard — generator toolchain not needed: 1 commit(s) in range touch no register path the lift recomputes
Governed Surface Queue Guard — generator toolchain not needed: 3 commit(s) in range touch no register path the lift recomputes
Local gates on 64d0d4f: 32 derived, 32 run, all exit 0

Derived with node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack on 64d0d4fd (branch with origin/main at a3009cbf merged in, so the tree is not stale against the families). Each run's exit code captured before any pipe; check:pm-dispatch-gates ran under os-verify-lock.sh (slot issue-26316): VERDICT command-exit 0 · held the lock 380s.

  • node scripts/check-closing-keyword-parity.mjs → exit 0
  • node scripts/check-closing-keyword-parity.mjs --self-test → exit 0
  • node scripts/check-comment-mask-corpus.mjs → exit 0
  • node scripts/check-declaration-mirrors.mjs → exit 0
  • node scripts/check-declaration-mirrors.mjs --self-test → exit 0
  • node scripts/check-self-test-wired.mjs → exit 0
  • node scripts/check-self-test-wired.mjs --self-test → exit 0
  • node scripts/check-self-test-workflow-commands.mjs → exit 0
  • node scripts/check-self-test-workflow-commands.mjs --self-test → exit 0
  • node scripts/check-whole-set-label-write.mjs → exit 0
  • node scripts/check-whole-set-label-write.mjs --self-test → exit 0
  • node scripts/ci/plan-test-core-shards.mjs --self-test → exit 0
  • node scripts/pm/bare-root-worklist.mjs --self-test → exit 0
  • node scripts/pm/check-governed-queue-guard.mjs --self-test → exit 0
  • pnpm check:agent-test-spelling → exit 0
  • pnpm check:bash32-floor → exit 0
  • pnpm check:ci-filter-parity → exit 0
  • pnpm check:cli-command-ids → exit 0
  • pnpm check:closing-target-claim → exit 0
  • pnpm check:cross-package-test-inputs → exit 0
  • pnpm check:driver-memory-census → exit 0
  • pnpm check:entry-guard → exit 0
  • pnpm check:gitlink-declared → exit 0
  • pnpm check:nul-bytes → exit 0
  • pnpm check:parse-guard → exit 0
  • pnpm check:pm-dispatch-gates → exit 0
  • pnpm check:pnpm-filter-targets → exit 0
  • pnpm check:pnpm-flag-spellings → exit 0
  • pnpm check:ratchet-remedy-authority → exit 0
  • pnpm check:refd-timer-probe → exit 0
  • pnpm check:scripts-symbol-anchors → exit 0
  • pnpm check:watch-hint-literal → exit 0

--ran reconciliation (exit codes recorded): Run reconciliation — 32 derived, 32 run, 0 NOT-MEASURED, 0 UNRUN. · ✓ dispatch-gates --ran: 32 derived famil(ies) accounted for — 32 run, 0 NOT-MEASURED (a DERIVED zero — all 32 recorded an exit code and none of them is 3). That reading was taken with origin/main at 1c632311, 2 commits past the merge; the one derived-from file those moved is scripts/engine-double-contract.pinned.json, which no family on this card's list reads.

Left to CI, as the derivation names: the workflow-payload families (check:closing-target-claim's live leg), ci.yml · Plan Test Core shards, the type-check program steps, the repo-wide pnpm lint. No package was touched, so there is no build closure and no package test or typecheck to run; this script has no *.test.ts beside it, and its own --self-test is the suite.

Human Minimum

Each item is one rule from CONTRIBUTING.md › Human Minimum.

  • 1. Schemas start as Zod; packages/spec holds no runtime logic. (No schema change.)
  • 2. Every name I added uses the casing for its kind.
  • 3. No formatter ran; pnpm lint is CI's run (repo-wide scan).
  • 4. One issue, one branch off an up-to-date main, one pull request.
  • 5. Nothing publishes (scripts/pm/**, skip-changeset); both declaration lines are filled in above.
  • 6. packages/spec not touched.
  • 7. No hand edits to content/docs/references/, packages/*/CHANGELOG.md or content/docs/releases/.
  • 8. Title, body and comments are in English (the 维护者速读 block is the required exception).

Generated by Claude Code

… PR and reads the net diff

On the pull_request leg, enumerateRows now returns one row: the net diff
from the merge-base to the head, attributed to the payload's own pull
request number. A commit subject naming an issue no longer sends the
review read to that issue, and a branch that merged origin/main no longer
forecasts every path main already landed. The merge_group leg keeps its
per-first-parent-commit decomposition and subject attribution unchanged.

Self-test: seven cases in the decomposition battery (6 -> 13) and one in
the pull_request early-warning battery (5 -> 6), driven through a new
runGit seam on enumerateRows.

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

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: TIER_DEFAULT
Head-sha: 64d0d4fd561bf912ca5fbae46658e9c52de44b18
Local-runs: none

Director seat (summon #52, ledger objectstack#12708), by the maintainer's word 「#26566、#26568、#26570、#26585 也要追加复审」: the PR is an open draft at this head with no review on it; this record is read-only — the diff, card #26316, the head's check runs. dispatch-gates --tier on scripts/pm/check-governed-queue-guard.mjs: no governed or published path, Clause-②: no, Metadata-protocol: none ⇒ the declaration face, TIER_DEFAULT; served at CONTRACT_REVIEW_TIER. The seat does not ready, queue or merge; landing stays with the lane seat and the queue guard's own Tier.

① Derived judgments

  • merge_group leg unchanged. enumerateRows(root, baseSha, headSha, fallbackPull, { event = EVENT_MERGE_GROUP, runGit }) — with the default event the per-commit enumeration is the previous code path, byte for byte; the only new behaviour is behind event === 'pull_request'.
  • pull_request leg: one net row against the merge base. Instead of one row per commit (which, on a branch that merged origin/main, attributed every governed path main had moved to the PR), the leg returns a single row { sha: headSha, subject, pr: fallbackPull, commits: n, paths: diff-tree base…head }. That is the card's Wanted: a PR is judged on its net diff, not on the commits it carried in from main. readEventDiff already computed the merge base (L2793, untouched) and now passes { event: context.event }; the header and toolchain lines count via commitsInRange(rows) so the "N commit(s) in range" text stays true on both legs.
  • Guard semantics tighten, never loosen. Fewer rows on pull_request can only remove false governed attributions from main's commits; a governed path in the PR's own net diff is still in paths and still refuses. The body's live case (this very branch, 25 main commits merged, "2 commit(s) in range", no governed path) matches what the head's Governed Surface Queue Guard run reports.
  • Self-tests follow. Eight cases through the runGit seam cover the two shapes the card names (own commits only; own + merge of main carrying workflow paths); batteries decomposition 6→13 and EARLY WARNING 5→6 are raised to the counts the new cases add, which the lint tooling self-tests check on this head.

② Semver level

scripts/pm/** publishes nothing; skip-changeset; both declaration lines present (Clause-②: no, Metadata-protocol: none). No level owed. Correct.

③ Boundary flags

  • Check runs pinned to 64d0d4fd: 28 success, 17 expected skips, 0 other — Lint · PM dispatch-gates, Lint · scoped · tooling self-tests, Governed Surface Queue Guard, Test Core (2 shards), TypeScript Type Check, the three claim guards. No local re-run; the body's fixture worktree run (throwaway branches, closed-port reviews URL) is the dev's measurement, not the seat's.
  • The guard reads scripts/pm/check-governed-queue-guard.mjs from the PR's own tree on the pull_request leg (the workflow checks out the head), so this PR's green run exercised the new code path on itself — a real, if single, live case.
  • Open draft, no reviewer yet; nothing here blocks pr_ready. The record is head-pinned: a new push owes a new record.

Implemented-by: claude/project-thread-lewq7j
Reviewed-by: session_01ADHkmQr732toX135tyo5zs

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet

objectstack-fleet Bot commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor Author

Retracted: this comment was a duplicate post of the director seat's contract-review record on this PR; the record of record is comment 6439760427 above (same head, same verdict). Nothing else changes.


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

维护者速读

改了什么:scripts/pm/check-governed-queue-guard.mjs 的 pull_request 预警腿不再按提交拆行,改为读「合并基点 → head」的一行净差异,归到事件载荷里的 PR 号;merge_group 队列腿(按首父提交拆、按标题 (#N) 归属、各种拒绝)走原路径,一字不改。自测加 8 例(393 → 401),两组电池下限按实测抬高。
为什么改:提交标题写 issue 号时,预警去读那个 issue 的 reviews(404)并报「review list could NOT be read」;分支合过 origin/main 时,合并提交把 main 已落地的路径全算进预警。预警本不变红,但会误导读的人。
风险与回滚:只改 PR 上的预警输出;队列腿仍是同一段代码(新增的 event 参数默认 merge_group)。回滚 = revert 本 PR。
先看:① enumerateRows 新的 pull_request 分支;② readEventDiff 把 context.event 传进去的一行;③ 自测 decomposition 电池的新用例。
两分钟验证:node scripts/pm/check-governed-queue-guard.mjs --self-test 末行 401 cases pass;本 PR 自己的 Governed Surface Queue Guard run 绿(分支合过 main,正是新路径的现场用例)。
需要你做的:无。Tier S,总监契约复核 PASS + 本核对后席位 ready 并布自动合并。
席位意见:同意落地。

席位核对

ACCEPT · domain:skills#2 · session_01LqKsGyMNFwwnbF5FTAqyPn · 2026-10-27T10:39Z — seat review of record for #26316 on head 64d0d4fd, against GitHub, not the report's own account. One path, scripts/pm/check-governed-queue-guard.mjs — no governed or published path ⇒ Tier S: the record of the review tier is the director seat's independent ## Contract review at comment 6439760427 (Served-tier: TIER_DEFAULT declared face, served at CONTRACT_REVIEW_TIER, head 64d0d4fd, VERDICT: PASS, Local-runs: none; reviewer session_01ADHkmQr732toX135tyo5zs, not the implementer). This seat adds the landing read and lands it itself.

  1. Shape. Draft, base main, mergeable_state: clean, 2 commits (one a merge of origin/main), 29 behind main, no file of this PR touched there; +143/−12 in the one file the claim (6435659452) names. Body: harness attribution lines, Fixes #26316, Clause-②: no, Metadata-protocol: none line-initial, six-line ## 维护者速读. Labels size/m, skip-changeset (nothing published). Branch claude/project-thread-lewq7j, as the claim names.
  2. Diff, read by the seat. enumerateRows gains { event = EVENT_MERGE_GROUP, runGit = null }; the merge_group branch is the old commits.map(…) with git(root, …) routed through readGit (same argv, -m --first-parent per commit, same pullNumberFromSubject ?? fallback attribution). The pull_request branch returns ONE row: diff-tree baseSha headSha (the merge-base the caller already computed), pr: fallbackPull (the payload's number), commits: n. commitsInRange(rows) keeps the header and toolchain lines' counts equal on merge_group (no commits field ⇒ 1 per row). Matches the card's 「Wanted」 and the order's ⛔ (no workflow, register, REQUIRED_CONTEXTS or refusal text touched).
  3. Gates, pinned to head 64d0d4fd. 45 check-runs: 28 success, 17 skipped (path-filtered lanes), none red or pending (seat read 2026-10-27T10:38Z) — Lint · PM dispatch-gates, Lint · scoped · tooling self-tests, Governed Surface Queue Guard (the new code path on this branch's own merge of main), Test Core, TypeScript Type Check, the claim guards. The dev's report (6437394506): 32/32 derived gates exit 0, --ran reconciled; follow-up 6437438814 quotes the live replay 「pull request fix(pm): the queue guard's pull_request leg attributes to the payload PR and reads the net diff #26568 — 2 commit(s) in range」, 0 governed, 0 unattributed.
  4. Accepted deviations. (a) The card finished one tier down after the maintainer's word of 2026-10-27T07:14Z (claim amended 2026-10-27T07:20Z, quota exemption); the review of record ran at CONTRACT_REVIEW_TIER. (b) Fixes behind the harness's two attribution lines. (c) The check-run log text is not readable from a seat container (302 to blob storage); the live line is the dev's local replay of the same payload, declared as such.
  5. Carrier. Nothing filed from this review: the director named no should-fix. The one open edge — an empty pull_request range returns no row — reads as CLEAR, the same as before the change.
  6. Landing. Tier S ⇒ on this record the seat readies the PR and arms auto-merge through the relay (pr_ready, automerge_enable), follows it to MERGED, and closes ci(governed-surface-guard): the pull_request leg attributes governed rows by commit subject, so a subject naming an issue reads that issue's /reviews (404) and the early warning misleads #26316 with the landing record.

Generated by Claude Code

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

3 participants