docs(pm,agents): three rules-layer lines catch up with the charter rulings - #18051
Conversation
…the board `os-dev.md` rule 3 still told the dev to dedupe before filing — a targeted REST scan of open issues, with an MCP `search_issues` fallback. The triage ruling moved dedupe to the triage seat and left the filer with keywords only, verbatim and untranslated: 「立卡者不查重,只在卡面附 3–5 个查重词」 The line now states that rule: no dedupe by the filer, 3–5 keywords on the card, never a board pull. Net 0 lines (403/403), 115 bytes against the 120-byte cap. Claude-Session: https://claude.ai/code/session_01DAcomhvR9kKizeYgg89Vo8 Co-authored-by: Claude <[email protected]>
…card cannot take Two rows in 〈状态模型〉 lagged bullets that landed with the charter rules layer. The assignee row listed 跨车道移交 as a fourth release cause, and the `pm:retriage` row offered 跨域 PR 指定车道 and 改路由 with no pre-dispatch qualifier — while the landed bullet reads 「`pm:retriage` 改路由只对未派发卡」 and its neighbour holds a claimed card to 认领即跟到 MERGED, the cross-lane surface reviewed in place (contract-review.md: 借复核不移卡). The governing ruling, verbatim and untranslated: 「C. approve 后不管后续改动都由席位落地:」 — ownership follows the claim through to landing, so a lane handover is not one of the ways an in-flight card leaves its seat's hands. The assignee row now carries three causes with 改路由 qualified 限未派发, and the retriage row says 定车道与改路由限未派发卡. Net 0 lines (812/812), widest table row unchanged at 342 B (L244), the two edited rows 235 B and 269 B, 〈决策分析四轴〉 frame block L733-754 md5 3327d02c56f8a0eca88569dad2270f32 byte-unchanged. Claude-Session: https://claude.ai/code/session_01DAcomhvR9kKizeYgg89Vo8 Co-authored-by: Claude <[email protected]>
… at a human merge Line 25 read a governed-surface hit as unconditional draft-until-a-human- merges, with 不留批准 next to it. The ruling that landed with the charter rules layer says otherwise, verbatim and untranslated: 「C. approve 后不管后续改动都由席位落地:」 Once an authorized APPROVED review exists on a governed PR, the owning seat lands it; the draft hold applies only until then. The ban that survives is the one on the seat approving — never on the seat landing what an authorized account already approved. Net 0 lines (38/38); the line is 118 bytes against the 120-byte cap and is still the file's widest. Claude-Session: https://claude.ai/code/session_01DAcomhvR9kKizeYgg89Vo8 Co-authored-by: Claude <[email protected]>
Contract reviewHead: ① derived judgments — each line now states the rule that already governs it, nothing new is ruled:
Seat measurements on the head tree ( ② semver: ③ boundary flags: Implemented-by: Verdict: PASS — three residue lines now say what PR #18018 and PR #18038 already rule, net zero everywhere; awaiting an authorized approval. 维护者速读(终稿)改了什么:三个文件各改一行(SKILL.md 是两行表格行),把还写着旧规矩的句子换成已经生效的规矩:立卡人只在卡面附 3–5 个查重词、不再自己扫板查重;在飞的卡不跨车道移交,改路由只对未派发的卡;受管面 PR 停 draft 等的是「授权批准」,批准到手后由认领席自己 ready 加入队落地,席位永不批准。 Generated by Claude Code |
…ontrols Offline rows only: #18045, objectui#9404, PR #18051 and comment 5652138683 fire; comment 5654046782 (the same claim shape authored `claude[bot]`) is the clean control, and every fire has a control differing in exactly one feature. Two of the filing card's five signature forms were measured against the specimens it named and would not reach them: the os-tesla claim carries no `Session:` line, and PR #18051's only session token sits in its attribution footer on the last line. Both widenings are pinned as measurements. Claude-Session: https://claude.ai/code/session_01DAcomhvR9kKizeYgg89Vo8 Co-authored-by: Claude <[email protected]>
|
Ruling C landing step — skills seat (session Generated by Claude Code |
… a user account (objectstack-ai#18073) Fixes objectstack-ai#18069 Lock 5 of the mechanism-layer plan. Maintainer, skills seat chat 2026-09-13T16:14Z, verbatim: 「机制层的五道锁 现在就派发处理」. One REPORT-ONLY patrol row in `scripts/pm/check-half-states.mjs` (the whole file surface). **H64** fires on an artefact that carries a seat/dev signature while GitHub records its author as a USER account rather than the App. A suspended user account hides everything it authored, so the artefact's text and its account field disagree about who wrote it and the record is held by an account the protocol does not control. ⛔ No new listing, ⛔ no label, ⛔ no write path, ⛔ no roster of seat accounts — the artefact is recognised structurally (H44's refusal, taken for H44's reason) and the author is read only as the defect. ## Two of the card's five signature forms were measured and widened | form | the card | measured | now | |---|---|---|---| | claim | `Claim:` block **with a `Session:` line** | comment 5652138683 — the card's own os-tesla fixture — carries no `Session:` line in its 35 | `CLAIM_COMMENT_MARKER` alone, the marker H2/H33/H34/H37 already share | | session id | bare id **in the first three lines** | PR objectstack-ai#18051's only session token is line 45 of 45, in the attribution footer; the head window fires on 0 of 9 open PRs, the footer form on 4 | head window stays narrow, the `claude.ai/code/session_…` footer is a SIXTH form; ⛔ the platform's own bare footer is not a signature | ## Two mechanisms the live measurement forced - **A pin (H55's shape).** 870 of 1075 signed texts on this board are user-authored. `created_at` before `USER_AUTHORED_WRITE_SINCE` (2026-09-13) is a CENSUS count and files no row — 816 of them, reaching back to 2026-08-05 — because "re-post it through the proxy" is not a remedy anyone performs 816 times. - **A 10-row cap, newest first.** `renderMarkdown` sorts by card number ASCENDING and the body trim eats the tail, so for this family the newest write — the one the lock exists to surface within the hour — is the first row removed. Changing that sort belongs to every family, so the family bounds itself instead; the clause prints the full judged count on every run. `user.type` is the test, `user.login` is printed and never tested: the login form is a one-name roster wearing an equality sign and judges `github-actions[bot]` (2 open cards, 1 open PR here) as a user account. ## Verification - `--self-test` 3915 → 4042 cases, green. Ablation, both legs restored byte-identical to the HEAD blob: removing the author test reddens 4 cases, removing the footer form reddens 11. - Live read-only run on today's objectstack board: 2073 texts read, 1075 signed, 870 user-authored, **54 judged, 10 filed, 816 census, 0 declined**. Rows name PR objectstack-ai#18051 and 9 newer artefacts; nothing authored `claude[bot]`. - All four specimens fire against their LIVE payloads — objectstack-ai#18045 (`session`), PR objectstack-ai#18051 (`footer`), objectui#9404 (`filer`), comment 5652138683 (`claim`); comment 5654046782, the same claim shape authored `claude[bot]`, is silent. objectstack-ai#18045 is inside the judged 54 and outside the rendered 10: the cap, working. - Every family from `dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` run in the foreground at this head, exit codes captured before any pipe. Full list and readings in the `os-dev-report` on objectstack-ai#18069. ## Acceptance notes - Declared residuals, both stated in the row's banner and in the summary clause: a PULL-REQUEST comment thread is outside the corpus (H44's declared residual, restated), and an EDIT re-writes a body through the same channel without moving `created_at`. - Noted, not filed: the self-test's row-wrapper discipline covers the three-valued PREDICATES but nothing states it for three-valued HELPERS; this row's own ablation aborted 4042 cases at a bare `seatSignature(...).kind` before the wrappers went in. Carrier: the next row that exports a nullable helper. `skip-changeset`: `scripts/pm/**` ships in no package's `files[]`. --- _Generated by [Claude Code](https://claude.ai/code/session_01DAcomhvR9kKizeYgg89Vo8)_ --------- Co-authored-by: Claude <[email protected]>
… writes, REST-only dev writes with `api_writes`, `batch` default 2, user-account roles (objectstack-ai#18072) Fixes objectstack-ai#18068 Dev session `session_01DAcomhvR9kKizeYgg89Vo8` on branch `claude/issue-18068-write-identity-locks` (worktree `objectstack-issue-18068`), off `origin/main` 6d64785, merged e248c4d before opening (no incoming commit touched these files). One commit per lock; each quotes its ruling. ## Rulings (verbatim, untranslated) - Maintainer, skills seat chat, 2026-09-13T16:14Z: 「机制层的五道锁 现在就派发处理」 — the whole card. - Maintainer to the services seat, 2026-09-13: 「当前任务处理完,后续并发降到2」 — lock 3. - Triage ruling ③: 「立卡者不查重,只在卡面附 3–5 个查重词」 — why os-dev.md :51–:58 were stale (lock 2's payment). - Standing exception, pm-dispatch SKILL.md: 「唯一例外:`platform-readings.md` 增量抬上限到落地行数,免决策卡,记 `ruledRaises` 引常设裁决。条件:席位验收评论逐条核实、去重计数(候选/落地/已有/拒收)、一事一行、不计重排」 — the +2 on platform-readings. ## What landed 1. **Lock 1** `.claude/settings.json`: `permissions.deny` with the 14 content-writing `mcp__github__*` tools the card lists; `allow` and hooks untouched; `JSON.parse` passes. Docs reading (code.claude.com/docs/en/permissions and /settings): rules evaluate deny, then ask, then allow; a deny at any scope beats an allow at any scope; `mcp__server__tool` is the per-tool spelling; the shared `.claude/settings.json` is read in cloud sessions and deny needs no workspace trust — lock 1 is ENFORCED, and a denied tool is removed from the roster. Recorded as two 文档载明未实测 lines in `references/platform-readings.md` (references tier, declared): 451 → 453, THIRTEENTH `ruledRaises` record; candidates 2 / landed 2 / already present 0 / refused 0, one matter per line. 2. **Lock 2** `.claude/agents/os-dev.md` 403/403: :51–:58 replaced by the REST-proxy write rule (`curl` + environment `GITHUB_TOKEN`, authored `claude[bot]`), the four-write budget, ⛔ no MCP GitHub write tool and no board enumeration, payload-or-single-card reads, findings reported for the seat to file, zero writes outside the budget (no PR-body `PATCH`), the rest-channel pointer (kept), and `api_writes` + `mcp_calls` in the report; the report template gains `"api_writes"`. In place, net 0: the control-word rule now sits under rule 6; resource rule 6 routes late results to the report; the label-write fallback no longer prescribes an MCP `issue_write`; the `out_of_scope_findings` example no longer shows a dev-filed card number. 3. **Lock 3** SKILL.md :60 「默认 `3`」 → 「默认 `2`」; ceiling `5` unchanged; core-rules :11 states no default, so nothing mirrored. 4. **Lock 4** SKILL.md 〈全体座位的不变量〉 +2 lines (account roles; REST-proxy content and approver never seats), paid in the section (state and resume lines merged; the four Chinese channels named inline, dropping two parentheticals restated in 复核 and 升级与决策); the 〈认领〉 shared-identity line now reads 「身份只认正文 session ID,⛔ 不认作者字段」. core-rules 〈全体座位的不变量〉 one mirror line, paid by folding the three language lines into two. 812/812 (widest row 342 B), 151/151, frame :733–:754 md5 `3327d02c56f8a0eca88569dad2270f32` unchanged. ## Executable criterion, BASE 6d64785 → HEAD 7e1aeca `grep -c mcp__github__issue_write .claude/settings.json` 0 → 1 (inside `deny`); SKILL.md 「默认 `2`」 0 → 1 and 「默认 `3`」 1 → 0; os-dev.md `api_writes` 0 → 2 (rule + template), `search_issues` 1 → 0; SKILL.md 「批准账号」 / 「永不跑席位」 0 → 1, core-rules 「批准账号」 0 → 1. Lit controls unchanged: 「每个方案必须沿四条固定评估轴分析」 1 → 1, 「一座位一车道双射」 1 → 1 in both files, os-dev.md `mcp_calls` 2 → 2. ## Gates `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` at 88e0610: 42 families, every one exit 0 in the foreground with the code captured before any pipe; `--ran`: 42 derived, 42 run, 0 NOT-MEASURED, 0 UNRUN. `check:doc-formula-expressions` first answered exit 3 (PREREQUISITE NOT MET, lint/formula unbuilt) — built under the verify lock (187 s held) and re-run: exit 0. Rule ⑤ for the ratchet-script edit: its `--self-test` (inside `check:pm-skill-ratchet`), `check:ratchet-remedy-authority`, `scripts/check-published-list-mirrors.mjs` and `scripts/check-skills-token-ratchet.mjs` all exit 0. Re-run at 7e1aeca after the merge: `pm-skill-ratchet`, `pm-skill-id-lint`, `skill-frame-sync`, `pm-governed-prose`, `nul-bytes`, `agent-model-declared` exit 0. Every added prose line ≤ 120 B (the one longer added line is inside the report's JSON fence, structurally exempt); SKILL.md over-120 baseline 23 → 23; control-character scan empty. `skip-changeset`: nothing published moves (`.claude/**`, `scripts/pm/**` only). ## Deviations, declared - Lock 4 is two SKILL.md lines, not one: the card's four clauses do not fit one 120-byte line; both are paid inside the section. - Lock 2 edits four lines outside :51–:58 (rule 6 premise line, resource rule 6, the label fallback, the report template), each net 0, each of which would otherwise contradict the budget. - The card's budget has no PR-body `PATCH`; the rule says so explicitly and routes late gate results to the report comment. Allowing a body refresh would be one clause on that line, the seat's call. - PR objectstack-ai#18051 (open draft) edits os-dev.md :50, adjacent to this diff's :51 — whichever lands second takes a one-hunk merge; its SKILL.md :106/:113 lines do not overlap. ## Acceptance notes - noted, not filed (承接者: the skills seat reviewing this PR): `mcp__github__update_pull_request_branch` writes merge commits, and `request_copilot_review` writes a review request, yet neither is on the card's list, so both stay allowed. - noted, not filed (承接者: the skills seat): SKILL.md :778 still reads 「只列三类立卡与 noted, not filed」; under lock 2 the dev lists findings to file and the seat files them. ## 维护者速读(草稿) **改了什么**:四道机制锁。① 仓库的 Claude 设置里禁掉所有"以用户账号写内容"的 MCP GitHub 工具(建 issue、开 PR、评论、审查、推文件、合并等 14 个),状态类与只读工具照旧;② 开发 agent 对 GitHub 的写只走 REST 代理(署名 `claude[bot]`),预算固定四笔,报告新增 `api_writes` 供席位核对;③ 并发默认 3 → 2,天花板 5 不变;④ 写明用户账号只做三件事(assignee、授权批准、维护者亲手),批准账号永不跑席位,内容身份只认正文里的 session ID。 **为什么改**:今天的封号事故证明,用 MCP 工具写的内容署在关联用户名下,用户一被停,内容整批消失;走 REST 代理的内容署在 App 名下不受影响。并发只是放大器,身份才是被封的对象。 **风险与代价(含回滚)**:deny 名单在会话启动时读入,已开的会话不受影响;席位仍可用 `update_pull_request` 等状态工具翻 ready、挂 auto-merge。回滚 = revert 本 PR 的任一 commit(每锁一个 commit,互不依赖)。platform-readings 上限 +2 走常设例外,不另开决策卡。 **席位意见**:(留空,由席位定稿) **你要做的**:一个动作 —— 在本 PR 上给出授权批准;受管面,席位按裁决 C 落地。 --- _Generated by [Claude Code](https://claude.ai/code/session_01DAcomhvR9kKizeYgg89Vo8)_ --------- Co-authored-by: Claude <[email protected]>
…he pre-ruling landing (objectstack-ai#18148) Fixes objectstack-ai#18083 `scripts/pm/check-governed-queue-guard.mjs` decides on an authorized APPROVED review and has since the 2026-09-04 predicate landed. What had not moved was the text a seat is told to consult: the docblock called a draft awaiting the maintainer's own merge the regime's terminal state, and the printed remedy's preferred option told a seat to take the PR out of the queue "and leave the merge to the maintainer". Since the charter chain landed (PR objectstack-ai#18018 `9489e2c0`, PR objectstack-ai#18038 `c185d087`, PR objectstack-ai#18051 `137eb00e`) the rule is ruling C — issue objectstack-ai#17971, maintainer 2026-09-13, verbatim and untranslated: > C. approve 后不管后续改动都由席位落地: So the narration under-permitted: it told a seat not to do the thing the rule now says it should do once an authorized approval is on record. ## What moved Wording only. ⛔ No decision branch changed — `entrySatisfied`, the approval reduction, the tier split and every exit code are untouched. Anchored by content, not by line number. 1. **Docblock, the "why the PR leg must not redden" paragraph.** The healthy resting state is now a draft *waiting for an authorized approval*, in the landed rule's own words from `.claude/skills/pm-dispatch/references/landing-operations.md`: 「四件套留 draft 等人批,⛔ 不翻正式不入队;获授权批准后认领席落地。」 2. **Docblock, a new paragraph for what happens after the approval** — ruling C, quoted verbatim, plus `SKILL.md`'s operational half: 「席位落地 = 过落地前检、清标、ready、auto-merge,踢出/变基同法。」 The seat's ready → enqueue is now stated as the correct next act there rather than a violation. 3. **Docblock, the "this guard cannot stop a direct merge" paragraph.** The direct merge is now the OTHER landing rather than the only one — `references/core-rules.md`: 「受管面由维护者人合或授权批准后席位落地」. The objectstack-ai#11387 measurement it cites is kept verbatim as history. 4. **The `pull_request` leg's EARLY WARNING rendering.** The ⛔ list (flip ready / enqueue / arm auto-merge) is now explicitly conditional — "while no authorized APPROVED review is on record", with AGENTS.md Prime Directive objectstack-ai#14's own "lift only for that approval" — and a new ✅ line says what a seat DOES do after the approval. 5. **The `merge_group` refusal remedy.** Steps 1 → 2 are now an order rather than a menu: out of the queue first, then the authorized approval, and the CLAIMING SEAT lands it from there. The unapproved direct merge (人工直合) is kept as the landing that PR still has. 6. **Two internal comments** that restated the same pre-ruling shape: the tier-default asymmetry cost (was "costs one hand merge") and the exit-code precedence note. The references tier is untouched and still says the in-seat review of record lands it: remedy option 3 renders byte-for-byte as before, and both of its pins (offered on a references-only refusal, withheld on a rules-layer one) still pass. ## Acceptance readings Taken on `a2e4cd7d9`, in the worktree, against merge base `a90a9f267`. **The two pre-ruling phrases, in the narration and the printed text: N → 0.** | reading | before | after | |---|---|---| | `git grep -c -E 'leave the merge to the maintainer\|human merge IS the review record'` | 2 | 2 | | … of those, hits in narration or printed remedy | 2 | **0** | | … of those, hits inside the self-test pin that FORBIDS them | 0 | 2 | Both remaining hits are the new negative assertion itself — a comment and the regex literal in `⛔ a-refusal-never-tells-a-seat-to-leave-the-merge-to-the-maintainer-nor-calls-that-merge-the-record`. A pin has to name what it forbids; neither is text the guard ever prints. **The landed phrase: 0 → 5** (`git grep -c 'CLAIMING SEAT lands\|claiming seat lands'`). **Every hit of the pre-ruling wording enumerated** (`git grep -n -E 'hand merge|human merge|leave the merge|人工合'`), 6 before → 4 after, and each remaining one accounted for: - `:44` 「人工合并即人工审核」 (docblock) — **moved**. - `:52` "the human merge IS the review record" (docblock) — **moved**. - `:536` "costs one hand merge" (tier-default comment) — **moved**, now "costs one authorized approval". - `:1171` 「人工合并即人工审核」 (EARLY WARNING rendering) — **moved**. - `:1242` "leave the merge to the maintainer. A human merge" (remedy) — **moved**. - `:2010` (was `:1983`) the `objectstack-ai#9319` replay fixture's name, quoting PR objectstack-ai#9238's own body: "a .claude/skills PR whose own body said 'awaiting a human merge'" — **stays**. It is a historical measurement naming what that PR said in 2026; rewriting it would falsify the fixture. - `:2429`, `:2436`, `:2468` — **new**, the three negative pins (two English phrases, one 人工合并即人工审核). **Self-test:** `node scripts/pm/check-governed-queue-guard.mjs --self-test` :: exit 0, **233 cases before → 238 after** (5 added, none removed, no battery floor lowered). **Diff surface:** `git diff --stat a90a9f2` names exactly one file, `scripts/pm/check-governed-queue-guard.mjs`, 104 insertions / 32 deletions. `origin/main` advanced under this worktree during the run (`a90a9f267` → `739ab526d`, a shared-ref hazard AGENTS.md names), so the anchor above is the merge base, not the moving ref. **Governed?** No — `node scripts/pm/check-governed-merges.mjs --test scripts/pm/check-governed-queue-guard.mjs` :: exit 0, "NOT governed — ordinary queue landing applies". This PR is opened as a draft and the seat lands it; nothing here flips ready or arms anything on its own. ## Reverse verification — the new pins can actually fail One-shot, from the committed state, with a `trap … EXIT INT TERM` restoring absolute paths. Predicted direction: **turns red**. - HEAD blob `f7938efe94a20f34a3c1e6f07e2aaca393f16a47`. - Mutation: the remedy's step-2 line rewritten back to the pre-ruling wording. On-disk observation, anchored on both texts: landed anchor 1 → **0**, injected stale phrase 0 → **1**; blob `87b912dbc8acaf5af5d02fb86cc06eb2bcffd986` ≠ HEAD blob, so it reached disk. - Mutated leg: `--self-test` :: **exit 1**, `✗ 2 of 238 case(s) failed` — exactly `a-refusal-orders-the-remedy-DRAFT-then-the-authorized-APPROVAL-then-the-CLAIMING-SEAT-lands-it` and `⛔ a-refusal-never-tells-a-seat-to-leave-the-merge-to-the-maintainer-nor-calls-that-merge-the-record`. - Restore leg: `git checkout HEAD -- FILE` → blob back to `f7938efe…` (byte-identical), `git diff HEAD` empty, `git status --porcelain` empty, anchor restored 1, injected 0. - Restored leg: `--self-test` :: exit 0, 238 cases pass. ## Gates Derived with `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` (no paths, three-dot against merge base `a90a9f267`) — **33 commands, all run in the foreground, every exit code captured before any pipe, all `exit 0`.** Reconciled: ``` node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --ran RAN_FILE Run reconciliation — 33 derived, 33 run, 0 NOT-MEASURED, 0 UNRUN. ✓ 33 derived famil(ies) accounted for — a DERIVED zero — all 33 recorded an exit code and none of them is 3. ``` Including `node scripts/pm/check-governed-queue-guard.mjs --self-test` :: exit 0 and `pnpm check:nul-bytes` :: exit 0. `pnpm check:pm-governed-merges` :: exit 0 was run too, though the derivation does **not** place it for this path — it is a `--self-test`-only checker-health family here, so its green grades that checker's fixtures, not this diff. Control characters: `grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]'` over the changed file — no hits. No changeset: `scripts/pm/**` ships in no package's `files[]`, so this publishes nothing — the `skip-changeset` label carries it. ## Acceptance notes Two observations outside this card's one-file surface, noted and not filed (this seat's write budget for the round is the branch push, this PR, the label and one report comment): - `scripts/pm/check-governed-merges.mjs` :140 and `scripts/check-required-contexts.mjs` :288 both still say "a human merge IS the review record" for a governed PR. Neither is wrong — it is one of the two terminals the charter names 「终局两条:人工直合即审核记录;授权批准 ⇒ 席位落地。」 — but neither mentions the second one. That is an omission rather than a contradiction, so it is not the class this card is. Issue objectstack-ai#18083 fences the first file off from this PR by name. - `.claude/hooks/guard-governed-enqueue.sh` :548 was checked and is already ruling-C shaped (its steps read draft → wait for the approval → "Then enqueue"), so it needed nothing. _Generated by [Claude Code](https://claude.ai/code/session_01DAcomhvR9kKizeYgg89Vo8)_ --- _Generated by [Claude Code](https://claude.ai/code/session_01DAcomhvR9kKizeYgg89Vo8)_ Co-authored-by: Claude <[email protected]>
…count (objectstack-ai#18257) Fixes objectstack-ai#18237 H64 keyed on the author account. The rule it was built on had already been superseded: write identity follows the CHANNEL, the REST actor is `claude[bot]` **or** a user per session, and `user.login` records the token rather than the seat — attribution is the `session_` id in the text (`.claude/skills/pm-dispatch/references/rest-channel.md` :54, `.claude/skills/pm-dispatch/SKILL.md` :98). So the row fired on compliant REST writes from user-token sessions, told their authors the write had gone through the MCP GitHub tool, and prescribed a re-post through the REST proxy that lands under the same login and fires again. This re-keys the row onto the attribution the landed rule actually requires. One file, `scripts/pm/check-half-states.mjs` (non-governed PM tooling; `check-governed-merges.mjs --test` on this file list reads `0 of 1 path(s) hit the register`). ## The measurement first — the discriminator the card doubted does not exist The PM's mechanism assumption P1 was that `performed_via_github_app.slug` tells the MCP tool from the REST proxy. Measured with GET-only reads on live rows, 2026-09-15: | row | `user.login` | `user.type` | `performed_via_github_app` | |---|---|---|---| | comment 5673548571 on objectstack-ai#7623 — REST-proxy write, App token | `claude[bot]` | `Bot` | `{ id: 1236702, slug: 'claude', name: 'Claude' }` | | comment 5673265919 on objectstack-ai#17076 — REST-proxy write, user token | `os-warren` | `User` | `{ id: 1236702, slug: 'claude', name: 'Claude' }` | | comment 5673413139 on objectstack-ai#18237 — REST-proxy write, user token | `os-warren` | `User` | `{ id: 1236702, slug: 'claude', name: 'Claude' }` | And the four specimens H64 was built from, all read the same slug: objectstack-ai#18045 (`os-project-manager` / `User`), objectui#9404 (`os-project-manager` / `User`), PR objectstack-ai#18051 (`os-project-manager` / `User`), comment 5652138683 on objectui#9370 (`os-tesla` / `User`); with the clean control comment 5654046782 (`claude[bot]` / `Bot`). **P1 is falsified.** The field names the App whose credential signed the write, never the tool. The first two rows above are both REST-proxy writes differing only in the token class the session was handed, and they are byte-identical in this field. So no channel is inferred anywhere in this family any more, and `artefactChannel`'s docblock now says what it does read: an App credential versus none. Following P1's own instruction for this outcome, the MCP half of the seat direction (candidate (i)) collapses — it has no readable population — and candidate (ii) is the whole firing row. ## What fires now A seat/dev-signed artefact — a claim, a report, a contract review, a filing header — carrying **no `session_` id anywhere in its text**. The text asserts a seat or a dev wrote it; nothing says which session, and the author field records only the token. Remedy: the owner gives the artefact its id (an edit in place is enough). The account is explicitly stated not to be the repair. A structural consequence worth naming: two of the six signature forms **are** session ids (the bare id in the head window, and the attribution footer's `claude.ai/code/session_` URL), so a text matching either carries its attribution by construction and can never fire. The firing population is the other four forms with no id anywhere. The id test reads the whole body while the signature's head window stays narrow — pinned as a case. ## The exposure is not relaxed — it moved to where it costs nothing The addendum stands: a suspended user account 404s everything it authored, measured on this board. Nothing here relaxes it. It is now reported as an **informational count plus a login roster** inside the H64 summary clause, on every run, with no remedy named and no row filed. That is P5's first option, chosen for the reason P5 gives: no act available to a user-token session moves its content to the App, because the token class is handed to a session at start rather than chosen at write time. A row naming no remedy re-files every sweep at full weight against an anchor that trimmed 423 of 441 rows. The clause states the same fact for free, unconditionally, and is strictly **more** visible than the old rows were — those competed for the cap and were trimmed; a clause is not. ## Board measurement, before and after (live sweep, GET only) | reading | before (`ecf91cb4`) | after (`c509364b`) | |---|---|---| | texts read / signed | 1697 / 873 | 1703 / 875 | | finding population | 671 user-authored | 116 naming no session id | | judged (on/after the pin) | 70 | 26 | | rows filed (cap 10) | 10 | 10 | | census behind the pin | 601, back to 2026-08-05T19:32:57Z | 90, back to 2026-08-08T13:49:21Z | | informational exposure | not reported | 671 texts, 17 logins, 3 PAT, 3 unread channel | The two sweeps ran ~40 min apart, so the corpus moved slightly on its own (1697 to 1703 texts). **Reverse verification on the live board.** Every row the card named as a false positive is gone: comments 5673315903 (objectstack-ai#17396), 5673859871 (objectstack-ai#17502), 5673413139 (objectstack-ai#18237), 5670954004 (objectstack-ai#13801) and 5671601791 (objectstack-ai#16695) each appear in 0 rows after, having appeared in 1 before. objectstack-ai#18237's own two rows are gone. **And the retired test was blind in the other direction too.** Of the 10 rows filed now, 7 are authored `claude[bot]` — for example comment 5674562475 on objectstack-ai#16166 and comment 5674534458 on objectstack-ai#18174, both `os-dev-report` payloads whose only footer is the platform's bare one, and card objectstack-ai#18167's `Filed by the ... seat` body. Every one of those is exactly as unattributable as the ones the old row shouted about, and the old row could not see any of them. Spot-checked by GET: none carries a session id anywhere. ## Fixtures — one kept its role, three were re-classified Each with its reason, as P4 asks: | fixture | before | after | why | |---|---|---|---| | comment 5652138683 (objectui#9370, `os-tesla`) | fires | **fires** | names its seat by ACCOUNT and carries no session id in 35 lines — verified against the live comment. It is now the row's live positive rather than one specimen of four. | | objectstack-ai#18045 (`os-project-manager`) | fires | clean | its bare session id is on line 1: attributed. Its lit control is its own silence — strike the id and it leaves the population entirely, because `Filed and claimed by` is not the filing header, so the id was its whole signature. Pinned. | | objectui#9404 (`os-project-manager`) | fires | clean | same id, beside a filing header that survives id removal — so **this** is the specimen carrying the lit control (`BODY9404_NOID` fires). | | PR objectstack-ai#18051 (`os-project-manager`) | fires | clean | its only id is in the footer on line 45 of 45, and the id test reads the whole body. The `/pulls` fire control is a filer-signed body with only the platform bare footer. | | comment 5654046782 (`claude[bot]`) | clean | clean | now clean for a different reason: it carries a `Session:` line. The byte-identical comment under a USER login is also clean, which is the inversion of the old case. | Two new fixtures were added, `restBot64` and `restUser64`, carrying the 2026-09-15 fields above, so the indistinguishability is pinned offline and the channel inference cannot be re-derived from the reader's name. ## Ablation (one-off proof, not a landed test) Predicted direction: removing the session-id gate from `h64SpeaksAbout` should turn the attributed-side silences RED, and should not abort the suite. - HEAD blob `ac93c610dc39c93444e72605ab45fafb827c6b32`; mutated blob `35505df98ecef337f80d1ebccc514257a9ff4897` (distinct, so the mutation reached disk). - On-disk proof before reading any result: the anchor line count went 1 to 0 and the injected marker count went 0 to 1. - Ablated run: `8 of 4113 case(s) failed`, exit 1 — and the 8 are exactly the attributed-side silences (objectstack-ai#18045, objectui#9404, PR objectstack-ai#18051, the cleared claim, comment 5654046782 under both logins, and the two structural cases). No abort. - Restored with `git checkout HEAD --`, under a `trap ... EXIT INT TERM` with an absolute repo root: `git status --porcelain` empty, `git diff HEAD` empty, restored blob `ac93c610dc39c93444e72605ab45fafb827c6b32` equal to HEAD's, injected marker count 0. ## Deviations from the suggested route, stated rather than buried 1. **"The `claude[bot]` fixtures stay clean" does not survive the re-keying, by design.** A `claude[bot]`-authored claim with no session id now fires, and a case pins it. The account was the retired test; keeping the App side categorically clean would keep half the retired premise alive, and the live board shows 7 of 10 rows are that shape. 2. **`USER_AUTHORED_WRITE_SINCE` was renamed `UNATTRIBUTED_WRITE_SINCE`** and `h64UserAuthoredSeatContent` became `h64UnattributedSeatContent`. Both names encoded the superseded premise, and both are file-local (grepped: no reference anywhere else in either repo tree). The pin's instant is unchanged. 3. **The summary window anchor changed** from `User-authored seat content (H64): ` to `Unattributed seat content (H64): `; the window key `h64SeatSigned` is unchanged, and `summaryClause`'s uniqueness cases still pass. 4. `artefactChannel` was kept rather than deleted — with corrected semantics, and used by the exposure clause's PAT/unread split. Deleting it would have removed the only place the measurement is enforceable. ## Self-test and gates - `pnpm check:pm-half-states`: **4075 cases before, 4113 after**, all passing. (The 4075 baseline was read by running the `ecf91cb4` copy of the file; 7 of its cases fail when it is run outside the tree because they read sibling scripts by path, which is a harness artefact, not a baseline failure — in the tree it is green.) - Gate roster derived with `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack`, no paths (change set: 1 path, three-dot vs merge base `ecf91cb44`): 41 commands. 40 ran in the foreground with exits captured by redirect — **all exit 0**. The first pass of 14 read exit 3 `PREREQUISITE NOT MET` in a fresh worktree with no `node_modules`; after `pnpm install` all 14 re-ran green, and no exit-3 reading is reported here as a measurement. - `pnpm check:pm-dispatch-gates` was run detached per its own header (it exceeds the foreground cap), and waited on rather than polled. - `skip-changeset`: `scripts/pm/**` publishes nothing — no package's `files[]` ships it. ## Acceptance notes Out-of-scope observations, noted and not filed: - The four-form firing population makes every `os-dev-report` comment a candidate, because the report JSON carries `branch` but no session id and the platform appends only a bare footer. That is the row working as the landed rule specifies, not a defect — but if the maintainer would rather the report template carry the id, that is a change to `.claude/agents/os-dev.md`, a different (governed-adjacent) surface and a different card. Successor: the next `domain:skills` card touching the dev report template. Noted, not filed. - `H64_ROW_CAP` still binds at 10 with 26 judged findings, so 16 clearable rows are unfiled per sweep. The cap is correct as designed (the trim eats the tail) and the clause states the full count. Successor: whoever next revisits `renderMarkdown`'s ordering, which is the real constraint. Noted, not filed. --- _Generated by [Claude Code](https://claude.ai/code/session_01HZfg2AwVX191qCizp88gQr)_ Co-authored-by: Claude <[email protected]>
Fixes #18019
Three governed lines outside the charter rules-layer PR's declared file surface still carried pre-ruling text after
9489e2c0(rules layer) andc185d087(references) landed. Each now states the rule that governs it — one commit per file, each quoting its ruling verbatim and untranslated..claude/agents/os-dev.md:50.claude/skills/pm-dispatch/SKILL.md:106.claude/skills/pm-dispatch/references/lanes/ui.md:25check:pm-skill-ratchetgreen with every touched ceiling still at headroom 0 and no ceiling moved; SKILL.md frame block L733-754 md53327d02c56f8a0eca88569dad2270f32byte-unchanged.Executable criterion — measured
origin/main226970b to HEADgit grep -cper file: 先搜再立 inos-dev.md1 to 0 · 跨车道移交 inSKILL.md1 to 0 · 停在 draft 等人合 inlanes/ui.md1 to 0. Lit controls on neighbouring surviving phrases, same greps, same two refs:noted, not filed3 to 3 · 离手恒走释放 1 to 1 · 异议评论写明所求 1 to 1 · 未命中的 PR 按同节走合并队列落地 1 to 1.Gates
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackat HEADac73575cderived 18 families; all 18 ran in the foreground with the exit code captured before any pipe, all exit 0;--ranreconciles 18 derived / 18 run / 0 NOT-MEASURED / 0 UNRUN.check:doc-formula-expressionsfirst answered exit 3 PREREQUISITE NOT MET (unbuilt workspace package, nothing measured) and was re-run to exit 0 afterturbo run build --filter=@objectstack/formula --filter=@objectstack/lint.Repo-wide
pnpm lintis not owed here and can produce no verdict on this diff: eslint's own config supplies no configuration for.md, and--format jsonover the three files reports 3 files, 0 errors, 3 warnings, each of themFile ignored because no matching configuration was supplied.skip-changesetapplies —.claude/publishes nothing from any released package.Premise checks
P1, P2 and P3 confirmed on
origin/main226970b. P4 confirmed: all 12 open PRs were fetched into a private ref namespace and diffed against their own merge-base — none touches these files. P5 measured NO:references/core-rules.mdmirrors none of the three pre-ruling lines; it already carries the post-ruling statements at :8, :51 and :123, so it is not in this diff.Acceptance notes
Noted, not filed:
os-dev.md:53, :54 and :58 still describe the dedupe channel the filer no longer uses (thesearch_issuesfallback on 403, the control-word rule, 那一次查重). Same residue class as :50, but outside this card's declared one-line surface and the file's 403/403 ceiling. Carrier: the seat that owns the charter-revision family.维护者速读(草稿)
改了什么 — 三个文件各改一处(SKILL.md 是两行表格行),把还写着旧规矩的句子换成已经落地的规矩:立卡人只在卡面附查重词、不再自己查重;在飞的卡不跨车道移交,改路由只对未派发的卡;受管面 PR 停在 draft 等的是「授权批准」,批准到手后由认领席自己 ready 加入队落地,席位仍然永不批准。
为什么改 — 这三行落在章程改版 PR 的文件面之外,规则合入后它们就和已生效的规则直接打架。留着,下一个读到的人会照旧规矩办事。
风险与代价(含回滚) — 纯指令文本,没有运行时影响。三个文件都卡在行数上限,改动是等行替换,净增 0 行,棘轮一个都没抬。回滚就是 revert 这三个 commit,彼此独立、互不依赖。
席位意见 —
你要做的 — 受管面,PR 留 draft 等你。请确认第三条的措辞:拿到授权批准后由认领席自己落地,是不是你要的形态?
Generated by Claude Code