Repository navigation
pm(post-stamped): every seat post opens with a signature line LANE#N · ACCOUNT · SESSION, refused without one; the recognisers skip it - #27263
Conversation
…t-stamped's refusal readSignatureLine / stripSignatureLine / signatureRefusal live beside the first-line markers in check-half-states.mjs. Every first-line recogniser reads through stripSignatureLine; OS_DEV_REPORT_MARKER composes the one spelling. post-stamped refuses an unsigned body in both modes, listed beside the stamp contract's refusals under one exit. Claude-Session: https://claude.ai/code/session_01QSeKYmhU5KEj1nukYhp1Tg Co-authored-by: Claude <noreply@anthropic.com>
…ies; pins on every first-line reader; one skill line issue-create refuses an unsigned card body beside the stamp-token rule; fleet-write/dispatch refuses an unsigned comment_edit / issue_patch body in a seat's actions file (the roster's generated body exempt by its imported marker). record-recognisers pins every named first-line reader with and without a leading signature; label-write pins its two readers. seat-post-protocol.md states the signature in one line, paid by the standing-liveness line whose content lives in dispatch-runbook.md. Claude-Session: https://claude.ai/code/session_01QSeKYmhU5KEj1nukYhp1Tg Co-authored-by: Claude <noreply@anthropic.com>
Contract reviewServed-tier: Read at 2026-10-28T13:33Z by the ① Derived judgments
② Semver level
③ Boundary flags
④ Design judgment
Implemented-by: VERDICT: PASS |
维护者速读 — 席位终稿 · PR #27263 · head
|
Fixes #27113
Clause-②: no
Metadata-protocol: none
维护者速读
post-stamped、建卡脚本issue-create、中继的改评论/改正文两条通道,现在要求帖子第一行是署名「车道#席位号 · 账户 · 会话」,缺署名就拒发;所有按首行识别记录的脚本先跳过这一行署名再识别。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。What changes
The signature. The first non-empty line of a seat-authored body is
LANE#N · ACCOUNT · SESSION, for exampledomain:skills#2 · os-justin · session_01QSeKYmhU5KEj1nukYhp1Tg. The parts:domain:orrepo:plus a lowercase name,epic:#plus the parent card,triage,directororcoordinator.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/signatureRefusalTextinscripts/pm/check-half-states.mjs. Every writer and reader below imports it.Write side: refused, nothing inserted.
post-stamped.mjs,--commentand--bodyalike: an unsigned body exitsEXIT_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: acomment_editorissue_patchcarrying abodyin a seat's--actions-fileis 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 ownROSTER_MARKER, imported. That is the one generated body that reaches these ops through this door; the ruling keeps generated cards out of scope.pr_createis out of scope, and so is the library path (packRequest). A pull request body's line 1 isFixes #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/findIndexon the first non-blank line, anchored markers without themflag, and the named keys.Changed (each now reads through
stripSignatureLine):OS_DEV_REPORT_MARKERos-dev-reportmarker; composed fromSEAT_SIGNATURE_SOURCE, still anchored, still nomyieldedClaimIndexesisTriageRulingCommenth56StampedReadings/h56AmbiguousLinesh65HeadlinereadMailboxLineMAILBOX_KEYS)firstContentLine→classifyRulingCommentRuling:commentsfirstLineKeyText/isRecheckComment,releaseTextRefusalRelease:linedecisionAnalysisSignalsNeed nothing:
mflag) or read line by line:CLAIM_COMMENT_MARKER,RELEASE_COMMENT_MARKER(viamarkerMatches),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.commentRowis display only. It prints the signature, which is the useful thing to show.writeRefusal, and sweep-closed-cards' marker (direct REST, not a seat writer).Contract-review-claim:andBrief-claimed-by:have no machine reader inscripts/**. They are skill prose only, so there is nothing to pin.Skill. One line in
.claude/skills/pm-dispatch/references/seat-post-protocol.mdstates 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 atreferences/dispatch-runbook.mdline 41 (andseat-lifecycle.mdline 37). No re-wrap was used to make room.Tests
Battery counts, base
9ff8154cto head:post-stamped.mjsissue-create.mjsrecord-recognisers.mjsfleet-write/dispatch.mjslabel-write.mjscheck-half-states.mjscheck-prior-rulings.mjswith-fleet.shcheck-governed-queue-guard.mjsThe record-recognisers battery covers three things:
SESSION_SHAPE(fleet-write/ops.mjs; mirrored becausecheck-half-states.mjsmay not import it).## 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-endissue_patch(its corrupted-byte offset is now stated asATplus 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:codeis stillstripMarkdownCode(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
record-recognisers.mjs: that file importscheck-half-states.mjs, which owns the first-line markers, so the reverse edge would close a top-level-await cycle. It lives incheck-half-states.mjs, the module every writer and reader already imports. The lane class[a-z]+would refuse the realdomain:engine-core. A desktop-only session shape would refuse acse_-derived container session.renderBodyis 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.commentorissue_createop handed to the dispatch door (for instancemailbox-open.mjs's mailbox-close comment) and label-write'sRelease: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_MARKERkeeps itsiflag (the shippedOS-DEV-REPORTdialect), 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.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.Gate derivation and reconciliation
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack(exit 0): 65 commands, 9 paths vs merge base9ff8154cf. All 65 were run locally and recorded asCOMMAND :: 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-expressionsfirst exited 3 (PREREQUISITE NOT MET:@objectstack/formula/@objectstack/lintunbuilt in a fresh worktree). Afterturbo run buildof those two under the verify lock (VERDICT command-exit 0), it exited 0.pnpm check:pm-dispatch-gatesexit 0 (fast tier, 1935 cases). Not derived but named by the dispatch:pnpm check:pm-governed-proseexit 0, andcheck-governed-queue-guard.mjs --self-testexit 0 (401). Consumers also run:fleet-roster78,mailbox-open45,check-handover46. Control-byte scan over the changed files: no hits.The refusal, end to end (dry runs)
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.Generated by Claude Code