Repository navigation
Commit a61f61a
docs(pm-dispatch): a security finding on a private repository is an ordinary
<!-- claude-projects-attribution: {"github_login":"huangyiirene"} -->
_Requested by **Irene** · [project
thread](https://claude.ai/code/project/chan_01TJXtQ1tVD26ecLDZxFS67x?thread=cmsg_01TJXtQ1tVD26ecLDZxFS67xCF4DobLCow88r565SX1vj9)_
Fixes #30114
Clause-②: no
Metadata-protocol: none
`dispatch-gates --tier` reads a MANDATORY model tier for
`.claude/skills/pm-dispatch/SKILL.md` (clause ① of the model-tiering
ruling). It is waived by the maintainer's standing order of 2026-11-03,
and this card ran on the project's default model. The dispatching seat
runs the contract review.
Serial: #30271 waits on this card
## 维护者速读
- 改了什么:私有仓(今天是 cloud)的安全发现改为该仓带 `security` 标签的普通
issue,经中继立卡、机制写在卡里、照卡派修;公开仓仍是草稿安全公告。中继执行器在请求被拒时,不再把引用了请求正文的平台 `message`
印进公开运行日志。
- 为什么改:你的原话「cloud 私有仓库可以直接用带 security 标签的普通 issue,立卡修改 skills」,以及裁定「A2
本卡里一起修中继」;行数上限按你 07:54Z 的裁定(选项 A)64 → 65。
- 风险与回滚:只改内部 agent 规程与 `scripts/pm` 中继脚本,不发包(`skip-changeset`);回滚即
revert。
- 先看:`security-findings.md` 第 3 节(L19–L30)与第 6 节 L45;`execute.mjs` 的
`requestLanded` 失败路径。
- 两分钟验证:`node scripts/pm/fleet-write/execute.mjs --self-test`,预期 284
例全过;`node scripts/pm/check-skill-line-ratchet.mjs`,预期退出
0、`security-findings.md` 65 行(上限 65)。
- 需要你做的:无,席位落地。
## What changes
### The rule
(`.claude/skills/pm-dispatch/references/security-findings.md`)
- §3: a seat tells a public repository from a private one by the
affected repository's `private` flag, read from the API when the private
card is filed, never recalled (L19).
- §3: on a private repository, the private card is an ordinary issue
with the `security` label, filed through the relay's `issue_create`, and
it holds the mechanism (L29). Its fix is dispatched from that issue like
any card at `xhigh`, with no card-less order and no advisory (L30).
- §2's "no issue" and §3's advisory lines, including fallback A, apply
to public repositories (L13, L20, L27; fallback A at L25 names only the
advisory operations, so it is public-only by its own terms).
- §6, item 2 of the card: on a private repository too, PR titles, PR
bodies, commits and changesets state only the rule and link the issue,
because a private repository can be made public and its history goes
with it (L45).
- §9's "two things" line: a seat files the private card per §3 and rings
the doorbell with `#N` on a private repository (L63). A QA runner's
finding reaches its PM session first (`checklist-test` L266), so the
line names seats only.
- §4's public line names the owner seat (L34), as §9's blocked-task line
(L65) does, because a relay refusal now goes to `security#1` first
(L64).
- §8 (one place) is kept.
### Pointers whose wording would now be wrong
- `filing-gate.md` L15: "not a card" becomes "does not pass this gate".
- `lanes/security.md` L10 and L16: the private card per §3, and the
doorbell carries `#N` on a private repository. L48: the patrol treats an
open `security` card as mis-disclosed on a public repository only; on a
private repository it is the private card.
- `SKILL.md` L165 (lane table): drops "card-less order" from the fix
route.
- `checklist-test/SKILL.md` L266 (claim widened by the seat): "a
security finding is not a card" becomes the runner's own duty, "the
runner files no card for a security finding". The PM files the private
card.
- `project-forms.md` L48 (claim widened by the seat, comment
6704480966): the `Notify: security` doorbell rings when a private card
is built (「私卡建成」, either branch of §3), not only a draft advisory.
`.claude/agents/os-dev.md` L49, `lanes/server.md` L52, `lanes/review.md`
L21, `project-forms.md` L52 and `rest-channel.md` L50–51 point at the
file without wording that is now wrong. Left unchanged.
### The relay (`scripts/pm/fleet-write/execute.mjs`)
`requestLanded`'s generic failure path used to print `HTTP {status} —
{json.message}` as the platform gave it. A refusal whose message quoted
the request body (an `issue_create` title or body, say) reached the row,
the `✗ action` log line and both step-summary lines of a public run. Now
the status is always kept, and the message is printed only when it
reproduces no prose of the body sent. The test is the one the advisory
rows use (`advisoryRefusalCause`): `advisoryProseOf` of the body and a
16-character `quotesKnown` window, raw and flattened. A quoting message
is replaced by "the platform's message is not printed: it quotes the
body sent, and no row prints that". Nothing else changes: the GraphQL
branch and the 2xx legs are untouched.
## Tests
The executor self-test battery for an `issue_create` on a private
repository (added by seat 1's work, 5 cases) gains 3 cases, now 8. The
battery floor goes 23 → 24, because `main` added the mailbox branch
delete battery meanwhile. All fixtures use synthetic text.
- A 422 whose message quotes the body: FAILED, status 422 kept, the
marker in no log line, row, step summary or annotation.
- A title whose every 16-character run holds a pipe, quoted back with
slashes: caught on the flattened side.
- A 410 whose message quotes nothing: printed as given.
Mutation proof (each run on the final code with one change, then
restored):
| Mutation | Result |
|---|---|
| none | `284 cases pass across 24 batteries`, exit 0 |
| fix reverted (the old one-line `return`) | 2 of 284 fail (the quoting
refusal, the flattened run), exit 1 |
| flattened comparison dropped | 1 of 284 fails (the flattened run),
exit 1 |
## Acceptance notes
### Ruling of record for the ceiling
Comment 6704167516 on #30114 (seat 3, 2026-11-07T07:55Z): "Maintainer
`huangyiirene` ruled at 2026-11-07T07:54Z (option A of the seat's
question in the project thread): raise the line ceiling of
`.claude/skills/pm-dispatch/references/security-findings.md` from 64 to
65 for the one-line remainder the card thread measured after three real
same-file deletions. Not option B (folding L31 and L47 into one line)."
(Those were the line numbers before the deletions; on the head they are
L30 and L45.)
Seat 1's earlier raise to 67, which cited the card body, is reverted.
The 65 row stands on this ruling alone.
### Line budget (`scripts/pm/check-skill-line-ratchet.mjs`, exit 0)
| File | Lines (main → head) | Ceiling | Bytes (main → head) |
|---|---|---|---|
| `references/security-findings.md` | 64 → 65 | 64 → 65 (ruling above) |
3,601 → 3,800 |
| `references/lanes/security.md` | 53 → 53 | unchanged | 3,156 → 3,190 |
| `references/filing-gate.md` | 67 → 67 | unchanged | 6,558 → 6,561 |
| `pm-dispatch/SKILL.md` | 319 → 319 | unchanged | 32,177 → 32,159 |
| `checklist-test/SKILL.md` | 276 → 276 | unchanged | 19,278 → 19,288 |
| `references/project-forms.md` | 59 → 59 | unchanged | 4,531 → 4,525 |
Every touched prose line is ≤ 120 B (largest: `security-findings.md` L20
117, `lanes/security.md` L48 119; `security-findings.md` L63 is now 105,
L34 84; `project-forms.md` L48 110 → 104). The `SKILL.md` L165 table row
goes 333 → 315 B, under its pin. Bytes measured without the newline.
`security-findings.md`: +4 lines added (L19, L29, L30, L45), three
deleted, net +1:
| Deleted line | Surviving home |
|---|---|
| §3 "a private repository has no private vulnerability reporting, so
those operations stay a hand-off" | stale: the private-repository branch
(L19, L29, L30) replaces it |
| §3 "a seat never reports privately or writes an advisory under its own
account: GitHub writes go only through the relay, as the App" |
`pm-dispatch/SKILL.md` seat invariants L94–95 (content writes only
through the REST relay, the write side always `objectstack-fleet[bot]`),
plus the same file's L22 (a seat-account read is refused) and L25 (no
re-send under another account) |
| §4 the cross-repo finder's filing line, "file through the relay in the
framework repository (§3); relay refused ⇒ hand the mechanism to the
maintainer in session" | §3 L19–L20 (the affected repository's flag
decides; the advisory sits on the affected repository) and §9 L63 (every
seat files the private card per §3), L64 (relay refused ⇒ to
`security#1`, the maintainer when vacant). This line had also named the
maintainer where §9 names the seat. |
L20 gets "on the affected repository" back, so the repository the card
goes on stays named after the §4 line goes.
## Review fixes (contract review 6704473385)
- **M1** `security-findings.md` L63: drops 「与 QA runner」, so only a seat
files the private card. That matches `checklist-test` L266 and the
mailbox recogniser (a QA runner's finding reaches its PM session
privately first). 119 → 105 B, no line cost.
- **S1** `project-forms.md` L48 (claim widened, 6704480966): 「草稿公告建成」
becomes 「私卡建成」, so the doorbell also rings for a private repository's
`security` issue. 110 → 104 B, net 0 lines.
- **N1** `security-findings.md` L34: 「with the maintainer」 becomes 「with
the owner seat」, as L65 says. The rule means the seat: L64 routes a
relay refusal to `security#1` first and to the maintainer only when the
seat is vacant. Same bytes (84), no line cost.
- **S2** is filed as #31103 (a repository-qualified number for the
`Notify: security · #N` doorbell in
`scripts/pm/record-recognisers.mjs`); it is outside this claim.
- **N2** (fallback A L25 stays public-only by its own terms) and **N3**
(a non-quoting refusal message is printed unflattened, as before) are
left as they are.
## Verification
<details><summary>Local gates on bb396b5: 47 derived, 47 run with exit
0, 0 NOT MEASURED (`dispatch-gates --ran`)</summary>
- `node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` derived 47 commands (32 pnpm, 15 direct
node) for the 8 changed paths. Each ran with its exit code captured
before any pipe, after building `@objectstack/formula`,
`@objectstack/lint` and `@objectstack/objectql`. All 47 exited 0.
- They include `check:pm-skill-ratchet`, `check:pm-fleet-write-execute`,
`check:pm-fleet-write-validate`, `check:pm-fleet-write-dispatch`,
`check:pm-governed-merges`, `check:pm-governed-prose`,
`check:pm-skill-id-lint`, `check:skill-frame-sync`,
`check:doc-authoring`, `check:ratchet-remedy-authority` and
`check-comment-mask-corpus.mjs`.
- `--ran` printed: `47 derived famil(ies) accounted for — 47 run, 0
NOT-MEASURED (a DERIVED zero — all 47 recorded an exit code and none of
them is 3)`.
- Earlier runs (6969dfe before the ceiling ruling, 1a0dc42 before the
review fixes) do not count here.
</details>
- `node scripts/pm/check-governed-merges.mjs --branch
claude/project-thread-kk5w88`: GOVERNED, Tier S (席内达档复核落地): `.claude/**`
×6. `scripts/pm/check-skill-line-ratchet.mjs` and
`scripts/pm/fleet-write/execute.mjs` are not on the register. 94 changed
lines (+73 / −21), under 3000.
- `node scripts/check-commit-card-trailers.mjs --range
origin/main..HEAD`: exit 0 before every push.
- History: seat 1's commit 5274bbc merged in, then `origin/main` twice.
No rebase, no force-push.
## Human Minimum
- [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic.
(No schema touched.)
- [x] 2. Every name I added uses the casing for its kind.
- [x] 3. No formatter ran.
- [x] 4. One issue, one branch off an up-to-date `main`, one pull
request.
- [x] 5. Nothing publishes (`.claude/**` and `scripts/pm/**`), so the PR
takes `skip-changeset`. Both declaration lines are filled in above.
- [x] 6. `packages/spec` not touched; nothing to regenerate.
- [x] 7. No hand edits to `content/docs/references/`,
`packages/*/CHANGELOG.md` or `content/docs/releases/`.
- [x] 8. Title, body and comments are in English. (The 维护者速读 section is
Chinese, per the lane template.)
- [x] The *Before Submitting* list in CONTRIBUTING.md is done.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01J8VVAjG6DxvHuUKNbj3JB1
<!-- signed: domain:skills#3 · huangyiirene ·
session_01J8VVAj7CvjkGXvgXzd6RZ7 -->
---------
Co-authored-by: Claude <noreply@anthropic.com>security issue; the relay withholds a refusal message that quotes the body (#31102)1 parent f810c34 commit a61f61a
8 files changed
Lines changed: 73 additions & 21 deletions
File tree
- .claude/skills
- checklist-test
- pm-dispatch
- references
- lanes
- scripts/pm
- fleet-write
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
263 | 263 | | |
264 | 264 | | |
265 | 265 | | |
266 | | - | |
| 266 | + | |
267 | 267 | | |
268 | 268 | | |
269 | 269 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
162 | 162 | | |
163 | 163 | | |
164 | 164 | | |
165 | | - | |
| 165 | + | |
166 | 166 | | |
167 | 167 | | |
168 | 168 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
12 | 12 | | |
13 | 13 | | |
14 | 14 | | |
15 | | - | |
| 15 | + | |
16 | 16 | | |
17 | 17 | | |
18 | 18 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
7 | 7 | | |
8 | 8 | | |
9 | 9 | | |
10 | | - | |
| 10 | + | |
11 | 11 | | |
12 | 12 | | |
13 | 13 | | |
14 | 14 | | |
15 | 15 | | |
16 | | - | |
| 16 | + | |
17 | 17 | | |
18 | 18 | | |
19 | 19 | | |
| |||
45 | 45 | | |
46 | 46 | | |
47 | 47 | | |
48 | | - | |
| 48 | + | |
49 | 49 | | |
50 | 50 | | |
51 | 51 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
45 | 45 | | |
46 | 46 | | |
47 | 47 | | |
48 | | - | |
| 48 | + | |
49 | 49 | | |
50 | 50 | | |
51 | 51 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | | - | |
| 1 | + | |
2 | 2 | | |
3 | 3 | | |
4 | 4 | | |
| |||
10 | 10 | | |
11 | 11 | | |
12 | 12 | | |
13 | | - | |
| 13 | + | |
14 | 14 | | |
15 | 15 | | |
16 | 16 | | |
17 | | - | |
| 17 | + | |
18 | 18 | | |
19 | | - | |
| 19 | + | |
| 20 | + | |
20 | 21 | | |
21 | 22 | | |
22 | 23 | | |
23 | | - | |
24 | 24 | | |
25 | 25 | | |
26 | | - | |
27 | 26 | | |
28 | | - | |
| 27 | + | |
29 | 28 | | |
| 29 | + | |
| 30 | + | |
30 | 31 | | |
31 | 32 | | |
32 | 33 | | |
33 | | - | |
34 | | - | |
| 34 | + | |
35 | 35 | | |
36 | 36 | | |
37 | 37 | | |
| |||
42 | 42 | | |
43 | 43 | | |
44 | 44 | | |
| 45 | + | |
45 | 46 | | |
46 | 47 | | |
47 | 48 | | |
| |||
59 | 60 | | |
60 | 61 | | |
61 | 62 | | |
62 | | - | |
| 63 | + | |
63 | 64 | | |
64 | 65 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1348 | 1348 | | |
1349 | 1349 | | |
1350 | 1350 | | |
1351 | | - | |
| 1351 | + | |
| 1352 | + | |
| 1353 | + | |
| 1354 | + | |
| 1355 | + | |
| 1356 | + | |
| 1357 | + | |
1352 | 1358 | | |
1353 | 1359 | | |
1354 | 1360 | | |
| |||
0 commit comments