Repository navigation
Commit 39ea1d6
pm(post-stamped): every seat post opens with a signature line
Fixes #27113
Clause-②: no
Metadata-protocol: none
## 维护者速读
- 改了什么:发帖脚本 `post-stamped`、建卡脚本
`issue-create`、中继的改评论/改正文两条通道,现在要求帖子第一行是署名「车道#席位号 · 账户 ·
会话」,缺署名就拒发;所有按首行识别记录的脚本先跳过这一行署名再识别。
- 为什么改:所有回复都以同一个机器人账号出现,您看不出是哪个车道、哪个席位、哪个账户写的;按您「缺署名就拒发」的裁决落地。
- 风险与回滚:席位第一次发帖会被拒(拒发信息印出格式,补一行即可);回滚 = revert 本 PR。没有署名的旧帖子,识别结果不变。
- 先看:`scripts/pm/check-half-states.mjs`
的署名读取器(唯一拼写);`scripts/pm/post-stamped.mjs` 的 `main()`
拒发分支;`scripts/pm/fleet-write/dispatch.mjs` 的
`unsignedEditBodies`(含名册豁免)。
- 两分钟验证:`printf 'hello\n' | node scripts/pm/post-stamped.mjs --dry-run
--comment=1 --repo=o/r` 预期退出码 2,并印出「opens with no signature
line」和格式;第一行加上 `domain:skills#2 · os-justin · session_…` 再跑,预期退出码 0。
- 需要你做的:无,席位落地(Tier S,席位按合同复核落地)。
## What changes
**The signature.** The first non-empty line of a seat-authored body is
`LANE#N · ACCOUNT · SESSION`, for example `domain:skills#2 · os-justin ·
session_01QSeKYmhU5KEj1nukYhp1Tg`. The parts:
- LANE: one of `domain:` or `repo:` plus a lowercase name, `epic:#` plus
the parent card, `triage`, `director` or `coordinator`.
- N: the seat number, from 1.
- ACCOUNT: GitHub's login rule.
- SESSION: the relay envelope's shape, `session_` plus 6 or more
alphanumerics.
The separator is exactly one space, U+00B7, one space. The spelling
exists once: `SEAT_SIGNATURE_LINE` / `readSignatureLine` /
`stripSignatureLine` / `signatureRefusal` / `signatureRefusalText` in
`scripts/pm/check-half-states.mjs`. Every writer and reader below
imports it.
**Write side: refused, nothing inserted.**
- `post-stamped.mjs`, `--comment` and `--body` alike: an unsigned body
exits `EXIT_REFUSED` (2) before any request, a dry run included. It is
judged beside the render, under the same exit, so a body that also
breaks the stamp contract gets both refusals.
- `issue-create.mjs`: an unsigned card body is a plan error (exit 2),
listed beside the stamp-token error. Nothing calls this door from a
workflow.
- `fleet-write/dispatch.mjs`: a `comment_edit` or `issue_patch` carrying
a `body` in a seat's `--actions-file` is refused as usage (2), right
after the file is parsed. The check runs before the session, the packer
or a dry run. The fleet roster's generated body is exempt by its own
`ROSTER_MARKER`, imported. That is the one generated body that reaches
these ops through this door; the ruling keeps generated cards out of
scope.
- ⛔ `pr_create` is out of scope, and so is the library path
(`packRequest`). A pull request body's line 1 is `Fixes #n`, a closing
keyword the platform reads at that position. The ruling covers posts,
and the dev writes PR bodies. The library's callers are the tools above,
and each judges its own body first.
- `--sign-from` (the ruling's optional half) is not built. The refusal
prints the shape, and the seat types one line.
**Read side: one leading signature line changes no reading.** I searched
`scripts/pm/**` for every first-line idiom: `split(…)[0]`,
`find`/`findIndex` on the first non-blank line, anchored markers without
the `m` flag, and the named keys.
Changed (each now reads through `stripSignatureLine`):
| reader | file | what keys on line one |
|---|---|---|
| `OS_DEV_REPORT_MARKER` | check-half-states | the `os-dev-report`
marker; composed from `SEAT_SIGNATURE_SOURCE`, still anchored, still no
`m` |
| `yieldedClaimIndexes` | check-half-states | a claim's yield line |
| `isTriageRulingComment` | check-half-states | triage ruling openings |
| `h56StampedReadings` / `h56AmbiguousLines` | check-half-states | the
act's OPENING-line stamp, the reader post-stamped's positional refusal
imports |
| `h65Headline` | check-half-states | round records (round-open marker,
round close, stand-down brief) |
| `readMailboxLine` | record-recognisers | the seven mailbox doorbell
keys (`MAILBOX_KEYS`) |
| `firstContentLine` → `classifyRulingComment` | check-prior-rulings |
director `Ruling:` comments |
| `firstLineKeyText` / `isRecheckComment`, `releaseTextRefusal` |
label-write | the owned-24h knock key; the release text's `Release:`
line |
| `decisionAnalysisSignals` | post-stamped | a decision frame on the
plain opening line |
Need nothing:
- These match at any line start (the `m` flag) or read line by line:
`CLAIM_COMMENT_MARKER`, `RELEASE_COMMENT_MARKER` (via `markerMatches`),
`CONTRACT_REVIEW_HEADING_MARKER`, `SEAT_RECORD_MARKER` (`## 席位核对`),
`readContractReviewHandoff`, `readServedTier`, `readLocalRuns`,
`claimSeatNumber`, `H44_ROUND_OPEN_MARKER`, `H44_VERDICT_MARKER`,
`ACCEPT_VERDICT_MARKER`, `MAINTAINER_BRIEF_MARKER`, `STAY_BEHIND_LINE`.
- H64's head window: a signature carries a session id, so it can only
strengthen that reading.
- post-stamped's `commentRow` is display only. It prints the signature,
which is the useful thing to show.
- Generated artefacts: fleet-roster's `writeRefusal`, and
sweep-closed-cards' marker (direct REST, not a seat writer).
- `Contract-review-claim:` and `Brief-claimed-by:` have no machine
reader in `scripts/**`. They are skill prose only, so there is nothing
to pin.
**Skill.** One line in
`.claude/skills/pm-dispatch/references/seat-post-protocol.md` states the
signature and points to the refusal. The file stays at 88 of 88 lines;
the new line is 118 bytes. It is paid for by deleting the
standing-liveness pointer line. That line's content lives at
`references/dispatch-runbook.md` line 41 (and `seat-lifecycle.md` line
37). No re-wrap was used to make room.
## Tests
Battery counts, base `9ff8154c` to head:
| self-test | before | after |
|---|---|---|
| `post-stamped.mjs` | 724 cases / 25 batteries | 735 / 26 (new: the
signature, 11) |
| `issue-create.mjs` | 48 / 7 | 56 / 8 (new: the signature, 8) |
| `record-recognisers.mjs` | 254 / 14 | 276 / 15 (new: the signature
line, 22) |
| `fleet-write/dispatch.mjs` | 247 / 22 | 260 / 23 (new: the signature
at the seat's door, 13) |
| `label-write.mjs` | 134 / 11 | 136 / 11 (release battery pin 30 to 32)
|
| `check-half-states.mjs` | 5034 | 5034 |
| `check-prior-rulings.mjs` | 155 | 155 |
| `with-fleet.sh` | 32 | 32 (its issue-create dry-run fixture now
signed) |
| `check-governed-queue-guard.mjs` | 401 | 401 |
The record-recognisers battery covers three things:
- The reader's shape, including every lane form and nine near misses.
- A pin holding the session half equal to the relay's `SESSION_SHAPE`
(`fleet-write/ops.mjs`; mirrored because `check-half-states.mjs` may not
import it).
- Each recogniser the card names, driven unsigned and signed. Each pin
asserts the reading is positive and identical. These cover `## Contract
review` (locator, `Served-tier:`, `Local-runs:`), `os-dev-report`,
`Contract-review-handoff:`, claim and release lines, the yield line,
round records, `## 席位核对`, all seven mailbox keys, triage and director
rulings, and the H56 opening stamp.
Readers in other modules are imported inside the self-test body, so the
module the queue guard loads lazily gains no edge.
Three fixtures had to be signed, each because its writer now refuses
unsigned bodies: issue-create's `body.md` / `stamped.md` / `b.md`,
`with-fleet.sh`'s issue-create dry-run body, and dispatch's CLI
end-to-end `issue_patch` (its corrupted-byte offset is now stated as
`AT` plus the signature's bytes). Two structural pins moved with the
code they pin. post-stamped's refused-render block now matches the
combined refusal. Its facet-marker composition pin is unchanged: `code`
is still `stripMarkdownCode(String(body ?? ''))`, and only the frame's
line list is stripped.
Ablation: not run. The diff is scripts plus one doc line, which the role
contract excludes from ablation.
## Acceptance notes
- PM mechanism hypotheses, measured:
- ① Partly falsified, three ways. The reader cannot live in
`record-recognisers.mjs`: that file imports `check-half-states.mjs`,
which owns the first-line markers, so the reverse edge would close a
top-level-await cycle. It lives in `check-half-states.mjs`, the module
every writer and reader already imports. The lane class `[a-z]+` would
refuse the real `domain:engine-core`. A desktop-only session shape would
refuse a `cse_`-derived container session.
- ② Confirmed: body mode has no marker-line contract, so the rule
applies to both modes.
- ③ Refined: the render fails with three different texts, and
`renderBody` is shared with label-write's release comment. So the
signature is a second block under the same exit, not a row in the
stamp-contract list, whose header and footer are about stamps.
- ④ Refined: per-recogniser with/without pins rather than doubling every
fixture.
- Writers outside the card's named set still accept an unsigned body.
Examples are a raw `comment` or `issue_create` op handed to the dispatch
door (for instance `mailbox-open.mjs`'s mailbox-close comment) and
label-write's `Release:` comment. Their readers skip a signature, so a
signed body reads correctly; the requirement just is not enforced there.
Widening the requirement is the seat's call, so I did not file anything.
- `OS_DEV_REPORT_MARKER` keeps its `i` flag (the shipped `OS-DEV-REPORT`
dialect), so the composed signature is read case-insensitively in that
one marker. Every writer refuses a mis-cased signature before posting,
so this matters only on the read side.
- H64's `SEAT_SESSION_ID` (the 26-character desktop form) is narrower
than the signature's session shape. It is untouched.
## Verification
All readings were taken at head `bce074a0`.
<details><summary>Gate derivation and reconciliation</summary>
`node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` (exit 0): 65 commands, 9 paths vs merge base
`9ff8154cf`. All 65 were run locally and recorded as `COMMAND :: exit
N`. `--ran` (exit 0): `65 derived famil(ies) accounted for — 65 run, 0
NOT-MEASURED (a DERIVED zero — all 65 recorded an exit code and none of
them is 3)`.
`pnpm --filter @objectstack/lint run check:doc-formula-expressions`
first exited 3 (PREREQUISITE NOT MET: `@objectstack/formula` /
`@objectstack/lint` unbuilt in a fresh worktree). After `turbo run
build` of those two under the verify lock (`VERDICT command-exit 0`), it
exited 0. `pnpm check:pm-dispatch-gates` exit 0 (fast tier, 1935 cases).
Not derived but named by the dispatch: `pnpm check:pm-governed-prose`
exit 0, and `check-governed-queue-guard.mjs --self-test` exit 0 (401).
Consumers also run: `fleet-roster` 78, `mailbox-open` 45,
`check-handover` 46. Control-byte scan over the changed files: no hits.
</details>
<details><summary>The refusal, end to end (dry runs)</summary>
An unsigned comment exits 2 with `post-stamped: REFUSED — the body opens
with no signature line. Nothing was written.` plus the shape. An
unsigned comment with a bare opening stamp exits 2 and prints both the
stamp-contract block and the signature block. A signed body whose claim
line (now line 2) carries a bare stamp still gets the positional
refusal. A signed body with `{{NOW}}` dry-runs to exit 0 with the
signature sent as written.
</details>
---
_Generated by [Claude
Code](https://claude.ai/code/session_01QSeKYmhU5KEj1nukYhp1Tg)_
---------
Co-authored-by: Claude <noreply@anthropic.com>LANE#N · ACCOUNT · SESSION, refused without one; the recognisers skip it (#27263)1 parent c082af6 commit 39ea1d6
9 files changed
Lines changed: 459 additions & 36 deletions
File tree
- .claude/skills/pm-dispatch/references
- scripts/pm
- fleet-write
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
25 | 25 | | |
26 | 26 | | |
27 | 27 | | |
28 | | - | |
29 | 28 | | |
30 | 29 | | |
| 30 | + | |
31 | 31 | | |
32 | 32 | | |
33 | 33 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1255 | 1255 | | |
1256 | 1256 | | |
1257 | 1257 | | |
| 1258 | + | |
| 1259 | + | |
| 1260 | + | |
| 1261 | + | |
| 1262 | + | |
| 1263 | + | |
| 1264 | + | |
| 1265 | + | |
| 1266 | + | |
| 1267 | + | |
| 1268 | + | |
| 1269 | + | |
| 1270 | + | |
| 1271 | + | |
| 1272 | + | |
| 1273 | + | |
| 1274 | + | |
| 1275 | + | |
| 1276 | + | |
| 1277 | + | |
| 1278 | + | |
| 1279 | + | |
| 1280 | + | |
| 1281 | + | |
| 1282 | + | |
| 1283 | + | |
| 1284 | + | |
| 1285 | + | |
| 1286 | + | |
| 1287 | + | |
| 1288 | + | |
| 1289 | + | |
| 1290 | + | |
| 1291 | + | |
| 1292 | + | |
| 1293 | + | |
| 1294 | + | |
| 1295 | + | |
| 1296 | + | |
| 1297 | + | |
| 1298 | + | |
| 1299 | + | |
| 1300 | + | |
| 1301 | + | |
| 1302 | + | |
| 1303 | + | |
| 1304 | + | |
| 1305 | + | |
| 1306 | + | |
| 1307 | + | |
| 1308 | + | |
| 1309 | + | |
| 1310 | + | |
| 1311 | + | |
| 1312 | + | |
| 1313 | + | |
| 1314 | + | |
| 1315 | + | |
| 1316 | + | |
| 1317 | + | |
| 1318 | + | |
| 1319 | + | |
| 1320 | + | |
| 1321 | + | |
| 1322 | + | |
| 1323 | + | |
| 1324 | + | |
| 1325 | + | |
| 1326 | + | |
| 1327 | + | |
| 1328 | + | |
| 1329 | + | |
| 1330 | + | |
| 1331 | + | |
| 1332 | + | |
| 1333 | + | |
| 1334 | + | |
| 1335 | + | |
| 1336 | + | |
| 1337 | + | |
| 1338 | + | |
| 1339 | + | |
| 1340 | + | |
| 1341 | + | |
| 1342 | + | |
| 1343 | + | |
| 1344 | + | |
| 1345 | + | |
| 1346 | + | |
| 1347 | + | |
| 1348 | + | |
| 1349 | + | |
| 1350 | + | |
| 1351 | + | |
| 1352 | + | |
| 1353 | + | |
| 1354 | + | |
| 1355 | + | |
| 1356 | + | |
| 1357 | + | |
| 1358 | + | |
| 1359 | + | |
| 1360 | + | |
| 1361 | + | |
| 1362 | + | |
| 1363 | + | |
| 1364 | + | |
| 1365 | + | |
| 1366 | + | |
| 1367 | + | |
| 1368 | + | |
| 1369 | + | |
| 1370 | + | |
| 1371 | + | |
| 1372 | + | |
| 1373 | + | |
| 1374 | + | |
| 1375 | + | |
| 1376 | + | |
| 1377 | + | |
| 1378 | + | |
| 1379 | + | |
| 1380 | + | |
| 1381 | + | |
| 1382 | + | |
| 1383 | + | |
| 1384 | + | |
| 1385 | + | |
| 1386 | + | |
1258 | 1387 | | |
1259 | 1388 | | |
1260 | 1389 | | |
| |||
5584 | 5713 | | |
5585 | 5714 | | |
5586 | 5715 | | |
5587 | | - | |
| 5716 | + | |
| 5717 | + | |
5588 | 5718 | | |
5589 | 5719 | | |
5590 | 5720 | | |
| |||
7979 | 8109 | | |
7980 | 8110 | | |
7981 | 8111 | | |
7982 | | - | |
| 8112 | + | |
| 8113 | + | |
7983 | 8114 | | |
7984 | 8115 | | |
7985 | 8116 | | |
| |||
12014 | 12145 | | |
12015 | 12146 | | |
12016 | 12147 | | |
12017 | | - | |
| 12148 | + | |
| 12149 | + | |
| 12150 | + | |
| 12151 | + | |
| 12152 | + | |
| 12153 | + | |
| 12154 | + | |
| 12155 | + | |
12018 | 12156 | | |
12019 | 12157 | | |
12020 | 12158 | | |
| |||
12886 | 13024 | | |
12887 | 13025 | | |
12888 | 13026 | | |
12889 | | - | |
| 13027 | + | |
| 13028 | + | |
12890 | 13029 | | |
12891 | 13030 | | |
12892 | 13031 | | |
| |||
12902 | 13041 | | |
12903 | 13042 | | |
12904 | 13043 | | |
12905 | | - | |
| 13044 | + | |
12906 | 13045 | | |
12907 | 13046 | | |
12908 | 13047 | | |
| |||
15788 | 15927 | | |
15789 | 15928 | | |
15790 | 15929 | | |
15791 | | - | |
| 15930 | + | |
| 15931 | + | |
15792 | 15932 | | |
15793 | 15933 | | |
15794 | 15934 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
215 | 215 | | |
216 | 216 | | |
217 | 217 | | |
218 | | - | |
| 218 | + | |
219 | 219 | | |
220 | 220 | | |
221 | 221 | | |
| |||
333 | 333 | | |
334 | 334 | | |
335 | 335 | | |
336 | | - | |
| 336 | + | |
| 337 | + | |
337 | 338 | | |
338 | 339 | | |
339 | 340 | | |
| |||
0 commit comments