Skip to content

Read the os-dev report open_questions array, and give a residual question its own card - #17091

Merged
os-zhuang merged 5 commits into
mainfrom
claude/issue-16662-open-questions-reader
Sep 9, 2026
Merged

os-zhuang merged 5 commits into
mainfrom
claude/issue-16662-open-questions-reader

Conversation

@yinlianghui

Copy link
Copy Markdown
Contributor

Fixes #16662

The os-dev report contract carries an open_questions array. A dev that hits a question only
the maintainer can answer writes it there, with options and a recommendation, exactly as the
contract asks. Nothing read that array. The decision inbox is the needs-user-decision
label, and a question inside a report comment's JSON body carries no label — so the question was
filed, well-formed and unreachable: not in the inbox, not in any candidate query, not in any
staleness alert, while the card kept a pm:* state label that reads as work in flight on every
board.

This PR ships the reader, and the rule that lets a question outlive the card it was raised
on. ⛔ It does not touch the report contract: the array is the right place for the question.

What landed

  1. scripts/pm/check-half-states.mjs — H52, one report-only patrol row. For the NEWEST
    os-dev-report on an open card whose complete comment thread is already in hand, a non-empty
    open_questions on a card carrying no needs-user-decision is a row naming the report, its
    stamp, how many questions it holds and the first of them. Registered in
    HALF_STATE_FAMILY_BAND as a stall row, with a three-number coverage clause
    (openQuestionCandidates / openQuestionJudged / openQuestionUnparsed) rendered on every
    run.
  2. .claude/skills/pm-dispatch/references/dispatch-runbook.md — the residual-question rule,
    two lines in ## 落卡与裁决记录细则: questions raised while executing a ruling go on a new
    card linking the ruled one, ⛔ never back onto the ruled card as a re-hung label, plus the two
    reasons.

The row buys nothing: commentCache.get never fetches, so it is judged at the FOOT of the
sweep, after every pass that fills the cache — and above all after H50's completing walk, which
lands the LAST page for every open pm:dispatched card. That placement is load-bearing: a
report is posted at the END of a dev round, so on a long thread a first page would miss it, and
miss it silently. A card whose thread nobody bought, or whose thread is in hand but not
complete, or whose newest report payload does not parse, is UNJUDGED — counted apart, never
folded into clean.

Why the rule sentence sits in the runbook and not in state-machine.md

The claim named state-machine.md or decision-analysis.md, "whichever already carries the
ruled-card rule". Re-checked on this tree: neither carries it — grep for 呈递 / 已裁卡 over
SKILL.md and every references/*.md returns the rule nowhere, so that half of the claim's
premise does not hold and the file had to be chosen on a reader instead.

The runbook has a NAMED reader at exactly the moment the rule fires. SKILL.md:730, inside
落卡/升级流程 ③ (「答复/代裁到手,四件同笔」 — i.e. a ruling has arrived and is being executed),
points at references/dispatch-runbook.md for that step's cases and its two mechanical flags.
The section the sentence landed in, ## 落卡与裁决记录细则, already carries the decision-box
neighbours it belongs beside (出决策箱须引可取回的裁决 id; the read-side 决策复读 rule) and the two
report-only flags that this very patrol owns. state-machine.md is the per-state line contract
for the six labels and has no ruling-execution step; decision-analysis.md scopes itself to how
a decision analysis is WRITTEN. So the sentence stays where the previous round put it.

Acceptance notes

The triage ruling's six-item 验收口径 (comment 5578847560), verbatim, each with its evidence:

  1. 先跑一次全量扫描并贴出结果:所有 open 卡上的 os-dev-report 评论里 open_questions 非空、而卡不带 needs-user-decision 的清单。⛔ 这份清单本身就是本卡的第一个交付物 —— 它会告诉我们这个洞现在有多大,而 ⛔ 目前没有人知道。

Posted as comment 5597739635 on the card. 587 open cards read; 65 carriers holding 126
questions, none of them in the inbox.
The full list is in that comment, card by card.

  1. ⭐ 扫描必须覆盖已关闭的卡至少一次(见上 [finding] Sweep: which consumers compare or format a value whose runtime type differs between the Date-materialising drivers and the ISO-text ones #13973 的读数)。⛔ 只扫 open 会漏掉恰恰最难发现的那一类。之后常态运行可以只扫 open,但首次普查必须包含 closed,否则得到的是一个系统性偏低的数。

Same comment, section 2: 2,107 closed pm:dispatched cards read — 255 carriers holding 436
questions
, i.e. the closed half is larger than the open half and permanently out of reach of
any open query. The standing row is open-only by design and says so in its own coverage clause;
that gap is what the residual-question rule exists to cover.

  1. 发火对照必须先红:用一张已知带非空 open_questions 且不带标签的卡(第 1 条的扫描结果里任取一张)证明脚本报红;再用一张带标签的证明它不报。⛔ 一个只在修复后跑过一次绿的扫描,与没有扫描无法区分。

Red: all 65 census carriers make the shipped predicate return a finding sentence, and a LIVE
sweep on this tree filed 60 rows, every one independently confirmed by the census (zero
uncorroborated rows). Green: the one open carrier that DOES carry needs-user-decision (#17041,
2 questions) is silent; an earlier census at 03:07Z, with a fuller inbox, read 9 labelled
carriers and 0 reported.

  1. 第 3 条落成一句可执行的规则,写进 PM 协议:执行一次裁决时新产生的问题 ⇒ 另立卡并回链已裁卡,⛔ 不得给已裁卡重挂 needs-user-decision。规则要说明理由(标签说不出"哪个问题",且母卡关闭会带走可见性)。

Two lines in the runbook, both reasons stated:

- 裁决执行中新生的问题另立卡并回链已裁卡,⛔ 不给已裁卡重挂 `needs-user-decision`。
- 理由两条:标签说不出是哪个问题;母卡一关就把这问题的可见性一起带走。
  1. ⛔ 不改 os-dev 报告契约(你已明写:数组是对的地方,缺的是读者)。

.claude/agents/os-dev.md is not in the diff — two files changed, nothing else.

  1. 阴性对照:一张 open_questions 为空数组的卡 ⛔ 不得被报出;一张已带 needs-user-decision 的卡 ⛔ 不得被重复报出。

Both are pinned self-test cases (H52 negative control: …) AND measured live: 22 open reports
and 615 closed reports carry an EMPTY open_questions and produce zero rows between them; the
labelled carrier produces none either. A third state is kept apart from both: a payload that
does not PARSE is UNJUDGED, never an empty array (3 open, 126 closed).

The successor's first item — #13973's three residuals are ANSWERED, not lost

The triage ruling asked whoever picked this card up to establish where the three
open_questions of comment 5573240453 went, because a third instance would mean the defect had
struck again while this card was open. Comment 5577121900 (2026-09-08T00:17:34Z, director
seat, decision batch #80) records the maintainer's reply and rules all three A, A, A — leave
as landed
, item by item. ⇒ No third instance.

⚠️ That ruling is also the reason the row over-reports on purpose: a ruling comment is not a
report, isTriageRulingComment's anchor set does not match it, and teaching the row to
recognise a ruling would be a change to H33 rather than an addition beside it. A card answered
only in prose keeps firing until a later report writes open_questions: [] or the label lands —
both repairs the board needs anyway, and the row's sentence asks for whichever applies.

Verification

Gate union — 42 derived, 42 run, 42 green. Derived with
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (no paths
passed; the script took its own change set from the merge base) at HEAD 75b5ec0a7, exit codes
captured by redirect before any pipe, then reconciled:

Run reconciliation — 42 derived, 42 run, 0 NOT-MEASURED, 0 UNRUN.
✓ dispatch-gates --ran: 42 derived famil(ies) accounted for — 42 run, 0 NOT-MEASURED.

Named in the dispatch and green: check:pm-half-states (self-test, 2856 cases pass),
check:pm-skill-ratchet, check:pm-skill-id-lint, check:skill-frame-sync,
check:pm-governed-merges, check:nul-bytes, plus check:pm-dispatch-gates.

Ablation — the row can fail. h52OpenQuestionsUnrouted was made to return null
unconditionally, the mutation proved on disk before the run (HEAD blob
29babc062217aed2c77c658fcb1953c24cf1d5c0, mutated blob
55dac2ba3afeaf1f3e8484cda5c08f2a54d4b552, marker occurrences 0 → 1), and the self-test went
red: 25 of 2856 cases failed, led by
✗ H52 fires: a non-empty open_questions on a card with no needs-user-decision. Restored with
git checkout HEAD -- scripts/pm/check-half-states.mjs under an EXIT INT TERM trap and proved
byte-identical (worktree blob back to 29babc06…, git diff HEAD empty, marker occurrences 0),
then the self-test re-run green at 2856/2856. Negative-control cases stay green under the
mutation, which is what makes the 25 the row's own.

Line budget (governed text). .claude/skills/pm-dispatch/references/dispatch-runbook.md
241 → 241 lines (ceiling 241, headroom 0), widest new line 110 bytes against the 120-byte
cap. ⛔ No ceiling raise and ⛔ no re-wrap: the two lines are paid for inside the section that
gains them, by deleting content SKILL.md already carries
— the two-carriers line
(「一条裁决已记录的权威载体 = …摘要台账」 + 「卡上裁决评论是详注」, SKILL.md) and the
「时间戳加回忆不是引用」 clause (SKILL.md), whose one surviving increment — an id can be carried
and still not be retrievable — folds into the line above it, whose subject it already is. The
runbook's own header scopes it to increments over SKILL.md, so a line SKILL.md states is
not one. An earlier draft of this branch bought the same two lines by joining two unrelated
line pairs; that is a re-wrap, it deletes nothing, and it was reverted. No skills/**
(published package) file is in the diff, so that surface's two-reading budget does not apply.

Governed surface.

$ node scripts/pm/check-governed-merges.mjs --test scripts/pm/check-half-states.mjs \
    .claude/skills/pm-dispatch/references/dispatch-runbook.md
governed-surface predicate: 1 of 2 path(s) hit the register (5 surfaces, repo-agnostic).
  ⛔  GOVERNED — a human merge is the review record for this PR (#9495 regime).
      No seat flips it ready, enqueues it, or arms auto-merge (AGENTS.md Prime Directive #14).
      One hit governs the whole PR — 「混合 diff 一条命中即整 PR 分叉」; proportion is not a question.
      .claude/** ×1 — the agent instruction tree (skills, agents, hooks, settings)
        - .claude/skills/pm-dispatch/references/dispatch-runbook.md
  paths not on the register: scripts/pm/check-half-states.mjs
(exit 3)

⇒ Draft, human merge. ⛔ Not readied, ⛔ not enqueued, ⛔ no auto-merge. skip-changeset: nothing
published moves — scripts/pm/** and .claude/** are both off every package's files[].

维护者速读(草稿)

改了什么。 给"开发 agent 报告里那个写给您的问题"装了一个读者。过去 dev 把只有您能拍板的问题
写进报告的 open_questions 数组里,而收件箱只认 needs-user-decision 这枚标签 —— 数组里的问题
没有标签,于是它被规规矩矩地提交、然后谁也看不见。现在半状态巡查每轮会把这类卡列出来。同时在 PM
协议里写死一条规则:执行您的裁决过程中新冒出来的问题,另开一张卡并回链已裁的那张,⛔ 不许把标签重新
挂回已裁卡。

为什么改。 这不是"整洁"问题,是"您应得的一个决定从未到达"。本次普查第一次量出了洞的大小:
65 张开放卡上压着 126 个问题,一个都不在收件箱里;已关闭的卡上还有 255 张、436 个问题 ——
那一半连查询都查不到,因为卡一关,标签就跟着走了。已知最长的一例压了 17 天,而那张卡对应的部署
曾整整一天返回 503。这类失效不会自曝:dev 尽了责、报告通过校验、PM 接收了 —— 三个成功信号,一个
错误答案。

风险与代价(含回滚)。 代价接近零:这一行不额外发一次请求,只读别的巡查项已经买回来的评论
线程,读不到就明说"未判定",⛔ 不冒充干净;它也不写任何标签、不阻塞任何东西,只是巡查报告里
多一段清单。有一个刻意保留的过度报告:如果某个问题您已在卡上用中文回答、而没有人再补一份报告
或挂标签,这张卡会继续被列出来 —— 这是有意选的方向,因为它要求的两种收尾(该进收件箱的进收件箱,
已答的记一笔)都是看板本来就欠的。回滚成本:删掉这一行即可,零迁移、零数据形状变更;协议那两行同理。

席位意见。 (留空,待席位定稿)

你要做的。 只有一个动作:人工合并这个 PR(受管面,⛔ 不入队、⛔ 不自动合并)。合并之后,
普查列出的那 65 张卡会由 PM 席位按新规则逐张归位 —— 不需要您逐张看。


🤖 Generated with Claude Code

https://claude.ai/code/session_01HxLw5aKDPR5RJgyUR7Exkd


Generated by Claude Code

…l row

The dev report contract carries an `open_questions` array and nothing read it.
The decision inbox is the `needs-user-decision` label, so a question written
into a report comment's JSON body was filed, well-formed and unreachable.

H52 reads the newest `os-dev-report` on every listed open card whose thread is
already in hand and reports a non-empty `open_questions` on a card carrying no
`needs-user-decision`. It buys no request (H47's shape) and writes nothing.

The residual-question rule lands beside it in the dispatch runbook: questions
raised while executing a ruling go on a new card linking the ruled one, never
back onto the ruled card as a re-hung label.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HxLw5aKDPR5RJgyUR7Exkd
Both corrections came out of the first census rather than from review.

The reader required a ```json fence. Of the 13 open cards whose newest report
it could not parse, most carried the object with no fence at all, and one was a
report whose own `status` was `needs_decision` — the exact card the row exists
to find, reading as UNJUDGED because of a markdown fence. A fence is not part
of the report contract. The open board's unparseable tail falls 13 -> 4.

The marker was case-sensitive on ACCEPT_VERDICT_MARKER's reasoning. The census
found five shipped reports opening `OS-DEV-REPORT`, so CLAIM_COMMENT_MARKER's
precedent applies instead and the flag is a measurement.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HxLw5aKDPR5RJgyUR7Exkd
…spatch

Both were written before the census widened the row and were left behind by it.
`SWEEP_COUNT_KEYS` described `openQuestionCandidates` as OPEN `pm:dispatched`
cards and the self-test block headed its population cases the same way, while
`h52SpeaksAbout` takes every open card on purpose — the census measured 52 of
63 live carriers outside `pm:dispatched`, and the cases directly under that
heading assert exactly that.

Claude-Session: https://claude.ai/code/session_01HxLw5aKDPR5RJgyUR7Exkd
Co-authored-by: Claude <noreply@anthropic.com>
The rule sentence needed two lines in a file at its ceiling, and the previous
round bought them by joining two unrelated line pairs — one of them a pure
re-wrap that deleted nothing. Line merging is not currency for the ratchet.

Both merges are reverted and the two lines are paid for inside the section that
gains them, by deleting content `SKILL.md` already carries: the two-carriers
line (「一条裁决已记录的权威载体 = …摘要台账」 plus 「卡上裁决评论是详注」) and the
「时间戳加回忆不是引用」 clause, whose one surviving increment — an id can be
carried and still not be retrievable — folds into the line above it, whose
subject it already is. The runbook holds increments over `SKILL.md`, so a line
`SKILL.md` states is not one.

241 lines before and after, ceiling 241, widest new line 110 bytes.

Claude-Session: https://claude.ai/code/session_01HxLw5aKDPR5RJgyUR7Exkd
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added the size/l label Sep 9, 2026
@yinlianghui yinlianghui added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 9, 2026 — with Claude
@github-actions github-actions Bot added the documentation Improvements or additions to documentation label Sep 9, 2026

Copy link
Copy Markdown
Contributor Author

维护者速读 — PR #17091(#16662,skills 席终稿,2026-09-09T07:40Z)

改了什么 — 半状态巡查脚本新增一行 report-only 的 H52:一张 open 卡上最新的 os-dev-report 里 open_questions 非空、而卡没挂 needs-user-decision ⇒ 报一行(报告 id、时间、问题数、前两条问题),不写任何东西、不多花请求。派发 runbook 补一条可执行规则:执行裁决时新生的问题另立卡回链已裁卡,⛔ 不给已裁卡重挂 needs-user-decision(标签说不出是哪个问题;母卡一关就把可见性带走)。两文件,runbook 241 行不变。

为什么改 — dev 报告里写在 open_questions 数组的问题,对所有席位都是不可见的。首轮普查(评论 5597739635)量出:65 张 open 卡上挂着 126 个问题,不在任何人的收件箱里;已关闭的卡上还有 255 张 / 436 个。分诊席点名的那个案例(#13973 三条问题)查明是被裁了 A/A/A,不是丢了。

风险与代价(含回滚) — 脚本只报不写;回滚 = revert 本 PR。已知的刻意过报:只在散文里答过的问题会一直报,直到后续报告清空数组或标签落地(docblock 与自测钉住了这个选择)。这条飞行的第一位 dev 03:29Z 限流死亡,续飞的 dev 保留了它的全部产出,并把 runbook 的付账从「合并两对不相干的行」改回「删两句主文件已有的复述」。

席位意见 — 接受。本席在分支头上复核:自测 2856 例通过;消融使 25 例转红、阴性对照保持绿;runbook 241/241、被删两句在 SKILL.md :732–734 都在;id-lint、frame-sync 绿;控制字节 0;mergeable: true;CI 收敛中。

你要做的 — 合并;或由 os-zhuang / hotlong 批准,本席随即入队。另有一项交给下一任席位、不需要你动:那 65 张卡 / 126 个问题的路由。


Generated by Claude Code

@os-zhuang
os-zhuang marked this pull request as ready for review September 9, 2026 07:52
@os-zhuang
os-zhuang enabled auto-merge September 9, 2026 07:52
@os-zhuang
os-zhuang added this pull request to the merge queue Sep 9, 2026
Merged via the queue into main with commit 0874723 Sep 9, 2026
41 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-16662-open-questions-reader branch September 9, 2026 08:29
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 needs-user-decision size/l skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Maintainer questions filed inside a dev report's open_questions array are invisible to every sweep — one sat 17 days, and it is not the only one

3 participants