feat(pm): teach the merge-queue guard the governed surface references tier - #18036
Conversation
…es tier The maintainer tiered the governed surface on 2026-09-13 and the charter text landed with PR #18018, but the queue guard still demanded an authorized approval for every governed path — so the tier was declared and not enforced. The merge-group leg now classifies each governed pull request's governed paths and, when they ALL lie under `.claude/skills/pm-dispatch/references/`, accepts the skills seat's review of record on the CURRENT head in place of the approval: a `## Contract review` comment on the PR thread carrying a `Reviewed-by:` line and a `Served-tier:` reading that stands. Every other governed path is the rules layer and keeps today's predicate byte-for-byte — 16 predicate bodies are md5-identical to their pre-change selves. Recognition is IMPORTED, never re-implemented: the heading marker, the head-sha span test, the newest-of resolution, the `Reviewed-by:` key line and the `Served-tier:` reader all come from the two files that own them. Reaching them needed the module-eval cycle solved rather than worked around, so the import is lazy and this file's dispatch no longer carries a top-level await; the battery pins that precondition against this file's own source. The leg is MONOTONE by construction — consulted only for an entry no authorized approval satisfied — so it can lift a refusal and can never create one. Claude-Session: https://claude.ai/code/session_01DAcomhvR9kKizeYgg89Vo8 Co-authored-by: Claude <noreply@anthropic.com>
Contract reviewHead: ① derived judgments — the merge-group leg gains the references tier the charter declared in PR #18018; the rules layer keeps today's predicate:
Seat measurements on the head tree ( ② semver: ③ boundary flags: Implemented-by: Verdict: PASS — the tier the charter declared is now enforced on the merge-group leg, monotone, with imported recognition and a fail-closed load. Generated by Claude Code |
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.mjsstill 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 reviewcomment on the PR thread carrying aReviewed-by:line and aServed-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), theReviewed-by:/Served-tier:readers (check-clause2-carriers.mjs). Zero new parsers;check-clause2-carriers.mjscarries one export-only change (const→export const REVIEWED_BY_LINE, value expression md5-identical).Acceptance greps (both directions)
REFERENCES_TIER_PREFIXin the guardreadServedTier/servedTierStandsimportedGOVERNED_APPROVERS(lit control — still hits)Two measurements the route turned on
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-levelawait. That precondition is pinned against this file's own source; ablation D (restoringawait main()) reds exactly that one case, and ablation C (in the self-test dispatch) reproduces the exit-13 deadlock.GET /repos/{o}/{r}/issues/{n}/commentsanswersX-Accepted-GitHub-Permissions: issues=read; pull_requests=read, and GitHub documents the semicolon as separating alternative permission sets. The workflow's existingpull-requests: readis sufficient; ⛔ no workflow change.Reverse verification (mutate → prove on disk → run → restore)
entrySatisfiedaccepts any record stateawaitin the self-test dispatchawaitin the live dispatchEach leg proved its mutation on disk before running, and its restore by blob hash against
HEAD.Acceptance notes
AGENTS.mdPD feat: Comprehensive CRM example demonstrating all ObjectStack protocol features #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-proseis not in the derived set for this diff and is green when run anyway.makeLabelReader's docblock says the issues-labels route "needsissues: read, which this workflow does not grant". The live API answersissues=read; pull_requests=readfor 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:skillsseat, sessionsession_01DAcomhvR9kKizeYgg89Vo8.Gates (all at
f088df57)node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderived 37 families; all 37 run, every one with its exit code captured before any pipe, all 0.--ranreconciles: 37 derived, 37 run, 0 NOT-MEASURED (a DERIVED zero), 0 UNRUN. Includescheck: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-proseis 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-configran the whole population rather than a narrowing: 6722 files, 0 errors, 0 warnings. NoparserOptions.projectand no typed rules are configured, so no untouched file's verdict can move with this diff.Generated by Claude Code