Skip to content

feat(pm): teach the merge-queue guard the governed surface references tier - #18036

Merged
os-project-manager merged 1 commit into
mainfrom
claude/issue-18020-queue-guard-references-tier
Sep 13, 2026
Merged

os-project-manager merged 1 commit into
mainfrom
claude/issue-18020-queue-guard-references-tier

Conversation

@claude

@claude claude Bot commented Sep 13, 2026

Copy link
Copy Markdown
Contributor

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 (constexport 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 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-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

…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>
@claude

claude Bot commented Sep 13, 2026

Copy link
Copy Markdown
Contributor Author
  • Served-tier: 1084/1084 claude-fable-5-1 — harness model stamp counted over this seat's own transcript (non-sidechain assistant messages a model served; <synthetic> harness notices excluded) at 2026-09-13T14:47Z; get_session external_metadata.last_served_model read claude-fable-5-1 at 2026-09-13T14:41Z.

Contract review

Head: f088df57 (PR #18036, card #18020) — read at 2026-09-13T14:49Z by the skills seat at the contract-review tier. NOT GOVERNED, measured: the diff touches scripts/pm/check-governed-queue-guard.mjs (+779 −28) and one export keyword in scripts/pm/check-clause2-carriers.mjs ⇒ in-seat review, then ready + auto-merge by this seat.

① derived judgments — the merge-group leg gains the references tier the charter declared in PR #18018; the rules layer keeps today's predicate:

  1. Tier (governedTierFor): ALL governed paths of the entry under .claude/skills/pm-dispatch/references/ (prefix with its trailing slash, never a glob) ⇒ references; empty or mixed ⇒ rules. The asymmetric default is the right one and the sibling-directory case is pinned in the refusing direction.
  2. Satisfaction (entrySatisfied): the approval limb first and unchanged; the tier limb only ADDS a pass, and only on a merge_group entry whose record key exists. Monotone, measured with throwing spies (an approved references PR reads no thread and loads no recogniser); the pull_request leg's rendering is byte-identical by construction.
  3. The record (recordVerdict): five-valued — unreadable / absent / unsigned / below-tier / stands — with unreadable kept on exit 4 and the rest on exit 3. Recognition is imported: heading marker, head-sha match, newest-of and the sha floor from check-half-states.mjs; readServedTier, servedTierStands and the Reviewed-by: key line from check-clause2-carriers.mjs. No second parser; a failed or half load is a refusal.
  4. The lazy import is load-bearing and pinned: the module-scope cycle through check-half-states.mjs is real (measured both ways, exit 13), so the recognisers load from a function body after evaluation, legal only because the dispatch dropped its top-level await (main().then); the battery reads this file's own source for that precondition and ablation D reds exactly that case.
  5. Boundary, accepted as claimed: the leg measures EXISTENCE and PROVENANCE of the review of record (heading, current head, Reviewed-by:, standing Served-tier:), not its verdict word; the PASS reading stays precondition ① of the landing check and is the seat's. The claim (5653641998) specified exactly this shape.
  6. Deviation, declared, accepted: constexport const REVIEWED_BY_LINE in check-clause2-carriers.mjs, value expression unchanged (md5 equal before and after) — one keyword outside the claimed surface, forced by rule 3 above.
  7. Coupling noted: PR feat(pm): two report-only patrol rows — the decision card's four-facet block, and finding outliving its grade #18033 (in flight) edits check-half-states.mjs but keeps the four names this leg imports; whichever lands second runs the merge-group checks on the merged tree and the battery's live loadRecordRecognisers() exercises the real edge.

Seat measurements on the head tree (git archive f088df57): check-governed-queue-guard.mjs --self-test → 229 cases pass, exit 0 (base 183; one battery added, floor 19 → 20). --pair 18036 → exit 0 at 2026-09-13T14:47Z (cards #18020 and #17950 both agree). Checks on f088df57 at 2026-09-13T14:32Z: 35 runs, 0 red, 0 running. The dev's four ablations (prefix slash, any-record-state, top-level await in each dispatch) each red the named cases and restored by blob hash.

② semver: scripts/pm/** publishes nothing; skip-changeset is right.

③ boundary flags: open_questions empty. Two 「noted, not filed」 items stand as notes (the stale reason in makeLabelReader's docblock; the objectui guard copy is this seat's own card, filed at landing). Landing consequence: a references/-only governed PR with this seat's review of record on its head enqueues without a maintainer click — the enforcement half of #17950.

Implemented-by: claude/issue-18020-queue-guard-references-tier
Reviewed-by: session_01DAcomhvR9kKizeYgg89Vo8

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

@os-project-manager
os-project-manager marked this pull request as ready for review September 13, 2026 14:59
@os-project-manager
os-project-manager added this pull request to the merge queue Sep 13, 2026
Merged via the queue into main with commit 60b9955 Sep 13, 2026
51 checks passed
@os-project-manager
os-project-manager deleted the claude/issue-18020-queue-guard-references-tier branch September 13, 2026 15:24
This was referenced Sep 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/l skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

pm-dispatch: the merge-queue guard still demands an approval for a references/-only governed PR — #17950's tier is declared, not enforced

2 participants