Skip to content

Commit d8785b4

Browse files
docs(pm-dispatch): the security lane — a security#1 seat owns every security finding; its charter, the Notify: security doorbell and the seat signature (#29695)
domain:skills#2 · os-justin · session_01QSeKYn5CmmdShCJifm6WYj Part of #29665 — the lane text, the owner section, the triage hand-over, the `Notify: security` doorbell and the seat signature land here; the seat post is the seating session's act, so this PR does not close the card. Clause-②: no Metadata-protocol: none ## 维护者速读 - 改了什么:新增 `security` 车道与 `security#1` 席——章程 `references/lanes/security.md`、`security-findings.md` 第 9 节「归属」、分诊转交两行、信箱新键 `Notify: security · GHSA-ID`、SKILL.md 域车道表一行、席位签名接受 `security#N`。 - 为什么改:维护者裁定「设 security#1 座位」并补「这个应该算一个车道吧」;此前安全发现谁发现谁处理,无台账、无归属。 - 风险与回滚:纯规则文本与 PM 工具脚本,不发布任何包,回滚即 revert 本 PR;风险 hunk 是签名正则多接受一个词 `security`(`check-half-states.mjs` 与 `fleet-roster.mjs` 各一处),自测已钉非车道词仍拒。 - 先看:① `scripts/pm/record-recognisers.mjs`(新键、读法与自测行);② `scripts/pm/check-half-states.mjs` 的 `SIGNATURE_LANE_SOURCE`;③ `references/lanes/security.md`。 - 两分钟验证:`pnpm check:pm-record-recognisers`,预期四个自测全绿,且 `node scripts/pm/record-recognisers.mjs --mailbox-keys` 第 32 行起打印 `Notify: security · GHSA-ID` 与「finder → security#1」行。 - 需要你做的:无,席位落地(Tier S,达档复核 PASS 后由席位入队)。 ## What changes - `references/lanes/security.md` (new, 34 lines): the lane charter — who sits it, intake, the opaque ledger and its five states, the no-card fix order at `xhigh`, rule-level review, the §7 take-over, hand-over at sign-off. Pointers only: every never-public rule stays in `security-findings.md` (§8 「一处成文」 holds), and the text names no finding, no advisory, no path, no object and no mechanism. - `references/security-findings.md`: §9 「归属」 names `security#1` as the owner from intake to publish, the finder's two acts (a relay `advisory_create` draft, one opaque-id doorbell) and the relay-refused fallback; §8's pointer list gains the security lane in place. +6 lines. - `references/lanes/triage.md`: push ① stays; then triage forwards to `security#1` and stops, grading nothing past `security` + p1 on a public card; while the seat is vacant, today's §7 handling. +2 lines. - `references/project-forms.md` 〈信箱 PR〉: the `Notify: security` row (finder → `security#1`, the opaque id only); the 不敲 line keeps the mechanism, paths and object names out. +1 line. - `SKILL.md`: one 域车道 row for `security` (266 bytes against the 341-byte row pin), spelled from the amendment's text "security findings (no file surface; fixes land in the owning package via no-card orders)". Line count unchanged at 319 — the ratchet note below. - `scripts/pm/record-recognisers.mjs`: `MAILBOX_KEYS` row `notify:security`, template `Notify: security · GHSA-ID`; its reader accepts the GHSA id alone (`ADVISORY_ID_REF`, mirrored from the relay's `GHSA_ID_SHAPE` in `fleet-write/ops.mjs` and held equal by a self-test row, the way `SESSION_SHAPE` already is); self-test rows for the reading, for the refusals (free text, a second field, a path, a card number, an upper-case or short id, no id at all) and the roster count 14 → 15. `--mailbox-keys` prints it. - `scripts/pm/check-half-states.mjs`: `SIGNATURE_LANE_SOURCE` admits `security`; the banner lists it. `scripts/pm/fleet-roster.mjs`: the mirrored pattern admits `security` and `security#1` joins the `signedSeatOf` corpus; the byte-equality pin already holds the two copies together. Self-test rows in `record-recognisers.mjs`: `security#1 · …` reads, `reviewer#1` / `finder#1` stay refused, `signatureRefusal` returns no row for the security seat. - `scripts/pm/mailbox-open.mjs` (patch round, surface extended by the seat): `SEAT_BARE_LANES` admits `security` — the third copy of the lane grammar, which the doorbell `To:` line, every `LANE#N` key field and the mailbox slug read through; the header's closed-set sentence names three and points at `SIGNATURE_LANE_SOURCE` as the list it must stay in step with; `seatKey` self-test rows pin `security#1` → key `security#1` / slug `security-1`; `domain:security` is reserved. The `Notify: security` self-test row now rings under `To: security#1`. - `scripts/pm/check-skill-line-ratchet.mjs`: `CEILINGS` rows `security-findings.md` 56 → 62, `project-forms.md` 55 → 56, `lanes/triage.md` 15 → 17, new `lanes/security.md` 34 with its `MAX_TABLE_ROW_BYTES` pin 0; `SKILL.md` unchanged at 319. This file was not on the claim's file surface: the order's ratchet clause ("raise the cap by citing the ruling record") lives only in this map, and a new lane file reds `laneRosterVerdict` without a row, so its data rows were edited and the addition is declared in the report's `deviations`. ### The ratchet, and the one row paid by deletion Ruling of record for every raised row: the card body (maintainer, verbatim 「设 security#1 座位」) and the amendment comment 6592303181 (maintainer, verbatim 「这个应该算一个车道吧?虽然你不需要打标签,应该也有处理的规则需要写入skills」), which orders one row in SKILL.md's lane table and the lane's rules in skills. Measured deltas: `security-findings.md` +6, `project-forms.md` +1, `lanes/triage.md` +2, `lanes/security.md` new at 34, `SKILL.md` +0. `SKILL.md`'s row was PAID BY DELETION, the PM's preferred route: the 红线 「⛔ 未经人工的发布痕迹按事故立案,不代跑补救性发布;机械通道存在不构成授权。」 restated two sentences of AGENTS.md Prime Directive #15 (a publish nobody ordered is filed as an incident, never counter-published; the existence of a path to a release is not authorization to walk it), which outranks this file (SKILL.md 优先序: AGENTS.md > 红线) and whose preceding 红线 already defers the release list to AGENTS.md; the landed precedent is the two 红线 paid the same way for the external-text rule. No re-wrap funded anything; the meaning of SKILL.md is unchanged because the rule still binds every seat from AGENTS.md. ## Tests Round 1 at head 3868301; the patch round at head 4181cc2 re-ran the same 58 derived families (identical list) and adds `node scripts/pm/mailbox-open.mjs --self-test` :: exit 0 — `✓ mailbox-open self-test: 82 cases in 10 batteries pass`. Each exit captured before any pipe. - `node scripts/pm/record-recognisers.mjs --self-test` :: exit 0 — `✓ record-recognisers self-test: 355 cases pass` (the new rows: the `Notify: security` reading, its six refusals, the roster at fifteen, `security#1` in the signature lanes, the non-lane words refused, the `GHSA_ID_SHAPE` mirror held equal). - `node scripts/pm/fleet-roster.mjs --self-test` :: exit 0 — `✓ fleet-roster self-test: 104 cases in 9 batteries pass` (the mirrored pattern byte-equal to `SEAT_SIGNATURE_LINE`; `security#1` in the corpus). - `node scripts/pm/check-half-states.mjs --self-test` :: exit 0 — `✓ check-half-states self-test: 5166 cases pass`. - `node scripts/pm/record-recognisers.mjs --mailbox-keys` :: exit 0 — header `ONE of these 15 keys`; lines 32–33: `Notify: security · GHSA-ID` / `the finder — any seat, dev or QA runner → security#1; its relay advisory_create draft landed; the opaque advisory id ALONE — never a mechanism, a path or an object name`. - `pnpm check:pm-skill-ratchet` :: exit 0 — `✓ check-skill-line-ratchet self-test: 164 cases pass`; every file at its ceiling, `lanes/security.md is 34 lines (ceiling 34; headroom 0)`, `SKILL.md is 319 lines (ceiling 319; headroom 0)`, `SKILL.md: widest table row is 341 bytes (pin 341)`. - `pnpm check:pm-skill-id-lint` :: exit 0 — `✓ check-skill-id-lint: 109 file(s) clean`. - No ablation: no tested runtime behaviour changed; the readers' new branches are pinned by the self-test rows above (a reader that refused `security` would red the signature row; a `MAILBOX_KEYS` row without its reader would red the roster row). Readings behind the change, taken with the modules at the base fc55892 (the PM's reading, confirmed): `signatureRefusal('security#1 · os-justin · session_…')` returns a refusal row and `domain:security#1 · …` returns null; `readSignatureLine('security#1 · …')` is null. At this head both the bare `security#1` line reads and `signatureRefusal` returns null for it. ## Acceptance notes - **Three copies of the lane grammar, kept in step by text alone.** `SEAT_BARE_LANES` (`mailbox-open.mjs`) is held in step with the two signature patterns (`check-half-states.mjs` `SIGNATURE_LANE_SOURCE`, `fleet-roster.mjs` `SIGNATURE_LINE`) only by its header sentence; the `fleet-roster` self-test pins the two patterns byte-equal and nothing pins the third. Evidence: `coordinator` is in both patterns and absent from `SEAT_BARE_LANES`, so a coordinator seat can sign but not be addressed. Out of scope here; the seat judges whether it needs a card. - `pnpm --filter @objectstack/lint run check:doc-formula-expressions` from the derived list: exit 3 in the derived loop (PREREQUISITE NOT MET: `@objectstack/formula` and `@objectstack/lint` unbuilt, nothing measured), then measured after a locked build of the two (`os-verify-lock.sh`: `VERDICT command-exit 0 · held the lock 1s · waited 465s`): re-run :: exit 0 (its five self-test cases and the sweep green). - PR #29656 (`advisory_read` holds its sender to target-write) is merged at this base; the charter points at the relay read and does not restate the gate. - `test:repo` (the vitest `repo` project) is CI's (`Test Core`), not run locally; this diff touches no package, so step ① (the dependency closure) is empty. - Changeset: none — `.claude/**` and `scripts/pm/**` ship in no package (AGENTS.md fast path), so `skip-changeset` is declared on the create. ## Verification Patch round, head 4181cc2 (after merging `origin/main` 2ea6042, no conflict): 58/58 derived families at exit 0 after the locked build; `--ran` 58 run / 0 NOT-MEASURED. Round 1 follows. Derived gate families (`node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack`, derived from the tree at fc55892 / 3868301): 58 commands; 57 at exit 0, one at exit 3 before the build above. Reconciliation `--ran`: `58 derived famil(ies) accounted for — 58 run`. Model tier line from `--tier`: `MANDATORY: claude-fable-5-1` (clause ①, SKILL.md is the PM dispatch skill main file). Changed lines 122 (+108 / −14) vs the human-merge threshold 3000: under. `check:pm-governed-prose` exit 0 (SKILL.md still names all 13 governed surfaces); `check:skill-frame-sync` self-test and gate exit 0. <details><summary>Derived gate commands and exit codes (58)</summary> ```text exit 0 · node scripts/check-closing-keyword-parity.mjs exit 0 · node scripts/check-closing-keyword-parity.mjs --self-test exit 0 · node scripts/check-comment-mask-corpus.mjs exit 0 · node scripts/check-declaration-mirrors.mjs exit 0 · node scripts/check-declaration-mirrors.mjs --self-test exit 0 · node scripts/check-issue-citations.mjs exit 0 · node scripts/check-platform-checklist-watchdog.mjs exit 0 · node scripts/check-platform-checklist-watchdog.mjs --self-test exit 0 · node scripts/check-release-section-coverage.mjs exit 0 · node scripts/check-release-section-coverage.mjs --strict exit 0 · node scripts/check-self-test-wired.mjs exit 0 · node scripts/check-self-test-wired.mjs --self-test exit 0 · node scripts/check-self-test-workflow-commands.mjs exit 0 · node scripts/check-self-test-workflow-commands.mjs --self-test exit 0 · node scripts/check-whole-set-label-write.mjs exit 0 · node scripts/check-whole-set-label-write.mjs --self-test exit 0 · node scripts/ci/plan-test-core-shards.mjs --self-test exit 0 · node scripts/ci/scheduled-full-run.mjs --self-test exit 0 · node scripts/pm/bare-root-worklist.mjs --self-test exit 0 · node scripts/pm/board-snapshot.mjs --self-test exit 0 · node scripts/pm/check-governed-queue-guard.mjs --self-test exit 0 · node scripts/pm/check-harness-current.mjs --self-test exit 0 · node scripts/pm/sweep-closed-cards.mjs --self-test exit 0 · node scripts/render-release-coverage-anchor.mjs --self-test exit 3 · pnpm --filter @objectstack/lint run check:doc-formula-expressions exit 0 · pnpm check:agent-test-spelling exit 0 · pnpm check:bash32-floor exit 0 · pnpm check:ci-filter-parity exit 0 · pnpm check:cli-command-ids exit 0 · pnpm check:closing-target-claim exit 0 · pnpm check:commit-card-trailers exit 0 · pnpm check:cross-package-test-inputs exit 0 · pnpm check:doc-authoring exit 0 · pnpm check:driver-memory-census exit 0 · pnpm check:entry-guard exit 0 · pnpm check:gitlink-declared exit 0 · pnpm check:issue-citations exit 0 · pnpm check:nul-bytes exit 0 · pnpm check:parse-guard exit 0 · pnpm check:partof-closing-keyword exit 0 · pnpm check:platform-checklist exit 0 · pnpm check:pm-dispatch-gates exit 0 · pnpm check:pm-expected-skips exit 0 · pnpm check:pm-governed-merges exit 0 · pnpm check:pm-governed-prose exit 0 · pnpm check:pm-half-states exit 0 · pnpm check:pm-post-stamped exit 0 · pnpm check:pm-record-recognisers exit 0 · pnpm check:pm-skill-id-lint exit 0 · pnpm check:pm-skill-ratchet exit 0 · pnpm check:pnpm-filter-targets exit 0 · pnpm check:pnpm-flag-spellings exit 0 · pnpm check:ratchet-remedy-authority exit 0 · pnpm check:refd-timer-probe exit 0 · pnpm check:scripts-symbol-anchors exit 0 · pnpm check:single-claim-paths exit 0 · pnpm check:skill-frame-sync exit 0 · pnpm check:watch-hint-literal ``` </details> <details><summary>Ratchet verdict lines for the touched files</summary> ```text ✓ check-skill-line-ratchet: .claude/skills/pm-dispatch/SKILL.md: widest table row is 341 bytes (pin 341; headroom 0). ✓ check-skill-line-ratchet: .claude/skills/pm-dispatch/SKILL.md is 319 lines (ceiling 319; headroom 0). ✓ check-skill-line-ratchet: .claude/skills/pm-dispatch/references/security-findings.md is 62 lines (ceiling 62; headroom 0). ✓ check-skill-line-ratchet: .claude/skills/pm-dispatch/references/project-forms.md is 56 lines (ceiling 56; headroom 0). ✓ check-skill-line-ratchet: .claude/skills/pm-dispatch/references/lanes/triage.md is 17 lines (ceiling 17; headroom 0). ✓ check-skill-line-ratchet: .claude/skills/pm-dispatch/references/lanes/security.md: widest table row is 0 bytes (pin 0; headroom 0). ✓ check-skill-line-ratchet: .claude/skills/pm-dispatch/references/lanes/security.md is 34 lines (ceiling 34; headroom 0). ``` </details> Governed surface: `.claude/**` is Tier S — this PR stays draft until a contract-review-tier review PASS at its final head; no reviewer requested, not readied. --- _Generated by [Claude Code](https://claude.ai/code/session_01QSeKYn5CmmdShCJifm6WYj)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 0b2b309 commit d8785b4

