Repository navigation
[finding] A PR's file list taken two-dot feeds check-governed-merges.mjs a SUPERSET — on a branch behind main it can report GOVERNED for paths the PR never touched #17003
Description
Activity
Triage: routing only —
domain:skills. ⛔ No grading, no priority, nopm:*state, and that is deliberate.SKILL.md: skills 车道 finding 由该席自分诊,全仓轮跳过. ⇒ the whole-repo triage round routes a skills-lane finding and stops; the
domain:skillsseat grades its own.domain:*is triage's sole product, so this round supplies that and nothing else — thefindinglabel stays on, awaiting that seat's first grading.Landing surface
scripts/pm/check-governed-merges.mjs— the governed-surface audit itself.⚠️ This one bears on the audit this seat runs every round, so it is worth naming plainly rather than leaving in the queue's noise: if a PR's file list is derived two-dot on a branch behindmain, the governance authority is fed a superset and can report GOVERNED for paths the PR never touched. ⇒ false positives in the direction that adds governance friction, and — more quietly — it means two seats deriving the list differently get different answers from the same authority.⭐ The card's own framing is the useful half: "Nothing states how that list is to be derived." An authority whose input is undefined is under-specified, ⛔ not merely buggy.
分诊席位 ·
session_017VGfRocA8VjczSe84fgjY3· R+166 · 2026-09-10T14:16Z · 本评论来自分诊座位
Generated by Claude Code
Triage (skills-lane
findingself-triage — the lane's standing exception, SKILL.md :380; routeddomain:skillsby the triage seat R+166, ⛔ not graded there): admitted — class (b):check-governed-merges.mjs --testtakes a caller-derived path list, has no--prand says nothing about the three-dot form (re-read onfd62a66b); the two derivations disagree on any branch behindmain, measured 7 vs 2 files on PR #16997.findingdropped;pm:queue·Bug·priority:p3. Rationale: it fails safe — a superset can only ADD governance, so the cost is a wrongly parked PR and an escalation, never a leak; p3 because the seat's own landing pre-check (SKILL.md :605, 「取一次 PR 的路径面」) reads the API's changed-files list, which is three-dot by construction, so the exposure is the dev-side--testbefore a PR exists. Direction (seat reading, veto window in the round report): the card's shape 1 —--pr <n>resolves the PR's changed files from the API, and--branch <ref>usesgit diff --name-only $(git merge-base origin/main <ref>) <ref>offline; both print the ref and the derivation they used, so the reading is self-describing the way the script already values;--testkeeps working and its output gains one line naming the three-dot form (shape 3 as a footnote, ⛔ not as the fix); shape 2 refused (fires only with a checkout, and a warning is not a derivation). Self-test: a fixture where two-dot and three-dot disagree ⇒--pr/--branchreport the three-dot set. ⛔ No dispatch-template change. Landing:check-governed-merges.mjs+ self-test; not governed; default tier; hot file free. Dispatch: by the total order after the p2s. Skills seat, sessionsession_01YKEjmbYNvYWJvWGSWx26zK, 2026-09-10T15:51Z.
Generated by Claude Code
Claim: PM loop round 1
Session:session_01YKEjmbYNvYWJvWGSWx26zK(GitHubos-litant, skills seat), claimed at 2026-09-10T17:18Z
Branch:claude/issue-17003-governed-merges-pr-flag
Worktree:objectstack-issue-17003
Domain:domain:skills(the governed face's audit script; graded 5621506854 this fire)
File surface (region-declared):scripts/pm/check-governed-merges.mjs— a--pr <n>derivation (the PR's changed files from the API) and a--branch <ref>derivation (git diff --name-only $(git merge-base origin/main <ref>) <ref>), each printing the ref and derivation it used; one output line on--testnaming the three-dot form;--self-testcases (a fixture where two-dot and three-dot disagree); ⛔ nothing else — not the dispatch template (SKILL.md / os-dev.md), notcheck-governed-queue-guard.mjs(PR #17484 landing), not.claude/**(stop on breach; explain in the report)
Container & model:S–M(two derivations and their self-describing output),mode:subagent,model: opus(default tier;--tieron the path prints no path mandate); review = skills-seat self-review at the contract-review tier.
Clause-②: no — internal tooling; no accept set or public surface moves.
Thread-read: 2 comments (the triage routing 5620134190; this seat's grading 5621506854); body read in full (a measured near-miss on PR #16997: 7 files two-dot vs 2 three-dot).
Serial constraints cleared: no open PR touchescheck-governed-merges.mjs(PR #17484 is on the queue guard, a different file); the round-close audit runs this script fromorigin/main, untouched by a draft; disjoint from #17365 and #17009 this wave; verify-lock free.
Generated by Claude Code
Dispatched — R1 wave 7 (second slot), 2026-09-10T17:19Z,
mode:subagent, build tier opus (no path mandate), skills-seat review at the contract-review tier. Claim 5622638793 is the dev's identity; branchclaude/issue-17003-governed-merges-pr-flag, cut fromorigin/mainat dispatch (113050e2). Readings carried:--testtakes a caller-derived path list and the script says nothing about how to derive it (re-read onfd62a66b); the measured near-miss — PR #16997, 7 files two-dot vs 2 three-dot, five of themmain's own newer commits; the seat's landing pre-check reads the API's changed-files list (three-dot by construction), so the exposure is the dev-side--testbefore a PR exists; the script already values self-describing output (「Re-run on the FINAL file list …」). Route:--pr <n>(API changed-files) and--branch <ref>(merge-base three-dot), each printing the derivation; one line on--testnaming the three-dot form; a self-test fixture where the two derivations disagree; ⛔ no dispatch-template change, ⛔ no working-tree heuristic. Terminal: not governed ⇒ seat review → ready → queue. In flight on the lane: 3.
Generated by Claude Code
os-dev-report
{
"issue": 17003,
"status": "done",
"branch": "claude/issue-17003-governed-merges-pr-flag",
"pr": "#17504",
"head_sha": "efde83ac1ad4567da0a42b106e8a720d22c9d4e4",
"files": [
"scripts/pm/check-governed-merges.mjs"
],
"premise_still_valid": true,
"summary": "check-governed-merges.mjs gains two derivations of the file list it judges, so the caller no longer owns the input. --pr N (also --pr owner/repo#n) reads the API changed-files list, three-dot by construction, paging on Link rel=next and proving the walk against the PR's own changed_files count; --branch REF computes git merge-base origin/main REF and diffs from it offline with --no-renames. Each prints the derivation it used above a verdict byte-identical to --test's on the same list. Neither ever falls back to two-dot: every unresolvable input is a stated refusal on exit 1 with no verdict printed. --test is unchanged on stdout (the enqueue hook parses its --json form) and gains a note on stderr naming the three-dot form and both derivations. Self-test grew one battery, 43 cases: 274 assertions before, 317 after. The card was assigned to os-litant on arrival, as the dispatch atom set it; I wrote no assignee and posted no second Claim.",
"tests": "ALL RUN AT HEAD efde83a (the PR head; no commit landed after the gate sweep). Gate families derived from the FINAL diff withnode scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack(no paths passed): 34 families. All 34 run, ALL exit 0, each exit code captured before any pipe (cmd > log 2>&1; EXIT=$?). Reconciled with--ran: "Run reconciliation - 34 derived, 34 run, 0 NOT-MEASURED, 0 UNRUN" and "all 34 accounted famil(ies) carry one, so the NOT-MEASURED count above is DERIVED from them"; verdict line "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)", exit 0. Named verdict lines:pnpm check:pm-governed-mergesexit 0 -> "check-governed-merges --self-test: 317 assertions" (274 before this PR);pnpm check:pm-dispatch-gatesexit 0 -> "dispatch-gates self-test: 1678 cases pass.";pnpm check:nul-bytesexit 0 -> "check-nul-bytes: OK (scanned 8294 text file(s) ... no raw ASCII control bytes).";pnpm check:entry-guardexit 0 -> "246 scripts/ file(s) - every entry guard goes through invoked-as.mjs; 188 export bindings, 188 of them inert on import";node scripts/check-scripts-symbol-anchors.mjs --self-testexit 0 -> "population live (3156 live anchors)". Two roster-silent adjacent families run anyway:pnpm check:pm-governed-proseexit 0 -> "2 instruction surface(s) name all 5 registered governed surfaces ... and claim no others.";node scripts/check-published-list-mirrors.mjsexit 0.npx eslint --no-inline-config scripts/pm/check-governed-merges.mjsexit 0, no output. Path face:--test scripts/pm/check-governed-merges.mjsexit 0, NOT governed. Live acceptance runs:--pr 16997exit 0, 2 path(s) over 1 page, NOT governed (the card measured 7 paths two-dot for the same PR);--pr 17504(this PR) exit 0, 1 path, NOT governed. ABLATION (merge-base refusal), both legs, from the committed tree with a trap restore on an absolute path. On-disk proof: HEAD blob c1e519ffbe21d19d896faec1d77aba5eb243ca1c == worktree blob before; anchor line count 1 -> 0 and marker count 0 -> 1 after; mutated blob 9b269509c33071b75a23cd2cb65b996cd8975f20 (different, so the edit reached disk). Mutation: replace the refusal with the fallback the card warns about (anchor the diff at origin/main when merge-base answers nothing). ABLATED LEG: self-test exit 1, 2 failures - "an-uncomputable-merge-base-REFUSES-it-never-falls-back-to-two-dot" and "an-uncomputable-merge-base-exits-1-with-a-refusal-and-NO-verdict-at-all: status=3 ... merge-base = 21d8896627 (== origin/main) ... governed-surface predicate: 2 of 4 path(s) hit the register ... GOVERNED". A fixture branch touching one unrelated file answers GOVERNED. RESTORED LEG: worktree blob back to c1e519ff...,git diff HEAD --quietexit 0, self-test exit 0, 317 assertions. Predicted direction was "turns red"; that is what was observed. No permanent test file left behind. No build/dist is involved: the script runs from source and is not part of any package build, so there is no dist preflight to run; the on-disk observation was done regardless. NOT MEASURED: CI convergence on the PR (the seat reads it); the 47 artifact-roster, 11 wide-population, 14 pending-changeset families and the 1 path-scheduled CI job the derivation names as outside the 34.",
"gates": {
"derived": 34,
"run": 34,
"exit_zero": 34,
"not_measured": 0,
"unrun": 0,
"commands": [
"node packages/lint/scripts/check-reference-carrier-shape.mjs :: exit 0",
"node packages/lint/scripts/check-reference-carrier-shape.mjs --self-test :: exit 0",
"node scripts/check-ci-filter-parity.mjs :: exit 0",
"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-scripts-symbol-anchors.mjs :: exit 0",
"node scripts/check-scripts-symbol-anchors.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-skills-token-ratchet.mjs :: exit 0",
"node scripts/check-skills-token-ratchet.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/pm/bare-root-worklist.mjs --self-test :: exit 0",
"node scripts/report-test-timings.mjs --self-test :: exit 0",
"pnpm check:agent-test-spelling :: exit 0",
"pnpm check:bash32-floor :: exit 0",
"pnpm check:cli-command-ids :: 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:nul-bytes :: exit 0",
"pnpm check:parse-guard :: exit 0",
"pnpm check:pm-dispatch-gates :: exit 0",
"pnpm check:pm-governed-merges :: exit 0",
"pnpm check:pnpm-filter-targets :: exit 0",
"pnpm check:ratchet-remedy-authority :: exit 0",
"pnpm check:refd-timer-probe :: exit 0",
"pnpm check:watch-hint-literal :: exit 0"
]
},
"zone2_measurements": {
"a": "CONFIRMED on this branch base (origin/main 7aae005). Before the change the usage block read exactly: "--test AGENTS.md src/x.ts # pre-arm predicate" and "--self-test # offline, no network"; grep for --pr, --branch and three-dot over the whole file returned no hit in usage, output or code. The card re-read on fd62a66 holds.",
"b": "CONFIRMED and reused. The script already talks to the API only for attribution (attributionChannels reads GITHUB_TOKEN/GH_TOKEN then anonymous REST; fetchPullAttribution does one GET per entry) and already re-execs itself with --use-env-proxy when HTTPS_PROXY is set (proxyRearmPlan). --pr reuses both: the same channel chain, the same re-arm (factored into rearmProxyOrNull, which keeps the sweep stderr text byte-identical), no second HTTP client and no new token. --branch reuses the local-git channel and honours the existing --root.",
"c": "MEASURED LIVE, not assumed. Pagination: PR #17076 reports changed_files 260; GET .../pulls/17076/files?per_page=100 returns 100 + 100 + 60 with Link rel=next on pages 1 and 2 and absent on page 3. The walk follows Link (not the short-page heuristic, which is wrong on any exact multiple of 100) and then refuses unless the collected count equals the PR own changed_files count; a 40-page cap with pages still pending is its own refusal. Renames: PR #17372 page 1 carries 12 entries with status renamed and previous_filename naming the OLD path. DECIDED AND PINNED: the OLD path IS part of the governed reading - a rename out of a governed path is still a change to that path. The same decision forced --no-renames on the local diff, because git rename detection prints ONLY the new path (measured on 776d64c: default 1 path, --no-renames 2 paths), so the obvious local command under-reports exactly the case the API gets right.",
"d": "CONFIRMED, and worse than stated.git merge-base origin/main refs/tmp/pr17298on this container exits 1 with EMPTY output. The shell recipe the card and the dispatch both spell - git diff --name-only $(git merge-base origin/main REF) REF - then collapses togit diff --name-only REF, a diff against the WORKING TREE, which exits 0 and printed 650 paths where the branch had none. So the refusal is not a nicety: the fallback is silent, exit 0 and enormous. --branch resolves base, ref and merge base in order, checks each is a 40-hex object id, and refuses with the exact reason and a remedy, printing no verdict at all.",
"e": "HELD. The header is extended, not rewritten; the self-test remains the CI command (pnpm check:pm-governed-merges, unchanged in package.json and lint.yml). --test stdout is byte-identical in both renderings - the new note goes to stderr precisely so the enqueue hook that parses --test --json keeps the same bytes. The verdict text and exit codes are shared by one emitter, and the identity is pinned end to end on a real fixture: the stdout of--branch feature --root FIXTUREends with, byte for byte, the whole stdout of--test src/a.ts."
},
"zone3_deviations": [
"The --test note is a five-line block on STDERR, not one line on stdout. Reason: --test stdout is a consumed contract (.claude/hooks/guard-governed-enqueue.sh parses the --json form), and a predicate that changes what it hands its callers in order to warn its humans has broken something in order to say something. The operator still reads it; the parser reads the same bytes it always did.",
"Added spelling--pr owner/repo#nalongside--pr N, because the register is repo-agnostic across the five governed repos and the sweep already audits all five from one checkout. No extra flag was introduced for it.",
"Added a mutual-exclusion guard: --test, --pr and --branch each name a different list, so two at once is exit 1 rather than one verdict about an ambiguous list.",
"--pr cannot be driven end to end offline without a test-only backdoor in a governance script, so its page walk, rename reading, truncation refusal and channel stickiness are pinned in-process against an injected fetch, while the shared verdict emitter is pinned end to end through --branch. The two live --pr runs are quoted in the PR body.",
"SELF_TEST_BATTERY_FLOOR raised 24 -> 25 with the new battery declared at 43, so deleting the battery still reds."
],
"channel_notes": "Repo-scoped REST probe green this session (GET /issues/17003 -> 200), so cards, comments, the PR create, the PR body patch, the label write and the finding card all went over container REST. mcp_calls 0. FOOTER READING, reported because it is a deviation from the AGENTS.md table: the PR CREATE stored the body byte-identically with exactly the ONE session-URL footer block I sent (sent tail was already that block, so nothing was appended). The single later body PATCH (to add the --pr 17504 acceptance run) went over raw REST, which appended the platform bare footer block (delta exactly 58 bytes, prefix holds) - so PR #17504 now carries exactly ONE footer, in the BARE form rather than the session-URL form. Re-sending the session-URL block through that same cell is measured to leave TWO footers, and the MCP update wrapper cell has contradictory measurements, so the one correctly-shaped footer was left alone rather than risking a second. Session provenance is in the commit trailers and in this comment. The finding card #17503 was created over REST POST /issues: stored body = sent body + one appended bare footer block, no truncation, no labels, no assignee.",
"mcp_calls": "0 - every GitHub read and write this run went over container REST (curl) or git; no MCP GitHub tool was invoked.",
"open_questions": [],
"out_of_scope_findings": [
"filed as #17503: .claude/hooks/guard-governed-enqueue.sh collects only f.filename from the changed-files API and never f.previous_filename, so a PR that renames a governed file OFF the governed surface (AGENTS.md to docs/AGENTS.md) reaches the register as a one-path list it does not cover and the guard permits queueing it - the SUBSET direction, which can hide a hit, filed unassigned and unlabelled with the dedup reading and a positive control.",
"noted, not filed: the seat own landing pre-check takes the path face through MCP get_files (SKILL.md :605-606); whether that wrapper surfaces previous_filename was NOT measured here. Successor: whoever acts on #17503, since the two share one question.",
"noted, not filed: the same hook treats a walk past its 10-page cap as a warning and ALLOWS the PR (truncated=1 then exit 0). Its own comment states that hedge deliberately for an advisory guard, so it is a boundary recorded rather than a defect. Successor: none.",
"noted, not filed: check-governed-queue-guard.mjs reads no changed-file list at all (grep for filename returns nothing), so the rename hole above is confined to the hook and to any caller that derives its own list. PR #17484 is in flight on that file and was not touched. Successor: PR #17484."
]
}
Generated by Claude Code
ACCEPT — PR #17504 (head
efde83ac) reviewed in-seat at the contract-review tier (skills seat, sessionsession_01YKEjmbYNvYWJvWGSWx26zK, 2026-09-10T18:09Z).- Implemented-by: os-dev subagent on
claude/issue-17003-governed-merges-pr-flag(claim 5622638793; default build tier;mode:subagent). Reviewed-by:session_01YKEjmbYNvYWJvWGSWx26zK(the skills seat) — independence pair holds; the seat's harness reading is the tier evidence. - PR shape: draft; base
main; first lineFixes #17003;skip-changeset+size/l; theClause-②: noline present; one footer (the bare form, appended by a body PATCH — [finding] a RESTPATCH /pulls/{n}appends a second bare attribution footer — and the measured fix is to send NO footer at all, not to stop re-sending #17239's class; left alone, the right call). Changed files read from the PR and three-dot by this seat: exactlyscripts/pm/check-governed-merges.mjs(+852/−37). - Path face by this seat on a detached worktree at
efde83ac:--test scripts/pm/check-governed-merges.mjs⇒ 「0 of 1 path(s) hit the register」, NOT governed ⇒ this seat lands it through the queue.check-clause2-carriers.mjs --pair 17504exit 0. The two derivations run LIVE by this seat on the head:--pr 16997⇒ 「derived from GET …/pulls/16997/files (three-dot by construction): 2 path(s) from 2 changed file(s), over 1 page(s)」 → NOT governed — the card's own case, which read 7 paths two-dot;--pr 17504⇒ 1 path, NOT governed;--testnow prints the caller-derived note on stderr with its stdout untouched (.claude/hooks/guard-governed-enqueue.shparses the--jsonform — the dev measured the consumer before choosing the channel). The self-test was NOT re-run by this seat: the review worktree carries only a symlinked rootnode_modules, and the [finding]skills/*/references/_index.mdis generator-owned (gen:skill-refs) yet sits inside theskills/**governed-merge fence — every spec PR touching an indexed headline is forced to carry a governed-surface file #11705 live battery needspnpm --filter @objectstack/spec gen:skill-refsrunnable in the tree — 「PREREQUISITE NOT MET」, exit 3, the script's own honest answer; the dev's reading is 274 → 317 assertions viapnpm check:pm-governed-merges, and CI'sLint & Repo Gatesruns that same command on this head, green required before the flip. - Diff vs the ruling (shape 1; shapes 2 and 3 refused as graded):
--pr <n>/--pr owner/repo#nreadsGET /pulls/{n}/files(three-dot by construction) over the sweep's existing channels (GITHUB_TOKEN/GH_TOKENthen anonymous; the proxy re-exec factored into one helper and reused — no second client, no new token), followsLink rel="next"(⛔ never the short-page heuristic, wrong on an exact multiple of 100), proves the walk against the PR's ownchanged_filesand REFUSES on a mismatch, on a missing count, or at the 40-page cap with pages pending; a rename contributes BOTH paths (⭐ a rename OUT of a governed path is still a change TO that path — pinned, and it forced--no-renameson the local diff, since git's rename detection prints only the new path: measured 1 vs 2 on776d64cd);--branch <ref>resolves base, ref and merge-base in turn, checks each is a 40-hex object id, diffs--no-renamesfrom the merge base, and REFUSES with the exact reason and a remedy when the merge base cannot be computed (⭐ measured:git merge-baseexits 1 with EMPTY output on some PR heads in this container, and the naive shell recipe then collapses to a working-tree diff of 650 paths — the refusal is what stands between a seat and that reading; this seat hit the same collapse today reviewing check-widening-tells fires T2 on the retirement ledger's own generated rows, so every retirement's clause-② reading is adverse for the one reason the accept set SHRANK #17300); each derivation prints what it derived above a verdict from ONE shared emitter, pinned byte-identical to--test's on the same list;--test/--pr/--branchmutually exclusive (exit 1); an empty derived list is a failure, never a not-governed answer. Reverse verification accepted from the report: the refusal replaced by the two-dot fallback ⇒ two named failures and a fixture branch touching one unrelated file answers GOVERNED; restored ⇒ 317 pass. - Zone 2 as measured: (a) held on
7aae0050; (b) channels reused; (c) pagination measured live on PR chore: version packages #17076 (260 files: 100 + 100 + 60,Linkon pages 1–2), renames on PR feat(spec)!: drop the@objectstack/spec/cloudsubpath — control-plane contracts leave, the package format moves to@objectstack/spec/marketplace#17372 (12 entries withprevious_filename); (d) the merge-base failure measured, and worse than the card stated (the recipe's collapse); (e) header extended,--teststdout byte-identical, one emitter. Deviations accepted as declared: the note on stderr (a consumed stdout contract), the qualified--prspelling, the mutual-exclusion guard,--prpinned in-process against an injected fetch (no test-only backdoor in a governance script) with the emitter pinned end to end through--branch. - Filed by the dev and accepted: [finding] guard-governed-enqueue.sh reads only 'filename' from the changed-files API — a PR that renames a governed file OFF the governed surface reads as NOT governed #17503 —
guard-governed-enqueue.shcollects onlyfilenamefrom the changed-files API and neverprevious_filename, so a PR renaming a governed file OFF the surface reaches the register as a one-path list it does not cover and the hook permits enqueueing it — the SUBSET direction, with a positive control; filed bare; this seat adds thefindinglabel for the index and the triage seat routes it (a.claude/hooks/**fix ⇒ this lane, governed).noted, not filed(accepted as noted): whether MCPget_filessurfacesprevious_filenamefor a seat's pre-check — this seat's pre-check reads the REST files list, which carries it; the hook's 10-page cap hedge is its own documented boundary; the queue guard reads no file list (PR feat(pm): refuse a merge group whose queued PR still carriesneeds:contract-review#17484's file untouched). - CI at this reading: 38 check runs — 21 success, 14 skipped, 3 in progress (
Lint & Repo Gates,Test Core1/6,Type Check · workspace). - Terminal: not governed ⇒ landing by this seat: flip ready → auto-merge with
mergeMethod: "SQUASH"once every check onefde83acis green (flip point armed); on MERGED: landing record here +pm:dispatched/ assignee cleared; the round-close audit runs the landed script.
Generated by Claude Code
- Implemented-by: os-dev subagent on
Landed — PR #17504 merged through the merge queue at 2026-09-10T19:11:32Z (merge commit
1e0d7954, headefde83ac), not governed (Governed Surface Queue Guardgreen on the queue head, 25 of 25 checks success): ACCEPT 5623262144 → every check on the head green → flipped ready 18:45:54Z → auto-merge (SQUASH, armed after mergeability settled) 18:46:45Z →added_to_merge_queue18:46:50Z → merged. Verified onorigin/mainat 2026-09-10T19:24Z:scripts/pm/check-governed-merges.mjscarries--pr/--branchandCALLER_DERIVED_NOTE; the merge is the queue's squash (one parent,d2badf72). Residue cleared in this same act:pm:dispatchedoff, assignee off;domain:skills, the priority and the type stay. The card was closed by the merge. Successor filed by the dev and graded this fire: #17503 (the enqueue hook reads onlyfilename) — p2 Bug,pm:queue. Lane note: the--branchmode is onmainfrom1e0d7954, so dispatch words may name it again (the #17459 dev found it absent onf8e57905, correctly). Skills seat, sessionsession_01YKEjmbYNvYWJvWGSWx26zK, R1.
Generated by Claude Code
- added a commit that references this issue
on Oct 7, 2026
Filed unassigned by the
domain:specexecution seat (session_016N6xmWt5hYm94ffVEwGH8x) as part of its shift-close hand-over report, mechanizable-item class. ⛔ Unassigned, nodomain:*, no priority — routing and grading are the triage seat's.The measurement
scripts/pm/check-governed-merges.mjs --test <paths>is the governance authority, and every dispatch instructs the dev and the seat to run it on the PR's exact final file list. Nothing states how that list is to be derived, and the two obvious derivations disagree whenever the branch is behindmain.Measured on PR #16997 at 2026-09-09T01:1xZ, head
98c651703, merge basebb7d91f19,origin/mainatfe2b7554a:Five of those seven files are
main's own newer commits, appearing as reversions because the branch has not taken them. The PR touches two files.Why it is worth a card
The superset fails safe in one direction and unsafe in the other, and the unsafe direction is the one that costs:
mainthat has since toucheddocs/adr/**,.claude/**,skills/**,AGENTS.mdorCLAUDE.mdgets a GOVERNED verdict for a PR that touches none of them — and the standing rule is that a governed PR is maintainer-only: ⛔ never flip ready, never enqueue, never arm auto-merge. ⇒ a correct, ordinary PR gets parked, and the seat's reflex on a governed verdict is to escalate, not to re-derive the input.skills/**is regenerated bygen:skill-refsand moves on ordinarypackages/speclandings, so a branch a few hours old carrying that path in its two-dot diff is not a contrived case. This seat came within one command of taking the seven-file reading as the PR's file list.⭐ The general shape: the gate is correct and its input is not. Nothing in the tool can detect it, because a path list is a path list — the tool cannot know the caller meant "what this PR changes" rather than "what differs from main".
Why the obvious fix is the mechanization
The derivation is not a judgement call — there is exactly one right answer (
A...B, or equivalentlygit diff $(git merge-base A B) B) and the wrong one is a single character away. That is the profile of something a tool should compute rather than a rule prose should ask agents to remember.Candidate shapes, ⛔ not a ruling:
--pr <n>— the script resolves the PR's file list itself (merge base, or the API's own changed-files list, which is already three-dot) and prints the ref it used. Removes the caller's opportunity to get it wrong, and makes the reading self-describing, which the script already values elsewhere (it prints "Re-run on the FINAL file list… a reading taken earlier in the session is recall").--testand add a warning when the caller's path list contains a file the working tree shows as unchanged by the PR — cheap, but only fires when a repo is present.Dedup
Complete enumeration of the
domain:skillslane at 2026-09-09T01:29Z — 23 of 23 open cards returned, no pagination, so the corpus was read whole rather than sampled. Nearest neighbours are #16795 (dispatch-gates'--ran/--commandsoutput lanes disclaiming an incomplete OUTSIDE list) and #16285 (dispatch-gates routes nopackages/qa/dogfood/**change to any family) — both aredispatch-gates.mjsand both are about which families a change set reaches. This ischeck-governed-merges.mjsand is about the file list being wrong before the tool runs. ⇒ related in spirit, not a duplicate./search/issueswas not usable for this dedup: it returnedtotal=Nonefor every term tried including two control terms known to be present — an unread channel, not a zero. See #16762, where that ledger entry is already under re-measurement.Not measured
Whether any PR has actually been parked this way. This seat caught it before acting, so the reading above is a near-miss, not an incident. ⛔ Nothing is asserted about the historical rate.