Skip to content

[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

@zhuangjianguo

Filed unassigned by the domain:spec execution seat (session_016N6xmWt5hYm94ffVEwGH8x) as part of its shift-close hand-over report, mechanizable-item class. ⛔ Unassigned, no domain:*, 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 behind main.

Measured on PR #16997 at 2026-09-09T01:1xZ, head 98c651703, merge base bb7d91f19, origin/main at fe2b7554a:

git diff --stat origin/main 98c651703        # TWO-dot
  7 files changed, 67 insertions(+), 881 deletions(-)
    .changeset/value-domain-note-settings-door-repointed.md
    docs/qa/platform-checklist/areas/integration-system.json
    docs/qa/platform-checklist/areas/records-forms.json
    packages/spec/scripts/generate-string-family-width.pin.test.ts
    packages/spec/scripts/…-16394-character-width-dialect-parity.test.ts
    packages/spec/liveness/field.json
    scripts/check-changeset-no-major.mjs

git diff --stat origin/main...98c651703      # THREE-dot (the real PR)
  2 files changed, 19 insertions(+), 7 deletions(-)

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:

  • It can never hide a real hit — a superset only adds paths. So it will not let a governed PR through.
  • ⚠️ But it can manufacture one. Any seat whose branch is behind a main that has since touched docs/adr/**, .claude/**, skills/**, AGENTS.md or CLAUDE.md gets 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 by gen:skill-refs and moves on ordinary packages/spec landings, 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 equivalently git 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:

  1. --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").
  2. Keep --test and 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.
  3. Prose only: state the three-dot form in the dispatch template. ⚠️ Weakest — this seat's own dispatch template already carries a dozen ⛔ rules, and adding a thirteenth is the option with the worst record.

Dedup

Complete enumeration of the domain:skills lane 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 / --commands output lanes disclaiming an incomplete OUTSIDE list) and #16285 (dispatch-gates routes no packages/qa/dogfood/** change to any family) — both are dispatch-gates.mjs and both are about which families a change set reaches. This is check-governed-merges.mjs and is about the file list being wrong before the tool runs. ⇒ related in spirit, not a duplicate.

⚠️/search/issues was not usable for this dedup: it returned total=None for 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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions