Skip to content

docs(pm-dispatch): fold the engine, services and cli lanes into domain:server - #25247

Draft
objectstack-fleet[bot] wants to merge 5 commits into
mainfrom
claude/project-thread-wjmmlj
Draft

objectstack-fleet[bot] wants to merge 5 commits into
mainfrom
claude/project-thread-wjmmlj

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

Requested by Elon · project thread

Fixes #24907

Clause-②: no
Metadata-protocol: none

Summary

Implements the maintainer ruling on #24907 (comment 6341410910, verbatim: 「Maintainer ruling: 乙. engine, services and cli fold into domain:server, and packages/qa moves to domain:devx.」), on the six-point scope the card states.

  1. SKILL.md 域车道 table — the domain:engine, domain:services and domain:cli rows become one domain:server row (「服务端:引擎 + 服务插件 + CLI/运行时」) scoped to the union of the three; packages/qa moves to the domain:devx row — and to keep that row under the width pin after the insertion, its path spellings were shortened to the table's own bare-prefix convention (content/docs/** → content/docs, docs/qa/** → docs/qa, .githooks/ → .githooks, scripts/ → scripts, .github/workflows/ → .github/workflows) and 「按锚定规则例外归」 → 「按锚定规则归」; meaning unchanged, the three still route to domain:spec, and no script parses the row (disclosed on the contract review's note 1); the retired-label rule takes one bullet that reads the three old spellings as domain:server. The file goes 319 → 318 lines and its widest row 342 → 341 bytes; both ceilings are lowered, nothing is raised. (After merging main with docs(pm-dispatch, os-dev): the claim template and the dev definition carry the Metadata-protocol: line beside Clause-②: #25240, which had paid one line down without moving the ceiling, the ceiling follows the landed count: 317/317.)
  2. Lane charters — references/lanes/{engine,services,cli}.md → references/lanes/server.md, three sections (one per former surface), with the rule that a seat reads only the section its claim's File surface: names. The three per-lane spec red lines became one lane-wide line and the two pointers between the former lanes left, so the merged file lands at 87 lines against the 88 the three rows summed to. lanes/spec.md's /meta pointer now names domain:server.
  3. scripts/pm/ensure-pm-labels.sh — creates domain:server; the three retired labels keep an alias row each so --reconcile writes a Retired 2026-10-22, alias of domain:server (… surface); in-flight cards keep it until they land description onto the live objects (≤100 chars each; the header's retired-lanes rule records them as the one transitional exception).
  4. Aliases in the readers — scripts/pm/check-half-states.mjs gains LANE_LABEL_ALIASES + canonicalLane(), and every place the sweep compares a card's lane with a seat's goes through it: seatLane (a post not yet retitled speaks for domain:server), newestLaneClaim (both directions), the H32 lane inventory and the census (deduplicated, so a card carrying an old and the new spelling is one lane). In-flight claims, branches and Seat: lines stay valid; nothing is relabelled or retitled here.
  5. Seat posts — not touched; each seat-holder retitles at their next round.
  6. No new gate. check-skill-line-ratchet.mjs only follows the merge in its ledger (ceilings, widest-row pins, the platform-readings cross-file-move sources, where lanes/server.md at was 97 = 35 + 30 + 32 carries the two merged sources' place so the 9-line decrease they had paid stays counted). Total ceilings down 488 lines.

Point 4 premise, measured: check-skill-line-ratchet.mjs and clause2-line.mjs have no label reader (clause2-line mentions domain:cli once, in a comment); the only code that compares the three labels is check-half-states.mjs, so the alias lives there alone. Fixtures naming the old labels in check-merge-queue-triage-outcome.mjs and osv-base-relative.mjs are test data, not readers, and stay.

Self-tests: check-half-states.mjs registers a new battery domain:server lane aliases (pinned at 22, 24 cases: the three aliases, pass-through controls for a live lane / a non-lane label / a non-string, the frozen map, the seat title in both spellings, the claim index in both directions with OR-not-AND and pm:queue controls, the census with its one-lane-not-two case) and the roster floor rises 9 → 10 with it. Three H38 cases whose fixture lane is the retired domain:services spelling now expect the merged spelling (the specimen post stays as measured); the "lane with no dispatched cards" control moved from domain:engine (now the same lane) to domain:skills.

CI fix, pushed after the contract review (Elon, 2026-10-22: 「Fix CI」) — commit d062675, scripts/pm/board-snapshot.mjs only. The Snapshot the board to the archive branch check was red on this PR, and on every scheduled run since 2026-10-21 (#24351), with a count-check SHORTFALL of the whole board. Measured 2026-10-22: GitHub's issues listing answers 200 [] to more than half of the requests made to it (39 of 68 probes; each with its own request id and counted against the rate limit, so not a cache; cache directives, a cache-busting query, sort, direction, page size, state and since make no difference; objectui and hotcrm blank the same way), while /repos/{repo}, the pulls listing and the per-number comment pages never did. The tool read a blank first page as the short page and declared the open set complete at zero records.

The fix: a blank listing page is asked again (same URL and page size, up to BLANK_PAGE_ASKS = 6 budgeted asks, no sleep); a blank that survives the bound is believed past an anchor (the cursor does not advance, so the next run re-reads from it) and, from the beginning, only when the board's own open_issues_count — read once as the witness — is zero. Otherwise the walk stops with its cursor (stopped_by: blank-listing), nothing is marked complete, the count check stays pending and the run exits 0 as a planned stop, which the freshness row turns red if it becomes a streak. The manifest counts the re-asks (run.blank_pages_reasked), the summary prints the count and the stop, a new header section records the measurement, and a new self-test battery drives the measured failure healed, the bounded stop and the anchored blank (13 cases, floor 12 → 13, 169 cases). The PR leg's own arguments (--dry-run --limit=5 --max-requests=25) ran six times against the live board: six exits 0 — one a blank-listing stop, two after re-asks. This supersedes the standing-down comment on this PR (6344588150), which read the failure as main's own: the cause is upstream, the handling of it is this PR's. The workflow is untouched (Tier H).

维护者速读(草稿)

改了什么

  • 车道表三行(engine / services / cli)合为一行 domain:server,范围是三者的并集;packages/qa 挪到 devx 行(为守住最宽行 pin,该行路径拼写改为表内惯用的裸前缀,如 content/docs/**→content/docs,「例外归」→「归」,语义不变);加一条「三旧标签读作 domain:server」。SKILL.md 319→318 行(合入 main 上 docs(pm-dispatch, os-dev): the claim template and the dev definition carry the Metadata-protocol: line beside Clause-②: #25240 后随落地行数再降到 317),最宽行 342→341,只降不升。
  • 三份车道岗位说明合并为 references/lanes/server.md(87 行,三节按文件面分读);spec.md 里 /meta 的指向改为 domain:server。
  • ensure-pm-labels.sh 建 domain:server;三旧标签改「retired, alias of domain:server」描述,对象保留。
  • check-half-states.mjs 加别名表:席位贴标题、认领索引、H32 库存、普查都把三旧标签读作 domain:server;自检新增一个电池,地板 9→10。
  • 棘轮台账跟着改(ceilings、最宽行 pin、跨文件搬迁来源),ceilings 总量再降 488 行。
  • 审查后追加(Elon「Fix CI」,提交 d062675):scripts/pm/board-snapshot.mjs 的空页规则。GitHub 的 issues 列表接口自 10-21 起间歇回 200 [](68 次探针 39 次空,请求 id 各异、计入配额,不是缓存;objectui、hotcrm 同样),工具把空页当「走完」、开放集记 0 条、count check 误报 SHORTFALL。现在同一页最多再问 6 次;从头开始仍空、而 /repos 见证板上有开放项,就带游标停下(退出 0,count check 保持 pending);带 since 或偏移页问满才信(游标不前进,下轮重读)。manifest 记 run.blank_pages_reasked,表头新增一节记录测量,自检新电池 13 例。工作流未动。

为什么改

裁决乙(#24907 评论 6341410910)原话:「engine, services and cli fold into domain:server, and packages/qa moves to domain:devx」。三条车道共用一批席位账户,分三贴意味着同一个人读三份岗位说明、认领走三个标签;合并后一席一贴一说明。

风险与代价(含回滚)

  • 过渡期四种拼写并存:在途卡保留旧标签、席位贴由各席自行改标题、开着的卡由分诊事后批量改签。别名表让巡逻不会把它们当四条车道报假警;⛔ 它不替任何人改签。
  • 合并说明把三条 spec 红线合成一条、去掉两条车道间互指,规则本身没有删;读者只读自己那一节。
  • 回滚:整 PR revert 即可——标签对象没有删,三旧标签的描述由下一次 ensure-pm-labels.sh --reconcile 恢复;别名表、新电池、地板一起退回。
  • 没有新门禁;棘轮只降不升,不需要维护者裁决。
  • 空页规则的代价:每个空页最多多花 6 个请求(主干每轮 800 预算内);停下即下一轮重读,不标记完成。若 GitHub 持续回空,主干轮次以 pending 退出 0,由 freshness 行在超窗后转红——这是设计内的告警路径,不是静默。

席位意见

(留空)

你要做的

  • 读 .claude/** 的 diff(Tier S):车道表那一行、lanes/server.md 的三节标题与共用红线、别名条目。
  • 也读 scripts/pm/board-snapshot.mjs 的 diff(受治理的 scripts/pm/**):表头新节、blankPageStep、walk 内的再问/停下、新电池;席位需对新头重做契约复核。
  • 过契约复核后由席位落地;落地后分诊把开着的 engine/services/cli 卡批量改签为 domain:server,各席在下一轮改席位贴标题,再在仓库跑一次 scripts/pm/ensure-pm-labels.sh --reconcile 把三旧标签的描述写上去。

Acceptance notes


Generated by Claude Code

os-elon-musk and others added 2 commits October 22, 2026 15:13
…n:server

Maintainer ruling 乙 on card #24907 (comment 6341410910, verbatim:
「Maintainer ruling: 乙. engine, services and cli fold into
`domain:server`, and `packages/qa` moves to `domain:devx`.」).

- SKILL.md 域车道 table: the three rows become one `domain:server` row
  scoped to their union; `packages/qa` moves to the devx row; the
  retired-label rule takes one bullet reading the three old spellings
  as `domain:server`. 319 -> 318 lines, widest row 342 -> 341 bytes,
  both ceilings lowered.
- references/lanes/{engine,services,cli}.md -> references/lanes/server.md,
  three sections (one per former surface); a seat reads only the section
  its claim's file surface names. The three per-lane spec red lines fold
  into one and the two intra-lane pointers leave: 88 summed -> 87.
  lanes/spec.md's `/meta` pointer now names `domain:server`.
- check-skill-line-ratchet.mjs: ceilings, widest-row pins and the
  platform-readings cross-file-move sources follow the merge
  (`lanes/server.md` enters at `was` 97 = 35 + 30 + 32, so the decrease
  the two merged sources had paid stays counted). Ceilings down 488.
- check-half-states.mjs: LANE_LABEL_ALIASES + canonicalLane; seatLane,
  newestLaneClaim, the H32 inventory and the census compare lanes
  through it, so in-flight claims, branches and `Seat:` lines stay
  valid. New self-test battery `domain:server lane aliases` (24 cases,
  pinned 22); roster floor 9 -> 10.
- ensure-pm-labels.sh: creates `domain:server`; the three retired labels
  keep alias rows carrying a "Retired 2026-10-22, alias of
  domain:server" description for --reconcile.

check-skill-line-ratchet.mjs and clause2-line.mjs have no label reader,
so the alias lives in check-half-states.mjs alone. Seat posts are
retitled by their holders and open cards relabelled by triage after
landing. No new gate.

Claude-Session: https://claude.ai/code/session_01VCLHrrmPbwQHZcc2yqdFHD
Co-authored-by: Claude <noreply@anthropic.com>
…as strings

The self-test harness judges `actual === expected`, so the cases of the
new `domain:server lane aliases` battery that read an array or a pair
now join them to one string before the comparison. Three H38 cases
whose fixture lane is the retired `domain:services` spelling expect the
merged spelling the readers now return, and the "lane with no dispatched
cards" control moves from `domain:engine` (the same lane since the
merge) to `domain:skills`. 4941 cases pass; nothing outside the
self-test changes.

Claude-Session: https://claude.ai/code/session_01VCLHrrmPbwQHZcc2yqdFHD
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet objectstack-fleet Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Oct 22, 2026
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation labels Oct 22, 2026
os-elon-musk and others added 2 commits October 22, 2026 15:24
Merging `main` brought #25240 (the claim template's `Metadata-protocol:`
line), which landed one line under SKILL.md's ceiling and left the
ceiling alone. With this card's row already in the ledger, the ceiling
takes the landed count: 318 -> 317, headroom 0. Nothing else changes.

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

Copy link
Copy Markdown
Contributor Author

CI: Snapshot the board to the archive branch is red on this PR, and it is not this PR's. Standing down on it; nothing here is being fixed by a push.

  • What fails: the pull_request leg of board-snapshot.yml (it runs because this PR touches scripts/pm/check-half-states.mjs, one of its paths:). Under its PR arguments (--out=board --dry-run --limit=5 --max-requests=25) the tool exits 2: count check SHORTFALL of 118 — the archive holds 0 open issue(s) where the board reports 118, after reading 5 numbers of the history walk of a fresh archive (run 39804623899 on c5c1735, same on 56b333e).
  • Why it is not this PR's: the same command on origin/main (80ec71dc26, this PR's diff absent) exits 2 with the identical shortfall — reproduced locally with PM_SWEEP_REPO=objectstack-ai/objectstack node scripts/pm/board-snapshot.mjs --out=/tmp/board --dry-run --limit=5 --max-requests=25. The scheduled runs on main have exited 2 on the count check five times in a row since 2026-10-21 (latest 39795166014). board-snapshot.mjs imports only EXIT_PREREQUISITE_NOT_MET, PROXY_FLAG, labelNames, proxyRearmPlan and resolveSweepRepo from check-half-states.mjs; none of those changes in this diff (it adds LANE_LABEL_ALIASES / canonicalLane after labelNames and routes the lane comparisons through them).
  • Fix ported: none exists yet that I know of — the count-check failure of the scheduled leg is tracked on tooling(board-snapshot): the scheduled Board Snapshot failed its count check twice running (exit 2 at 08:25Z and 14:18Z on 2026-10-18): the archive and the live board disagree on the open-issue count #24351 (open, pm:awaiting-maintainer), and no PR is open against it. A fresh dry-run archive under --limit can never hold the board's open count, so the PR leg fails by construction on today's main; that is the tool's (or the workflow's PR-mode arguments') to repair, in its own card, not this PR's.
  • Re-run: not spent — the failure is deterministic (reproduced on main's own code), so a re-run would only repeat it.

Also seen: TypeScript Type Check red on 56b333e is the aggregator reading two lanes as cancelled because the push of c5c1735 superseded that run (39804119528, run conclusion cancelled); the run on c5c1735 is the one that counts.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: c5c17359be5db4bd08a06b228f8550df204b3faa
Local-runs: none
Scope: .claude/skills/pm-dispatch/SKILL.md, .claude/skills/pm-dispatch/references/lanes/cli.md, .claude/skills/pm-dispatch/references/lanes/engine.md, .claude/skills/pm-dispatch/references/lanes/server.md, .claude/skills/pm-dispatch/references/lanes/services.md, .claude/skills/pm-dispatch/references/lanes/spec.md + scripts/pm/check-half-states.mjs (LANE_LABEL_ALIASES / canonicalLane and every lane comparison it feeds: seatLane, newestLaneClaim, censusLanesOf, the H32 inventory, the H38 fixtures and the new battery), scripts/pm/check-skill-line-ratchet.mjs (CEILINGS, CROSS_FILE_MOVES platform-readings sources, MAX_TABLE_ROW_BYTES), scripts/pm/ensure-pm-labels.sh (domain-lane block, header rule), .claude/skills/pm-dispatch/references/lanes/devx.md (read, unchanged: its scope defers to the SKILL.md row), .github/workflows/board-snapshot.yml on main (the red check's PR leg), packages/plugins, packages/drivers, packages/services, packages/connectors (tree listing on the head, for the union-scope check)

Inputs: card #24907 body and all four comments (binding ruling 6341410910, letter 乙; dev report 6344765480), PR #25247 body, file list (9 paths) and the net diff origin/main...c5c1735 (merge-base 80ec71dc26), the 50 check-runs on the head, the annotations API of the one red job and of main's own run of the same workflow. Not read: the dispatch order or the seat's conclusions.

① Derived judgments

  1. No Tier H path touched — right. The committed file list is 6 paths under .claude/skills/pm-dispatch/** and 3 under scripts/pm/**. AGENTS.md, CLAUDE.md, docs/NORTH-STAR.md, skills/**, .github/workflows/**, CODEOWNERS: absent. Governed-path face ⇒ this tier; the Governed Surface Queue Guard check is green.
  2. SKILL.md lane table: the domain:server row is the exact union of the three old rows — right. Old engine ∪ services ∪ cli = objectql, core, formula, plugin-pinyin-search, metadata*, platform-objects, drivers/driver-, services/, connectors/, triggers/, plugin-approvals/webhooks/email/reports, embedder-openai, knowledge-, plugin-auth/security/sharing/audit, cli, runtime, verify, qa, types, rest, mcp, observability, client, cloud-connection, create-objectstack, adapters/, plugin-hono-server, plugin-dev. New row names every one of these except qa, which moves to the devx row (judgment 3). The one generalisation, plugin-*, was checked against the tree at the head: packages/plugins/ holds exactly the ten plugin-* packages the three old rows named and nothing else that matches (organizations, knowledge-memory, knowledge-ragflow, embedder-openai are covered by their own spellings or were never in these rows), so plugin-* widens nothing. drivers/* ≡ packages/drivers/driver-* on the tree (five driver- dirs, nothing else). No other row of the table named a plugin, so no package changes lane except packages/qa.
  3. packages/qa → domain:devx — right. Present in the devx row, absent from the server row, and lanes/server.md CLI section says 「packages/qa 已归 domain:devx」. lanes/devx.md needs no edit: its 范围 says the path face lives in the SKILL.md row only (⛔ 不另抄).
  4. Retired-label rule — right, with a disclosed loss of four names. The new bullet 「退役 domain:* 只退流通,对象保留;domain:engine/domain:services/domain:cli 读作 domain:server」 keeps the generic rule the two old rows carried and adds the alias reading the ruling orders. The per-name list engine-core / metadata / drivers / identity leaves SKILL.md; its home is the header of ensure-pm-labels.sh (lines 42–50 on the head), and no script or gate anchors on SKILL.md naming them (grepped scripts/** and the skill tree at the head). Accept-set unchanged.
  5. The three charters → lanes/server.md: nothing silently lost — right. Word-level diff of engine.md + services.md + cli.md (main) against server.md (head) shows every bullet verbatim except: (a) the two duplicate 「见 seat-lifecycle.md…」 preambles deduplicated to one; (b) three heading levels (## → ###) and three section headings naming the former lanes; (c) the three per-lane spec red lines (engine 「改元数据格式或接受面 ⇒ domain:spec,判据是改变接受面而不是碰到 spec」, services 「零持有 packages/spec」, cli 「packages/spec 恒归 spec 座位」) consolidated into one lane-wide line 「触 packages/spec 的卡一律归 spec 席;改元数据格式或接受面的卡亦然,不论碰没碰 spec」; (d) engine's two pointers to domain:cli and domain:services removed — both targets are now sections of the same file (the /meta rule survives in the CLI section, packages/services/** in the services scope); (e) qa dropped from the CLI scope with the devx pointer added; (f) 「本车道/本席」 → 「本节」 twice. Section bullet counts match the old files one to one (engine 9 席内判断, services 4, cli 5, 常设承诺 2/4/5). The consolidation (c) is the union of the three red lines, which on the engine surface is a tightening: a card that touches packages/spec without changing the accept set now goes to the spec seat where engine.md's clause did not require it. That is consistent with the table (domain:spec = packages/spec 整包) and with the other two charters, and the PR body discloses it; named here so the engine-surface seat reads it as a change, not a carry-over. Judged right.
  6. The read-one-section rule — right. 「三节按文件面分读:认领的 File surface: 落在哪节就读哪节,⛔ 不读整篇」 is what the ruling's point 2 asked for.
  7. lanes/spec.md pointer — right. /meta 路由本体 now names domain:server; 「归 domain:spec」 → 「归本席」 on spec's own charter is a no-op in meaning. 42 → 42 lines; no other skill file points at lanes/engine.md, lanes/services.md or lanes/cli.md on the head (grepped).
  8. Ratchet ledger: only lowered ceilings and rows moved by measured lengths — right. SKILL.md ceiling 319 → 317 = the head's landed count (main is 318 after docs(pm-dispatch, os-dev): the claim template and the dev definition carry the Metadata-protocol: line beside Clause-②: #25240; the diff is −1 net: 3 rows out, 1 row + 1 bullet in). Widest-row pin 342 → 341 = the measured widest row on the head (the server row; next widest 339). engine 32 / services 27 / cli 29 rows deleted, lanes/server.md 87 added = its landed count (88 summed → 87). MAX_TABLE_ROW_BYTES rows follow the files (three out, server.md 0 in). No line in SKILL.md or the charters was re-wrapped to buy a line: the only edited SKILL.md lines are the table rows themselves and the new bullet.
  9. CROSS_FILE_MOVES platform-readings sources: cli.md 35 + services.md 30 → server.md 97 — right. crossFileMoveVerdict refuses a source that carries no ceiling, so the two deleted paths had to be replaced. was 97 = 35 + 30 + 32 (engine.md's ceiling at the merge): engine's lines enter was and the current ceiling alike, so they add nothing to the credited decrease; the credited decrease moves 9 → 10, the one extra line being what the merge itself deleted from the three sources, which under this arithmetic (pre-move ceiling minus current ceiling, the same reading the 2026-09-04 rewrite's reductions already got) is a genuine source deletion. The dev's claim 「what the merge itself deleted is counted once, here」 is correct. Dest ceiling 460 unchanged.
  10. check-half-states.mjs aliases — right, and complete for this file. LANE_LABEL_ALIASES is frozen, names exactly the three retired labels, and canonicalLane passes everything else through. Every non-test lane comparison in the file goes through it: seatLane (seat title), newestLaneClaim (both the wanted lane and each card label), censusLanesOf and the H32 inventory in sweepInto (both deduplicated). The only other domain: reader outside self-tests is the 「has any domain label」 test at line 3689, which the alias does not affect. H38 at line 23000–23006 reads lane and seat from seatLane, so it is covered.
  11. Alias not in check-skill-line-ratchet.mjs / clause2-line.mjs — right. Grepped at the head: the ratchet names the three lanes only in ledger comments; clause2-line.mjs once, in a JSDoc example. Neither reads a label. The card's point 4 premise was wrong about those two files, and the dev measured it rather than inventing a reader; no other scripts/pm/** file compares the three labels in non-test code (check-merge-queue-triage-outcome.mjs, osv-base-relative.mjs hits are fixtures).
  12. Self-tests — right. Battery domain:server lane aliases registered on the frozen roster (pin 22, 24 cases incl. pass-through, both-direction claim index, one-lane-not-two census), roster floor 9 → 10 with every floor assertion (BATTERY19129, 18901, 18664, 19255, 67, 68) moved in step. The three H38 expectations re-keyed from domain:services to domain:server are the direct consequence of the alias on a fixture titled with the old spelling; the fixture itself is unchanged, and the 「no dispatched cards」 control moved to domain:skills because domain:engine is now the same lane as the fixture's cards (it would have been found). The Lint · scoped · tooling self-tests and Live half-state sweep checks are green on the head.
  13. ensure-pm-labels.sh — right. domain:server created with the lane description; domain:engine/services/cli keep a create line each so the reconcile shim (which re-sends every create line's -d as an edit) writes the 「Retired 2026-10-22, alias of domain:server …」 description onto the live objects — 100 / 99 / 97 characters, under the cap (check:pm-label-desc-cap is inside the green repo & tooling lane). The header's retired-lanes-are-absent rule records them as the one transitional exception. No objectui or hotcrm block names the three lanes.
  14. Check-runs on the head: 37 success, 13 skipped, 1 failure. The failure is Snapshot the board to the archive branch (job 119914412539), the pull_request leg of board-snapshot.yml (triggered by its paths: entry for scripts/pm/check-half-states.mjs). Signature read from the API, not from prose: failing step 「Fail the run if the snapshot did not read the board, or the archive is behind it」, first error annotation 「board-snapshot exited 2 — 2 is a failed count check (the archive and the board disagree about how many open issues exist) …」. The merge-base commit 80ec71dc26 carries no run of this job (the leg is pull_request-only on PRs; main runs it on schedule), so the base signature was read off main's latest scheduled run 39795166014 at f2ade4afb0, an ancestor of the merge-base with board-snapshot.mjs and the workflow unchanged between them: same failing step, same first error line, and the four latest scheduled runs on main are all red. Same-signature red at the base ⇒ not counted against this head. The tool's own self-test step passed on the head. This PR changes none of the five symbols board-snapshot.mjs imports from check-half-states.mjs. The main-red card exists (tooling(board-snapshot): the scheduled Board Snapshot failed its count check twice running (exit 2 at 08:25Z and 14:18Z on 2026-10-18): the archive and the live board disagree on the open-issue count #24351). Everything else is green, including every Type Check and Lint lane, Test Core 1–6, both queue guards, the dispatch-gates lane and the live half-state sweep.

② Semver level

No changeset, skip-changeset label — matches the diff. The nine paths are .claude/skills/pm-dispatch/** and scripts/pm/**; no packages/**, no exports, no .d.ts, no published schema. Clause-②: no on the PR body (line-initial, with Metadata-protocol: none) — right: no accept set is relaxed and no public surface grows. The one widening in the diff is the sweep's lane comparison (three spellings read as one lane), which is PM tooling behaviour, not a published contract, and is floored by its own battery.

③ Boundary flags

  • Dev flag: alias only in check-half-states.mjs. Answered — verified at the head (judgment 11). The card's point 4 premise was wrong about the ratchet and clause2-line; the deviation is correct, and the ruling's intent (every reader that compares the labels sees every card) is met by the one reader that exists.
  • Dev flag: SKILL.md no longer names engine-core / metadata / drivers / identity. Answered — the names survive in the ensure-pm-labels.sh header, nothing anchors on SKILL.md for them, and the generic 只退流通,对象保留 rule stays (judgment 4). Not a loss of rule.
  • Dev flag: three H38 expectations re-keyed to the merged spelling. Answered — a consequence of the alias on fixtures titled with a retired spelling; the specimen is unchanged and the controls moved for the stated reason (judgment 12).
  • open_questions: [] on the dev report. Nothing to answer.
  • Found by this review, not flagged by the dev — not blocking, named for the seat and the next round:
    1. The devx row was trimmed beyond the packages/qa insertion to keep the width pin: content/docs/** → content/docs, docs/qa/** → docs/qa, .githooks/ → .githooks, scripts/ → scripts, .github/workflows/ → .github/workflows, and 「按锚定规则例外归」 → 「按锚定规则归」. Meaning preserved (bare prefixes are the table's own convention — apps/docs, packages/qa — and the three still route to domain:spec), no script parses the row's globs, but the PR body does not disclose it. Undisclosed editing of governed text, even neutral, is the kind of thing this review exists to catch; here the text is judged equivalent, so it stays a note.
    2. Transitional H38 noise: until the former engine/services/cli seat-1 posts are retitled (ruling point 5), seatLane reads them all as domain:server seat 1, and newestLaneClaim will hand each of them the newest seat-1 claim of the merged lane, so a claim by one former lane's seat 1 can read as 「post stale」 on another's. H38 is a report row, not a gate; it clears as each seat retitles at its next round. Named so the seats do not chase it.
    3. The red Snapshot the board check is main's own signature (judgment 14). Whether this head may be queued carrying it is the dispatching seat's call under SKILL.md 205–206 and the 主干红止血 rule; the standing-down comment 6344588150 on the PR names gate and cause, and tooling(board-snapshot): the scheduled Board Snapshot failed its count check twice running (exit 2 at 08:25Z and 14:18Z on 2026-10-18): the archive and the live board disagree on the open-issue count #24351 is the card.

Implemented-by: claude/project-thread-wjmmlj
Reviewed-by: session_01REMuzhz6MWRjg8uohedMnM

VERDICT: PASS

The Board Snapshot check went red on this PR (and on every scheduled
run since 2026-10-21, #24351) with a count-check SHORTFALL of the whole
board: the open walk's first listing page answered `200 []`, the tool
read it as the short page, declared the open set complete at zero
records, and compared zero with the board's own count.

Measured 2026-10-22: the issues listing answers blank to more than half
of the requests made to it (39 of 68 probes), each with its own request
id and its own count against the rate limit, so no cache is involved;
cache directives, a cache-busting query, sort, direction, page size,
state and since make no difference, and objectui and hotcrm answer the
same way. `/repos/{repo}`, the pulls listing and the per-number comment
pages never blanked.

So a blank listing page is asked again, same URL and page size, up to
`BLANK_PAGE_ASKS` (6) times, each ask a budgeted request and never a
sleep. A blank that survives the bound is acted on by where the walk is
(`blankPageStep`): past an anchor it is believed, because the cursor
does not advance and the next run re-reads from it; from the beginning
it is believed only if the board's own `open_issues_count`, read once as
the witness, is zero — otherwise the walk stops with its cursor
(`stopped_by: blank-listing`), nothing is marked complete, the count
check stays pending and the run exits 0 as a planned stop, which the
freshness row turns red if it becomes a streak. The manifest counts the
re-asks in `run.blank_pages_reasked`; the summary prints the count and
the stop. A new header section records the measurement.

Self-test: a new battery of 13 cases (the pure verdicts, the measured
failure healed on a fake board that blanks once, the bounded stop, the
anchored blank believed with its cursor unmoved), floor 12 -> 13, 169
cases. The PR leg's own arguments (`--dry-run --limit=5
--max-requests=25`) ran six times against the live board: six exits 0,
one of them a blank-listing stop and two after re-asks.

Claude-Session: https://claude.ai/code/session_01VCLHrrmPbwQHZcc2yqdFHD
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/l and removed size/m labels Oct 22, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: d062675f2edb243ac0691141a5e44a89dac97f36
Local-runs: none
Scope: .claude/skills/pm-dispatch/SKILL.md, .claude/skills/pm-dispatch/references/lanes/cli.md, .claude/skills/pm-dispatch/references/lanes/engine.md, .claude/skills/pm-dispatch/references/lanes/server.md, .claude/skills/pm-dispatch/references/lanes/services.md, .claude/skills/pm-dispatch/references/lanes/spec.md + scripts/pm/board-snapshot.mjs (the whole file at the head: the new header section, BLANK_PAGE_ASKS / blankPageStep, the walk closure, countCheckPendingReason / countCheck / freshnessCheck / materialManifest, the manifest assembly and resume block, renderRun and its exit code, rest / makeBudget / readBoardArithmetic, selectWalkPlan / selectDeltaPlan, fakeBoard and the new battery), .github/workflows/board-snapshot.yml (read, unchanged: pull_request paths and arguments, steps 4 / 6 / 10), package.json check:pm-board-snapshot (unchanged), scripts/pm/check-half-states.mjs, scripts/pm/check-skill-line-ratchet.mjs, scripts/pm/ensure-pm-labels.sh (carried from the prior record, unchanged between the two heads)

Inputs: card #24907 body and all four comments (ruling 6341410910; dev report 6344765480); card #24351 body and its three comments; PR #25247 body as rewritten (the CI-fix paragraph), its file list (10 paths), the net diff origin/main...d062675 and the one-commit diff c5c1735..d062675, both PR comments (6344588150, and the prior record 6344994978 on head c5c1735); the 42 check-runs on this head, the Board Snapshot workflow runs on this head, the prior head and main, and the annotations of the red jobs. Not read: the dispatch order, the seat's conclusions, job logs (blob redirect, unreadable from here).

① Derived judgments

  1. The governed hits are byte-identical between the two heads — the prior record's judgments 1–13 carry. Blob ids at c5c1735 and d062675: SKILL.md 15cf1084, lanes/server.md 91c42c37, lanes/spec.md 23c92d54, and lanes/{engine,services,cli}.md absent at both. check-half-states.mjs, check-skill-line-ratchet.mjs and ensure-pm-labels.sh are not in the one-commit diff either. The one commit between the heads touches scripts/pm/board-snapshot.mjs alone (+146/−4). Still no Tier H path: .github/workflows/board-snapshot.yml has blob 81c0824d at both heads and is not in the file list; AGENTS.md, CLAUDE.md, NORTH-STAR, skills/**, CODEOWNERS absent. Governed Surface Queue Guard is green on this head.
  2. The diagnosis matches the code the fix replaces — right. At the prior head a 200 [] first page went straight to nextWalkStep({batchLength: 0}), which is done on any short page; the open walk returned true at zero records, openSetCompletedHere made countCheckPendingReason return null, readBoardArithmetic read 118, the history walk read 5 numbers to the --limit, and countCheck reported a shortfall of 118 → exit 2. That is the exact line the standing-down comment quotes from the prior head's job and the signature the prior head's annotation carries (step 10, board-snapshot exited 2). The upstream measurement (39 of 68 probes blank) is the dev's own and was not re-run here; the code reading does not depend on it.
  3. blankPageStep — right, and pure. Below the bound: ask again. At the bound: stop iff the page is the first page of an un-anchored walk (since === null && page === 1) and the witness is not zero (null = unknown counts as not zero, so an unread or unreadable witness stops rather than believes); otherwise believe. The witness is /repos/{repo}.open_issues_count, which counts issues and pull requests together — the same population the /issues listing serves — so a non-zero witness is exactly the fact that a blank state=open first page contradicts.
  4. The walk — right. A re-ask is the same URL (since and page untouched, listingPath fixes per_page), every ask passes walkBudget.spend() first, and nothing sleeps: the loop is bounded by the constant and by the budget, and in a delta slice SliceExhausted is caught by the existing delta boundary. The witness read also spends the budget, once per walk, only when the first page of an un-anchored walk is blank. blankAsks resets on a non-blank page, so the bound is per page. A stop sets stopped = {kind: 'blank-listing'} and returns false: the caller leaves openSetComplete, historyComplete and walkComplete as they were; the !stopped guards skip readBoardArithmetic; countCheckPendingReason returns the count-not-read reason; countCheck returns pending with ok: null; the manifest's resume carries the walk's own unmoved cursor and stopped_by: 'blank-listing'; renderRun's exit is EXIT_OK (not rate-limit, check.ok === false is false, not stale). The 'blank-listing' branch in renderRun is placed before the generic else if (result.stopped) so it is reachable. A non-array 200 body (Array.isArray(batch) ? batch : []) now goes through the same re-ask instead of being a short page — a strict improvement.
  5. Not a silent pass — with the exposure named precisely. (a) The from-the-beginning stop marks nothing complete, so no verdict can be ok. (b) A blank believed past an anchor is done with since: lastUpdatedAt ?? since = the anchor itself, so the cursor does not move (next_since in the incremental phase, the delta cursor in the history phase; the battery asserts next_since unchanged); and because walkCompletedHere / deltaCompletedHere is then true, the count check still runs against the board's own count, so a believed blank that hid real open/close movement reads SHORTFALL or SURPLUS, exit 2 — a red, not a pass. The residual: a blank believed past an anchor whose missed rows do not move the net open count (edits, comments, a close paired with an open) reads ok / fresh for that one run; the cursor is unmoved and the next run re-reads. Before this commit one blank was believed on sight; now six consecutive are needed. At the measured 57 % blank rate, six independent blanks are about 3.5 % per anchored page per run — named in ③, not a defect.
  6. The freshness claim — right where the open set is complete, overstated where it is not. freshnessCheck ages a pending streak only after openSetComplete; while the open set is incomplete it returns building with no pending_since and never turns red — the tool's pre-existing semantics for an archive still being built, not something this commit changed. So: a blank-listing stop in the history walk's first page (open set complete, history cursor null) or any believed blank that leaves the check pending opens or carries the streak and exits 5 after 24 h — the PR body's claim holds there. A blank-listing stop in the OPEN walk from the beginning — a fresh archive, which is the PR leg (--dry-run --limit=5), or a scheduled archive that is back in the open phase — exits 0 as building on every run for as long as the listing blanks, and nothing ages. On the PR leg that is the intended smoke semantics (the --limit stop already exits 0 pending). On the scheduled leg it matters only if main's archive is in the open phase; that could not be read from the annotations (exit 2 only) and the job logs are unreadable here. Flagged in ③ for the PR body, not a FAIL: nothing is marked complete, the open-set line prints INCOMPLETE, the count check prints PENDING, and the never-aged building state predates this commit.
  7. A witness of zero believes a blank state=all first page too — note. blankPageStep does not know the walk's state: on a board with zero open items but a closed history, the history walk's first page (un-anchored) blanking six times would be believed and the history marked complete at zero closed records. Impossible on this board (118 open) and not reachable by the PR leg; named so the next edit of this rule carries the state.
  8. The battery — right. 13 t( calls under battery('the blank page: …'), matching the roster entry 13 and the floor 12 → 13; every floor assertion that names the count is the roster itself. The cases assert what the code does: the seven pure verdicts of judgment 3; the healed case (blankListings: 1) with complete === true, issues_open === 3, count_check.verdict === 'ok', blank_pages_reasked === 1; call order listing → /repos/o/r → the same listing URL with per_page=100 (matches the walk: witness read before the verdict); the always-blank board stops with walk.open_set.complete === false, count_check.verdict === 'pending', resume.stopped_by === 'blank-listing', resume.phase === 'open', exactly BLANK_PAGE_ASKS listing requests plus one witness (requests === 7), exitCode === EXIT_OK with the STOPPED line; and the anchored case on a built archive completes the incremental walk with blank_pages_reasked === 5 and next_since equal to the unblanked run's. fakeBoard's blank counter applies to the issues listing only — never /repos/o/r or the pulls — which is the measured shape.
  9. Manifest hygiene — right. run.blank_pages_reasked sits in the run block, which materialManifest strips, so a counter that moves every run cannot commit a manifest-only diff on the archive branch; the summary prints it only when non-zero.
  10. Workflow and gate — unchanged, and the gate verdict is green on this head. board-snapshot.yml triggers its pull_request leg on scripts/pm/board-snapshot.mjs (paths), so the leg ran on this head: run 39816905983, job 119957650821 success — step 4 The tool's own cases success (the --self-test behind pnpm check:pm-board-snapshot), step 6 Run the snapshot success, step 10 skipped (exit 0). On the prior head the same job (119914412539) failed at step 10 with the exit-2 annotation, and main's latest scheduled run 39795166014 (job 119880551843) carries the identical signature; main's eight latest scheduled runs are all red. The red is gone on this head by the change, not by a re-run. Lint · scoped · tooling self-tests is green too.
  11. Check-runs on this head: 42 — 33 success, 9 skipped, 0 failure. Every Type Check and Lint lane, Test Core 1–6 and the aggregator, Governed Surface Queue Guard, both claim guards, Part-of PR must not also close its card, The card this PR closes must claim this branch, Live half-state sweep, PM dispatch-gates, Dogfood Regression Gate: success. The skips are the path-conditional families (Build, Dogfood matrix, Console Pin, Temporal, packed-tarball, 18.x harness, Check Changeset). No same-signature exception is needed any more.

② Semver level

No changeset, skip-changeset label — matches the diff. Ten paths, all .claude/skills/pm-dispatch/** and scripts/pm/**; no packages/**, no exports, no .d.ts, no published schema. Clause-②: no on the PR body (line-initial, Metadata-protocol: none) — right: the new commit changes the behaviour of a scheduled backup tool (re-asks, a new planned stop, a new manifest run counter and resume.stopped_by value), none of it a published contract; it is floored by its own battery and read by nothing for state (the tool's own header).

③ Boundary flags

Implemented-by: claude/project-thread-wjmmlj
Reviewed-by: session_01REMuzhz6MWRjg8uohedMnM

VERDICT: PASS

This branch has not been deployed

No deployments
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 size/l skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants