Repository navigation
Commit c053f74
pm(skills): probe the unexercised CCR routes and the shared MCP user bucket into the REST channel register (#18259)
Fixes #18130
Measurement-first register work on
`.claude/skills/pm-dispatch/references/rest-channel.md`.
The card's readings 2 and 3 (`ccr/ready_for_review`, `PUT
ccr/auto_merge`) were already on
:48 / :51 and are not re-recorded. What this branch owes is one probe
per UNEXERCISED CCR
route named by the session proxy's own 403 body, plus the shared
per-user MCP bucket fact.
Every route below earned its row by being invoked once; nothing here is
written from the
card's text.
Session: `session_01HZfg2AwVX191qCizp88gQr` (skills seat, dev subagent).
All probes ran on
THIS draft PR, 2026-09-15T04:48Z–04:49Z. No probe touched another seat's
PR.
## Probe log
| # | Route | Method / body sent | Status | Body stored / returned |
Read-back |
|---|---|---|---|---|---|
| P1 | `https://api.github.com/graphql` | POST,
`markPullRequestReadyForReview` mutation | **403** | proxy's own body,
629 B, naming the five CCR routes verbatim | n/a — refused before GitHub
sees it |
| P2a | `POST /repos/{o}/{r}/pulls/18259/comments` | `commit_id` +
`path` + `line: 49` + `side: RIGHT` | **201** | review comment `id:
4012020890`, `user.login: claude[bot]` | thread appears in
`ccr/review_threads` |
| P2b | `GET .../pulls/18259/ccr/review_threads` | GET | **200** |
`[{"resolved":false,"outdated":false,"path":"…rest-channel.md","line":49,"comment_ids":[4012020890]}]`
| — |
| P2c | `POST .../ccr/comments/4012020890/resolve` | POST `{}` | **200**
| `{"comment_ids":[4012020890],"resolved":true}` | `review_threads` →
`"resolved":true` |
| P2d | `POST .../ccr/comments/4012020890/unresolve` | POST `{}` |
**200** | `{"comment_ids":[4012020890],"resolved":false}` |
`review_threads` → `"resolved":false` |
| P2e | `POST .../ccr/comments/4012020890/resolve` | POST `{}` | **200**
| `{"comment_ids":[4012020890],"resolved":true}` | `review_threads` →
`"resolved":true` (final state) |
| P3a | `PUT .../pulls/18259/ccr/auto_merge` | PUT
`{"merge_method":"SQUASH"}`, PR in DRAFT | **422** | `{"message":"Pull
request Pull request is a draft"}` | `GET /pulls/18259` → `auto_merge:
null` (nothing stored) |
| P3b | `DELETE .../pulls/18259/ccr/auto_merge` | DELETE, nothing armed
| **422** | `{"message":"Can't disable auto-merge for this pull
request."}` | `GET /pulls/18259` → `auto_merge: null` |
| P4a | `POST .../pulls/18259/ccr/ready_for_review` | POST `{}` |
**200** | `{"draft":false}` | `GET /pulls/18259` → `draft: false`;
timeline `ready_for_review`, actor `claude[bot]` |
| P4b | `POST .../pulls/18259/ccr/convert_to_draft` | POST `{}` |
**200** | `{"draft":true}` | `GET /pulls/18259` → `draft: true`;
timeline `convert_to_draft`, actor `claude[bot]` |
P1's route list, quoted from the 403 body itself rather than from the
card: `GET
/repos/{owner}/{repo}/pulls/{n}/ccr/review_threads`, `POST
/repos/{owner}/{repo}/pulls/{n}/ccr/comments/{comment_id}/resolve` (or
`/unresolve`), `PUT`
or `DELETE /repos/{owner}/{repo}/pulls/{n}/ccr/auto_merge`, `POST
/repos/{owner}/{repo}/pulls/{n}/ccr/ready_for_review`, `POST
/repos/{owner}/{repo}/pulls/{n}/ccr/convert_to_draft`. It matches the
card's quotation
word for word.
### What the probes settled, against the dispatch's assumptions
- **P2 (which id does `resolve` want?)** — the review-COMMENT id.
`ccr/review_threads`
returns no thread id at all: each element carries `resolved`,
`outdated`, `path`, `line`
and `comment_ids`, and `comment_ids[0]` is exactly what the resolve
route accepts. The
register row names which.
- **P3 (auto-merge on a draft)** — the PUT is REFUSED, and the refusal
is the reading: 422,
nothing stored, `auto_merge` still `null` on read-back. The DELETE then
also answers 422
("Can't disable auto-merge for this pull request") because nothing was
armed — so a 422
from DELETE is not evidence of a failed disarm, and the read-back is
what decides.
- **`DELETE` on an ARMED PR stays unmeasured here, deliberately.**
Arming auto-merge would
require a non-draft PR, and this PR's diff touches a governed surface,
where Prime
Directive #14 forbids arming outright. Recorded as not measured rather
than inferred.
- **`convert_to_draft`** was already written as a fact on :48 under a
tick it had never
earned; the probe makes the tick true. No new row is owed for it, so
none is added.
## Rows landed — five, and what paid for each
The file is at its 82-line ceiling with no standing raise exception, so
the currency is
deleted content, never a re-wrap. Line count 82 → 82; bytes 6,738 →
6,965.
| Row added (bytes) | Paid by |
|---|---|
| `- 两只桶:MCP 记链接用户 5000/时…` (102 B) | tick-convention row + the no-tick
prohibition, 108+56 B over 2 lines → 114 B over 1: `不是全局事实` restated
`按席位类别限定`, and `的形状` / `复述` / `一个` went with it |
| `- ✓ 线程 GET …/ccr/review_threads…` (115 B) | check-runs/actions row +
quota-read row, 88+61 → 120 over 1: `端点自身` and `自读` deleted |
| `- 线程自己建:POST …/pulls/{n}/comments…` (109 B) | update-branch row + its
rationale tail, 91+81 → 114 over 1: the `它是…手段` framing deleted, and
`不重写历史` is entailed by `真合并提交` |
| `- ✓ POST …/ccr/comments/{id}/resolve…` (114 B) | the two
`expected_head_sha` rows, 70+85 → 112 over 1: the pinned error prose `no
new commits on the base branch` deleted — nothing parses it, and the
standing rule is not to pin error copy |
| `- ⛔ PUT …/ccr/auto_merge 在 draft 上 422…` (112 B) | bare-`PATCH`-draft
row + the read-back rule, 97+113 → 115 over 1: `timeline 的
ready_for_review` restated the read-side timeline row |
A sixth deletion came first, in its own commit: the two provenance dates
left on the
bare-`PATCH` row and the MCP-fallback row (19 B and 33 B). They are the
class the file's own
rules-only lowering removed; deleting them is what let the rows above
fit.
No rule was dropped. Every merged row keeps both of its rules; what left
the file is
restatement, framing, an entailment and one pinned error string.
## Gates
`node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack`, no paths,
derived 15 families on head `95065f36`; all 15 run in the foreground
with `$?` captured by
redirect before any pipe; reconciled with `--ran`:
```
Run reconciliation — 15 derived, 15 run, 0 NOT-MEASURED, 0 UNRUN.
✓ dispatch-gates --ran: 15 derived famil(ies) accounted for — 15 run, 0 NOT-MEASURED
(a DERIVED zero — all 15 recorded an exit code and none of them is 3).
```
One family first answered `exit 3` (PREREQUISITE NOT MET —
`@objectstack/formula` and
`@objectstack/lint` not built), which is not a red gate; after
`turbo run build --filter=@objectstack/formula
--filter=@objectstack/lint` under the shared
verify lock it exits 0. `check:pm-dispatch-gates` is not in this card's
derivation.
Reverse verification on the head:
- `wc -l` → **82**, equal to the ceiling.
- `awk 'length($0)>120'` → prints nothing, for touched and untouched
lines alike.
- `pnpm check:pm-skill-ratchet` → 0 · `pnpm check:pm-skill-id-lint` → 0
(27 files clean,
pattern `/#[0-9]{3,}/g`) · `pnpm check:pm-governed-prose` → 0.
`pnpm lint` is CI-owned and was narrowed, with the narrowing proved
rather than assumed:
every `files:` selector in `eslint.config.mjs` is a JS/TS extension glob
and none names
`.md`; `eslint --no-inline-config --format json` over the one changed
file reports
`File ignored because no matching configuration was supplied`, 0 errors
and 0 warnings from
rules; and since the diff is a single markdown file outside that
population, no untouched
file's verdict can move.
`skip-changeset`: `.claude/**` is on the fast track — nothing any
package's `files[]` ships
moves here.
## 维护者速读(草稿)
**改了什么** —— PM 席位的 REST 通道对照表新增五条读数:评审线程的三条 CCR 路由(取线程、
解决、取消解决)、auto-merge 在 draft 上的两个拒绝,以及 MCP 与 REST 分属两只限流桶这一事实。
**为什么改** —— 会话代理自己的 403 报文点名了这批路由,但表里此前只有其中两条被实调过。
未实调的路由写进表里就是「未带 ✓ 的形状当已验证事实」,而这正是该表第一节明令禁止的。
这一轮每条路由都在本 PR 自己的 draft 上跑过一次才落行。两只桶那条解决的是另一件事:
席位的 MCP 状态动作会被同一 GitHub 用户下别的会话的读耗光,而 CCR 路由走的是另一只桶,
所以「换通道」在这里是合法退路,不是违规续写。
**风险与代价(含回滚)** —— 只改一个 markdown 参考表,不改任何运行时代码,发布面零变化。
文件行数仍是 82(天花板),字节数 +227。回滚 = revert 本 PR,无迁移、无残留状态。
探针在本 PR 上留下一条评审评论线程(已 resolved)与两次 draft 翻转,PR 终态是 draft、
auto-merge 未挂载。
**席位意见** ——
**你要做的** —— 无需维护者动作:本 PR 的受管路径全部落在 `references/**`,按分层裁定
走席位 contract-tier 复审后进队列。
## Acceptance notes
- noted, not filed: `added_to_merge_queue` now appears three times in
this one file (the
read-side timeline row, the write-side queue-read row, and queue
criterion ②), and the
write-side row still spells `git rev-list --parents` although :66 routes
that spelling to
`platform-readings.md` — against the file's own `⛔ 不在两处各存一份`.
Consolidating it
would free two more lines. Carrier: the next PR that pays density on
this file — the
rules-layer change already queued behind this one touches :53 in the
same block.
- noted, not filed: `DELETE .../ccr/auto_merge` on an ARMED pull request
has no reading in
this repo yet, and cannot get one from a governed-surface PR. It needs a
non-governed
card that legitimately arms auto-merge and disarms it again. Carrier:
none today.
- The probe thread on this PR is left RESOLVED. The PR ends draft,
`auto_merge: null`.
---
_Generated by [Claude Code](https://claude.ai/code)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent b722547 commit c053f74
1 file changed
Lines changed: 11 additions & 11 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
5 | 5 | | |
6 | 6 | | |
7 | 7 | | |
8 | | - | |
| 8 | + | |
9 | 9 | | |
10 | 10 | | |
11 | 11 | | |
| 12 | + | |
12 | 13 | | |
13 | 14 | | |
14 | | - | |
15 | 15 | | |
16 | 16 | | |
17 | 17 | | |
| |||
26 | 26 | | |
27 | 27 | | |
28 | 28 | | |
| 29 | + | |
29 | 30 | | |
30 | 31 | | |
31 | | - | |
32 | | - | |
| 32 | + | |
33 | 33 | | |
34 | 34 | | |
35 | 35 | | |
| |||
41 | 41 | | |
42 | 42 | | |
43 | 43 | | |
44 | | - | |
45 | | - | |
46 | | - | |
47 | | - | |
| 44 | + | |
| 45 | + | |
48 | 46 | | |
49 | | - | |
50 | | - | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
51 | 50 | | |
| 51 | + | |
52 | 52 | | |
53 | | - | |
| 53 | + | |
54 | 54 | | |
55 | 55 | | |
56 | 56 | | |
| |||
0 commit comments