10 files changed

Lines changed: 125 additions & 28 deletions

File tree

‎.claude/skills/pm-dispatch/SKILL.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ PM 的工作是循环:选卡 → 认领 → 派发 → 收集 → 复核 → 报
3030
- ⛔ 永不代维护者答产品或架构问题,⛔ 无代裁通道:席位只分析与推荐,裁决恒出自人工。
3131
- ⛔ GitHub 之外永不维护跟踪状态;看板(org Project)只是视图,权威层是 issue 正文 + REST。
3232
- ⛔ 永不跑版本发布、不合并 Version Packages PR;发布动作的完整清单以 AGENTS.md 为权威。
33-
- ⛔ 未经人工的发布痕迹按事故立案,不代跑补救性发布;机械通道存在不构成授权。
3433
- ⛔ 分诊席永不认领、派发或写码;执行席只在本车道认领,改 `domain:*` 仅限整树归本席。
3534
- 唯一例外:无主 p0 锚卡一轮即轮报、再一轮分诊只代派止血:每轮一张,复核非分诊。
3635
- ⛔ 报告缺席永不读作成功,永不虚构未回报 agent 的结果。
@@ -163,6 +162,7 @@ PM 的工作是循环:选卡 → 认领 → 派发 → 收集 → 复核 → 报
163162
| `domain:spec` | `packages/spec` 整包:schema 形状、`contracts/**`、退役行为半边、strictness 台账;describe/JSDoc/墓碑散文/错误 guidance 与 alias 表;`packages/spec/scripts/**`、`packages/spec/docs/**` 及按锚定规则的例外归本域的工具链(域边界枚举与席内分派见 `references/lanes/spec.md`) |
164163
| `domain:ui` | `ui/**` 全部代码:`@object-ui/*` 库与 `ui/apps/*`、`ui/packages/types` 与 schema 语料、`ui/scripts` 与门禁工具、文档随所记录的面走;`packages/console`(dist 由 `pnpm console:build` 生成 ⛔ 不手改);协议恒归 `domain:spec`,受管面归 `domain:skills`;详见 `references/lanes/ui.md` |
165164
| `domain:cloud` | 涉 cloud 即整卡归 cloud PM(`repo:cloud` 席,同挂 `repo:cloud`):读或改 cloud 代码、生产方、事实或带 cloud 半边;本仓车道 ⛔ 不认领;cloud PM 跟到关卡,余下纯本仓活由它写明 cloud 事实后另立卡;判据见 `references/cross-repo-coordination.md` |
165+
| `security`(车道,无 `domain:*` 标签) | 安全发现(无文件面;修复经无卡派发令落在归属包);席位 `security#N`、接收、台账、派修与复核见 `references/lanes/security.md`;永不公开的规则只住 `references/security-findings.md` |
166166
| (无固定归属,按落点分诊) | `packages/apps/*`、`examples/*`(归它演练的子系统)、`docs/audits/**` |
167167

168168
- 表未覆盖的包首次分诊时归类并走 PR 更新本表;新增或退役 `domain:*` 必须同批改本表。
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# 车道章程:安全席(security)
2+
3+
见 seat-lifecycle.md;本文是本车道岗位说明,现值状态恒在座位贴,⛔ 不迁入本文。
4+
永不公开的规则只住 `../security-findings.md`(下文「第 N 节」皆指它),本文只指向、⛔ 不复述。
5+
本文与座位贴不载任何发现、编号、路径、对象名或机制。
6+
7+
## 座位
8+
9+
- 席位 `security#N`,首席 `security#1`;签名、座位贴、坐席与收班同他席。
10+
- 无 `domain:security` 标签、无 `pm:queue`、无文件面:安全发现永不成卡,修复落在归属包。
11+
- 坐席者 = 能读写本仓安全公告的账号(管理员或安全经理)的会话;读不了公告 ⛔ 不坐。
12+
- 读公告只经中继 `advisory_read`;建草稿与写公告只经中继,⛔ 永不以自身账号写。
13+
14+
## 接收与台账
15+
16+
- 接收 = 发现者的一次 `Notify: security · GHSA-ID` 门铃,草稿公告已由其经中继建好。
17+
- 中继拒 ⇒ 机制在会话里交本席;本席空缺 ⇒ 交维护者(第 3 节);⛔ 不换账号重发。
18+
- 台账住座位贴,一份一行:不透明编号 · 会话 · 状态;⛔ 无细节。
19+
- 状态词表:`received` → `fixing` → `merged` → `awaiting publish` → `published`。
20+
21+
## 派修与复核
22+
23+
- 修复 = 本席向 `os-dev` 下无卡派发令,`xhigh`;机制只进派发令与公告,⛔ 不进 PR 与评论。
24+
- 修复 PR 的契约复核归本席:记录停在规则层,机制只在本席本地的 `security-note.md`(第 6 节)。
25+
- 要定契约的修复交维护者;发布公告恒归维护者,本席把状态推到 `awaiting publish` 即止。
26+
27+
## 误公开的卡(第 7 节)
28+
29+
- 本席接管:挂 `security` 标签,⛔ 不追加细节;是否移入公告并关卡交维护者定,⛔ 不代关。
30+
- 分诊只即推维护者并转交本席(`triage.md`);本席空缺时分诊照第 7 节处置。
31+
32+
## 收班
33+
34+
- 台账与每份在飞公告留在座位贴,⛔ 不释放进 `pm:queue`;接任者坐席即接台账。

