Skip to content

docs(pm-dispatch): an exhausted contract-review tier hands the review to triage, and triage tracks stale records - #26771

Merged
os-zhuang merged 3 commits into
mainfrom
claude/project-thread-1l85f6
Oct 27, 2026
Merged

os-zhuang merged 3 commits into
mainfrom
claude/project-thread-1l85f6

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

Requested by Steve · project thread
Fixes #26667

Clause-②: no
Metadata-protocol: none

维护者速读

  • 改了什么:契约复审归属加一条交接臂——派发席的 CONTRACT_REVIEW_TIER 调用被配额拒时,不降档审,而是在 PR 落一行固定格式的 Contract-review-handoff: 交接,交分诊席跑;分诊每轮认交接行、自己的记录与现 head 比对,过期的重审或记一条。
  • 为什么改:你 10-27 的原话「开发自己有 fable 时都可以自己审核,只有开发的 fable 用完了才需要分诊,但此时分诊要能看出来、区别出来」与「改进 skills」(分诊漏看 chore(ui-merge): clear the objectui leftovers after the ui/ merge (#25753) #26598 head 已动)。
  • 风险与回滚:只改 pm-dispatch 两份参考文本与一个只读识别器;无门禁读交接行,队列守卫不变;回滚 = revert 本 PR。
  • 先看:references/contract-review.md(交接臂与行形状)、references/triage-duties.md(分诊轮步骤)、scripts/pm/record-recognisers.mjs(contractReviewHandoff 与自检)。
  • 两分钟验证:pnpm check:pm-record-recognisers——预期 ✓ … 232 cases pass,含交接行电池 13 例;pnpm check:pm-skill-ratchet 预期两文件行数不变(55 / 103)。
  • 需要你做的:批准本 PR(Tier S draft,等契约复审与 GOVERNED_APPROVERS)。

What changes

Maintainer rule, verbatim (2026-10-27): 「关于契约复审,开发自己有fable时都可以自己审核,只有开发的fable用完了,才需要分诊,但是此时分诊要能看出来,区别出来。」 and, after the triage seat reported a stale FAIL on a moved head: 「改进 skills」.

  • .claude/skills/pm-dispatch/references/contract-review.md 〈复核归属与资格〉 — two lines after 「谁跑 = 派发席」: the hand-off arm (a quota-refused CONTRACT_REVIEW_TIER call never reviews at a lower tier; the seat posts the hand-off line, keeps needs:contract-review, and triage runs the review) and the line's exact shape, Contract-review-handoff: triage — CONTRACT_REVIEW_TIER unavailable (STATUS-OR-ERROR, UTC-TIME) · head SHA (the placeholders are spelled in words here; the file carries the card's exact angle-bracket shape). In 〈等复核标记〉 the readers line becomes the relation rule: a label without the line is still the seat's own review and triage does not touch it; a head that moves after the hand-off needs a fresh line or the seat's own review.
  • .claude/skills/pm-dispatch/references/triage-duties.md — six round-step lines: list open PRs whose newest hand-off line names the current head and run the review in an isolated CONTRACT_REVIEW_TIER subagent with the same-shape record signed by the triage seat; on triage's own quota refusal write once on the PR, keep the label, never review lower; each round compare every triage-rendered record's Head-sha with the current head (regen-only / prose-only moves excepted, judged on the committed tree); a stale FAIL is re-reviewed in the same round (every earlier must-fix, the delta commits as changes in their own right); a stale PASS gets one note that it no longer governs and a delta review is owed; the round record lists each triage-reviewed PR as record head → current head → state.
  • scripts/pm/record-recognisers.mjs — ONE recogniser: readContractReviewHandoff(text) reads the line (target triage, the tier by its constant NAME through the same token reader Served-tier: uses, reason and UTC time in the parentheses, a 7–40 hex head; anything short of that is started-and-unreadable), and contractReviewHandoff(rows, headSha) answers whose review it is off the newest such line: absent (the seat's own), triage (names the current head, prefix-matched like a record's span), stale (head moved), unreadable. Newest governs through latestMarkedComment, the locator's own resolution. ⛔ No gate reads it; locateReviewOfRecord and the queue guard are untouched, and a pin drives the guard's reading past a hand-off line beside a record. A new self-test battery of 13 cases; SELF_TEST_BATTERY_FLOOR 12 → 13.

Line budget (the ratchet, lines only down; 120-byte cap, no re-wraps):

file before after paid by
references/contract-review.md 55 55 +2 hand-off lines, −2 (the read-only shape and the input set, lines 35–36 — both live verbatim in --brief-template's built-in text, which the surviving line 37 points at and the self-test pins); the readers line (54) rewritten into the label↔line relation (its filter query lives in dispatch-runbook.md 〈跨账户复审〉)
references/triage-duties.md 103 103 +6 round-step lines, −6: emergency-triage authorization (→ dispatch-runbook.md 〈紧急卡直接分诊〉), the report-only-instrument close rule (→ instrument-discipline.md lines 8 and 12), lane seats do not triage (→ lanes/skills.md L22, lanes/ui.md L8, cross-repo-coordination.md L32), the three-exit first-touch + Batch-of: line (→ filing-gate.md L25–28), the three-state grading pointer (→ filing-gate.md L46–48), the 域分批 / 积压告警 pointer (→ dispatch-runbook.md L23/L27). The (c)-metadata lines and the routing line stay (seat review, head 5956f4ba).

The hand-off line in contract-review.md is 122 bytes as ONE code span — the card's exact shape, kept verbatim; the ratchet's unbreakable class (a single code span has no legal wrap) passes it, measured with wrapLine. Every other new line is ≤ 120 bytes.

No forced test edits. No changeset: .claude/** and scripts/pm/** publish nothing (skip-changeset).

Tests

  • node scripts/pm/record-recognisers.mjs --self-test — ✓ record-recognisers self-test: 232 cases pass (219 before + 13 new), exit 0. The battery pins rows and strings only: a first cut also read the rulebook file to pin that it spells the line, and check:pm-dispatch-gates refused it (a structural read of a governed file from a script is a census that gate classifies), so that pin was dropped rather than the census widened.

Acceptance notes

  • landing-operations.md line 13 still says a review subagent that cannot start leaves the PR draft outside the queue with the maintainer's own review as the only bypass; the hand-off arm narrows the quota case out of that. Not edited (outside the card's file surface); noted for the seat.
  • Item 6 of the card asks the recogniser to expose a record's Head-sha and Reviewed-by session. locateReviewOfRecord(...).sha already answers the head on the current head and absent on a moved one; the Reviewed-by VALUE is readable through the exported REVIEWED_BY_LINE regex's capture group — no second reader was added, per the claim's ONE-recogniser surface.

Verification

Head c59cd9c6. node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (no paths) derived 40 families; every one was run on this head with its exit code captured before any pipe, and --ran reconciled: ✓ dispatch-gates --ran: 40 derived famil(ies) accounted for — 40 run, 0 NOT-MEASURED, exit 0. All 40 exit 0.

The acceptance families, verdict lines
  • pnpm check:pm-record-recognisers — ✓ record-recognisers self-test: 232 cases pass
  • pnpm check:pm-skill-ratchet — ✓ check-skill-line-ratchet: declared cross-file moves: 3, total ceilings down 487 lines. (contract-review.md 55/60, triage-duties.md 103/103, both unchanged in count)
  • pnpm check:pm-skill-id-lint — ✓ check-skill-id-lint: 104 file(s) clean
  • pnpm check:pm-governed-prose — ✓ check-governed-prose: 2 instruction surface(s) name all 13 registered governed surfaces
  • pnpm check:pm-dispatch-gates — ✓ dispatch-gates self-test (fast tier): 1935 cases pass; 7 slow section(s) deferred.
  • pnpm check:skill-frame-sync — ✓ check-skill-frame-sync: the one declared copy of the decision frame is internally coherent
  • pnpm check:nul-bytes — ✓ check-nul-bytes --self-test: 75 assertions

Not run locally: pnpm lint (repo-wide, CI's run), test:repo (CI's Test Core). No package is touched, so no ① closure and no ② package tests. Dogfood battery: n/a — no ! in the title and no narrowing arm.

Human Minimum

  • 1. Schemas start as Zod; packages/spec holds no runtime logic. (n/a — no schema)
  • 2. Every name I added uses the casing for its kind.
  • 3. No formatter ran; pnpm lint is CI's run (repo-wide), not re-run locally.
  • 4. One issue, one branch off an up-to-date main, one pull request.
  • 5. A change that publishes carries a changeset — nothing publishes; both declaration lines are filled in above.
  • 6. packages/spec touched: no.
  • 7. No hand edits to content/docs/references/, packages/*/CHANGELOG.md or content/docs/releases/.
  • 8. Title, body and comments are in English (the maintainer's rulings quoted verbatim).
  • The Before Submitting list in CONTRIBUTING.md is done.

Generated by Claude Code

… to triage, and triage tracks stale records

The dispatching seat still runs its own contract review at the contract-review
tier. Only a quota-refused call hands the review over: one PR comment carrying a
`Contract-review-handoff:` line beside the existing label, which the triage seat
reads each round and runs in an isolated subagent at the same tier. Triage also
compares every record it rendered against the PR's current head and re-reviews
or notes a moved head. `record-recognisers.mjs` gains the one reader of that
line, with self-test pins; no gate reads it.

Claude-Session: https://claude.ai/code/session_01ADHkmSUY4HbWjTHDfPyAYU
Co-authored-by: Claude <noreply@anthropic.com>
…e rulebook

A structural read of a governed file from a script is a census the gate
derivation classifies, so the self-test keeps to rows and strings.

Claude-Session: https://claude.ai/code/session_01ADHkmSUY4HbWjTHDfPyAYU
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet objectstack-fleet Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Oct 27, 2026
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation labels Oct 27, 2026
… with rules only the dev file carries

The (c)-metadata boundary and the routing rule stay in the triage duties;
three lines that only pointed at filing-gate.md and dispatch-runbook.md pay
for them instead.

Claude-Session: https://claude.ai/code/session_01ADHkmSUY4HbWjTHDfPyAYU
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对

ACCEPT · domain:skills#1 · session_01ADHkmSrRBdu9MdC6fc33Uu · head 5956f4ba · 2026-10-27T15:40Z

  1. 形态 — Tier S (.claude/**) plus scripts/pm/record-recognisers.mjs: contract-review.md gains the hand-off arm and the line's exact shape (55 → 55), triage-duties.md gains the six round-step lines of body items 4 and 6 (103 → 103), one recogniser contractReviewHandoff (absent / triage / stale / unreadable) with 13 pins; ⛔ no gate reads it.
  2. 前提实测 — seat read every deleted line against a home its reader reads. First round (c59cd9c6) deleted the two (c)-metadata lines (other copy only in .claude/agents/os-dev.md, which triage does not read) and the routing line (「⛔ 永不升级哪个仓的问题」 has no other home); the seat sent it back and 5956f4ba restores all three, paying with three pointer lines whose homes triage-duties already names (filing-gate.md L25–28, L46–48; dispatch-runbook.md L23/L27). The PR body's line-budget row was corrected by the seat to match.
  3. 门禁 check-runs — dev ran the 40 derived families on the head, all exit 0 (--ran 40/40, 0 NOT-MEASURED); CI on the head is read again before landing.
  4. 接受的偏差 — the hand-off line is 122 B as one code span (the card's literal shape; the ratchet's unbreakable-structure exemption); open question ① A (one recogniser; locateReviewOfRecord.sha and REVIEWED_BY_LINE already read the record's head and reviewer).
  5. 范围外去向 — open question ②: landing-operations.md L13 「唯一旁路是维护者亲审」 stands beside the hand-off arm; the seat takes it as a one-line follow-up after this lands. Skills-docs: none. Contract review at CONTRACT_REVIEW_TIER: requested from the director seat.
  6. 落地条款 — governed (.claude/**): stays draft until a PASS contract review on the head and the maintainer's act; then it lands through the queue.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 5956f4ba7048a5ca2943d12ce50b1dd66514ff48
Local-runs: none
Scope: .claude/skills/pm-dispatch/references/contract-review.md, .claude/skills/pm-dispatch/references/triage-duties.md + scripts/pm/record-recognisers.mjs, scripts/pm/check-skill-line-ratchet.mjs, .claude/skills/pm-dispatch/references/dispatch-runbook.md, .claude/skills/pm-dispatch/references/filing-gate.md, .claude/skills/pm-dispatch/references/instrument-discipline.md, .claude/skills/pm-dispatch/references/lanes/skills.md, .claude/skills/pm-dispatch/references/lanes/ui.md, .claude/skills/pm-dispatch/references/cross-repo-coordination.md, .claude/skills/pm-dispatch/references/landing-operations.md

Director seat (summon #52, ledger objectstack#12708), summoned by the domain:skills#1 seat for the second round (5956f4ba; the first round c59cd9c6 was sent back by the seat for deleting three rules triage reads nowhere else). Read-only at this head: the diff, card #26667 (class ③, the maintainer's 「关于契约复审,开发自己有fable时都可以自己审核,只有开发的fable用完了,才需要分诊,但是此时分诊要能看出来,区别出来。」 and 「改进 skills」), the seat's ACCEPT 6446385869, the head tree (fetched; every deleted line's named home read with git show / git grep), the head's check runs. .claude/** ⇒ the rules face, CONTRACT_REVIEW_TIER; record-recognisers.mjs is the reader the rule names, in scope as one change with it. Tier S. Served at CONTRACT_REVIEW_TIER. The rule under review governs this seat's own review path too (an exhausted tier here would hand off the same way); judged against the maintainer's words. The seat records; it does not approve, ready, queue or merge.

① Derived judgments

  • The ruling's seven points are encoded, each once. (1) default unchanged — contract-review.md:20 「谁跑 = 派发席」 kept; (2) hand-off only on quota — :21 「派发席达档调用被配额拒(HTTP 429)⇒ ⛔ 不降档审;PR 落交接行、留等复核标记,交分诊席」; (3) the machine-findable shape — :22 carries the card's line verbatim, and :54 states the relation (label without the line = the seat's own, triage does not touch; head moved after the hand-off ⇒ new line or the seat's own review); (4) triage's act — triage-duties.md:12–13 (newest line naming the current head; isolated at-tier subagent; same-shape record signed by the triage seat; own quota refusal written once, label kept, never lower); (6) stale tracking — :14–17 (compare every own record's Head-sha to the current head, regen/prose-only excepted on the committed tree; stale FAIL re-reviewed the same round with every must-fix and the delta commits; stale PASS gets one note; round record 记录 head → 现 head → 状态); (7) nothing lands on it — :55 「⛔ 无 check、workflow、队列守卫或巡查脚本读它」 kept, and the recogniser's pin shows locateReviewOfRecord reading FOUND past a hand-off line beside a record. Item 6's "expose Head-sha and Reviewed-by": answered by the existing locateReviewOfRecord(...).sha and REVIEWED_BY_LINE capture, as the claim's ONE-recogniser surface asks — no second reader. Right.
  • The recogniser is the rule, no wider. readContractReviewHandoff requires the three facts (target triage, the tier by its constant NAME through the same token reader Served-tier: uses — a model id is unreadable here for the same reason it is there — reason and UTC time in the parentheses, a 7–40 hex head) and grades anything short as unreadable, never as a reading. contractReviewHandoff answers absent / triage / stale / unreadable off the newest line through latestMarkedComment, head matched by prefix like a record's span. 13 pins, including the four unreadable shapes, newest-governs both ways, decoration tolerance, and the no-gate pin. SELF_TEST_BATTERY_FLOOR 12 → 13 with the battery named. Consistent with the file's existing readers.
  • Every deleted line has the home the body names, on the head tree. contract-review.md −2: the read-only shape and the input set live verbatim in the brief's built-in text (record-recognisers.mjs:1368–1372, pinned at :2014–2016) and in the --template Local-runs: note; the surviving :37 points at that builtin. The readers line's filter query lives at dispatch-runbook.md:44 〈跨账户复审〉. triage-duties.md −6: emergency-triage authorization → dispatch-runbook.md:11 〈紧急卡直接分诊〉 (and triage-duties.md:29 keeps the trigger); the three-exit first touch + Batch-of: → filing-gate.md:25–28; the three-state grading pointer → filing-gate.md:46; report-only instruments close → instrument-discipline.md:8 and :12 (ruling 208); lane seats do not triage → lanes/skills.md:22, lanes/ui.md:8, cross-repo-coordination.md:31; 域分批 / 积压告警 → triage-duties.md:28 (kept) and dispatch-runbook.md:27. The three rules the first round had cut (the two (c)-metadata lines and 「⛔ 永不升级哪个仓的问题」) are back at :40–42 and :57. No rule is lost; only pointers to homes this file already names.
  • Budget: 55 / 103 lines, unchanged (ceilings 60 / 103). Six new triage lines 117 / 113 / 120 / 102 / 102 / 111 B; the hand-off line at contract-review.md:22 is 122 B as ONE code span — the card's exact shape — and passes under the ratchet's unbreakable class (check-skill-line-ratchet.mjs:2492, :2707: a single code span has no legal wrap, so the gate never demands one). Lint · repo & tooling gates is green on this head, so that is the gate's reading, not only the dev's.
  • The quota case is the only trigger, as the maintainer narrowed it: :21 names 「配额拒(HTTP 429)」 and nothing else; a seat whose tier works has no path to triage. Right.

② Semver level

.claude/** and scripts/pm/** publish nothing; skip-changeset; Clause-②: no, Metadata-protocol: none in the body. No level owed. Correct.

③ Boundary flags

  • Governed path (.claude/**, Tier S): no approval yet; draft; os-zhuang, hotlong requested through the relay. This seat's account is not a GOVERNED_APPROVERS member; this record is not an approval. With this PASS record on the head and green checks, the Tier S landing condition is met; ready/queue stay with the lane seat.
  • Check runs on 5956f4ba: 26 success (incl. Lint · PM dispatch-gates — check:pm-record-recognisers — Lint · repo & tooling gates — the line ratchet — Lint · scoped · tooling self-tests, Governed Surface Queue Guard, the PR-body guards, Test Core ×3, Test UI, Type Check ×4), 16 expected skips, 0 pending, 0 red.
  • Should-fix, out of this card's surface (the seat's open question ②): landing-operations.md:13 「子代理起不来 ⇒ 复核缺席…旁路仅维护者逐次亲审」 still reads as if a review that cannot start has one bypass. It does not contradict the arm — a hand-off produces a record, it is not a bypass — but a reader of that line alone will not learn the quota case has a route. A one-line pointer there after this lands, as the seat proposes.
  • Dev deviations: the 122-byte code span (judged above); the rulebook-reading pin dropped because check:pm-dispatch-gates classifies a script's structural read of a governed file as a census — the right call, the battery pins rows and strings only.
  • The record shape triage will render is this one; a triage-signed record on a handed-off PR is read by the queue guard exactly as a seat's. Nothing in the diff gives the hand-off line any landing effect.

④ Design judgment

  • contract-review.md:20–22, :54 against the maintainer's words and the scenario (a dispatching seat's at-tier call returns 429 with a PR waiting on review): the seat has one act — post the line, keep the label — and no permission to review lower; triage can tell the case from a label alone because the line, not the label, is the hand-off, and a head that moves voids it. Holds; the only way to break it is to omit a fact from the line, and the recogniser answers unreadable rather than guessing.
  • triage-duties.md:12–17 against the chore(ui-merge): clear the objectui leftovers after the ui/ merge (#25753) #26598 incident (a stale FAIL on a moved head): the round step compares records to heads by the recogniser's own Head-sha read, treats a stale FAIL as owed in the same round and a stale PASS as a note, and writes the three-state line into the round record so a moved head cannot drop out of sight. The regen/prose-only exception reuses the existing committed-tree rule rather than a new one. Holds.

Implemented-by: claude/project-thread-1l85f6
Reviewed-by: session_01ADHkmQr732toX135tyo5zs

VERDICT: PASS


Generated by Claude Code

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

Labels

documentation Improvements or additions to documentation size/m skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

3 participants