docs(agents): os-dev.md :286–:287 re-keyed to the register's two tiers; H43/H48 stand down on Tier S - #19802
Conversation
…ing tiers The two lines still spelled the superseded split (fact layer = the pm-dispatch references directory only, everything else rules layer). The register and AGENTS.md Prime Directive #14 now tier the landing: a PR whose governed paths all lie under `.claude/**` is Tier S and lands on the owning seat's contract review record; every other governed surface is Tier H and waits for the maintainer's word. The maintainer brief draft is owed on every governed PR, on both tiers — the second line stops exempting a "fact layer". Net 0 lines; both lines within the 120-byte cap. Claude-Session: https://claude.ai/code/session_01Wnstp2kTth7sGXfr8fXypc Co-authored-by: Claude <noreply@anthropic.com>
…e register, never a second list Both rows prescribed the maintainer's word on every governed PR: H43 a review request to GOVERNED_APPROVERS (or the assign fallback), H48 the needs-user-decision inbox label plus the final brief comment behind a seat's ACCEPT verdict. The landing is tiered now — a PR whose governed paths all lie under `.claude/**` lands on the owning seat's contract review record with no approval and no maintainer click — so on such a PR each remedy asked for exactly the click the tiering removed, and H43's "the queue guard still refuses an unapproved governed enqueue" rationale was false there. The register the sweep already loads lazily now also carries `landingTierOf` and `GOVERNED_TIER_S`; one helper, `governedLandsOnRecord`, asks it on the slice the matcher already returned (ALL-quantified: one Tier H row keeps a mixed diff in both rows; an empty, tier-less or unloaded answer stands nothing down). H43's probe policy reads the same slice as its row, so a Tier S PR buys no review page and takes no slot under the oldest-first cap. Row sentences and both summary clauses name Tier H as the population. Self-test: 15 cases driven by the real register's own slices; 4897 -> 4912, no new battery, no new row. Claude-Session: https://claude.ai/code/session_01Wnstp2kTth7sGXfr8fXypc Co-authored-by: Claude <noreply@anthropic.com>
…-rekey Pre-PR merge; the incoming commit touches neither os-dev.md nor check-half-states.mjs. Claude-Session: https://claude.ai/code/session_01Wnstp2kTth7sGXfr8fXypc Co-authored-by: Claude <noreply@anthropic.com>
Contract reviewServed-tier: ① Derived judgmentsRendered in-seat by the skills seat 1 session at the served tier the maintainer named (ruling record 5771798588; 「契约复审继续用 fable 不变」 5788482806 of this date). The contract is the card #19146 as graded by the lane (class (b), Path: none) with os-steve's Release 5773572115 / 5773579070 naming the remainder:
② Semver levelNone — no release. ③ Boundary flags
Implemented-by: VERDICT: PASS |
Part of #19146
Clause-②: no
维护者速读(草稿)
改了什么:两处。①
.claude/agents/os-dev.md:286–:287 两行(净 0 行,402/402)从已废止的「事实层 =references/目录、余为规则层」改写成登记表现行的两层:受管路径全在.claude/**者 Tier S,达档复核 PASS 在案即由席位入队落地;余皆 Tier H 等人批;## 维护者速读(草稿)两层同欠(第二行不再豁免「事实层」)。②scripts/pm/check-half-states.mjs的 H43(受管 PR 未向授权账户请审)与 H48(ACCEPT 后缺needs-user-decision标签或速读评论)在 Tier S PR 上停手:懒加载的登记表多带出landingTierOf与GOVERNED_TIER_S,一个governedLandsOnRecord助手在两行已匹配的切片上问登记表,S 则不判;混合 diff 有一条 Tier H 命中仍判;登记表没加载则一律不停手。自测新增 15 例(4897 → 4912),用真实登记表自己的切片驱动;⛔ 不新增巡查行、不新增电池。为什么改:这两行是每个 dev 子代理都读的自我定义,写的是 2026-09-18 分层裁决之前的边界;H43/H48 在 Tier S PR 上开的处方(请授权账户审、挂维护者收件箱标签)正是裁决取消的那一下点击,且 H43 的理由句「队列守卫仍拒收未批准的受管入队」对 Tier S 为假(PR #19351 零批准、凭复核记录经队列落地)。
风险与代价(含回滚):文字面零行为变化。H43/H48 在 Tier S PR 上不再报告——这是裁决的意图,不是丢失:Tier S 的落地由队列守卫按
## Contract review记录把关。Tier H 与混合 diff 的行为字节不变;登记表缺席时仍照旧报 NOT MEASURED。消融证明:把 tier 读取删掉,5/4912 例转红,还原后 blob == HEAD。回滚 = revert 两个提交(各一类,可单独回退)。席位意见:(席位填写)
你要做的:本 PR 为 Tier S(
.claude/**),由归属席位在## Contract reviewPASS 记录 + 全绿后经队列落地,⛔ 不需要您点击。卡上余项一条(SKILL.md:618,见下方 On the card),由 skills 席位自处置。Summary
The two
os-dev.mdlines still spelled the pre-tiering split (fact layer = thereferences/directory, everything else rules layer). They are re-keyed to the register's rule asAGENTS.mdPrime Directive #14 states it: a PR whose governed paths ALL lie under.claude/**is Tier S and lands on the owning seat's## Contract reviewrecord; every other governed surface is Tier H and waits for the maintainer's word. The maintainer-brief draft is owed on both tiers (SKILL.md :623 has no tier split; the seat fills 席位意见 on both), so the second line simply stops exempting a "fact layer".The card also names
check-half-states.mjsH48 and H43. Measured on the base: both rows still exist and are tier-blind (governedTierFor/landingTierOf: 0 hits in the file; H43 readsGOVERNED_APPROVERSonly). On a Tier S PR each remedy asks for the click the tiering removed, so both stand down there. The tier is the register's own answer on the slice the matcher already returned — no second list of surfaces lives in the patrol.Per-site before → after
.claude/agents/os-dev.md(402 → 402 lines, net 0; both lines within the 120-byte cap).claude/skills/pm-dispatch/references/者为事实层,席位复审即记录;余为规则层。」.claude/**者 Tier S,达档复核 PASS 在案即由席位入队落地;余皆 Tier H 等人批。」## 维护者速读(草稿)节,中文、业务角度,席位意见留空;事实层不欠。」## 维护者速读(草稿)节,中文、业务角度,席位意见留空;两层同欠。」The rule they now mirror:
AGENTS.md:272–:280 (Tier H =docs/adr/**,docs/NORTH-STAR.md,skills/**,AGENTS.md,CLAUDE.md, an authorized APPROVED review; Tier S = all of.claude/**, a## Contract reviewrecord for the current head withServed-tier: CONTRACT_REVIEW_TIERand a PASS verdict, the owning seat lands it through the queue) and SKILL.md :623 (「草稿归 dev:受管面 PR 正文带## 维护者速读(草稿)」, no tier split).grep -n 事实层 .claude/agents/os-dev.mdon the head: 0 hits.scripts/pm/check-half-states.mjs(H43 / H48)loadGovernedRegistergovernedPathsIn+GOVERNED_APPROVERSlandingTierOf+GOVERNED_TIER_S(tierOf,recordTier); a register missing any of the four is "did not export what this row reads", as beforegovernedLandsOnRecord(governed, register)trueonly when the register is available ANDlandingTierOf(slice) === GOVERNED_TIER_S;falsefor a Tier H or mixed slice, an empty or tier-less slice, or an unloaded registerh43NeedsReviewProbe(pr, governedCount, approvers)h43GovernedReviewRequestGapnullon a Tier S slice; the sentence reads "open and GOVERNED on Tier H" and "refuses an unapproved Tier H enqueue"h48SpeaksAbouth48GovernedVerdictWithoutBriefisnullthere and the sweep buys no PR comment thread for ith43NeedsReviewProbe(pr, governedByPr.get(n)?.length ?? 0, …)h43NeedsReviewProbe(pr, governedByPr.get(n) ?? [], …)Self-test: 15 new
t()cases (no new battery, no floor moved): the Tier S and mixed slices come fromGOVERNED_REGISTER.matcher(...)on the real register, so the tier answer is the register's; the hand-builtGOV43/GOV48fixtures carry notierand read as H (fail closed), which the comments now say. Register pin extended:tierOf(matcher(['.claude/agents/os-dev.md'])) === recordTier. Case counts: 4897 on the base → 4912.Why stand down rather than re-aim: the card offered both. Re-aiming H43 at "a Tier S PR without a review of record" would be a new patrol row in disguise (新增门禁默认否), and the queue guard already refuses a Tier S enqueue without the record — nothing ships through that gap. Standing down is the minimal, mechanical repair the card's item 3 spells for H48 and it is the same change class for H43, so the two ride together.
Measurement the change rests on (reads taken 2026-09-23T05:24Z–05:36Z against
origin/main=2cf9db7c4; each item names its own clock).claude/agents/os-dev.md:286–:287 read byte-identical to the card's quotation (premise valid).check-half-states.mjs@2cf9db7c4: H48 row at :10888–:11100, H43 row at :9607–:9850;governedTierFor/landingTierOf/recordTier: 0 hits in the 34,841-line file;GOVERNED_APPROVERSis H43's only firing control. PR pm: retire the needs:contract-review label from the tree (ruling B step ③) #19737 retired H31/H35/H51/H53/H61 and left both rows in place.Unlock-action:value — a label-transition exit on a named card (#19255) #19351, all.claude/**+scripts/pm/): 0 reviews, no review request, no**ACCEPT**on the thread, noneeds-user-decision, no## 维护者速读comment — one## Contract reviewPASS record, landed through the queue. H43's shape fires on exactly that PR; it fired on fix(pm): repair two carriers still spelling the superseded references-tier boundary #19379 too (card comment 5750573385).check-governed-merges.mjs --test→GOVERNED — Tier S(席内达档复核落地), exit 3 (= EXIT_TEST_GOVERNED);scripts/pm/check-half-states.mjsis not on the register.Reverse verification (ablation), run 2026-09-23T05:37Z at head
bd5bbd2bb(the file is byte-identical atc9617adde)node scripts/ablation-replace.mjs --file scripts/pm/check-half-states.mjs --anchor ' return register.tierOf(list) === register.recordTier;' --replacement ' return false; // ABLATION: tier reading removed' -- node scripts/pm/check-half-states.mjs --self-testd2d9ba38ac63→52c8e2e35f0d✗ check-half-states self-test: 5 of 4912 case(s) failed(the H43 Tier S clean case, the H43 Tier S probe case, the register-answer join case, the H48 Tier S population case, the H48 Tier S no-finding case); the mixed-slice controls stayed greend2d9ba38ac63== blob at HEAD;git diff HEADempty; re-read on the absolute path: anchor 1, marker 0dist/is involved (the patrol runs from source), so no build legGates on the final head
c9617adde(run 2026-09-23T06:02Z–06:19Z; exit codes captured before any pipe; verdict lines from the gate logs)Derived with
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commandson this head (47 commands, list identical to the derivation atbd5bbd2bb);--ranreconciliation:✓ dispatch-gates --ran: 47 derived famil(ies) accounted for — 46 run, 1 NOT-MEASURED (1 DERIVED from a recorded exit 3)..node scripts/check-ci-filter-parity.mjscoreorcrosspkg, everycrosspkgentry still covers one, and thetestjnode scripts/check-closing-keyword-parity.mjsnode scripts/check-closing-keyword-parity.mjs --self-testnode scripts/check-comment-mask-corpus.mjsnode scripts/check-declaration-mirrors.mjsnode scripts/check-declaration-mirrors.mjs --self-testnode scripts/check-scripts-symbol-anchors.mjsnode scripts/check-scripts-symbol-anchors.mjs --self-testnode scripts/check-self-test-wired.mjs--self-testhas that self-test run by CI.node scripts/check-self-test-wired.mjs --self-testnode scripts/check-self-test-workflow-commands.mjs--self-test(0 of them package-local gate(s) CI names by path, present because this population is the one checnode scripts/check-self-test-workflow-commands.mjs --self-testnode scripts/check-whole-set-label-write.mjsrun()refuse rather than pass if it ever stops finding them.node scripts/check-whole-set-label-write.mjs --self-testnode scripts/pm/bare-root-worklist.mjs --self-testnode scripts/pm/board-snapshot.mjs --self-testnode scripts/pm/check-governed-queue-guard.mjs --self-testnode scripts/pm/check-harness-current.mjs --self-testnode scripts/pm/sweep-closed-cards.mjs --self-testpnpm --filter @objectstack/lint run check:doc-formula-expressionspnpm check:agent-model-declaredpnpm check:agent-test-spelling--token(s) · 1810 launcher-rooted run(s) · 13 separator(s) JUDGED · 6 vitest-bapnpm check:bash32-floorpnpm check:cli-command-idspnpm check:closing-target-claimpnpm check:commit-card-trailerspnpm check:cross-package-test-inputspnpm check:doc-authoringpnpm check:driver-memory-censuspnpm check:entry-guardpnpm check:gitlink-declaredpnpm check:issue-citationspnpm check:nul-bytespnpm check:parse-guardpnpm check:partof-closing-keywordpnpm check:pm-governed-mergespnpm check:pm-half-statespnpm check:pm-post-stampedpnpm check:pm-skill-id-lintpnpm check:pm-skill-ratchetpnpm check:pnpm-filter-targets--filteroccurrence(s) across 41 file(s) resolve against 81 workspace package(s); 55 not judged (2 foreign, 30pnpm check:ratchet-remedy-authoritypnpm check:refd-timer-probepnpm check:single-claim-pathspnpm check:skill-frame-syncpnpm check:watch-hint-literalpnpm check:pm-dispatch-gatesRow 20 is exit 3 = PREREQUISITE NOT MET (the gate's own NOT-MEASURED code, see Acceptance notes); every other row exit 0. The same 47 ran at
bd5bbd2bbwith the same readings before the merge oforigin/main.Line budget
.claude/agents/os-dev.md: 402 / ceiling 402 before and after (headroom 0, net 0 lines);check:pm-skill-ratchetexit 0 on the head. Max content bytes per line 120 before and after.skills/**(the published catalog) is not touched, so no whole-file / whole-package token readings are owed..claude/**andscripts/pm/**publish nothing from any package'sfiles[](fast lane), soskip-changesetapplies and no changeset is written.On the card
landing-operations.md:27–:28 and the queue guard's SUCCESS line: landed by PR fix(pm): repair two carriers still spelling the superseded references-tier boundary #19379 (merged1f53b0b685); the guard's :236 hit is a verbatim ruling quotation and stays.os-dev.md:286–:287, H48, H43: this PR.origin/mainatfae870352and not in this PR's claimed file surface:.claude/skills/pm-dispatch/SKILL.md:618(the size clause ending 「⛔ 无事实层例外」) still spells the retired word (the skills seat's addendum 5737973707 joined it to this card), and 「规则层」 survives as a synonym for Tier H atSKILL.md:617,references/core-rules.md:122,references/landing-operations.md:26andreferences/lanes/skills.md:17(vocabulary only; each rule stays true). That is why the first line isPart ofrather than a closing keyword: skills: re-key the three 事实层 = references/ spellings the Tier S ruling leaves false (os-dev.md :286–:287, landing-operations.md :27–:28, H48) #19146 remains open after this PR merges, with its own addendum item still owed. The dispatch asked for a closing first line on the premise that the twoos-dev.mdlines were the whole remainder; the tree-wide grep says otherwise, and os-dev.md's rule (a PR whose merge should not close the card usesPart of) wins.Acceptance notes (observations, not filed)
check-half-states.mjsH48's sentence still says the handoff exists because "a Tier H surface lands only on the maintainer's word" — accurate for Tier H under PD feat: Comprehensive CRM example demonstrating all ObjectStack protocol features #14 (the maintainer's hand or an authorized approval); the pre-existing "by hand" wording was narrowed to that in the same edit.pnpm --filter @objectstack/lint run check:doc-formula-expressionsanswers exit 3 PREREQUISITE NOT MET in this worktree (the gate needs@objectstack/formula/@objectstack/lintbuilt; this diff touches no package, so no build closure is owed). Recorded as NOT MEASURED, not as a failure; its population (docs formula expressions) is disjoint from both changed paths.## Contract reviewrecord for headc9617addeand lands it through the queue after every check is green.Generated by Claude Code