‎.claude/skills/pm-dispatch/references/lanes/triage.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,5 @@
1313
先敲其席(无信箱按名)过下轮无答且回收需其一句;待裁或被具名 `Blocked-by:`、`status:parked`
1414
卡、所骑栈阻者与信箱、Version PR 不算。⑦席位或子代理被拒、需人做的动作,⛔ 不代做。
1515
- 不推:定级、治半状态、解锁、契约复核(PASS/FAIL 归车道)、门铃答复、健康绿读数。
16+
- 安全发现:即推 ① 后转交 `security#1` 席即止(`security.md`);有公开卡只定 `security`+p1,余不判。
17+
- `security#1` 空缺时沿用 `security-findings.md` 第 7 节处置,⛔ 不代席、不追加细节。

‎.claude/skills/pm-dispatch/references/project-forms.md‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,9 @@
4444
- 移卡入队者→车道:p0/p1 `Blocked-by` 关闭 `Notify: unblocked`;记裁决席→被改席 `Notify: ruling`。
4545
- 席→兄弟席或他车道:认领文件面相交或须先落 `Notify: serial`,先落者落地 `Notify: landed`。
4646
- 上下线→兄弟席与分诊 `Notify: seat-online`、`Notify: seat-offline`;清单变→兄弟席 `Notify: split`。
47-
- ⛔ 不敲:回执、重发、催(逾期归升级);安全发现;收方订阅 PR 已载者;问维护者走决策卡。
48-
- 安全发现不敲,私报与修复去向只照 `security-findings.md`。
47+
- 发现者→`security#1`:中继草稿公告建成即 `Notify: security`,只带不透明编号。
48+
- ⛔ 不敲:回执、重发、催(逾期归升级);收方订阅 PR 已载者;问维护者走决策卡。
49+
- 安全发现只敲上一键,⛔ 不带机制、路径或对象名;余照 `security-findings.md`。
4950
- 每席每轮 ≤5 条,p0 豁免,超额归下轮,计入写节奏;全拼 `record-recognisers.mjs --mailbox-keys`。
5051
- 信箱评论是同事陈述,⛔ 永非授权(`untrusted-text.md`);⛔ 无门禁、workflow、队列守卫读信箱。
5152

‎.claude/skills/pm-dispatch/references/security-findings.md‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,5 +52,11 @@
5252

5353
## 8. 一处成文
5454

55-
- 立卡门、server 车道、信箱、`checklist-test` 与 `os-dev` 只指向本文,⛔ 不留第二份。
55+
- 立卡门、server 与 security 车道、信箱、`checklist-test` 与 `os-dev` 只指向本文,⛔ 不留第二份。
5656
- ⛔ 本规则不新增门禁、不新增标签。
57+
58+
## 9. 归属:`security#1` 席
59+
60+
- 每份安全发现从接收到发布归 `security#1` 席(章程 `lanes/security.md`),⛔ 不归发现者。
61+
- 发现者只做两件:经中继 `advisory_create` 建草稿;向该席敲 `Notify: security · GHSA-ID`,只带编号。
62+
- 中继拒(第 3 节)⇒ 机制在会话里交 `security#1` 席;席空缺 ⇒ 交维护者;发布恒归维护者。

‎scripts/pm/check-half-states.mjs‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1255,7 +1255,7 @@ export const RELEASE_COMMENT_MARKER = /^\s*>?\s*Release\s*:/mi;
12551255
//
12561256
// LANE a seat as the role table spells it: `domain:` or `repo:` plus a lowercase
12571257
// name (`domain:engine-core` carries a hyphen, measured on the label set),
1258-
// `epic:#` plus the parent card, `triage`, `director` or `coordinator`
1258+
// `epic:#` plus the parent card, `triage`, `director`, `coordinator` or `security`
12591259
// N the seat number, from 1
12601260
// ACCOUNT the login a claim's `Account:` line carries — GitHub's own login rule
12611261
// SESSION the full id, in the relay envelope's shape (`SESSION_SHAPE`, fleet-write/ops.mjs,
@@ -1279,7 +1279,7 @@ export const RELEASE_COMMENT_MARKER = /^\s*>?\s*Release\s*:/mi;
12791279
// ---------------------------------------------------------------------------
12801280

