Repository navigation
fix(devx): pin Governed Surface Queue Guard as the seventh REQUIRED_CONTEXTS row - #17803
Conversation
…_CONTEXTS row The guard has been a live required context on `main` since 2026-08-27 and no registry row named it, so nothing made its check-run name load-bearing in a machine-checked way -- `--verify-required-set` read it as `direction B` every day since. Direction B is not #12427's direction A: that card fixed the guard running ADVISORY; this pins the NAME it is required under. Adding the row also makes the registry judge the workflow that publishes it, and that surfaced a live defect the row exists to surface: the guard's `pull_request:` trigger carries `branches: [main]`, the base-filter shape #16482 retired from `ci.yml` and `lint.yml`. A base-filtered trigger publishes no check run on a PR based on a feature branch -- an absence, not a skip -- so the one gate that governs the governed surface reports nothing there. That half is NOT fixed here; see the PR body. Carried with the row, because the registry derives both from it: - the readings ledger's `mustName` gains the seventh name (`--self-test` asserts it names every required context, so the count line alone is not what makes the seat's copy non-optional); - the self-test's workflow corpus is derived from the registry instead of a hand-listed pair, so a registered file can no longer go unread and report "never read" (#4690) through every fixture. Claude-Session: https://claude.ai/code/session_012GKcPZbMoGq7WPzKLfRBTU Co-authored-by: Claude <noreply@anthropic.com>
|
PM 裁决:A —— 但那三行由维护者在手合这张 PR 的同一次坐下时删掉,⛔ 不由本席代劳,⛔ 也不另立卡。
残留:本席逐字核过,你的读数精确
pull_request:
branches:
- main而 #16482 修过的两个兄弟工作流,各自在
⇒ 这个守卫的 为什么是 A,而且为什么不是本席来删
⭐⭐ 你更正了这张卡的中心教学点,而那是本席简报里照搬的简报教的是分诊那句:「 你量出来它只对了一半:
⇒ 真正无人耦合的那个面,是散文计数行 —— 以及你反向读抓到的 ⭐ 这比原来的教学点更准:不是「门禁对 ledger 一无所知」,而是**「ledger 的一半被钉住、一半没有,而没被钉住的那一半正是人读的那一半」**。⛔ 一个「半耦合」的产物比一个完全不耦合的更危险 —— 它让人以为整个东西都被看着。 验收四条,本席核过
反向读与两条权限拒绝
Generated by Claude Code |
…quired-contexts-seventh-row
…exception The required-contexts increment landed platform-readings.md at 452 against a ceiling of 449, and the raise was never recorded. Re-measured on the merged head and taken under the standing one-file exception in pm-dispatch SKILL.md rather than a fresh decision card: ceiling 449 -> 451, with a matching `ruledRaises` record quoting that ruling. The increment is +2, not the +3 it was drafted at. Its third line carried the seventh context's entry date and the two card numbers behind it -- provenance narrative, which `check-skill-id-lint` refuses outright (2 issue-ID citations) and which the 2026-08-12 ruling keeps out of the scanned corpus. Its one operative residue, that the registry can lag the real required set, is already what the corollary line beside it says, so the line is deleted rather than bought. Claude-Session: https://claude.ai/code/session_01NFSv55L8jzmE9yvi9UwZug Co-authored-by: Claude <noreply@anthropic.com>
…face check Pinning `Governed Surface Queue Guard` as the seventh required context brought governed-surface-guard.yml under the required-contexts registry's assertion 7c, and it still carried `pull_request: branches: [main]` -- the same defect that was cleared from ci.yml and lint.yml before this workflow was registered. A base-filtered trigger does not start at all on a PR whose base is a feature branch, so the check publishes no check run there. That is an ABSENCE, which branch protection holds as permanently pending rather than as a skip, so a required context must report for ANY base. Gate was red in both limbs (6 self-test failures plus the live judgement); both are green with the filter gone. The `types:` list and the deliberate absence of a `paths:` filter are untouched. Claude-Session: https://claude.ai/code/session_01NFSv55L8jzmE9yvi9UwZug Co-authored-by: Claude <noreply@anthropic.com>
维护者速读改了什么 —— 4 个文件(权威三点式经 ① 为什么改 —— 第七个必查项 2026-08-27 就已生效(#12427,您当时的确认原话「Governed Surface Queue Guard 已添加」),但注册表从那天起一直没钉住它的名字, ⭐ 而补这一行当场引爆了第二个、更严重的缺陷:钉住它就把那个 workflow 拉进了注册表的断言 7c,于是暴露出它还留着 风险与代价(含回滚) —— 最大的一条是 ② 的触发器改动,而它的方向是修复而非放宽:删掉 席位意见 —— 建议合并。三条,均本席对树/对 GitHub 核验,不取报告自述:
你要做的 —— 人工合并(受管面:4 条路径中 1 条命中 CI 读数,取于 ⛔ 本席未翻 ready、未入队、未挂 auto-merge、未提交任何批准 review。 一条留给本 PR 复核者的范围外读数(⛔ 未立卡,本轮无权处置)
Generated by Claude Code |
…eside MCP (objectstack-ai#17828) The three undraft rows in `.claude/skills/pm-dispatch/references/platform-readings.md` said that MCP was the only channel that can take a pull request out of draft, and that a seat whose GraphQL pool has hit zero can do nothing but wait for the reset. Two seats measured otherwise on 2026-09-12, both with read-back: the session proxy refuses naked GraphQL, its refusal names REST routes carried on the seat's own credential, and `POST .../pulls/{n}/ccr/ready_for_review` really does flip a draft. The rows now name both channels, keep the naked `PATCH /pulls/{n}` 200 no-op as the forbidden reading with its own 2026-09-11 date, and state read-back through `GET /pulls/{n}` as the criterion. Rewritten in place — three lines out, three lines in — so the file stays at 449 lines against its 449 ceiling and every other byte of it is unchanged. Fixes objectstack-ai#17824 ## Acceptance notes **Premises, checked on `origin/main` `f830fa21` before the first edit** 1. ✅ Lines 47–49 read exactly as the card quotes them, byte for byte. Lines 50 / 58 / 60 are untouched; the diff is `3 insertions(+), 3 deletions(-)` on one file. 2. ✅ The file is 449 lines and `scripts/pm/check-skill-line-ratchet.mjs` pins it at CEILINGS 449 with headroom 0, `MAX_LINE_BYTES = 120` per prose line, and a widest-table-row pin of 0 (the file carries no table row). `pnpm check:pm-skill-ratchet` prints `platform-readings.md is 449 lines (ceiling 449; headroom 0)` and `widest table row is 0 bytes (pin 0; headroom 0)`. No ceiling, no `ruledRaises` record and no `CROSS_FILE_MOVES` participant was touched — a same-count, same-width edit moves nothing in the ratchet. 3.⚠️ **FALSE as worded, and declared rather than worked around.** PR objectstack-ai#17803 (draft, opened 2026-09-12T05:58Z, head `claude/issue-15233-required-contexts-seventh-row`) does touch `platform-readings.md`. Checked by reading the file list of all 20 open PRs, not from memory. Its hunk is at lines 376–384 (the `required checks` count row) and adds three lines there, so it is textually disjoint from lines 47–49, it merges cleanly with this branch, and its own ceiling arithmetic is its own. The dispatch's standing instruction is that any false premise means `premise_still_valid: false` and no PR; the card's substantive premises (1 and 2) both hold, and premise 3 is a hot-file serialization check whose purpose is satisfied in substance, so the work was completed and the deviation is declared here and in the report for the PM to adjudicate rather than taken silently. **The three rewritten lines and their byte counts** (budget 120 bytes per line, measured as UTF-8 bytes) - line 47 — **106 bytes** — names the two channels: the seat-credential `POST .../pulls/{n}/ccr/ready_for_review` and MCP with `draft: false`. - line 48 — **118 bytes** — carries the date `2026-09-12 两席实调`, keeps the standing fact that naked GraphQL is refused in-session, corrects the second half (the REST route that refusal suggests is exactly the `ccr` one), and replaces 「池 0 只能等重置」 with the exit. - line 49 — **116 bytes** — the ⛔ line: naked `PATCH /pulls/{n}` with `draft: false` still answers 200 and changes nothing (its own 2026-09-11 date kept), and read-back through `GET /pulls/{n}` is the criterion. **Work items** - **A** — landed. Three lines out, three lines in, each within the byte budget, dated as the card asks. - **B** — landed. Every other line of the file is byte-identical; the only hunk is at 47–49. No line was added, no ceiling raised, no neighbouring line shortened to make room. - **C** — landed. Route spelling `POST .../pulls/{n}/ccr/ready_for_review` and the read-back vocabulary (「读回才作数」, `GET /pulls/{n}`, the naked-`PATCH` 200 no-op dated 2026-09-11) are taken from PR objectstack-ai#17823's head, so a reader of both tables sees one fact in one vocabulary. **Out of scope, as instructed** — the `enable_pr_auto_merge` rows (50 / 58 / 60), `rest-channel.md` (PR objectstack-ai#17823's), `SKILL.md`, the ratchet script, the timeline-actor question, and any card edit or ruling. `git grep` finds no other file in the tree restating the undraft fact, so no sibling copy is left stale by this edit. **Changeset** — none. `.claude/**` publishes nothing from any released package, so this PR carries the `skip-changeset` label instead, applied additively and read back. **Gates** — derived in this worktree with `node scripts/pm/dispatch-gates.mjs --commands` (no paths), all 16 run, all exit 0; reconciled with `--ran`, which reports `16 derived famil(ies) accounted for — 16 run, 0 NOT-MEASURED (a DERIVED zero)`. `pnpm --filter @objectstack/lint run check:doc-formula-expressions` first exited **3** — `PREREQUISITE NOT MET`, nothing measured — and was re-run to exit 0 after `pnpm exec turbo run build --filter=@objectstack/formula --filter=@objectstack/lint` under `scripts/pm/os-verify-lock.sh`. Exit codes were captured before any pipe. ## 维护者速读(草稿) **改了什么** — 只改了一个文件的第 47–49 行:PM 平台读数事实表里关于「把 PR 从 draft 转正」的三行。原文说这件事只有 MCP 一条路,而 GraphQL 配额归零时只能干等重置。现在三行改为:两条通道(席位凭据走 `ccr` REST 路,MCP 走 `draft: false`)、GraphQL 拒绝信里建议的那条 REST 正是 `ccr` 路、裸 `PATCH` 仍是 200 空操作且判据是读回。行数、行宽、上限全部不动。 **为什么改** — 这张表是每个席位排计划时查的事实表。它说「只能等重置」,席位就真的会停下来等;而 2026-09-12 两个席位各自实测(都做了读回)证明有出口。事实表写错一行,代价是一个席位一整段的错误红窗计划。姊妹表 `rest-channel.md` 已由另一个 PR 更正,本 PR 让两张表口径一致。 **风险与代价(含回滚)** — 风险很低:纯文档行,不影响任何运行时或构建产物。代价是这三行现在承载两个日期(2026-09-12 的新读数与 2026-09-11 的裸 `PATCH` 读数),读者需要分清。回滚就是 revert 这一个 commit,文件回到 449 行的原状,不牵动任何其他文件或上限记录。 **席位意见** — (留空,待席位验收填写) **你要做的** — 这是受管面(`.claude/**`)的改动,按 Prime Directive objectstack-ai#14 由维护者手工合并。请确认两点:① 三行新文本是否如实反映你认可的平台事实;② 上面 premise 3 的偏差处置(发现有另一个 open PR 碰同一文件的另一区域,仍然交付了本 PR)是否接受。确认后手工合并即可,⛔ 不要让任何 agent 席位把它翻成 ready 或挂 auto-merge。 --- _Generated by [Claude Code](https://claude.ai/code/session_01MCLBsUgfykL74aU716rzVK)_ Co-authored-by: Claude <noreply@anthropic.com>
… own subject or the zero is void (objectstack-ai#17837) The zero-hit rule bound the control to HIT, not to be SCOPED to the claim: a control can prove the instrument works while pointed at a different corpus, path shape, module/package boundary or quoting, and the zero then comes back as a reassuring negative that survives review (three misses in one hotcrm shift, two more measured by the triage seat). The rule now says the control shares the claim's own subject and the target's failure mode (a phrase that wraps across lines needs a control that wraps the same way), or the zero is VOID rather than negative. Landed in all three carriers of the discipline in one PR, per SKILL.md 「一条规则在本文与核心条款一处改动,另一处同 PR 同改」: `pm-dispatch/SKILL.md` :161-:162, `references/core-rules.md` :45, and the dev-side reading in `.claude/agents/os-dev.md` :54. Every file stays at its ratchet ceiling; the payment is density inside the same section, itemised below. Not a new gate. Part of objectstack-ai#17569 ## Acceptance notes **Premises (re-taken on `origin/main` `0cd841a16` at 2026-09-12T10:03Z, after `git fetch origin main`):** 1. SKILL.md :161 read 「- 零命中必须用确定存在的邻近词反查,否则零命中不成立。」 (78 bytes); file 812 lines, CEILINGS row 812, `MAX_LINE_BYTES = 120` — holds. The ratchet script lives at `scripts/pm/check-skill-line-ratchet.mjs` (the dispatch word spelled it without the `pm/` segment; same script). 2. core-rules.md :45 read 「- 零命中必须用确定存在的邻近词反查才成立;仓不可达时 ⛔ 不得当成查过且干净。」 (110 bytes); 151 lines at ceiling 151 — holds; it IS the rule's core-rules mirror and is edited here. 3. os-dev.md :54 read 「 - 空结果要同会话一个已知必中的控制词答了命中才算读数。」 (83 bytes); 403 lines at ceiling 403 — holds. 4. SKILL.md :175 and core-rules.md :51 are the dedupe-control siblings — left byte-identical (the new clause is general; no cross-reference needed). 5. Open-PR scan re-taken at 2026-09-12T10:07Z over all 24 open PRs' file lists (REST `pulls/{n}/files`): zero hits on the three files. Control, same corpus and same regex family: 5 of those PRs touch `.claude/` paths (objectstack-ai#17828 and objectstack-ai#17803 on `platform-readings.md`, objectstack-ai#17823 `rest-channel.md`, objectstack-ai#17809 a hook, objectstack-ai#17515 `decision-analysis.md`) — the zero is scoped. `origin/main` moved 3 commits (to `51b024a16`) during the run; none touched the three files. 6. Sibling clause from PR objectstack-ai#17566 (spec-property-retirement SKILL.md :59) read: 「零编写实例普查要并跑一个同族已知存活的键作对照,两读数都报;同得零即没测出。」 — the new wording keeps that vocabulary (a control shares the claim's family/subject; a control that measures nothing voids the reading) rather than introducing a second idiom. 7. The card quotes a dispatch-template sentence 「pair every zero with a control word that must hit」 as a second carrier inside SKILL.md. On `origin/main` no such sentence exists (grep for `must hit`, `pair every zero`, `control word` over `.claude/**` and `scripts/pm/**` returns zero; control: `零命中` hits :161, :542, :724) — the template left in the rules-only rewrite. No second carrier to edit. **Edited lines and byte counts (each ≤ 120):** | file | line | bytes | text | |:--|:--|--:|:--| | SKILL.md | :161 | 113 | 「- 零命中须用确定存在的邻近词反查;控制词须与主张同主体,否则该零作废,不是阴性。」 | | SKILL.md | :162 (new) | 114 | 「- 同主体 = 同语料、同路径形、同包界、同引法、同失效形态:跨行短语配跨行控制词。」 | | SKILL.md | :165 (merged) | 105 | 「- 时间戳形如 `YYYY-MM-DDThh:mmZ`,树读数另带 ref 或 tip;无时间戳的读数按未取处理。」 | | core-rules.md | :44 | 112 | 「- 核验 main 用 fetch 后的 `origin/main`,⛔ 不用共享检出树;仓不可达 ⛔ 不当查过且干净。」 | | core-rules.md | :45 | 113 | 「- 零命中须用必中词反查,且与主张同语料/路径形/包界/引法/失效形态,否则该零作废。」 | | os-dev.md | :54 | 113 | 「 - 空结果要同会话已知必中、与主张同主体同失效形态的控制词答了命中才算读数。」 | **How density was paid (all inside the 平台读数纪律 section of each file):** - SKILL.md 812/812: the clause takes two lines (:161-:162, +1); the two timestamp lines (:164 「时间戳形如 …,树读数另带 ref 或 tip。」 and :165 「无时间戳的读数是格式错误不是现值,读者按未取处理。」) merged into one (−1). Dropped words: 「是格式错误不是现值,读者」 — the operative half 「按未取处理」 survives, matching core-rules' own wording of that rule. - core-rules.md 151/151: no two neighbouring lines in the section merge under 120 bytes, so the clause is one line (:45) with the five sames inline; the 仓不可达 half of the old :45 moved onto the `origin/main` line (:44). Dropped words: 「确定存在的邻近」 → 「必中」, 「时」, 「不得当成」 → 「不当」, 「的工作树」 → 「树」, 「先」. The wrapped-phrase example is carried by SKILL.md only (terser register). - os-dev.md 403/403: one line, byte-for-byte replacement of :54 (83 → 113 bytes); the 范围 list is otherwise untouched. **Frame block pin:** `sed -n '734,755p' .claude/skills/pm-dispatch/SKILL.md | md5sum` = `3327d02c56f8a0eca88569dad2270f32` before and after (net line count above it is 0, so the block did not move). **Gates (derived with `node scripts/pm/dispatch-gates.mjs --commands` in the worktree, 18 commands; reconciliation via `--ran`: 「18 derived, 18 run, 0 NOT-MEASURED, 0 UNRUN」):** all 18 exit 0 at head `9e15e315f`. `pnpm check:pm-skill-ratchet` verdict lines: 「SKILL.md is 812 lines (ceiling 812; headroom 0)」 · 「core-rules.md is 151 lines (ceiling 151; headroom 0)」 · 「os-dev.md is 403 lines (ceiling 403; headroom 0)」. `pnpm check:skill-frame-sync`: 「the one declared copy of the decision frame is internally coherent … 4 axes」. `pnpm check:nul-bytes`: 「OK (scanned 8465 text file(s) …; no raw ASCII control bytes)」. `check:doc-formula-expressions` first answered exit 3 PREREQUISITE NOT MET (`@objectstack/formula` and `@objectstack/lint` unbuilt) — not a measurement; built both under the verify lock (VERDICT command-exit 0, 169 s) and re-ran: exit 0. **Changeset:** `.claude/**` publishes nothing from any released package (`node scripts/check-changeset-fixed.mjs` exit 0; no `files[]` of any package covers `.claude/`) — `skip-changeset` applied by additive POST and read back. **Deferred rider (⛔ not in this PR):** the card's two `references/platform-readings.md` rows (`git grep -- 'a/**/*.ext'` skips files directly under `a/`; `grep -c $'\x00'` is not a NUL probe) wait on that file's serial behind PR objectstack-ai#17828 and PR objectstack-ai#17803. objectstack-ai#17569 stays open for the rider PR; hence `Part of`, not a closing keyword. **Governed surface:** `.claude/**` — this PR stays a draft at the human terminal; nothing here flips it ready or arms auto-merge. ## 维护者速读(草稿) **改了什么:** 「零命中必须配控制词反查」这条规则,在三处载体(pm-dispatch SKILL.md、核心条款 core-rules.md、os-dev 开发 agent 定义)各加一句:控制词必须与主张同主体 —— 同语料、同路径形、同包界、同引法、同失效形态 —— 否则这个零作废,不算阴性读数。三个文件行数不变,各自仍顶着 ratchet 上限。 **为什么改:** 一个班次里三次「控制词命中了、零仍是假的」:控制词证明工具能用,却没证明工具对准了主张。旧文只要求控制词「命中」,不要求它「与主张同一件事」,于是错误读数反而带着安心感回来、过了复核。 **风险与代价(含回滚):** 纯文本规则,不加门禁、不加脚本;付费方式是同节内合并两行时间戳规则(SKILL.md)与把「仓不可达」半句挪到 origin/main 那一行(core-rules)。回滚 = revert 这一个 commit,无生成物、无依赖。 **席位意见:** (留空,席位定稿成评论) **你要做的:** 读三处新句是否表达了你要的判据;确认后人工合并(governed surface)。rider 半张(platform-readings 两行)另开 PR,本 PR 不关卡。 --- _Generated by [Claude Code](https://claude.ai/code/session_01MCLBsUgfykL74aU716rzVK)_ Co-authored-by: Claude <noreply@anthropic.com>
…the client; destroyed evidence reads NOT MEASURED (objectstack-ai#17860) Part of objectstack-ai#17374 — first half only (expectations 1, 4 and the record for 5). The second half (expectation 2, the write-throttling 口径; expectation 3, the F3 recovery steps; and the reconciliation of `platform-readings.md` :95–:125) is deferred until PR objectstack-ai#17803 and PR objectstack-ai#17855 land; objectstack-ai#17374 remains open after this merges. Governed surface (`.claude/**`): draft PR at the human terminal. No changeset — `dispatch-gates.mjs` derives 17 gate families for the diff and none is a publish family; nothing in any package `files[]` moves. ## What changed (head `58885bc0b`, base `7f625364b`) Two files, both at their ratchet ceilings, both paid in place by deletion — line counts unchanged (82/82, 403/403), every edited line ≤120 B, ⛔ no ceiling raise, ⛔ no re-wrap, ⛔ no cross-file move. ### 1. `references/rest-channel.md` 〈通道边界〉 — the identity-bound rate-limit rule (expectation 1) Added after the `/rate_limit` credential-shape line (:11), where a seat already reads 「先跑一条 repo-scoped 探针再选通道」: ``` - 限流拒绝绑定被拒身份(报文 user ID):同身份各写通道一并耗尽,⛔ 换通道续写与重试同罪。 - 他侧只在身份不同时是退路:凭据 `GET /user` ≠ 被拒 user ID 才换;席内 PM 与 dev 同一身份。 ``` This is the operative form of the card's F1 sentence 「一次限流信号绑定在身份上;⛔ 在同一身份的另一条通道上继续写,与重试同罪」, with the one legitimate fallback stated as a check the seat performs before switching: the refusal names a user ID; the other side is a different reading only if its credential answers a different user ID on `GET /user`. Within one seat the PM and its dev subagents are one identity, so a dev's refusal is the seat's refusal. Consistency with the standing facts in `platform-readings.md` (read-only here): :95–:97 (quota is per account, one identity holds only inside a seat, ⛔ no cross-seat pool inferred from a refusal's user ID) — the rule binds to the identity named in the refusal and nothing wider; :122 (a refusal is that side's reading) — the identity comparison is how a seat tells whether the other side is another reading or the same one. The 限流 clause of :123 「限流、403、传输失败都要试过另一侧才说得出我没手段」 is the reconciliation the claim assigns to the second half; it is not touched here. Paid by (deleted, with where the content survives): - :12 「按班矩阵与降级梯住 `platform-readings.md`,⛔ 不在本表复述。」 — a pointer restated at :3 (「见 `platform-readings.md` 配额段」) and :67 (「本表只指路,⛔ 不在两处各存一份」); the 降级梯 lives in the quota section :3 already points to. - :43 「GraphQL 池为 0 的同一分钟里开得出 draft PR ⇒ 交付不必等重置。」 — the capability survives at the ✓ row directly above it (:42, `POST .../pulls` 带 `draft=true`); the prescription half 「交付不必等重置」 is the unconditional form of the act the new rule conditions on identity (same identity ⇒ the same act as a retry), so it cannot stand beside the rule. Its conditional replacement is the new line 2. ### 2. `.claude/agents/os-dev.md` — destroyed evidence reads NOT MEASURED (expectation 4) Added directly after :174 「两类跑了却没测到,都读作 NOT MEASURED,不读作绿也不读作红。」, the home of the NOT MEASURED family (:96, :100, :174, :344 on main); the report-contract section defines `open_questions` as a field but carries no verdict-reading rules, so the family home is the tighter fit: ``` - 证据已销毁(评论、卡或 PR 答 404)的复核项记 NOT MEASURED 并写因,⛔ 不记通过或「无旗」。 ``` Paid by: :172 「引用门禁结果时点名它自己印的判定行,永不引裸 `$?`:判定行由门禁写,`$?` 由你的管道写。」 — restated at :166 「门禁结果的读法:退出码在任何管道之前捕获,报告里引门禁自己印的判定行。」 and at 资源纪律 1 「结论读它印的 `VERDICT command-exit` 行,⛔ 不读裸 `$?`」; the trailing clause was rationale. ### 3. Expectation 5 — already met on main, nothing built The half-state patrol's H40 「Dangling references」 row on anchor objectstack-ai#9857 (`scripts/pm/check-half-states.mjs`) lists every open card/PR whose `#` reference answers 404. Read on the anchor's current sweep body: 363 of 400 attempted resolutions answered over 6365 distinct references; 37 do not resolve; ⛔ only HTTP 404 is read as unresolvable; every count a lower bound. A destroyed card is therefore detected by patrol, not by someone noticing. ## Acceptance reading - Criterion 1 (「客户端 A 报限流、客户端 B 有额度」): the text now answers. Same identity (the refusal's user ID equals what B's credential answers on `GET /user`) ⇒ 「停」 — B's full quota is not a fallback. Different identity ⇒ verify the two user IDs differ, then switch. The seat does not infer; it compares two numbers. - Criterion 2 (normal-quota writes untouched): both new lines fire only on a refusal; nothing slows or batches a write under normal quota, and no transport is banned (REST, GraphQL and MCP all remain in the table). - Criterion 3 (rehearsed recovery): second half, with F3. - Criterion 4 (ablation, grep on the two files): at `origin/main` `git grep -c -E '身份|限流'` on rest-channel.md = 0 and `git grep -c -E '销毁|404'` on os-dev.md = 0 (lit controls: `探针`/`/rate_limit` hit :7/:10/:11/:31; `NOT MEASURED` hit :96/:100/:174/:344). At head `58885bc0b` the same greps read 2 and 1, and `NOT MEASURED` reads 5. With the new lines removed, criterion 1's question is again unanswerable by the text. - Criterion 5: item 3 above. The three 「⛔ 三条不要走的路」 hold: no transport is banned; nothing relies on 「下次注意」 (the rule is a check with two inputs); no new quota-exhaustion exit is introduced — the fallback that existed unconditionally is now narrower, not wider. ## A measured falsification of the dispatch word The dispatch cited a 2026-09-12 reading (MCP refusal naming `user ID 319429713`; seat `/rate_limit` 15000/15000; `GET /user` answering `os-sales`) as a switch between two different users. Measured on this container at PR time: `GET /user` answers `login: os-sales`, `id: 319429713` — the same user ID the MCP refusal named. Under the rule as written the two are one identity and that switch answers 「停」, consistent with the evidence comment on the card (5628795815: one client's bucket dry while another on the same user reads full — per-(user, app) buckets on one identity). The rule is unchanged by this; it is the comparison that catches it. Reported for the seat's reading, no state changed. ## Gates (run on head `58885bc0b`, exit codes captured by redirect before any pipe) `node scripts/pm/dispatch-gates.mjs --commands` derived 17 families; all 17 run, all exit 0; `--ran` reconciles 17/17 (0 UNRUN). Named: `check:pm-skill-ratchet` (rest-channel.md 82/82, os-dev.md 403/403, 「declared cross-file moves: 1, total ceilings down 9 lines」 unchanged), `check:pm-skill-id-lint` (27 files clean), `check:skill-frame-sync` (frame untouched), `check:nul-bytes`, `check:agent-model-declared`, `check:doc-authoring`, `check:agent-test-spelling`, `check:commit-card-trailers`, `check:watch-hint-literal`, `check:refd-timer-probe`, `check:driver-memory-census`, `check:pm-governed-merges`, closing-keyword-parity (+ self-test), comment-mask-corpus, governed-queue-guard self-test, and the lint-package `check:doc-formula-expressions` after building the `@objectstack/lint` closure under `os-verify-lock.sh` (`VERDICT command-exit 0`, held 176s). Repo-wide `pnpm lint` is CI's run, not run here. ## Acceptance notes - noted, not filed: `references/rest-channel.md` :53 carries a provenance date (「两条 2026-09-12 两席实调」) of the kind the rules-only rewrite removed elsewhere; a density candidate for any later net-reducing PR on this file. 承接者:无. ## 维护者速读(草稿) **改了什么**:两个文件各加一条纪律、各删一条已在别处写过的话,行数不变。① `rest-channel.md`〈通道边界〉:限流拒绝绑在被拒的身份上(报文里的 user ID),同一身份的所有写通道一起耗尽,换通道继续写等同重试;只有另一侧凭据 `GET /user` 答出的是不同的 user ID 才算退路;席内 PM 与 dev 是同一身份。② `os-dev.md`:复核项的证据被销毁(评论、卡、PR 答 404)时记 NOT MEASURED 并写原因,不记通过、不记「无旗」。期望 5 已由 H40 巡检行满足,不另建。 **为什么改**:2026-09-10 整队被停用的复盘卡指出,当时的规矩只禁「重试」,没说限流信号绑的是身份而不是手上的客户端,于是一个席位可以合规地换通道继续写。本 PR 只写纪律,不动舰队身份结构(objectstack-ai#17392 已关)。 **风险与代价(含回滚)**:纯文本纪律,不改代码、不改门禁、不发布任何包;正常额度下的写入不受影响,也不禁用任何传输方式。删掉的两条在同文件别处仍有原话或被新条款取代。回滚 = revert 本 PR 一个 commit。 **席位意见**:(留空) **你要做的**:确认 ① 「同一身份 ⇒ 停」这条线画在你要的位置(而非按传输方式画);② 期望 2、3 与 `platform-readings.md` :123 的对齐留给第二半;然后人工直合。 --- _Generated by [Claude Code](https://claude.ai/code/session_01MCLBsUgfykL74aU716rzVK)_ Co-authored-by: Claude <noreply@anthropic.com>
…t-stating surface is now a census (objectstack-ai#18244) Fixes objectstack-ai#17798 `AGENTS.md`'s merge-queue paragraph named SIX required contexts and called everything else "advisory and rides through". The live ruleset has SEVEN — `Governed Surface Queue Guard`, enrolled 2026-08-27 (objectstack-ai#12427) and pinned in `REQUIRED_CONTEXTS` by objectstack-ai#15233. The sentence a review seat acts on therefore classified the one gate that refuses a zero-review governed PR as advisory, which is verbatim the incident shape that guard exists to prevent. Nothing reddened on it, and that is the half worth fixing. `mustName` is a FLOOR: it reds when a listed literal goes stale and is blind to one going MISSING. So the registry grew, every surface's list stayed legal, every gate stayed green, and the sentence stayed behind. The same misclassification has now landed twice — 2 to 6 by the objectstack-ai#9677 ruling, 6 to 7 here. ## 维护者速读(草稿) **改了什么。** 两处。一、`AGENTS.md` 的合并队列段落从「六个必需上下文」改成七个,把第七个 `Governed Surface Queue Guard` 写进名单(等行数改写,1075 行没动)。二、`scripts/check-required-contexts.mjs` 的 `INSTRUCTION_SURFACES` 增加一个 `statesTheSet` 声明:声明了它的文件,其名单必须与注册表**逐个且等长**地对上。注册表本身(`REQUIRED_CONTEXTS`)一行未动,那是 objectstack-ai#15233 的面。 **为什么改。** 这句话不是描述,是审核席翻 ready / 挂 auto-merge / 入队前照着做的操作指令。它把治理面守卫说成 advisory,而那个守卫的职责恰恰是拒掉零审查的治理 PR —— objectstack-ai#12427 的事故形态。更关键的是:上一次入列(objectstack-ai#15233 加第七行)时,全部门禁是绿的,没有任何东西提示这句话已经过期。同一个漏洞已经发生两次,所以这次不只是手跟一遍数字,而是把「谁陈述了整个集合」变成机器可查的:下一次(第八个)入列时,加注册行的那个 PR 自己会变红。 **风险与代价(含回滚)。** 风险低。新规则只对**显式声明** `statesTheSet: true` 的条目生效,今天是两个文件(`AGENTS.md` 与 pm-dispatch 的 `platform-readings.md`);`review-checklist.md` 被明确归类为**不陈述集合**(它点名的是审核席亲手确认的两个 job,不是集合),保留它原有的两名下限,集合变大不会误伤它。声明也不能被悄悄摘掉换取豁免:名单已覆盖整个集合却没声明的条目同样变红。代价:每次入列多一处必须同 PR 跟进的数组。回滚 = `git revert`,两个文件都是纯文本,没有生成物、没有发布面、没有数据迁移。 **席位意见。** **你要做的。** 确认一件事:第七个上下文 `Governed Surface Queue Guard` 今天确实在 Settings 的必需集合里(卡面 2026-09-12 的实测读数是七个,本 PR 不改 Settings)。其余不需要你操作。本 PR 触及受管面 `AGENTS.md`,按 Prime Directive objectstack-ai#14 走人工合并或已批准的队列路径。 ## What changed **1. `AGENTS.md` :505-:510 — six to seven.** The seventh name inserted, "six" to "seven" in all three places, equal-line at the 1075 ceiling (before 1075 / after 1075 / ceiling 1075). Each context literal is kept whole on one line: the scan matches them contiguously, and a wrap that split `TypeScript Type Check` across a line break made the surface red. That is a real trap for the next hand-follow, so it is recorded here rather than only avoided. **2. `INSTRUCTION_SURFACES` gains `statesTheSet`.** An entry that declares it must name the registry EXACTLY — membership *and* count: - omitting a member reds, naming the omitted literal and the count it is short by; - padding past the registry length reds on the count, so a duplicate cannot mask a member lost to a typo; - a full list with the declaration dropped reds ("a list that covers the whole set IS a statement of it"), so the exemption cannot be taken silently; - ablating every declaration reds rather than ticking (objectstack-ai#4690). `review-checklist.md` is classified the other way and keeps its two-name floor: it names the two required jobs a seat confirms by hand — its own next line sends the seat to `true-green.md` for the rest — so it never claimed to enumerate the set, and the set growing must not red it. **3. Ten self-test cases, battery floor 31 to 41.** The 6-of-7 omission and its restore-leg ablation; the eighth-row enrolment end to end, plus the hand-off where following the ARRAYS clears the census red and leaves the naming floor demanding the PROSE; the padded duplicate; the undeclared full list; the no-declaration floor; and the two classification pins (which surfaces state the set, by NAME never by count; and the checklist's partial list staying legal). ## Evidence Baseline first, on `origin/main` `b3b43b6` in a clean worktree, BEFORE any edit — the known pit from hold note 5651882793 (PR objectstack-ai#17803 reported `--self-test` red on a pre-existing `branches: [main]` filter on `governed-surface-guard.yml`): ``` node scripts/check-required-contexts.mjs --self-test exit 0 159 assertions node scripts/check-required-contexts.mjs exit 0 7 required context name(s) pinned across 3 workflow(s) ``` **The known pit is NOT red on `main` today.** The residual named in the hold note is gone; the work below is measured against a green baseline, not against a standing red. After (`78959ab`): `--self-test` exit 0, 169 assertions; the pin exit 0. **Reverse verification, both legs from the committed implementation, each with its on-disk mutation proved and each restored byte-identical (`git hash-object` vs the HEAD blob, `git diff HEAD` empty):** | leg | mutation (proved on disk) | result | |---|---|---| | A — the registry array rots back | drop `'Governed Surface Queue Guard'` from the `AGENTS.md` entry's `mustName` (grep 2 to 1; `git diff --numstat` 0/1) | **RED**, exit 1: "declares statesTheSet: true, so its mustName must be the required set EXACTLY — it lists 6 name(s) against a registry of 7, missing 'Governed Surface Queue Guard'". Self-test exit 1 too. | | B — the numeral alone rots back | `seven contexts block` to `six contexts block` in `AGENTS.md`, all seven literals still listed (numstat 1/1) | **GREEN, exit 0** — reported as measured, not as expected. The scan pins literals, never the word introducing them. Recorded as a residual in the script header rather than implied covered. | | B2 — the prose drops the literal | delete `` and `Governed Surface Queue Guard` `` from the paragraph (grep 1 to 0; numstat 1/1) | **RED**, exit 1: "AGENTS.md no longer names the required context 'Governed Surface Queue Guard'" | | floor control | battery floor 41 to 42 | **RED**, exit 1, naming the exact count: "registered 41 case(s), below its pinned floor of 42" — so the floor binds at headroom 0 and 41 is the measured count, not a number below it | Leg B is the honest finding of this PR: the census forces every enrolled literal INTO the prose, so a stale numeral now sits next to a complete list rather than a short one. Bounded, not covered; pinning the numeral needs the arbitrary-literal recognition the script header already measured as out of reach. **Derived gate union**, run after the final commit, on `78959ab` (`git rev-parse --short HEAD`), each exit captured by redirect before any pipe: ``` node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack -> 37 command(s) from a 2-path change set (AGENTS.md, scripts/check-required-contexts.mjs) ``` 36 of 37 exit 0. The one not measured: `pnpm check:pm-dispatch-gates` (`scripts/pm/check-dispatch-gates.mjs`) runs past this session's foreground ceiling — it was still printing passing cases at 560s. NOT MEASURED, reason: runtime exceeds the foreground cap; CI owns it. Its subject matter — the `ROOT_FILE_WATCH_HINTS` declaration this file carries — is separately green under `pnpm check:watch-hint-literal` (exit 0) and under this script's own `the dispatch-gates declaration (objectstack-ai#9979)` battery (6 assertions). `--ran` reconciliation is reported in the dev report; the beyond-derivation families this card owes because it edits a gate script — `check:required-contexts` and its `--self-test` — are the two green readings above. `git grep` finds no `*.test.*` naming `check-required-contexts.mjs`, so that script has no separate test suite to owe. **Lint, narrowed with the three readings that make a narrowing a measurement:** 1. covered population read from eslint's own config, not guessed: `npx eslint --print-config scripts/check-required-contexts.mjs` reports exactly **2 rules enabled** for this path (`no-restricted-imports`, `comment-swallow/no-code-inside-block-comment`); `eslint.config.mjs` declares no markdown population at all, so `AGENTS.md` is outside the lint verdict in either direction; 2. file count read from `--format json`: 1 file, 0 errors, 0 warnings; 3. invariance for untouched files: this repo runs one `eslint.config.mjs` which **never enables type-aware linting for any file** (no `parserOptions.project`, no typed rules — `eslint.config.mjs` :326-:329, with its own positive-control measurement recorded there), so this diff cannot move any untouched file's verdict. The repo-wide `pnpm lint` scan is CI's run. **`skip-changeset`, measured rather than asserted:** both paths lie outside every package directory, and of the 70 published packages none has a `files[]` entry escaping its own directory (0 entries starting with `../` or `/`). Nothing published moves. ## Acceptance notes Out of scope, noted, not filed: - **`.claude/skills/pm-dispatch/references/platform-readings.md` :385-:386 becomes FALSE when this lands.** It reads 「⭐ 本表的 `mustName` 不要求排他 ⇒ 第七个加注册行不会让本表变红」 and 「⇒ ⛔ 门绿不是本行已对的读数:计数行只能手跟改」. After this PR that entry declares `statesTheSet: true` and the registry growing DOES red it, so a seat reading those two lines would keep hand-following a line the gate now holds. Not fixed here: the file is outside this card's declared REGION claim (`AGENTS.md` :505-:510 plus this script), and the script's own header records `.claude/skills/pm-dispatch/**` as a surface a dev seat may not edit — the reason the checklist half of objectstack-ai#9325 was its own card. **Successor: the `domain:skills` seat, in its own lane.** Dedupe words: `platform-readings`, `mustName 不要求排他`, `计数行只能手跟改`, `statesTheSet`, `required contexts 的名单`. - The count WORD in a set-stating surface stays hand-followed (leg B above). Recorded as a residual in the script header, in the paragraph that already records the paraphrase-drift and shortening-rename residuals. No card: it is bounded by the census and closing it needs recognition the header measured as out of reach. - The triage grading comment 5651027386, which this card's acceptance is quoted from, answers **HTTP 404** — it is not on objectstack-ai#17798 (the card carries exactly 3 comments) and the direct comment endpoint does not find it. Its content survives verbatim inside hold note 5651882793 and in this dispatch's own text, which is what acceptance items 1, 2 and 4 were read from here. Recorded as NOT MEASURED against the primary source, not as "no flags". Nothing else was touched. `REQUIRED_CONTEXTS` is byte-identical to `origin/main`. --- _Generated by [Claude Code](https://claude.ai/code/session_01HZfg2AwVX191qCizp88gQr)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
Closes #15233
Clause-②: no
Implements the director ruling on #15233 (summon #21, comment 5615315987) as written: the seventh
REQUIRED_CONTEXTSrow, plus the platform-readings count line six to seven.⛔ GOVERNED SURFACE — do not arm, do not enqueue. This diff touches
.claude/skills/pm-dispatch/references/platform-readings.md, so perAGENTS.mdPost-Task Checklist item 2 it is pushed, opened, and left awaiting a human merge. Auto-merge was not armed and will not be.Governed Surface Queue Guarddemanding an authorized approver on this PR is the guard working, not a failure.1. The row (acceptance 1)
scripts/check-required-contexts.mjs, all four fields as the ruling names them: workflowgoverned-surface-guard.yml(the registry spells the basename;scanWorkflowsjoins it under.github/workflows/), job idgoverned-surface-guard, contextGoverned Surface Queue Guard,authorizedciting #12427's 2026-08-27 confirmation (closedcompleted2026-08-27T07:51Z, comment 5436049459).2. The re-check, both readings (acceptance 2)
NODE_OPTIONS=--use-env-proxy node scripts/check-required-contexts.mjs --verify-required-set, exit 0 both times.BEFORE (at
952b9c5e59):AFTER (this branch):
⇒ 1 → 0, against an unchanged live set of 7. The contrasting 1 is what shows the row did the work.
3. The count line, hand-followed (acceptance 3)
platform-readings.mdnow reads 七个 and names the seventh. Hand-followed, exactly as triage 5586769327 warned: the ledger is anINSTRUCTION_SURFACESentry whosemustNamedoes not require exclusivity, so the scan half would have stayed green on a stale count line forever.--self-testdoes:That assertion derives the expected length from
REQUIRED_CONTEXTS.length, so the ledger'smustNamearray is machine-coupled to the registry. The prose count line is the part nothing reads. So: the count line was hand-followed (it had to be), andmustNamegained the seventh name (the gate demanded it).4. Reverse-read — including the zeros (acceptance 4)
Scanned every surface that states the required set, for
six/ 六个:.claude/.../review-checklist.md.claude/.../pm-dispatch/SKILL.mddocs/launch-readiness.mdCLAUDE.mddocs/releases-maintenance.md.claude/.../spec-property-retirement/SKILL.mdsixoutside theseAGENTS.md:505-510AGENTS.mdstates "six contexts block" and "A check outside those six is advisory and rides through." With the guard live-required, that sentence is false, and its failure mode is #12427's incident shape: a seat reads it, treats the guard as advisory, and arms.AGENTS.mdis explicitly off-limits for this card, andmustNamedoes not red on it either (same non-exclusivity). ⇒ Filed as #17798, unassigned and ungraded.⛔ 5. One residual, NOT fixed here —
check:required-contextsis RED on this branchAdding the row makes the registry judge the workflow that publishes the context, and that is where the row earns its keep.
--self-testhas 6 failures, all one root cause:This is a pre-existing live defect, not one this PR introduces: the guard has been required since 2026-08-27 while carrying
branches: [main], the exact base-filter shape #16482 removed fromci.ymlandlint.ymlafter measuring six card PRs showing zero of the required contexts. The registry had simply never been allowed to look.The remedy is mechanical and the repo has already ruled its shape — the self-test's own standing assertion is "the checked-in workflows carry NO base filter on pull_request — the required contexts report for any base (#16482)", and both sibling workflows carry a ⭐-comment saying so. Removing
branches: [main]from the guard'spull_request:leg turns all 6 green. Note it widens coverage (thepull_requestleg deliberately exits 0; the refusing leg ismerge_group), so it is not a loosening.⛔ I did not make that edit: this session's safety classifier refuses agent edits to
.github/workflows/**([CI Bypass]), and no ruling on this card covers a third file. ⇒ Maintainer's call, either on this PR or as its own card.What was carried with the row
mustNamegains the seventh name — forced by--self-test, see §3.REQUIRED_CONTEXTSinstead of a hand-listedlint.yml/ci.ymlpair. Without this, the newly-registered file is never read andjudgereports "never read — a scan that reads nothing cannot report a pass (check:react-declaration-parity 是唯一没接进任何 workflow 的源码审计门禁,且无 MANIFEST 时静默 skip 退出 0 —— 它现在永远不可能红 #4690)" through every fixture, so three fixtures were asserting against the wrong problem list. Enrolling an eighth context can no longer skip this step.Gates
--verify-required-setexit 0 (both readings above) ·check:nul-bytes0 ·check:pm-dispatch-gates0 ·check:agent-test-spelling0 ·check:doc-authoring0 ·check:parse-guard0 ·check:entry-guard0 ·check-self-test-wired(+--self-test) 0 ·check-scripts-symbol-anchors0 ·check-declaration-mirrors0 ·check-whole-set-label-write0 ·check-ci-filter-parity0 ·check-closing-keyword-parity0 ·check-self-test-workflow-commands0 ·check-governed-queue-guard --self-test0 ·check-comment-mask-corpus0. Derived fromdispatch-gates.mjs --commands --repo objectstack-ai/objectstackat952b9c5e59. ⛔check:required-contextsRED — §5, one root cause, deliberate.Re-measured independently (⛔ not taken on report):
check-cross-package-test-inputs.mjs --self-testis exit 0 both with and withoutNODE_OPTIONS=--use-env-proxy(156/156 cases each) — #15234's landing holds, so the ruling's re-check flag is safe to use.Changeset: skipped, measured — repo-root
scripts/and.claude/**are not shipped; no packagefiles[]carries either path, and the repo root isprivate: true. Applied as theskip-changesetlabel.维护者速读(草稿)
改了什么 — 给 CI「必需检查」注册表补上第七行,钉住
Governed Surface Queue Guard这个名字;并把 PM 席位手册里「六个」的计数改成「七个」。两处都只是记录,不改任何仓库设置。为什么改 — 这个守卫是保护受管面(
.claude/**、AGENTS.md、ADR 等)的那道门,2026-08-27 就已经生效,但注册表一直没写它。后果不是今天失守,而是哪天有人改了那个 job 的名字,这道门会无声脱落,而注册表存在的全部意义就是防这一件事。风险与代价(含回滚) — 改动只有记录性的两处加两处配套,风险极低;回滚就是还原这一个 commit,没有数据迁移、没有已发布产物。⚠️ 但补上这一行之后,门禁第一次去看那个工作流,发现它带着一个会让检查在部分 PR 上根本不报告的过滤器(详见上面第 5 节)。那一处本 PR 没有动,需要您定夺。
席位意见 — (留空,待席位填写)
你要做的 — ① 决定第 5 节那个
branches:过滤器是就在本 PR 删掉,还是另开一张卡;② 本 PR 属受管面,需要您手动合并,不会进合并队列。Generated by Claude Code