Commit 60b9955
feat(pm): teach the merge-queue guard the governed surface references tier (#18036)
Fixes #18020
Part of #17950. The maintainer tiered the governed surface on 2026-09-13
(「我点头」) and the charter text landed with PR #18018, but
`check-governed-queue-guard.mjs` still demanded an authorized approval
for every governed path — the tier was declared, not enforced. The
merge-group leg now learns it.
## What changed
A governed pull request whose governed paths **all** lie under
`.claude/skills/pm-dispatch/references/` is satisfied by the skills
seat's **review of record on the current head** — a `## Contract review`
comment on the PR thread carrying a `Reviewed-by:` line and a
`Served-tier:` reading that stands — in place of the approval. One
rules-layer path in the same diff and today's predicate is the only way
through.
**Monotone by construction.** The tier leg is consulted *only* for an
entry no authorized approval satisfied, so it can lift a refusal and can
never create one. Nothing that passes the queue today newly refuses.
**Recognition is imported, never re-implemented** — the heading marker,
head-sha span test and newest-of resolution (`check-half-states.mjs`),
the `Reviewed-by:` / `Served-tier:` readers
(`check-clause2-carriers.mjs`). Zero new parsers;
`check-clause2-carriers.mjs` carries one **export-only** change (`const`
→ `export const REVIEWED_BY_LINE`, value expression md5-identical).
## Acceptance greps (both directions)
| reading | before | after |
| --- | ---: | ---: |
| `REFERENCES_TIER_PREFIX` in the guard | 0 | 9 |
| `readServedTier` / `servedTierStands` imported | 0 / 0 | 4 / 4 |
| `GOVERNED_APPROVERS` (lit control — still hits) | 34 | 39 |
| self-test cases | 183 | 229 |
| the tier VALUE spelled in the guard | 0 | 0 |
| predicate bodies md5-identical to their pre-change selves | — | **16
of 16 untouched** |
## Two measurements the route turned on
1. **The cycle is real, and indirect.** A module-scope import of the
recognisers deadlocks (node exits 13, "Detected unsettled top-level
await") — measured directly *and* through `check-clause2-carriers.mjs`,
which imports H31's file. So the import is **lazy**, which is legal only
because this file's dispatch no longer carries a top-level `await`. That
precondition is pinned against this file's own source; ablation D
(restoring `await main()`) reds exactly that one case, and ablation C
(in the self-test dispatch) reproduces the exit-13 deadlock.
2. **The thread read widens no scope.** `GET
/repos/{o}/{r}/issues/{n}/comments` answers
`X-Accepted-GitHub-Permissions: issues=read; pull_requests=read`, and
GitHub documents the semicolon as separating *alternative* permission
sets. The workflow's existing `pull-requests: read` is sufficient; ⛔ no
workflow change.
## Reverse verification (mutate → prove on disk → run → restore)
| ablation | cases red of 229 |
| --- | ---: |
| A — drop the prefix's trailing slash | 2 |
| B — `entrySatisfied` accepts any record state | 12 |
| C — top-level `await` in the self-test dispatch | exit 13, deadlock |
| D — top-level `await` in the live dispatch | 1 (the precondition pin)
|
Each leg proved its mutation on disk before running, and its restore by
blob hash against `HEAD`.
## Acceptance notes
- `AGENTS.md` PD #14's sentence 「the queue guard refuses an unpinned
governed diff」 stays true: a references-only PR is pinned by its record.
No prose changed; `check:pm-governed-prose` is not in the derived set
for this diff and is green when run anyway.
- Noted, not filed: `makeLabelReader`'s docblock says the issues-labels
route "needs `issues: read`, which this workflow does not grant". The
live API answers `issues=read; pull_requests=read` for it too, so the
stated reason is stale — the choice to read the pull object is still
right (it reuses a call the leg already makes). Successor: whoever next
edits that reader, in this same file.
Authored by the `domain:skills` seat, session
`session_01DAcomhvR9kKizeYgg89Vo8`.
## Gates (all at `f088df57`)
`node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` derived **37** families; all 37 run, every
one with its exit code captured before any pipe, all **0**. `--ran`
reconciles: *37 derived, 37 run, 0 NOT-MEASURED (a DERIVED zero), 0
UNRUN*. Includes `check:pm-dispatch-gates` (1682 self-test cases),
`check:pm-clause2-carriers`, `check:nul-bytes`,
`check:refd-timer-probe`, `check:closing-target-claim`,
`check:whole-set-label-write`, and the guard's own `--self-test` (229
cases).
`check:pm-governed-prose` is **not** in the derived set for this diff —
no prose surface changed — and was run anyway: green, *2 instruction
surfaces name all 5 registered governed surfaces and claim no others*.
`eslint . --no-inline-config` ran the **whole** population rather than a
narrowing: **6722 files, 0 errors, 0 warnings**. No
`parserOptions.project` and no typed rules are configured, so no
untouched file's verdict can move with this diff.
---
_Generated by [Claude Code](https://claude.ai/code)_
Co-authored-by: Claude <noreply@anthropic.com>1 parent fb2f01d commit 60b9955
2 files changed
Lines changed: 780 additions & 29 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
2441 | 2441 | | |
2442 | 2442 | | |
2443 | 2443 | | |
2444 | | - | |
| 2444 | + | |
2445 | 2445 | | |
2446 | 2446 | | |
2447 | 2447 | | |
| |||
0 commit comments