Skip to content

Commit af515db

Browse files
docs(checklist-test): a QA security finding gets no card and goes privately to the PM session, runners only test, and runner dispatches state effort by card shape (#27642)
<!-- claude-projects-attribution: {"github_login":"os-elon-musk"} --> _Requested by **Elon** · [project thread](https://claude.ai/code/project/chan_01WedDzV26nniwxv7jC88B9D?thread=cmsg_01WedDzV26nniwxv7jC88B9DCvt1Z3RM8RYroegHc3zfLi)_ Fixes #27382 Fixes #27390 Clause-②: no Metadata-protocol: none ## 维护者速读 - 改了什么:测试中遇到的安全问题不再立任何 GitHub 卡,复现经会话间消息私下报给派发会话,由它派修复并持有 PR;runner 只测试;runner 派发按卡定 effort(跑测 `high`,修安全与对抗复核 `xhigh`),并记花费。 - 为什么改:你 10-29 的几句话(汇总在 #27377 评论 6500750765);只写 detail withheld 的卡仍在公开仓里预告某项有洞;第三波 runner 没人定 effort,一张卡半小时花了 $33。 - 风险与回滚:只改规则文本(RUNNER 规则 2、checklist-test 技能、os-dev 两行、行数棘轮上限),不动产品代码;回滚即 revert。 - 先看:`docs/qa/platform-checklist/RUNNER.md` 规则 2 的两条子项;`.claude/skills/checklist-test/SKILL.md` 的 Guardrails 与 §2。 - 两分钟验证:`pnpm check:pm-skill-ratchet && node scripts/check-platform-checklist.mjs`,两者 exit 0。 - 需要你做的:无,席位落地(Tier S,欠一次契约档复审)。 ## What changes One rule, written in both places — `docs/qa/platform-checklist/RUNNER.md` rule 2 and `.claude/skills/checklist-test/SKILL.md` — and neither text claims precedence over the other. **Security findings (#27382).** A security finding is an authentication or authorization hole, or any finding whose reproduction is an exploit recipe. - It gets **no GitHub card at all**. That includes the withheld-detail card, and it is not mentioned on the wave anchor or a tracking card either. Its reproduction appears nowhere on GitHub. - The run record keeps one row, `area.item · clause · security finding — detail withheld, reported to PM`. That row is a completed verdict, and the title counts it as a `FAIL`. - The runner reports the reproduction privately to the session that dispatched it, by session-to-session message. - That PM session dispatches the fix through pm-dispatch: an in-seat `os-dev` subagent at `xhigh`, with the reproduction only in the dispatch prompt and hardening-only wording in the PR, commit and changeset. A finding that needs a contract decision first goes to the maintainer. **Runners only test (#27382, widened to issues and PRs).** - Every GitHub write and every issue or PR handling step of a run (extraction, dedupe, close-out) goes through the pm-dispatch write doors. - A runner never spawns `os-dev` and never drives a PR. The dispatching PM session owns every PR the run produces: review, contract review where it is owed, readiness and landing. - When a runner's own permission check refuses a write door, it stages the writes in a file, reports the path and hands off. The dispatching session reads them and posts them through the same doors in the staged order. A staged file never carries a security reproduction. **Runner effort (#27390), skill §2 only.** - Effort by card shape, stated in every runner dispatch, never empty. A run card (Tier 1 pin batch, manual API/CLI or browser set, P0 smoke, UX walkthrough) is `high`. `xhigh` is only for the `os-dev` that fixes a security finding and for adversarial re-verification of a contested verdict. - Per backend: - An `Agent` subagent gets `effort` passed explicitly, and the skill line is what makes passing it lawful. - A cloud card (`create_session`) takes no effort. Its record carries no effort field either (measured, below), so the dispatch note says "not set" plus the dispatching session's own level. When that level is above the card's shape, the dispatcher follows the dispatch-runbook's 〈云卡五课〉 item 五 ② line, which the skill cites rather than restates, or opens a project thread instead and sets its effort at spawn. - The spend line: the wave anchor's dispatch note and each runner's final report carry each card's spend at report time, read from `get_session` `external_metadata.usage.cost_usd`. **Files.** - `docs/qa/platform-checklist/RUNNER.md`: rule 2's carve-out is rewritten, and a new sub-bullet, "The runner only tests", is added. Two later mentions of the retired card shape now match it: the *Run records* paragraph and the *Extraction obligation* bullet. - `.claude/skills/checklist-test/SKILL.md`: - Guardrails: the security bullet is rewritten, and a "runner 只测试" bullet is added. - §4: the extraction line is rewritten, and the write-door paragraph now covers issue and PR handling and the refused door. - §2: three bullets cover effort, backends and spend, and the QA-run line names PR ownership. - `.claude/agents/os-dev.md`: two existing lines now carry the no-card dispatch. The dispatch prompt stands in for the card, there is no `Fixes #`, and the report is the final message only. No line is added (402 lines, ceiling 402). - `scripts/pm/check-skill-line-ratchet.mjs`: the `checklist-test/SKILL.md` ceiling goes from 246 to 259. ## Why The maintainer's words on 2026-10-29 in the dispatching session, verbatim and untranslated, all collected on #27377 in **comment 6500750765** (the ruling of record): - 「你应该使用 pm-dispatch 技能处理 issue」 - 「对于测试中遇到的安全问题,不适合提交issue 的,只是使用pm-dispatch技能派发」 - 「这些都应该写到 checklist skills 中」 - widening (#27382, 6493929972): 「每个会话我都交代了:你应该使用 pm-dispatch 技能处理 issue 和 pr」 - scope change (#27382, 6493958591), which replaces "the runner dispatches the fix": 「但是我建议开发任务是不是应该由你同意派,云agent 只负责测试,能做到吗?安全问题他可以私下报告给你吗」 - #27390: 「你派的任务都需要 extra 吗」, then 「立卡更新 skills」 **pm-skill line ratchet raise.** Ruling of record: #27377 comment **6500750765**. Measured line delta: **+13**, `.claude/skills/checklist-test/SKILL.md` 246 → 259. The `CEILINGS` row moves by exactly that amount. No other ceiling moves, and `os-dev.md` stays at 402. ## Measured before writing (#27390 item 2) All readings were taken with `get_session` on 2026-10-29, between 15:20Z and 15:30Z. | session | kind | `session_context.effort_level` | |---|---|---| | project coordinator `session_016WV4cyMsvviaf7j5XzQrfC` | parent of the two threads below | `low` | | this card thread `session_016WV4cxs6a1nUyBHTEo84qV` | project thread, spawned by the coordinator | `xhigh` | | sibling card thread `session_016WV4d1oYpq8eEjYF141fC3` | project thread, spawned in the same minute | `xhigh` | | devx seat thread `session_01LKHDniRQSWjRJyDtacwpEd` | project thread | `high` | | spec seat 1 `session_01HjR8RUwTPQ3TgbfKihmxp9` | dispatching seat (web) | `xhigh` | | its card `session_01GDoMKJFY45jdSGzFmyumij` (origin `claude_code_mcp_seed`) | `create_session` cloud card | **field absent** | | services seat 2 `session_018VqJPGA1NDNSCZsnecsChP` | dispatching seat (web) | `xhigh` | | its card `session_013JuCowY7RrGXYbGEv3cWsQ` (origin `claude_code_mcp_seed`) | `create_session` cloud card | **field absent** | What this shows: - A project thread runs at the level it was given at spawn, not at its parent's. - A `create_session` card's record carries no effort level at all, so nobody can read back the level it ran at. That is why the skill tells the dispatcher to record "not set" and the dispatching session's level, and does not claim an inherited level. - `usage.cost_usd` sits at `external_metadata.usage.cost_usd`, and a session with no finished turn has no `usage` block yet (this thread's own record at 15:18Z). ## Verification All at head `6b95c870`, on a fresh worktree off `main` at `9c513147`. - `pnpm check:pm-skill-ratchet` (self-test and gate): exit 0. `checklist-test/SKILL.md` is 259 lines against a ceiling of 259, every line is within 120 bytes, and the table-row pin is unchanged. `os-dev.md` is 402 against 402. - `node scripts/check-platform-checklist.mjs`: exit 0. That is 16 areas and 352 items. - `pnpm check:pm-skill-id-lint`: exit 0, with no issue number in `os-dev.md`. - `node scripts/check-commit-card-trailers.mjs --range origin/main..HEAD`: exit 0. - `pnpm exec eslint scripts/pm/check-skill-line-ratchet.mjs`: exit 0. - `node scripts/pm/dispatch-gates.mjs --ran` accounts for all 43 derived families: 43 run, 0 unrun, every one exit 0. - `check:doc-formula-expressions` first exited 3 (its prerequisite was not met). It passed after `pnpm --filter '@objectstack/lint...' build`. - Changed lines: 112 (+71 / −41), under the 3000 human-merge threshold. <details><summary>The 43 derived families and their exit codes</summary> ```text node scripts/check-adr-links.mjs :: exit 0 node scripts/check-adr-links.mjs --self-test :: exit 0 node scripts/check-closing-keyword-parity.mjs :: exit 0 node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0 node scripts/check-comment-mask-corpus.mjs :: exit 0 node scripts/check-declaration-mirrors.mjs :: exit 0 node scripts/check-declaration-mirrors.mjs --self-test :: exit 0 node scripts/check-self-test-wired.mjs :: exit 0 node scripts/check-self-test-wired.mjs --self-test :: exit 0 node scripts/check-self-test-workflow-commands.mjs :: exit 0 node scripts/check-self-test-workflow-commands.mjs --self-test :: exit 0 node scripts/check-whole-set-label-write.mjs :: exit 0 node scripts/check-whole-set-label-write.mjs --self-test :: exit 0 node scripts/ci/plan-test-core-shards.mjs --self-test :: exit 0 node scripts/pm/bare-root-worklist.mjs --self-test :: exit 0 node scripts/pm/check-governed-queue-guard.mjs --self-test :: exit 0 node scripts/pm/check-harness-current.mjs --self-test :: exit 0 pnpm --filter @objectstack/lint run check:doc-formula-expressions :: exit 0 (after the lint closure build; first run exit 3) pnpm check:agent-model-declared :: exit 0 pnpm check:agent-test-spelling :: exit 0 pnpm check:bash32-floor :: exit 0 pnpm check:ci-filter-parity :: exit 0 pnpm check:cli-command-ids :: exit 0 pnpm check:commit-card-trailers :: exit 0 pnpm check:cross-package-test-inputs :: exit 0 pnpm check:doc-authoring :: exit 0 pnpm check:driver-memory-census :: exit 0 pnpm check:entry-guard :: exit 0 pnpm check:gitlink-declared :: exit 0 pnpm check:nul-bytes :: exit 0 pnpm check:parse-guard :: exit 0 pnpm check:platform-checklist :: exit 0 pnpm check:pm-dispatch-gates :: exit 0 pnpm check:pm-governed-merges :: exit 0 pnpm check:pm-skill-id-lint :: exit 0 pnpm check:pm-skill-ratchet :: exit 0 pnpm check:pnpm-filter-targets :: exit 0 pnpm check:pnpm-flag-spellings :: exit 0 pnpm check:ratchet-remedy-authority :: exit 0 pnpm check:refd-timer-probe :: exit 0 pnpm check:scripts-symbol-anchors :: exit 0 pnpm check:skill-frame-sync :: exit 0 pnpm check:watch-hint-literal :: exit 0 ``` </details> ## Acceptance notes - **The file surface is widened by two RUNNER.md mentions outside rule 2.** The *Run records* paragraph and the *Extraction obligation* bullet repeated the retired withheld-card shape, so left alone they would have contradicted rule 2. They are in the same file, and `docs/qa/**` is not governed. Declared to the seat. - **Three checklist item strings still quote the retired `detail withheld pending maintainer` wording**, as pointers to rule 2's carve-out: one in `areas/access-security.json` and two in `areas/identity-auth.json`. They defer to rule 2, which governs. Rewording them is an item revision (a `revision` bump and a `history` row), which is outside this card's surface. Left for the seat: either a follow-up or noted only. - **pm-dispatch `dispatch-runbook.md` 〈云卡五课〉 item 五 ② says a session that cannot take effort inherits the seat's level.** The readings above cannot confirm that for `create_session` cards, because their record has no effort field. The runbook is not edited here (pm-dispatch is outside this surface). The skill cites the line and records what can be read. - **Tier.** Touching `.claude/agents/os-dev.md` makes `dispatch-gates --tier` derive a MANDATORY contract-review tier for the surface. The two-line amendment there is one-line-class and mechanical. The build ran at the default tier, and the contract-tier review the seat arranges is the compensating control. - **No PR assignee.** `os-dev.md` says a PR carries none, and the maintainer reaches the PR through the seat's hand-off. ## Human Minimum - [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic. (`packages/spec` is not touched.) - [x] 2. Every name I added uses the casing for its kind. - [x] 3. No formatter ran; `pnpm lint` passes. (eslint on the one `.mjs` file changed: exit 0.) - [ ] 4. One issue, one branch off an up-to-date `main`, one pull request. (Two cards folded into one PR by the seat's dispatch order; one branch off `main` at `9c513147`.) - [x] 5. A change that publishes carries a changeset, and both declaration lines are filled in here and in the changeset. (Nothing publishes: `skip-changeset`. Both declaration lines are above.) - [x] 6. `packages/spec` touched: its generated artifacts are regenerated and committed. (Not touched.) - [x] 7. No hand edits to `content/docs/references/`, `packages/*/CHANGELOG.md` or `content/docs/releases/`. - [x] 8. Title, body and comments are in English. (The 维护者速读 block and the quoted rulings are Chinese, as the repo's rules require.) - [x] The *Before Submitting* list in CONTRIBUTING.md is done. --- _Generated by [Claude Code](https://claude.ai/code/session_016WV4cxs6a1nUyBHTEo84qV)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 9e73c08 commit af515db

6 files changed

Lines changed: 107 additions & 55 deletions

File tree

‎.claude/agents/os-dev.md‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
11
---
22
name: os-dev
33
description: >
4-
Developer agent for exactly ONE GitHub issue, dispatched by the /pm-dispatch
4+
Developer agent for exactly ONE GitHub issue (or one no-card order), dispatched by the /pm-dispatch
55
PM loop. Implements the issue end-to-end in a dedicated worktree — branch,
66
code, tests, changeset, push, draft PR — and returns a structured JSON
7-
report to the PM. Use only with a single fully-specified issue as input;
7+
report to the PM. Use only with a single fully-specified issue or order as input;
88
never for open-ended or multi-issue work.
99
model: opus
1010
---
1111

1212
<!-- `model: opus` 是刻意钉死的下限,不是上限 —— PM 的逐单定档优先。完整解析顺序、
1313
批量压死事故与两个陷阱(环境变量、允许名单)住 check:agent-model-declared 门禁脚本头部。 -->
1414

15-
你是 ObjectStack 开发 agent,由 PM 派发,恰好带一张 GitHub issue。
16-
交付物两件:该 issue 实现完毕并推成 draft PR,加下方的 JSON 报告。
15+
你是 ObjectStack 开发 agent,由 PM 派发,恰好带一张 GitHub issue;无卡时派发令即卡并点名分支。
16+
交付物两件:该 issue 实现完毕并推成 draft PR,加下方的 JSON 报告;无卡则无 `Fixes #`,仅交终报。
1717
报告交付两次,GitHub 优先:同一段 JSON 先作 issue 评论,首行单独一行是纯文本 `os-dev-report`。
1818
⛔ 不用 HTML 注释写标记。再作为终报消息:PM 机械解析它,终报消息就是 JSON 本身。
1919
仓库根的 AGENTS.md 有约束力,第一次编辑前先读它。
@@ -359,9 +359,9 @@ model: opus
359359

360360
```json
361361
{
362-
"issue": <n>,
362+
"issue": <n | null — null only on a no-card dispatch>,
363363
"status": "done | rework | blocked | needs_decision | reading",
364-
"branch": "claude/issue-<n>-<slug>",
364+
"branch": "claude/issue-<n>-<slug> | the branch a no-card dispatch order names",
365365
"pr": "<url or null>",
366366
"session": "session_<id> — this run's harness-stamped id (transcript Claude-Session: line; subagent = parent's)",
367367
"premise_still_valid": true,

‎.claude/skills/checklist-test/SKILL.md‎

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,16 @@ Tier 1 批量清掉,剩下的预算才对得起 Tier 2。
102102
`node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --tier <paths>` 的输出、
103103
⛔ 不凭记忆(floor sonnet · default opus · ceiling fable);每个给:该项 JSON、RUNNER.md、dogfood 技
104104
能、自己的端口/DB、结果不进仓规则(§4)。
105+
- **effort 按卡形状定,派发写明,⛔ 不留空。** runner 卡(Tier 1 pin 批、手工 API/CLI 与浏览器
106+
组、P0 冒烟、UX 走查)一律 `high`;`xhigh` 只给修安全发现的 `os-dev` 与争议判定的对抗复核。
107+
- **按后端传档。** `Agent` 子代理显式传 `effort`(本条即传参的依据)。云卡(`create_session`)不收
108+
effort,卡的会话记录也不带 `effort_level`,跑在哪档读不回(实测):派发记录写「未指定」并附
109+
发起会话 `get_session` 读到的档位;它高于卡形状时照 pm-dispatch `dispatch-runbook.md`
110+
〈云卡五课〉五之 ② 行办,或改开项目线程、开时给档位(实测按所给,不随父会话)。
111+
- **花费行。** 波次锚卡的派发记录与每个 runner 终报带各卡报告时的花费(`get_session` 的
112+
`external_metadata.usage.cost_usd`),供下一波校准。
105113
- **QA 运行不是车道卡。** runner(子代理或云卡)由发起本轮的会话直接派,⛔ 不走车道认领
106-
与座位贴;pm-dispatch 只管写入门(§4)与安全修复派卡(Guardrails)。
114+
与座位贴;pm-dispatch 管写入门(§4)、安全修复派发与 PR 持有(Guardrails)。
107115
- **没有子代理工具时,顺序跑 —— 并在运行记录里声明这一轮是顺序执行的。** 规则与它
108116
的论证住在 `checklist-author` 技能(维护者所定,一处成文);⛔ 不留第二份拷贝。
109117
- 忠实执行每项的 `steps`,按各自声明的 `oracle` 判定每条 `acceptance` 与 `negative`
@@ -126,10 +134,11 @@ expected-fail 探针 —— 永不在真实缺陷上把条款打成绿。
126134
**issue 是纯文本 —— 永不放图。** 截图只为让你和子代理**现场**得出判定;它们是判断辅
127135
助,随运行环境一起丢弃。持久报告需要的是**复现规则,不是图片**。
128136

129-
GitHub 写入只走 pm-dispatch 写入门:仓根直调 `scripts/pm/issue-create.mjs`、`post-stamped.mjs`、
130-
`close-cards.mjs`,批量先 `write-pace.mjs --announce-batch N`,云容器内自走 fleet relay;⛔ 永不 MCP
131-
`issue_write`(锁 1 已拒)或裸 `curl`。写入被会话权限拦下 ⛔ 不绕行:待发文本落文件、终报给
132-
出路径与正文,放行后照序补发 —— 写入未落地即未收口。记录这样写:
137+
一轮的每次 GitHub 写入与 issue / PR 处理(抽取、查重、收口)只走 pm-dispatch 写入门:仓根直调
138+
`scripts/pm/issue-create.mjs`、`post-stamped.mjs`、`close-cards.mjs`,批量先 `write-pace.mjs --announce-batch N`,云
139+
容器自走 fleet relay;⛔ 永不 MCP `issue_write`(锁 1 已拒)或裸 `curl`。写入门被本会话权限拦下
140+
⛔ 不绕行:待发文本落盘,路径与正文随终报或会话间消息交派发会话,由它照序经原写入门
141+
补发;其中 ⛔ 无安全复现 —— 写入未落地即未收口。记录这样写:
133142

134143
- **标题** —— `QA run · <selector> (<已判定>/<总数>) · <sha8> · <date> · <counts>`:唯一
135144
形状、严格解析、分数**强制**;语法与退役写法见 RUNNER.md「The record title」。
@@ -149,7 +158,7 @@ GitHub 写入只走 pm-dispatch 写入门:仓根直调 `scripts/pm/issue-create.
149158
取为独立 issue:标题自含、复现与机制条目化、指回本 run issue 取全量证据;缺陷卡不
150159
挂 `qa-run`,正常进分诊首触。清单准确性发现与 fixture 缺口归波次锚卡(sweep 跟踪
151160
issue)收口,⛔ 不抽取;环境阻塞记录在案即可。逐条 `fail` 的四分法处置、收口评论的
152-
机器行与抽取卡的溯源行见 §5。⛔ 认证 / 授权漏洞的抽取卡不写复现,见 Guardrails。
161+
机器行与抽取卡的溯源行见 §5。⛔ 安全发现不抽取、不立任何卡,见 Guardrails。
153162
- **完成判据** —— 一个区只有在**每一项都有判定**时才算跑完。跑不完的一轮照常立
154163
单:交出已有的判定,**外加一份点名未触达项的交接清单**,标题按上面的分数如实写。
155164
`not-run` 是诚实判定 —— 判 1 项、11 项没跑就写 `(1/12) · … · 11 NOT-RUN`。
@@ -175,9 +184,9 @@ GitHub 写入只走 pm-dispatch 写入门:仓根直调 `scripts/pm/issue-create.
175184
| **过期规格** | 两边都按各自的声明行事,条款钉的是已被裁决改掉的旧形状 | 立 re-point 卡:把条款重新指向现行规格,引裁决 |
176185
| **配对无效** | 该项与这个 subject / fixture 根本不成立(错配的 revision、不适用的环境) | 记 `blocked` 说明,⛔ 不立卡 |
177186

178-
四选一是**穷尽的**:落不进任何一格的 `fail` 说明你还没判完 —— ⛔ 永不把它留成一条无
179-
处
180-
置的红行。
187+
除安全发现外,四选一是**穷尽的**:落不进任何一格的 `fail` 说明你还没判完 —— ⛔ 永不把
188+
它留成一条无处置的红行。安全发现那一行本身即处置:⛔ 不立卡、不写 `Extracted:` 行,修复
189+
由派发它的 PM 会话派(Guardrails)。
181190

182191
与 §4「清单准确性发现归波次锚卡」的分界,是**有没有一条 `fail` 要清**:由 `fail` 判出
183192
的断言缺陷 / 过期规格各自立卡(那条红行得有人负责变绿),运行中顺带看到的清单准确性问
@@ -235,12 +244,16 @@ QA-source: #运行记录号 · area.item · 条款
235244
或记 `blocked(environment)`;证到一半的项是 `partial`,不是 `pass`。带证据的
236245
blocked 判定是一次成功运行;伪造的 pass 不是。
237246
- **每个产品问题同一轮经 §4 立 issue。** `fail` 条款与条款外探测同样,⛔ 不只留在运行记
238-
录或聊天;认证 / 授权类按下一条。
239-
- **认证 / 授权漏洞的复现,永不发布到 GitHub 的任何地方** —— 不进运行 issue,不进跟踪
240-
卡,不进评论。卡只载项 id、条款与 `detail withheld pending maintainer`,复现留在会话里;修复直
241-
接经 pm-dispatch 派卡:席内 `mode:subagent` 派 `os-dev`,复现只进派发令,⛔ 不等维护者;runner 无
242-
派发通道时由派它的会话接手。⚠ 这与 RUNNER 规则 2 是**同一条规则写在两处**,⛔ 两边
243-
都不是对另一边的优先级主张;完整论证与它的维护者裁定在 RUNNER。
247+
录或聊天;安全发现按下一条。
248+
- **安全发现不立卡,复现不上 GitHub**(安全发现 = 认证 / 授权漏洞,或复现即利用配方的发
249+
现):⛔ 不立任何卡,锚卡、跟踪卡不提;复现经会话间消息私报给**派发它的 PM 会话**(无此
250+
通道则留本会话,终报报 blocked),由它经 pm-dispatch 派修复:席内 `os-dev` 子代理、`xhigh`,复现只
251+
进派发令,PR、提交与 changeset 只写加固;要定契约的交维护者。运行记录只留一行
252+
`area.item · 条款 · security finding — detail withheld, reported to PM`,算已完成判定、标题计 `FAIL`。
253+
- **runner 只测试。** 记录、缺陷卡、评论与 issue / PR 的每步处理都走 §4 写入门;⛔ 不派
254+
`os-dev`,⛔ 不驱动 PR:一轮产出的每个 PR 归派发的 PM 会话复核、欠时契约复审、翻 ready 与
255+
落地。⚠ 这两条与 RUNNER 规则 2 是**同一条规则写在两处**,⛔ 两边都不是对另一边的优先
256+
级主张;完整论证与它的维护者裁定在 RUNNER。
244257
- **不把 blocked 项当可运行的跑** —— 解析器隐藏它们正为此。
245258
- **一个选择器、一次运行、一张 issue;验证重测不立 issue,证据进 PR 正文(§5)。** release
246259
sweep 把 `since:vN` 与 `priority:P0` 作为分开的两轮跑 → 两张 issue,不要糊在一起。

‎docs/qa/platform-checklist/RUNNER.md‎

Lines changed: 42 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -78,22 +78,38 @@ selector handed you has a verdict; planned ids are listed, not chased.
7878
issue. A `fail` with no reproduction rule in its issue is not a completed verdict.
7979
(The screenshot that convinced you is a live judgment aid, not report content —
8080
describe what it showed in one line; never attach it.)
81-
- **Carve-out — authentication and authorization findings only.** ⛔ Never publish a
82-
reproduction for an authentication or authorization hole **anywhere on GitHub**: not
83-
the run issue, not a tracking or extracted card, not a comment. Relocating it is not a
84-
mitigation — a tracking card is a public issue in a public repo just the same. Such a
85-
`fail` **is a completed verdict** when it records the item id, the clause, and `detail
86-
withheld pending maintainer`; the reproduction stays in the runner session. The fix is
87-
**dispatched directly through `pm-dispatch`**, never waiting on the maintainer: a card
88-
carrying only the item id, the clause and `detail withheld pending maintainer`, then an
89-
in-seat `mode:subagent` `os-dev` dispatch (a disclosure-discipline card) whose prompt
90-
alone carries the reproduction. A one-shot runner with no dispatch channel hands that to
91-
the session that dispatched it. Existence published, recipe withheld, is a complete and
92-
actionable report — getting a defect fixed never requires handing anyone a working
93-
exploit. (Maintainer disclosure ruling, 2026-08-18, recorded on #9387; fix-dispatch
94-
ruling, 2026-10-27, recorded on #25807; the `checklist-test` skill states the same
95-
guardrail in the same terms — one rule written in both places, not a precedence claim
96-
by either.)
81+
- **Carve-out — security findings.** A security finding is an authentication or
82+
authorization hole, or any finding whose reproduction is an exploit recipe. ⛔ It gets
83+
**no GitHub card at all**: no withheld-detail card, no tracking or extracted card, and
84+
no line about it on the wave's anchor. A card that withholds the recipe still announces,
85+
in a public repository, that a named item carries a hole before its fix lands. ⛔ Its
86+
reproduction appears nowhere on GitHub: not the run issue, not a card, not a comment,
87+
not a PR. The run record carries one row,
88+
`area.item · clause · security finding — detail withheld, reported to PM`; that row
89+
**is a completed verdict**, and the title counts it as a `FAIL`. The runner reports the
90+
reproduction **privately** to the session that dispatched it, in a session-to-session
91+
message, and keeps it nowhere else; a runner with no such channel keeps it in its own
92+
session and reports the hand-off as blocked, without the reproduction. **That
93+
dispatching PM session** decides the fix and dispatches it through `pm-dispatch`: an
94+
in-seat `os-dev` subagent at `xhigh` whose dispatch prompt alone carries the
95+
reproduction, and whose PR, commit and changeset describe a hardening without the
96+
exploit steps. A finding that needs a contract decision first goes to the maintainer
97+
instead. Getting a defect fixed never requires handing anyone a working exploit.
98+
- **The runner only tests.** Its record, its defect cards, its comments, and every issue
99+
or PR handling step of the run (extraction, dedupe, close-out) go through the
100+
`pm-dispatch` write doors (`scripts/pm/`). ⛔ It never spawns `os-dev` and never drives
101+
a PR: the dispatching PM session owns every PR the run produces, through review,
102+
contract review where it is owed, readiness and landing. When the runner's own
103+
permission check refuses a write door, it does not route around it: it stages the
104+
writes in a file and hands off to the dispatching session with the path **and the
105+
staged text itself**, in its final report or by session-to-session message, because
106+
the dispatching session cannot read a file in a cloud runner's container. That session
107+
posts them through the same doors in the staged order. ⛔ The staged text never carries
108+
a security reproduction; that travels only in the private report. (Maintainer
109+
disclosure ruling, 2026-08-18, recorded on #9387; fix-dispatch ruling, 2026-10-27,
110+
recorded on #25807; no-card, runner-only-tests and PM-dispatches-the-fix rulings,
111+
2026-10-29, recorded on #27377; the `checklist-test` skill states the same guardrail in
112+
the same terms — one rule written in both places, not a precedence claim by either.)
97113
**No other failure class is softened by this**: everything else owes its reproduction
98114
rule in full.
99115
3. **Classify blockers honestly.** Missing seed/persona/fixture → `blocked(fixture)`,
@@ -763,10 +779,11 @@ its README.
763779
reach a verdict — never report artifacts. What the report carries for a defect is the
764780
**reproduction rule**: ordered steps / API calls (method · path · body) / the
765781
ref-targeted selector path + expected-vs-actual from the oracle, enough to re-hit it on a
766-
fresh boot with no picture — **except under rule 2's authentication/authorization
767-
carve-out**, where the report carries the item, the clause and `detail withheld pending
768-
maintainer`, and nothing else. A clause whose oracle was a screenshot is recorded as a
769-
one-line text description of what it showed, not a link.
782+
fresh boot with no picture — **except under rule 2's security-finding carve-out**, where
783+
the report carries the one row
784+
`area.item · clause · security finding — detail withheld, reported to PM` and nothing else.
785+
A clause whose oracle was a screenshot is recorded as a one-line text description of what it
786+
showed, not a link.
770787

771788
The in-environment JSON scratch (RUNNER shape, never committed):
772789

@@ -860,12 +877,11 @@ closing out the report includes the extraction, owed by the runner:
860877
- **Product defects found during the run**: at close-out, extract each one into its own
861878
standalone issue — self-contained title, reproduction and mechanism itemized in the
862879
card, a pointer back to the run record for the full evidence chain. A defect card does
863-
not carry `qa-run`; it enters triage first-touch normally. ⛔ An authentication or
864-
authorization defect is extracted under rule 2's carve-out — item, clause, `detail
865-
withheld pending maintainer`, no recipe — and its fix is dispatched at once (rule 2). The
866-
extracted card is public too. Every product defect the run meets, a clause `fail` or an
867-
off-clause probe finding alike, is extracted in the same run — ⛔ never left only in the
868-
run record or the chat.
880+
not carry `qa-run`; it enters triage first-touch normally. ⛔ A security finding is never
881+
extracted: it gets no card at all, and its reproduction goes privately to the dispatching
882+
session, which dispatches the fix (rule 2's carve-out). Every other product defect the
883+
run meets, a clause `fail` or an off-clause probe finding alike, is extracted in the same
884+
run — ⛔ never left only in the run record or the chat.
869885
- **Checklist-accuracy findings and fixture gaps** close out through the wave's anchor
870886
card (the sweep's tracking issue) — ⛔ not extracted.
871887
- **Environment blockers**: recorded in the run record is enough.

‎docs/qa/platform-checklist/areas/access-security.json‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3051,7 +3051,7 @@
30513051
"title": "OS_PLATFORM_OWNER_EMAIL platform-admin anchor: only a VERIFIED stored-email match confers PLATFORM_ADMIN — unset, unverified match, non-match and malformed lists each confer nothing, fail closed",
30523052
"since": "v17",
30533053
"status": "active",
3054-
"revision": 11,
3054+
"revision": 12,
30553055
"priority": "P1",
30563056
"surface": "api",
30573057
"personas": [
@@ -3115,7 +3115,7 @@
31153115
}
31163116
],
31173117
"negative": [
3118-
"no runtime write reaches the anchor: OS_PLATFORM_OWNER_EMAIL is declared a NON-TABLE derivation input (admin-standing-surface.ts) — revocation is a configuration change plus a process roll, by design. Any in-product surface found mutating who is a platform administrator is an authorization finding: record it under RUNNER rule 2's carve-out (item, clause, detail withheld pending maintainer), never with a reproduction",
3118+
"no runtime write reaches the anchor: OS_PLATFORM_OWNER_EMAIL is declared a NON-TABLE derivation input (admin-standing-surface.ts) — revocation is a configuration change plus a process roll, by design. Any in-product surface found mutating who is a platform administrator is an authorization finding: record it under RUNNER rule 2's carve-out (no card; one run-record row `area.item · clause · security finding — detail withheld, reported to PM`), never with a reproduction",
31193119
"the derivation reads the caller's OWN STORED sys_user row, never the session-seeded grants.email — the ⭐ P1 pin of the leg. That property is asserted by the unit pin (the '⭐ … reads the STORED row, never the seeded email' describe); do not attempt to re-prove it against a live server",
31203120
"run every refused probe as the seeded admin too: a 200 proves each refusal keyed on the member's standing, not on a broken route (wrong-persona, both sides)",
31213121
"out of scope here: the WALLED-posture boot refusal when the variable is unset (#11184) is plugin-auth's separate door — do not score it on this item"
@@ -3215,6 +3215,12 @@
32153215
"date": "2026-10-27",
32163216
"change": "automated.kind unit -> dogfood (#26571 slice 3; the slice-2 ACCEPT noted it): revision 10's ref already leads with a dogfood pin over the in-process HTTP stack, with the unit pins beneath. No ref, clause or status changed",
32173217
"ref": "#26571"
3218+
},
3219+
{
3220+
"revision": 12,
3221+
"date": "2026-10-29",
3222+
"change": "RUNNER rule 2's security-finding carve-out re-worded (#27382; maintainer rulings of 2026-10-29, recorded on #27377): a security finding gets no GitHub card at all, the run record carries the one row `area.item · clause · security finding — detail withheld, reported to PM`, and the reproduction goes privately to the dispatching PM session. negative[0] quoted the retired 'detail withheld pending maintainer' row and now names the new one. The same PR re-words every item string that quoted the retired row. No clause, oracle or status changed",
3223+
"ref": "#27642"
32183224
}
32193225
]
32203226
},

0 commit comments

Comments
 (0)