Skip to content

pm: verify a contract-review sub-agent was served at CONTRACT_REVIEW_TIER before its record can claim the tier (repo script, post-stamped refusal, rule line) #33605

Open

Description

@objectstack-fleet

domain:skills#1 · os-project-manager · session_01FEkQ5pjSPT7ui2EUcunaVb

Filing gate: ① (Leehom's ruling: card 「决定 #33531 合并前要先补齐哪些缺口」, option A 「先补三项再合并」, tapped 2026-11-15T02:21:59Z: 「安全路径规则、增删改名仍归人合、达档校验脚本先落地,之后再合并 #33531;其余四项立卡后跟。」 This card is the third item, the tier-evidence script). Priority p1 by that ruling: it lands before PR #33531 (card #33501) is handed to him. reach: every seat that adopts a sub-agent contract review and writes Served-tier: CONTRACT_REVIEW_TIER.

What is wrong. A contract-review record names its tier as the constant CONTRACT_REVIEW_TIER (scripts/pm/dispatch-gates.mjs, 'claude-fable-5-2'), but nothing checks that the review was served by that model. On 2026-11-14 from 18:19Z one seat launched its review sub-agents without model: fable; 19 records claimed the tier falsely and 7 of those PRs merged before the seat found it by grepping its own task output (corrections posted on every PR; the at-tier re-review found a must-fix two off-tier rounds had passed, PR #33465). The queue guard cannot verify the tier either: the record is a self-written name, and AGENTS.md keeps model identifiers out of comments. The only check today is a script in one seat's scratch folder.

What lands.

  1. A repo script (for example scripts/pm/verify-review-tier.mjs) that reads a review sub-agent's output transcript and exits 0 only when every served model it records equals CONTRACT_REVIEW_TIER (imported, not respelled) and the transcript carries no model-fallback notice. An unreadable, empty or unparseable transcript refuses (non-zero, with the reason). A --self-test pins: all-tier passes; one off-tier turn refuses; a fallback notice refuses; no model field refuses.
  2. scripts/pm/post-stamped.mjs refuses to write a ## Contract review body whose Served-tier: names CONTRACT_REVIEW_TIER unless the call carries the evidence (for example --tier-evidence PATH) and the script in 1 passes on it. The evidence stays local: no model identifier and no path enters the comment. Records that name another tier, and every non-record comment, are unaffected. Pin both directions in post-stamped's self-test.
  3. One rule line in .claude/skills/pm-dispatch/references/contract-review.md: every contract-review Agent call passes the tier's model explicitly, and the record is posted only through the evidence check.

Boundary. This catches honest failure (a missing model parameter, a mid-run fallback). It does not stop a seat writing a false record; that is the post-merge audit's job (#33603).

Open for the developer to measure and state: the transcript format(s) seats actually produce (the Agent tool's JSONL output, a card thread's session), and whether a review run in a separate thread can produce evidence the seat can pass. If one format is all that is reachable now, say so and refuse the rest.

Surface: scripts/pm/**, .claude/skills/pm-dispatch/references/contract-review.md; skip-changeset. Clause-②: no. Related: #33501 (PR #33531 waits on this), #33541, #33601, #33603, #33604.

Dedupe (open AND closed): is:issue Served-tier evidence verify and is:issue contract review tier fallback script → none open; #10299 (closed, the fuse's "self-check the current model" step not self-executable) is the older form of this gap. New.

Activity

  1. added
    toolingFix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface
    priority:p1High: required for production / M2
    findingRecorded observation — held, not dispatchable until the findings triage round grades it
    domain:skillsDomain lane — seat card indexed by label:pm:seat
    on Nov 15, 2026
  2. objectstack-fleet commented on Nov 15, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:skills#1 · os-project-manager · session_01FEkQ5pjSPT7ui2EUcunaVb

    Claim: PM loop round 5
    Session: session_01FEkQ5pjSPT7ui2EUcunaVb
    Account: os-project-manager
    Branch: claude/project-thread-f3mcct
    Thread session: session_01FEkQ5paMCiUCGnMqBWAio9
    Worktree: objectstack-issue-33605
    Domain: domain:skills
    Seat: domain:skills#1
    File surface: scripts/pm/** and .claude/skills/pm-dispatch/references/contract-review.md (stop on breach; explain in the report)
    Container & model: M, mode:thread, model: opus (default; dispatch-gates --tier on the real diff decides), effort default
    Clause-②: no
    Metadata-protocol: none
    Responsibility: n/a — missing control, found by the 2026-11-15 off-tier incident
    Thread-read: none
    Premise: holds (re-read on main 2026-11-15T02:23Z: post-stamped.mjs checks keyed lines of a record but no tier evidence; no repo script reads a reviewer's transcript)
    Serial constraints: none (PR #33531 touches other files under scripts/pm/** and .claude/skills/pm-dispatch/**; it waits for this one to land)
    Order: /mnt/project-files/pm-orders/skills/order-33605.md (project file). Ruling-ref: Leehom card 「决定 #33531 合并前要先补齐哪些缺口」, option A 「先补三项再合并」 (2026-11-15T02:21:59Z). Claimed 2026-11-15T02:23Z.

  3. objectstack-fleet commented on Nov 15, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:skills#1 · os-project-manager · session_01FEkQ5paMCiUCGnMqBWAio9
    os-dev-report
    {
    "issue": 33605,
    "status": "done",
    "branch": "claude/project-thread-f3mcct",
    "pr": "#33616",
    "session": "session_01FEkQ5paMCiUCGnMqBWAio9",
    "premise_still_valid": true,
    "summary": "New scripts/pm/verify-review-tier.mjs reads a review sub-agent JSONL output transcript and passes only when every assistant turn (served model and requestedModel) and every model attachment equals CONTRACT_REVIEW_TIER (imported from dispatch-gates.mjs) and no harness system entry carries the fallback notice; empty, unparseable, unreadable or no-turn transcripts refuse with a reason, and no verdict line carries a model id or path. post-stamped.mjs gains --tier-evidence=PATH (comments only) and judgeBody refuses a ## Contract review comment whose Served-tier reads CONTRACT_REVIEW_TIER unless that evidence passes; TIER_DEFAULT records, non-record comments and card bodies are untouched. contract-review.md gains a two-line rule under the Served-tier bullet, paid by deleting the intro pointer line and its blank line (ceiling 63 unchanged).",
    "tests": "head 97bbc81: node scripts/pm/verify-review-tier.mjs --self-test: 18 cases pass across 5 batteries. node scripts/pm/post-stamped.mjs --self-test: 783 cases pass across 29 batteries (new battery "the record tier", floor 12; SELF_TEST_BATTERY_FLOOR 18 to 29). node scripts/pm/label-write.mjs --self-test: 203 pass. Real transcripts: an at-tier probe sub-agent PASSES (2 turns); a probe launched with another model REFUSES at its model attachment line; this session main transcript REFUSES. post-stamped --comment --dry-run of a signed at-tier record: exit 2 with no evidence, exit 2 with the off-tier transcript, exit 0 with the at-tier one. No ablation: scripts and docs only (os-dev.md).",
    "gates": "Local, head 97bbc81: all 40 families from dispatch-gates --commands exited 0 (record: project file pm-orders/skills/33605-gates-ran.txt), check:doc-formula-expressions after the lint closure build under the verify lock (VERDICT command-exit 0, 103s). dispatch-gates --ran: 40 derived, 40 run, 0 NOT-MEASURED, 0 UNRUN. dispatch-gates --tier: no path-derived mandate, 371 changed lines. check-governed-merges --branch: Tier S (.claude/** only); owes a ## Contract review PASS at CONTRACT_REVIEW_TIER on the current head. CI check-runs on PR 33616: in_progress at report time.",
    "dogfood_battery": "n/a, no ! in the PR title and no (narrowing) arm",
    "mcp_calls": "2, mcp__github__issue_read (get, get_comments) on the card; no write tools",
    "api_writes": "2, relay pr_create (PR 33616) and this os-dev-report comment via post-stamped; plus one git push",
    "open_questions": [
    {
    "question": "verify-review-tier.mjs --self-test is not wired into package.json (outside the claim surface; new gates default no). Its refusal paths run in CI only through check:pm-post-stamped. Wire a check:pm-verify-review-tier script?",
    "options": [
    "A leave it unwired; post-stamped battery covers the verifier through its file reader",
    "B add one package.json check line in a follow-up"
    ],
    "recommendation": "A, because the seat-facing path is post-stamped and its battery already pins both directions; B adds a gate nobody named."
    }
    ],
    "security_findings": "none",
    "out_of_scope_findings": [
    "carrier: none · noted, not filed: the fallback notice lives in the parent session completion notification, not in the sub-agent transcript (two clean probes read); a mid-run switch is caught by the per-turn model check instead",
    "carrier: none · noted, not filed: a review run in a separate card thread writes its transcript in that thread container, which the seat cannot read, so the seat cannot post an at-tier record for it through this path"
    ],
    "deviations": [
    "contract-review.md line paid by deleting the intro pointer line (landing-operations.md already routes here at lines 13 and 77); the seat may prefer another payment",
    "standing lines ask for a trailing signed HTML comment in the PR body; os-dev.md bans dev-written HTML comments, so the signature is a prose line under Fixes"
    ]
    }


    Generated by Claude Code

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

Metadata

Metadata

Labels

domain:skillsDomain lane — seat card indexed by label:pm:seatfindingRecorded observation — held, not dispatchable until the findings triage round grades itpm:dispatchedDispatched to a dev agent by /pm-dispatchpriority:p1High: required for production / M2toolingFix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions