Skip to content

docs(pm): base the REST draft-flip row on a probe, not the docs - #17597

Merged
os-zhuang merged 2 commits into
mainfrom
claude/issue-17582-rest-draft-flip-silent-200
Sep 11, 2026
Merged

os-zhuang merged 2 commits into
mainfrom
claude/issue-17582-rest-draft-flip-silent-200

Conversation

@os-litant

@os-litant os-litant commented Sep 11, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #17582

.claude/skills/pm-dispatch/references/rest-channel.md, 不可迁移 item 1 — three lines rewritten in
place. The verdict is unchanged (the draft-to-ready flip is a GraphQL-only mutation; the egress
proxy serves only the pinned PR-review GraphQL set). Only the basis moves: from documentation to
a dated probe. Net 0 lines, 82 / 82 on the ratchet, no line over the 120-byte cap.

The row, before and after

Before (bytes per line: 100 / 111 / 109):

1. draft 转 ready 翻转:GraphQL-only mutation;出口代理只放钉住的 PR-review GraphQL 集。
   判据 = REST update-a-pull-request 只收 `title`/`body`/`state`/`base`/`maintainer_can_modify`,无 `draft`
   (与第 5 条同批核对官方文档,未逐个实调)。断粮出路:等 MCP 恢复,或人工点一下。

After (bytes per line: 100 / 119 / 119):

1. draft 转 ready 翻转:GraphQL-only mutation;出口代理只放钉住的 PR-review GraphQL 集。
   判据 = 2026-09-11 实调:REST `PATCH /pulls/{n}` 带 `{"draft": false}` 回 200 且什么都没改,读回仍 draft
   ⇒ 状态码不作数,读回才作数。通道 = MCP `update_pull_request`;断粮:等它恢复或人工点一下。

Line 1 is byte-identical. Line 2 swaps the documentary field list — which the card's own triage ruled
must not be asserted on this evidence, since only draft was probed — for the dated measurement.
Line 3 states the consequence that the old row did not: a success code proves nothing, so the
read-back is the criterion; and it names the channel that does work instead of implying it.

Patch round 1 — 不可迁移 item 5, one token wider. The parenthetical deleted above was the only
place in the file disclosing that item 5 (issue transfer as GraphQL-only) also rests on a
documentation check rather than a probe; item 5 did not say so itself. The skills seat widened the
declared region by one token rather than open a card, so the disclosure now sits on item 5, where a
reader of item 5 actually meets it — before (82 B), then after (105 B, cap 120):

5. `issue transfer`:issues 端点表无 transfer 路由 ⇒ 同为 GraphQL-only。
5. `issue transfer`:issues 端点表无 transfer 路由(核对文档,未实调)⇒ 同为 GraphQL-only。

Item 5's continuation line, and every other line in the file, stays byte-identical; the file stays
82 / 82 and the widest-table-row pin stays 0.

The source of the measurement

Not re-derived here — re-running a REST draft flip would touch PR state this branch did not set. The
reading is the filer's, quoted from the card body (domain:spec execution seat,
session_01MkQhmuuJAVDjmeWNixwDDH, measured on PR #17572 at 2026-09-11T00:12Z):

PATCH /repos/objectstack-ai/objectstack/pulls/17572   {"draft": false}
  → HTTP 200                     ⬅ a success code
  → readback: draft = true       ⛔ the field was silently ignored
  → body_len unchanged (7123)    (the PATCH disturbed nothing else)

⭐ Why the distinction earns a row. A documented absence tells a seat "don't bother". A
measured silent 200 tells it something stronger: any seat that flips a draft over REST and
reads the status code will record the PR as ready and move on
, and the PR will sit in draft until
someone notices.

Two readings carried on the card are deliberately not written into the row, because neither is
evidence about this endpoint: the GraphQL markPullRequestReadyForReview 403 is a structural refusal
by the pinned-operation proxy (already what line 1 says), and the MCP rate-limit reading belongs to
the identity-versus-client question, which is open elsewhere and remains open. The working channel is
named on the seat's own corroboration — every flip this shift went through MCP update_pull_request
and was read back.

The one judgement, on the four axes

The only judgement in this PR is the wording of the basis, and specifically whether to keep the
unmeasured field list (title/body/state/base/maintainer_can_modify) or spend those bytes on
naming the working channel. 实际业务需求: the field list was never probed and the row said so —
the triage explicitly forbade widening the row to those fields on this evidence, so it is a list no
seat may cite; what a seat actually needs at the instant it reads this row is which call to make, and
update_pull_request is measured (every flip this shift, read back each time). 项目长远合理性:
the row now states exactly what was measured, dated, and nothing beyond it — no invented REST path,
no hedge. 防 AI 写错 is the axis that decides it: a documented absence invites the tolerant
reading "REST will refuse and I will see the error", which is precisely the inference the measurement
falsifies; writing 「状态码不作数,读回才作数」 tightens the criterion at the point of authorship
rather than leaving a 200 to be read as landing — the metadata-shaped failure here is a seat
recording a ready flip that never happened. 创业阶段不扩散: no new capability, no second line, no
ceiling raise, net 0 at 82 / 82 — and the unmeasured half is retired immediately rather than kept as
a hedged dual basis. ⇒ Recommendation, on axes three and four: the landed wording.

Verification

Gate families derived from the final diff in the worktree —
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (no paths; the
script derives its own changeset). Its stderr names the tree it answered for:
gate list derived from the tree of 'objectstack-ai/objectstack' at commit 29d00cc5, change set
1 path(s) vs merge base 29d00cc53 (three-dot). Every exit code below was captured before any
pipe
(cmd > file 2>&1; EXIT=$?). Run at head 776d4491, then re-run in full at the patch
round 1 head 00297d13: the same 14 families, the same exit codes, and verdict lines byte-identical
to the first run. The derivation at 00297d13 warns STALE TREE (the branch is 4 commits behind
origin/main, 3 family-defining files having moved). That was measured rather than waved past: the
same changeset derived from a clean tree at origin/main 82cb69fe returns an identical
14-family list, so nothing landed in that window that this diff owes.

# command exit
1 node scripts/check-closing-keyword-parity.mjs 0
2 node scripts/check-closing-keyword-parity.mjs --self-test 0
3 node scripts/check-comment-mask-corpus.mjs 0
4 pnpm --filter @objectstack/lint run check:doc-formula-expressions 0
5 pnpm check:agent-test-spelling 0
6 pnpm check:doc-authoring 0
7 pnpm check:driver-memory-census 0
8 pnpm check:nul-bytes 0
9 pnpm check:pm-governed-merges 0
10 pnpm check:pm-skill-id-lint 0
11 pnpm check:pm-skill-ratchet 0
12 pnpm check:refd-timer-probe 0
13 pnpm check:skill-frame-sync 0
14 pnpm check:watch-hint-literal 0

Each gate's own verdict line, for the ones this diff can move:

  • pnpm check:pm-skill-ratchet — ✓ check-skill-line-ratchet: .claude/skills/pm-dispatch/references/rest-channel.md is 82 lines (ceiling 82; headroom 0). and ✓ check-skill-line-ratchet: .claude/skills/pm-dispatch/references/rest-channel.md: widest table row is 0 bytes (pin 0; headroom 0). — byte-identical before and after the edit.
  • pnpm check:pm-skill-id-lint — ✓ check-skill-id-lint: 27 file(s) clean (pattern /#[0-9]{3,}/g). — identical before and after; the new text carries no internal id.
  • pnpm check:nul-bytes — check-nul-bytes: OK (scanned 8349 text file(s) -- 8349 tracked, 0 untracked-not-ignored; skipped 7 binary; no raw ASCII control bytes). Plus a self-scan of the edited file outside the gate: grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]' exits 1 (clean).
  • pnpm check:doc-authoring — ✓ doc authoring guard: 44 published skill files clean — no internal issue-id references.
  • pnpm check:skill-frame-sync — ✓ check-skill-frame-sync: the one declared copy of the decision frame is internally coherent.
  • node scripts/check-closing-keyword-parity.mjs — check-closing-keyword-parity: OK (3 parsers agree on all 9 keywords and both measured separators; sweep found 5 file(s) carrying the grammar across 8356 tracked file(s), all registered).

Reconciliation — node scripts/pm/dispatch-gates.mjs --ran RANFILE --repo objectstack-ai/objectstack,
with each family recorded as command :: exit 0:

✓ dispatch-gates --ran: 14 derived family(ies) accounted for — 14 run, 0 NOT-MEASURED
  (a DERIVED zero — all 14 recorded an exit code and none of them is 3).

One honest wrinkle, recorded rather than smoothed: family 4 first exited 3 —
check-doc-formula-expressions: PREREQUISITE NOT MET — the workspace package @objectstack/formula is not built … Nothing was measured. That is a NOT-MEASURED code, not a finding. It was cleared by
building the two prerequisites through the shared verify lock
(bash scripts/pm/os-verify-lock.sh -c 'pnpm exec turbo run build --filter=@objectstack/formula --filter=@objectstack/lint --concurrency=2', VERDICT command-exit 0 · held the lock 224s), after
which the family re-ran green with the verdict
✓ check:doc-formula-expressions: 22 record-scoped formula example(s) across 438 files / 1375 TS blocks judged clean.

Path face — node scripts/pm/check-governed-merges.mjs --branch claude/issue-17582-rest-draft-flip-silent-200,
exit 3 (the GOVERNED verdict code):

⛔  GOVERNED — a human merge is the review record for this PR (#9495 regime).
    .claude/** ×1 — the agent instruction tree (skills, agents, hooks, settings)
      - .claude/skills/pm-dispatch/references/rest-channel.md

⇒ this PR stays draft; no seat flips it ready, enqueues it, or arms auto-merge. The maintainer
merges.

Closing-keyword parity on this body, run against the exact file that became this description —
node scripts/check-closing-keyword-parity.mjs --body BODYFILE, exit 2 (the register's "at least
one parser binds"; 1 would mean the instrument did not run):

duplicate-fix-guard          (same-repo)   KEYWORD #17582
h7-partof-closing-keyword    (bare)        KEYWORD #17582

check-closing-keyword-parity --body: 2 closing declaration(s) in pr-body.md, bound by 2 of 3
registered parser(s).

⇒ exactly one closing declaration — the one on line 1 of this body — reported once per parser
that recognises it. ⚠️ In the quotation above the closing verb is written as KEYWORD: the parsers
read fenced text too, so quoting the real output verbatim would have made this description declare
the close four times over instead of once. That was measured, not assumed — the verbatim quotation
took the count to 8 declarations across the same 2 parsers, and this spelling takes it back to 2.
Every other card number in this body sits beside zero verbs, which is the safe spelling.

Changeset: none, skip-changeset applied. .claude/** is on the fast track — nothing any
package's files[] ships moves in this diff, which touches exactly one instruction-surface markdown
file and no package source.

Clause-②: no

Acceptance notes

维护者速读(草稿)

改了什么 —— rest-channel.md 不可迁移第 1 条的三行原地重写:结论不变(draft 转 ready 是
GraphQL-only mutation,出口代理只放钉住的 PR-review GraphQL 集),换掉的是判据。原来的判据是
「核对官方文档、未逐个实调」;现在是 2026-09-11 的实测:REST PATCH /pulls/{n} 带
{"draft": false} 回 HTTP 200 且什么都没改,读回仍是 draft。行数不变(82 / 82),每行不超
120 字节。补丁轮追加一处:第 5 条(issue transfer)行尾补进「(核对文档,未实调)」,82 →
105 字节。

为什么改 —— 「文档说不支持」和「实测回 200 却什么都没做」是给读者的两条不同指令。前者让人以为
会看到一个错误;后者说明:只看状态码的席位会把 PR 记成 ready 然后走人,而 PR 一直躺在 draft 里,
直到有人发现。这次抓住它的是读回,不是状态码 —— 所以新行把「读回才作数」写成判据,并点名真正
可用的通道 MCP update_pull_request。

风险与代价(含回滚) —— 风险低:纯事实表一行,无代码、无已发布内容、无 changeset。代价是删掉了
原来那串未实测的字段清单(title/body/state/base/maintainer_can_modify),这串本来就只
有文档背书,分诊席也明确禁止据本次证据把结论推广到这些字段。那句被删的括号同时是文件里唯一提示
第 5 条也未实调的地方 —— 席位在补丁轮把区域放宽一个 token,这处披露已落到第 5 条自己身上,
读第 5 条的人现在直接读得到,没有丢失。回滚 = 两个 commit revert,无迁移、无下游。

席位意见 ——

你要做的 —— 这是受管面(.claude/**),PR 保持 draft,由你人工合并。看一件事:措辞是否就是
你要的口径 —— 第 1 条那句「状态码不作数,读回才作数」,以及第 5 条补进的「(核对文档,未实调)」。


Generated by Claude Code

`rest-channel.md` 不可迁移 item 1 recorded the draft-to-ready flip as
REST-unsupported on documentation — its own words, 「与第 5 条同批核对官方
文档,未逐个实调」. It is measured now, and the measurement is stronger than
the doc implied: REST `PATCH /pulls/{n}` with `{"draft": false}` answers
HTTP 200 and changes nothing (read-back still `draft = true`, body
untouched). A success code therefore proves nothing and the read-back is
the only tell — the failure mode a documented *absence* does not warn
about, because a reader pictures an error they would notice.

The verdict is unchanged (GraphQL-only mutation; the egress proxy serves
only the pinned PR-review GraphQL set) and the 断粮出路 stays; only the
basis moves, and the working channel is now named rather than implied.

Two lines rewritten in place, net 0 lines (82/82 on the line ratchet),
each line within the 120-byte cap.

Claude-Session: https://claude.ai/code/session_01YKEjmbYNvYWJvWGSWx26zK
Co-authored-by: Claude <noreply@anthropic.com>
@os-litant os-litant added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 11, 2026 — with Claude
@github-actions github-actions Bot added the documentation Improvements or additions to documentation label Sep 11, 2026
The parenthetical the previous commit deleted from item 1 was the only
place in `rest-channel.md` disclosing that item 5 — `issue transfer` as
GraphQL-only — also rests on a documentation check rather than a probe.
Item 5 did not say so itself, so that disclosure would otherwise exist
nowhere in the file. It now sits on item 5, where a reader of item 5 will
actually meet it.

One line, one token wider: line 61 goes 82 to 105 bytes, the file stays
82 lines at ceiling 82, and every other line — item 5's continuation
included — is byte-identical.

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

Copy link
Copy Markdown
Contributor Author

维护者速读

改了什么 —— rest-channel.md 不可迁移表两处原地改写,行数不变(82 / 82):第 1 条(draft 转 ready)三行重写——结论不变(GraphQL-only mutation,出口代理只放钉住的 PR-review GraphQL 集),换掉的是判据:原来是「核对官方文档、未逐个实调」,现在是当天实测——REST PATCH /pulls/{n} 带 {"draft": false} 回 200 且什么都没改,读回仍是 draft;新行写明「状态码不作数,读回才作数」,并点名真正可用的通道 MCP update_pull_request,断粮出口保留。第 5 条(issue transfer)行尾补进「(核对文档,未实调)」(82 → 105 字节)——原第 1 条被删的那句括号是文件里唯一提示第 5 条也未实调的地方,补丁轮把披露落回第 5 条自己身上,没有事实丢失。

为什么改 —— 「文档说不支持」和「实测回 200 却什么都没做」是给读者的两条不同指令:前者让人等一个错误;后者说明只看状态码的席位会把 PR 记成 ready 然后走人,PR 却一直躺在 draft 里。本席当晚就被它咬过一次(#17582 的起因),抓住它的是读回,不是状态码——所以判据写成「读回才作数」。

风险与代价(含回滚) —— 纯事实表六行(+3/−3),无代码、无已发布内容、无 changeset;棘轮两行读数本席在 head 00297d13 上实测与 main 逐字相同,id-lint 27 文件干净;CI 19 过 / 15 跳 / 2 在跑,无失败。代价是删掉了原来那串未实测的字段清单(title/body/state/base/maintainer_can_modify)——它本来只有文档背书,分诊席也禁止据本次证据把结论推广到这些字段。回滚 = revert 两个 commit,无迁移、无下游。

席位意见 —— 通过。两轮共四行本席逐字读过、逐行量过;第 5 条那一个 token 是本席在复审时补的区域放宽(REWORK 5628341593),不是 dev 越界。受管面(.claude/**):本席不翻 ready、不入队、不批准。dev 另测到一条平台事实——PR 正文每次 REST PATCH 都会追加 58 字节的裸页脚,「不累加」只对从不回送已存正文的作者成立——归 platform-readings.md(#17239 那张卡),本 PR 不碰。

你要做的 —— 一个动作:人工合并本 draft PR。看一眼措辞是否就是你要的口径——第 1 条「状态码不作数,读回才作数」,第 5 条「(核对文档,未实调)」。

skills 席,session session_01YKEjmbYNvYWJvWGSWx26zK,2026-09-11T02:07Z。


Generated by Claude Code

@os-zhuang
os-zhuang marked this pull request as ready for review September 11, 2026 06:32
@os-zhuang
os-zhuang enabled auto-merge September 11, 2026 06:32
@os-zhuang
os-zhuang added this pull request to the merge queue Sep 11, 2026
Merged via the queue into main with commit 76c9fab Sep 11, 2026
43 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-17582-rest-draft-flip-silent-200 branch September 11, 2026 07:15
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/xs skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

3 participants