12811281
/** The four parts' patterns, unanchored, in order — the ONE spelling every reader and writer composes from. */
1282-
const SIGNATURE_LANE_SOURCE = '(?:domain:[a-z][a-z0-9-]*|repo:[a-z][a-z0-9-]*|epic:#[1-9][0-9]*|triage|director|coordinator)';
1282+
const SIGNATURE_LANE_SOURCE = '(?:domain:[a-z][a-z0-9-]*|repo:[a-z][a-z0-9-]*|epic:#[1-9][0-9]*|triage|director|coordinator|security)';
12831283
const SIGNATURE_ACCOUNT_SOURCE = '[A-Za-z0-9](?:[A-Za-z0-9]|-(?=[A-Za-z0-9])){0,38}';
12841284
/** The relay envelope's session shape without its anchors — `SESSION_SHAPE` in `fleet-write/ops.mjs`. */
12851285
export const SEAT_SIGNATURE_SESSION_SOURCE = 'session_[A-Za-z0-9]{6,}';

‎scripts/pm/check-skill-line-ratchet.mjs‎

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,13 @@ export const CEILINGS = new Map([
403403
// Unchanged at 319 by #25712 item 4: its `domain:ui` lane-table row was paid
404404
// by deleting the hold rule's pin-consumer clause (no pin is left to consume)
405405
// and folding the rest of that line into the `pm:on-hold` rule it qualified.
406+
// Unchanged at 319 by the security lane (#29665; ruling of record: the
407+
// amendment comment 6592303181 quoting the maintainer's verbatim 「这个应该算
408+
// 一个车道吧?虽然你不需要打标签,应该也有处理的规则需要写入skills」, which
409+
// orders ONE 域车道 row): the row was paid by deleting the 红线 that restated
410+
// two sentences AGENTS.md Prime Directive #15 carries and outranks this file
411+
// on (a publish nobody ordered is filed as an incident, never counter-published;
412+
// a path to a release is not authorization to walk it), never by re-wrap.
406413
['.claude/skills/pm-dispatch/SKILL.md', 319],
407414
// Raised 223 → 244 by the triage reading-cost card (maintainer ruling
408415
// 2026-08-20, quoted in the raising PR): three mandated conventions land in
@@ -1308,7 +1315,12 @@ export const CEILINGS = new Map([
13081315
// §3 had it read under its own account (a cloud seat's account is refused). The ruling of record is that card's
13091316
// body, filed under the maintainer's verbatim 「按一次性密钥立卡」 and 「并派发」, which names this file "by the
13101317
// measured delta only". Measured delta +1 (55 → 56 lines); landed count, headroom 0.
1311-
['.claude/skills/pm-dispatch/references/security-findings.md', 56],
1318+
// 56 → 62 (#29665): the owner section — every finding is `security#1`'s from intake to publish, the finder's two
1319+
// acts (a relay draft and one opaque-id doorbell), the relay-refused fallback — plus the security lane added to
1320+
// §8's pointer list in place. Ruling of record: that card's body, filed under the maintainer's verbatim
1321+
// 「设 security#1 座位」, and its amendment comment 6592303181 (「这个应该算一个车道吧?…」) naming this file's
1322+
// new section. Measured delta +6 (56 → 62 lines); landed count, headroom 0.
1323+
['.claude/skills/pm-dispatch/references/security-findings.md', 62],
13121324
['.claude/skills/pm-dispatch/references/external-contributions.md', 22],
13131325
['.claude/skills/pm-dispatch/references/reading-discipline.md', 37],
13141326
// Lowered 51 → 47 by #25712 item 4: objectui stopped being a sister repo, so
@@ -1425,7 +1437,13 @@ export const CEILINGS = new Map([
14251437
// mailbox at the shared rule; the pointer does not fit the full 不敲 line,
14261438
// so the measured delta is +1. Not a CROSS_FILE_MOVES destination. Landed
14271439
// count, headroom 0.
1428-
['.claude/skills/pm-dispatch/references/project-forms.md', 55],
1440+
// 55 → 56 (#29665): the `Notify: security` row (finder → `security#1`, the
1441+
// opaque id alone) — the 不敲 line drops 安全发现 and the pointer line narrows
1442+
// to the mechanism, in place; the ring itself needs one line. Ruling of
1443+
// record: the card body (「设 security#1 座位」) and amendment comment
1444+
// 6592303181, which names this file's mailbox row. Measured delta +1
1445+
// (55 → 56 lines); landed count, headroom 0.
1446+
['.claude/skills/pm-dispatch/references/project-forms.md', 56],
14291447
// The app-vs-platform boundary — the deciding question (could this be written
14301448
// from the metadata alone, with no knowledge of this company?), the publication
14311449
// test (one consumer is a use, two is a contract) and the two anti-patterns.
@@ -1490,6 +1508,14 @@ export const CEILINGS = new Map([
14901508
// on demand (Recommended)"). A NEW file, so this is an added row and no other
14911509
// row moves; set at the landed line count (headroom 0, same convention).
14921510
['.claude/skills/pm-dispatch/references/lanes/mobile.md', 56],
1511+
// security lane charter — the `security#N` seat that owns every security
1512+
// finding from intake to publish (maintainer, #29665 card body, verbatim:
1513+
// 「设 security#1 座位」; amendment comment 6592303181: 「这个应该算一个车道吧?
1514+
// 虽然你不需要打标签,应该也有处理的规则需要写入skills」). Pointers only: the
1515+
// never-public rules stay in security-findings.md. A NEW file, so this is an
1516+
// added row and no other row moves; set at the landed line count (headroom 0,
1517+
// same convention).
1518+
['.claude/skills/pm-dispatch/references/lanes/security.md', 34],
14931519
// Project Director seat charter (maintainer ruling 2026-08-27, verbatim:
14941520
// 「项目总监 由人工定期指挥,负责处理 contract-review 类别的和需要决裁的。」; the file
14951521
// carries it as a rule line since the rules-only rewrite, not as this quotation)
@@ -1547,7 +1573,13 @@ export const CEILINGS = new Map([
15471573
// the same reader), so the measured delta is +7; triage-duties.md fell 102 → 99
15481574
// in the same PR, net +4 over both. Not a CROSS_FILE_MOVES destination, so no
15491575
// `ruledRaises` record applies. Landed count, headroom 0, same convention.
1550-
['.claude/skills/pm-dispatch/references/lanes/triage.md', 15],
1576+
// 15 → 17 (#29665): the security hand-over — push ① stays, then triage
1577+
// forwards to `security#1` and stops, grading nothing past `security` + p1
1578+
// on a public card; while the seat is vacant, today's §7 handling. Ruling
1579+
// of record: the card body (「设 security#1 座位」, Change 4) and amendment
1580+
// comment 6592303181. Measured delta +2 (15 → 17 lines); landed count,
1581+
// headroom 0.
1582+
['.claude/skills/pm-dispatch/references/lanes/triage.md', 17],
15511583
// 399 → 405 (#11126): maintainer-ruled (2026-08-23, option B, quoted in that
15521584
// PR) — the +6-line cross-repo dispatch-gates caveat, sized so the queued
15531585
// #11137 (395→399 on main) and this PR's +6 compose to exactly 405.
@@ -2334,6 +2366,7 @@ export const MAX_TABLE_ROW_BYTES = new Map([
23342366
['.claude/skills/pm-dispatch/references/lanes/ui.md', 0],
23352367
['.claude/skills/pm-dispatch/references/lanes/hotcrm.md', 0],
23362368
['.claude/skills/pm-dispatch/references/lanes/mobile.md', 0],
2369+
['.claude/skills/pm-dispatch/references/lanes/security.md', 0],
23372370
['.claude/skills/pm-dispatch/references/lanes/director.md', 0],
23382371
['.claude/skills/pm-dispatch/references/lanes/triage.md', 0],
23392372
['.claude/agents/os-dev.md', 0],

‎scripts/pm/fleet-roster.mjs‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -297,7 +297,7 @@ const isEpicLane = (lane) => lane.startsWith(`${SEAT_EPIC_KIND}:`);
297297
* (#27113) — not imported, so the relay never loads the patrol; the self-test holds the two patterns byte-equal.
298298
*/
299299
const SIGNATURE_LINE = new RegExp(
300-
'^((?:domain:[a-z][a-z0-9-]*|repo:[a-z][a-z0-9-]*|epic:#[1-9][0-9]*|triage|director|coordinator))#([1-9][0-9]*) · ([A-Za-z0-9](?:[A-Za-z0-9]|-(?=[A-Za-z0-9])){0,38}) · (session_[A-Za-z0-9]{6,})$',
300+
'^((?:domain:[a-z][a-z0-9-]*|repo:[a-z][a-z0-9-]*|epic:#[1-9][0-9]*|triage|director|coordinator|security))#([1-9][0-9]*) · ([A-Za-z0-9](?:[A-Za-z0-9]|-(?=[A-Za-z0-9])){0,38}) · (session_[A-Za-z0-9]{6,})$',
301301
'u',
302302
);
303303

@@ -735,7 +735,7 @@ async function selfTest() {
735735
t('⛔ an ACCEPT line ABOVE the heading is not the record\'s verdict', hasLandingVerdict([{ body: 'ACCEPT\n\n## 席位核对\n\nREWORK · rework' }]), false);
736736
t('the heading is H69\'s marker, imported not re-spelled: the three measured bodies read record alike', ['## 席位核对\n\n**ACCEPT** · x', '## 席位核对 — ACCEPT · x', '## Contract review\n\n**VERDICT: PASS**'].map((b) => `${seatRecord.SEAT_RECORD_MARKER.test(b)}/${hasLandingVerdict([{ body: b }])}`), ['true/true', 'true/true', 'false/true']);
737737
t('the mailbox constants are the opener\'s own, imported not re-spelled', [MAILBOX_LABEL, LEDGER_EXCLUSION, MAILBOX_BRANCH_PREFIX], [mailboxOpen.MAILBOX_LABEL, mailboxOpen.LEDGER_EXCLUSION, mailboxOpen.MAILBOX_BRANCH_PREFIX]);
738-
t('whose mailbox a pull is, and a seat row\'s slug, are the opener\'s own readings — imported, not re-spelled', [mailboxOwner === mailboxOpen.mailboxOwner, seatKey === mailboxOpen.seatKey, seatSlugOf({ lane: 'domain:ui', seat: 4 }), seatSlugOf({ lane: 'triage', seat: 1 })], [true, true, 'ui-4', 'triage-1']);
738+
t('whose mailbox a pull is, and a seat row\'s slug, are the opener\'s own readings — imported, not re-spelled', [mailboxOwner === mailboxOpen.mailboxOwner, seatKey === mailboxOpen.seatKey, seatSlugOf({ lane: 'domain:ui', seat: 4 }), seatSlugOf({ lane: 'triage', seat: 1 }), seatSlugOf({ lane: 'security', seat: 1 })], [true, true, 'ui-4', 'triage-1', 'security-1']);
739739

740740
battery('the fixture snapshot: rows for the three repos');
741741
const fixture = JSON.parse(readFileSync(FIXTURE_PATH, 'utf8'));
@@ -826,7 +826,7 @@ async function selfTest() {
826826
t('the comment read starts STALE_HOURS before now, and the footer names it', [activitySince(NOW), readsFor([R]).some((q) => q.startsWith(`GET /repos/${R}/issues/comments?since=`))], ['2026-11-01T22:00:00Z', true]);
827827
const { SEAT_SIGNATURE_LINE, readSignatureLine } = await import('./check-half-states.mjs');
828828
t('the signature pattern is check-half-states\' SEAT_SIGNATURE_LINE, byte for byte — mirrored, not imported', [SIGNATURE_LINE.source === SEAT_SIGNATURE_LINE.source, SIGNATURE_LINE.flags === SEAT_SIGNATURE_LINE.flags], [true, true]);
829-
const corpus = ['', sig('domain:server#2'), `\n\n ${sig('domain:engine-core#3')}\r\nbody`, `body\n${sig('triage#1')}`, 'epic:#27293#1 · sunhaolin · session_017xRBcVBKAFk1ntXSjvPt7U', 'domain:server#0 · os-pm · session_01SharedSeatSess', 'domain:server#2 · os-pm · cse_01SharedSeatSess'];
829+
const corpus = ['', sig('domain:server#2'), `\n\n ${sig('domain:engine-core#3')}\r\nbody`, `body\n${sig('triage#1')}`, sig('security#1'), 'epic:#27293#1 · sunhaolin · session_017xRBcVBKAFk1ntXSjvPt7U', 'domain:server#0 · os-pm · session_01SharedSeatSess', 'domain:server#2 · os-pm · cse_01SharedSeatSess'];
830830
t('signedSeatOf reads every body as readSignatureLine does', corpus.map(signedSeatOf), corpus.map((b) => { const r = readSignatureLine(b); return r ? `${r.lane}#${r.seat}` : null; }));
831831
}
832832

0 commit comments

Comments
 (0)