Repository navigation
[finding] post-stamped: a malformed WAS token (non-timestamp payload) is neither rendered nor refused — the raw token and its payload are stored verbatim #18284
Description
Activity
claude commented
on Sep 15, 2026 claudeboton Sep 15, 2026 – with ClaudeContributorAuthorMore actionsClaim: PM loop round 1
Session:session_01HZfg2AwVX191qCizp88gQr(skills seat; claimed at 2026-09-15T08:05Z)
Branch:claude/issue-18284-post-stamped-refuses-malformed-tokens
Worktree:objectstack-issue-18284
Domain:domain:skills
File surface:scripts/pm/post-stamped.mjs(the token scanner and its--self-testcases) only (stop on breach; explain in the report)
Container & model:S,mode:subagent,model: default tier—dispatch-gates.mjs --tiernames no mandate forscripts/pm/**; seat review atCONTRACT_REVIEW_TIER
Clause-②: no
Thread-read: none (the card carries no comment at claim time)
Serial constraints cleared:scripts/pm/post-stamped.mjsis held by no open PR; batch 2/3 (#18281 in flight; PR #18279 in the merge queue)
Generated by Claude Code
claude commented
on Sep 15, 2026 claudeboton Sep 15, 2026 – with ClaudeContributorAuthorMore actionsos-dev-report
{ "issue": 18284, "status": "done", "premise_still_valid": true, "branch": "claude/issue-18284-post-stamped-refuses-malformed-tokens", "worktree": "/home/user/objectstack-issue-18284", "pr": "https://github.com/objectstack-ai/objectstack/pull/18286", "head_sha": "9ec6e4051cca47873f754dd9d5e23693dbfabd62", "report_written_at": "2026-09-15T08:46Z", "files_changed": ["scripts/pm/post-stamped.mjs"], "summary": "post-stamped now scans the body for EVERY double-brace opener BEFORE substitution and refuses any that is not the act-clock token or a quoted-stamp token with a brace-free, closed payload. The old check ran AFTER substitution over the two payload regexes, both of which spell their payload as a negated brace class, so an opener whose payload carried a brace, or that never closed, matched neither: not substituted because nothing recognised it, not refused because the leftover scan could not see it. Four shapes are now named and refused, with the offending span clipped to 60 bytes and control characters escaped. The scan judges SHAPE only; whether an admitted payload is an instant the clock has reached stays with stampRefusals, so exactly one rule still decides what a quoted stamp is. The post-substitution leftover check was removed rather than kept beside the scan, because after the scan it could never fire.", "gates": [ {"command": "node packages/lint/scripts/check-reference-carrier-shape.mjs", "exit": 0, "note": ""}, {"command": "node packages/lint/scripts/check-reference-carrier-shape.mjs --self-test", "exit": 0, "note": ""}, {"command": "node scripts/check-ci-filter-parity.mjs", "exit": 0, "note": ""}, {"command": "node scripts/check-closing-keyword-parity.mjs", "exit": 0, "note": ""}, {"command": "node scripts/check-closing-keyword-parity.mjs --self-test", "exit": 0, "note": ""}, {"command": "node scripts/check-comment-mask-corpus.mjs", "exit": 0, "note": ""}, {"command": "node scripts/check-declaration-mirrors.mjs", "exit": 0, "note": "no hand-written declaration beside this tool; nothing to mirror"}, {"command": "node scripts/check-declaration-mirrors.mjs --self-test", "exit": 0, "note": ""}, {"command": "node scripts/check-scripts-symbol-anchors.mjs", "exit": 0, "note": "the new header section cites no path-and-line, so it adds no anchor to rot"}, {"command": "node scripts/check-scripts-symbol-anchors.mjs --self-test", "exit": 0, "note": ""}, {"command": "node scripts/check-self-test-wired.mjs", "exit": 0, "note": "211 scripts CI runs that ship a self-test all have it run; the lint.yml step for this tool was already there and was not touched"}, {"command": "node scripts/check-self-test-wired.mjs --self-test", "exit": 0, "note": ""}, {"command": "node scripts/check-self-test-workflow-commands.mjs", "exit": 0, "note": ""}, {"command": "node scripts/check-self-test-workflow-commands.mjs --self-test", "exit": 0, "note": ""}, {"command": "node scripts/check-whole-set-label-write.mjs", "exit": 0, "note": ""}, {"command": "node scripts/check-whole-set-label-write.mjs --self-test", "exit": 0, "note": ""}, {"command": "node scripts/pm/bare-root-worklist.mjs --self-test", "exit": 0, "note": ""}, {"command": "node scripts/report-test-timings.mjs --self-test", "exit": 0, "note": ""}, {"command": "pnpm check:agent-test-spelling", "exit": 0, "note": ""}, {"command": "pnpm check:bash32-floor", "exit": 0, "note": ""}, {"command": "pnpm check:cli-command-ids", "exit": 0, "note": ""}, {"command": "pnpm check:cross-package-test-inputs", "exit": 0, "note": ""}, {"command": "pnpm check:driver-memory-census", "exit": 0, "note": ""}, {"command": "pnpm check:entry-guard", "exit": 0, "note": ""}, {"command": "pnpm check:nul-bytes", "exit": 0, "note": "8687 tracked text files, no raw ASCII control bytes; the file carries the escape SPELLINGS only"}, {"command": "pnpm check:parse-guard", "exit": 0, "note": ""}, {"command": "pnpm check:pm-dispatch-gates", "exit": 0, "note": "run detached with setsid nohup, waited on with tail --pid; 443.9s self-reported, 7m01s wall; exit captured to a file by the detached shell, never through a pipe"}, {"command": "pnpm check:pm-post-stamped", "exit": 0, "note": "the tool's own self-test: 152 cases across 9 batteries"}, {"command": "pnpm check:pnpm-filter-targets", "exit": 0, "note": ""}, {"command": "pnpm check:ratchet-remedy-authority", "exit": 0, "note": ""}, {"command": "pnpm check:refd-timer-probe", "exit": 0, "note": ""}, {"command": "pnpm check:watch-hint-literal", "exit": 0, "note": ""}, {"command": "node --stack-size=4000 node_modules/eslint/bin/eslint.js scripts/pm/post-stamped.mjs", "exit": 0, "note": "named by the dispatch, outside the derived 32; no output"}, {"command": "node scripts/check-self-test-wired.mjs (named separately by the dispatch)", "exit": 0, "note": "same invocation as the derived family above"} ], "ran_verdict": "dispatch-gates --ran: 32 derived famil(ies) accounted for - 32 run, 0 NOT-MEASURED (a DERIVED zero - all 32 recorded an exit code and none of them is 3). Exit 0. The record was written as `command :: exit N` so the zero is derived rather than claimed; a first attempt with bare command lines was refused by the tool at exit 1 with 1 UNRUN, and is reported here rather than hidden.", "gates_not_measured": "Beside the derived 32 the tool prints families it will not place: 52 artifact-roster families, 11 declared wide-population families, 14 that apply only once a changeset exists (there is none - see skip-changeset), 2 that take a value from the workflow env, and 1 path-scheduled CI job with 5 steps that has no local invocation. Those are CI's, not this run's, and are NOT claimed here as measured.", "reverse_verification": { "method": "Ablation from the COMMITTED state, absolute paths, trap on EXIT INT TERM. The base blob was checked out over the file and its arrival proved by hash BEFORE any reading was taken; the restore leg was proved by hash AND by an empty `git diff HEAD`, never by an exit code.", "on_disk_proof": "BASE blob e90e4a1b6557fc5d5ef7330da94b0124ba7b7b9c equals hash-object of the file after ablation, and grep -c unrecognisedOpeners in the ablated file is 0. After restore: HEAD blob 26e7f6cef7675bfe9134e61255eefcf4a3bc6aca equals hash-object, grep -c is 16, `git diff HEAD` empty.", "fixture": "A body whose quoted slot holds a two-line non-timestamp payload - a Node dump, which is what the filed artefact's failed shell read produced. The braces in it are what make it invisible to both payload regexes.", "before": "EXIT=0. DRY RUN line verbatim: post-stamped: DRY RUN - nothing was written. 0 token(s) substituted with `2026-09-15T08:18Z`, 0 quoted stamp(s) rendered verbatim. Target would be objectstack-ai/objectstack#18284 (comment). And stdout was the body it would have written: opener, dump and closer intact. The unpatched self-test on that same tree: 117 cases across 8 batteries, green.", "after": "EXIT=2, stdout EMPTY (nothing rendered). stderr: post-stamped: REFUSED - 1 double-brace opener(s) in this body are not tokens this tool can render. Nothing was written. Then the row: 1. [brace-in-payload] a brace inside the payload, so no token ends here - followed by the span, clipped at 60 bytes with its newlines printed as escapes.", "direction": "Expected direction was RED (a refusal appears where none did). Observed exactly that: no reversal, no diagnostics-count change." }, "self_test_cases": { "before": "117 cases across 8 batteries", "after": "152 cases across 9 batteries", "added": 35, "battery": "the opener scan: every double-brace opener is a token this tool renders, or the body is refused", "floor": "SELF_TEST_BATTERIES gains a row floored at 35; SELF_TEST_BATTERY_FLOOR raised from 8 to 9 so deleting the new battery outright is still caught", "cases_the_card_asked_for": [ "(a) a quoted slot holding a non-timestamp payload - the filed Node-dump repro, refused; plus a case pinning WHY it used to pass (neither payload regex can cross a brace)", "(b) a quoted payload spanning LINES - refused; brace-free it lands on the pre-existing quoted-not-a-stamp rule, whose span is now escaped and clipped too", "(c) an unknown token NAME - refused; the recorded lowercase typo pinned as the same kind", "(d) an opener with no closer - refused, including an unclosed act-clock opener; plus the ordering pin that it is reported as the opener it is and NOT as the positional refusal the unmasked payload would otherwise raise", "(e) the CONTROL - one valid act-clock token and one valid quoted stamp still render, the scan finds nothing to refuse, and the rendered body carries no opener", "(f) the escaped/entity control - the tool has NO escape form and never had one, so this is pinned in two halves instead: the HTML-entity spelling is not an opener at all (it is prose), and a token inside backticks is STILL substituted, because a fence is a rendering instruction and substitution runs on bytes" ], "extra": "nested and doubled openers; every opener walked, not only the first; the no-narrowing controls (whitespace inside the declaration, the seconds grain, a bare stamp in prose); and seven cases on the span renderer itself - newline, CR, tab, other control bytes as escapes, the clip at 60 BYTES, no half-cut multi-byte character, no ellipsis when inside budget." }, "assumptions_verified": [ {"assumption": "a malformed quoted token with a non-timestamp payload is neither rendered nor refused", "verdict": "true, but narrower than stated", "evidence": "It holds when the payload carries a BRACE or the token never closes - then both payload regexes (a negated brace class) fail and the opener passes through at exit 0. A brace-FREE non-instant payload, multi-line included, was ALREADY refused before this PR as quoted-not-a-stamp at exit 2, measured on fe0ae5c1. The live artefact's payload was a Node dump, which carries braces, so the card's reading is right about the incident; the hole is the brace and the missing closer, not the newline."}, {"assumption": "the raw token and its payload are stored verbatim", "verdict": "true", "evidence": "The unpatched dry run's stdout is the body it would have written: opener, dump and closer intact, byte for byte."}, {"assumption": "--dry-run printed its DRY RUN line and reported nothing substituted", "verdict": "true", "evidence": "Quoted verbatim under reverse_verification.before: 0 token(s) substituted, 0 quoted stamp(s) rendered verbatim, exit 0."}, {"assumption": "keep whatever forms the tool already accepts; narrow none", "verdict": "true - none narrowed", "evidence": "The forms accepted before this PR, read off the CODE rather than the prose: (1) the act-clock token in its exact spelling and no other; (2) the quoted token whose payload, trimmed, is exactly one PROTOCOL_STAMP_RE match and nothing else - minute-grained OR seconds-grained, word-bounded, surrounding whitespace tolerated - and whose instant is not later than the span of the clock's own minute. Every one of those still clears the new scan, pinned by four cases: the seconds grain, the whitespace-tolerant declaration, the valid pair rendering, and a bare stamp in prose remaining nobody's business. The scan admits a SUPERSET of what stampRefusals then judges, so it cannot narrow the accepted set on its own."}, {"assumption": "the lint workflow runs the self-test at the step named Stamped-post helper self-test, and check-self-test-wired holds the wiring", "verdict": "true; neither file needed touching", "evidence": "lint.yml runs pnpm check:pm-post-stamped, whose script is this tool's --self-test. check-self-test-wired DERIVES its population rather than keeping a per-script row, so adding cases moves nothing in it; it exits 0 with 211 scripts accounted for. No workflow or ledger edit was made, and the wiring gate did not demand one."}, {"assumption": "scripts/pm/post-stamped.mjs is held by no open PR", "verdict": "true", "evidence": "Every open PR's file list was read; the only one touching the path is this card's own PR 18286."}, {"assumption": "check:pm-dispatch-gates is derived when the diff edits a gate source, and takes about 435s", "verdict": "true", "evidence": "It is in the derived 32 for this single-file diff. Self-reported battery time 457.6s on the first run and 443.9s on the final instrumented one; 7m01s wall."}, {"assumption": "pnpm is at /opt/node22/bin/pnpm and an offline frozen-lockfile install works in a fresh worktree in about 5s", "verdict": "true", "evidence": "5.6s real, exit 0."}, {"assumption": "a leftover double-brace opener in a stored artefact is never intended", "verdict": "true and unchallenged", "evidence": "The tool's own header already states there are exactly two spellings and no flag turns the contract off; an opener that renders to nothing contradicts that directly."} ], "four_axis": { "choice": "refuse every unrecognised opener vs. render-and-warn", "实际业务需求": "The consumer is the seat protocol itself - this tool writes every seat artefact. The need is measured, not speculative: one live comment carried an unrendered opener to the board and had to be repaired by a REST PATCH. Render-and-warn puts its warning on stderr, the same channel the DRY RUN line already occupies and the one the recorded failure's operator read straight past: the seat saw the DRY RUN line, then the posted line, and moved on. A warning on the channel that was demonstrably not read at the moment it mattered changes no outcome. Refusal does.", "项目长远合理性": "The tool's header states there are exactly two spellings and no flag turns the contract off. Render-and-warn invents a third state - posted, but the tool knew it was wrong - whose only record is a transcript line nobody keeps. That is a temporary patch by the no-workarounds standard; refusal keeps the stated invariant true.", "防 AI 写代码犯错": "Decisive here. The writer that failed IS an AI seat filling a slot from a shell variable. A warning it must notice is consumer-side leniency in exactly the shape the directive names: tolerate off-spec input and hope the reader catches it. A loud refusal at publish time is the contract-tightening form, and it is cheap to obey - re-read the variable, re-run. 声明即强制: the tool declares two tokens, so it must refuse the third rather than post it and complain.", "创业阶段不扩散需求": "Render-and-warn is the staged option - a grace window in which the broken spelling still posts. The 2026-08-27 ruling is explicit: 「项目在创业阶段,用户也很少,短期不考虑渐进。」 A staged option may be recommended only on named external-user evidence, and there is none: every caller is a seat inside this repo. So immediate refusal, no window, no flag, no opt-out.", "conflict": "none - all four axes point the same way", "recommendation": "Refuse. Implemented as such: the refusal fires on --dry-run and on the live write alike, because both run through renderBody, and there is no flag to disable it." }, "tests": "node scripts/pm/post-stamped.mjs --self-test: 117 cases / 8 batteries before, 152 / 9 after, exit 0 both times. All 32 derived gate families exit 0 (list above), reconciled by dispatch-gates --ran at exit 0 with 0 NOT-MEASURED. ESLint on the file: exit 0, no output. Ablation before/after readings under reverse_verification, each with on-disk hash proof and a proved restore. No package build or vitest run is owed: the diff is one root-level tool in no package, so there is no affected package to build or test - the tool's own --self-test is its whole test surface, and it is the thing CI runs.", "deviations": [ "Merged origin/main (9ed3f167) into the branch before opening the PR. dispatch-gates warned STALE TREE on the first derivation and named check-skill-line-ratchet.mjs as a changed derivation input; rather than report a gate list derived from a tree nobody is on, the merge was taken and every reading here comes from the merged tree at 9ec6e405. The incoming commit touches a skills reference doc and a skills line-ratchet script, both disjoint from this diff, and the PR's three-dot diff is still exactly one file.", "The first check:pm-dispatch-gates run was started against the pre-merge tree and killed once the merge was decided - by recorded PID and process group, never by name. It had already gone green. The reported result is the second, instrumented run on the final tree, whose exit code was written to a file by the detached shell.", "A first dispatch-gates --ran attempt used bare command lines and was refused at exit 1 with 1 UNRUN. Rewritten as `command :: exit N` per the tool's own instruction and re-run to exit 0. Both readings are reported rather than only the green one.", "The report field list given by the seat has no home for the four-axis analysis, so a four_axis field was added. Fields from the standing os-dev template that the seat's list omits - premise_still_valid, summary, tests, open_questions, out_of_scope_findings - are also present.", "PR body footer: the harness attribution reminder prescribes a two-line form, while AGENTS.md prescribes the session-URL `_Generated by [Claude Code](...)_` line under a rule for PR bodies. AGENTS.md was followed, as the repo instruction file the reminder itself defers to. The body was read back in full after the write: stored byte-identical except for the trailing newline the platform strips, one footer, no sanitizer mutation.", "The existing quoted-not-a-stamp refusal's detail now renders its payload through the same span renderer - escaped, clipped at 60 bytes. This changes no verdict (the same bodies are refused) but it is a message change to a rule the card did not name, made because case (b) lands there and the card asks for spans shown that way. Short single-line payloads render byte-identically to before." ], "out_of_scope_findings": [ "to file (class (a) reproducible defect; dedupe words: post-stamped, quoted stamp, impossible date, stampSpan, stampIsFuture) - a quoted payload that matches the stamp SHAPE but names an impossible instant is accepted and rendered verbatim. Repro on the patched tree: a body whose quoted slot holds the protocol's own shape filled with an impossible instant - year 2026, month 13, day 45, time 99:99, spelled exactly as the regex wants - exits 0 and prints '1 quoted stamp(s) rendered verbatim', putting that text on the board as if it were a reading. Cause: the shape rule is PROTOCOL_STAMP_RE, which is digit-shaped only, while the direction rule asks stampSpan, whose Date.parse returns NaN - so the span is null and stampIsFuture reads null as 'not future'. This is a DIFFERENT failure mode from this card's (rendered-but-unvalidated, not neither-rendered-nor-refused), so the bounded in-place-fix exemption does not apply and it was not fixed here.", "noted, not filed: the usage text and header describe the quoted payload as a minute-grained instant, while the shape actually accepted also takes the seconds grain. The seconds grain is deliberate and pinned by an existing case; only the prose is narrower than the contract. Carrier: the next PR that edits this header - it is the same paragraph the new scan section sits beside, so it will be read. Recorded in the PR's Acceptance notes." ], "open_questions": [], "mcp_calls": "0 - no MCP GitHub tool was called, read or write. Every GitHub read went through curl against the REST proxy.", "api_writes": "3 REST writes. (1) POST /repos/objectstack-ai/objectstack/pulls - the draft PR. (2) POST /repos/objectstack-ai/objectstack/issues/18286/labels - skip-changeset, applied through scripts/pm/label-write.mjs, which read back and reported MATCHES the target with labels size/m and skip-changeset; size/m was set by another actor and left alone. (3) POST /repos/objectstack-ai/objectstack/issues/18284/comments - this report, written by the patched tool itself, which is also the end-to-end smoke test: the act-clock token in this body was substituted by the code this PR changes, and every other instant here is declared through the quoted route. Git pushes are separate and not REST: three, to the branch only - the empty branch as the write-route probe, the implementation commit, the merge commit.", "session": "A subagent has no session_01 id of its own; this ran under the dispatching seat's session, session_01HZfg2AwVX191qCizp88gQr.", "timings": "worktree plus offline install 5.6s; reading the tool, the card and the claim and mapping the before-state of every token shape about 12 min; implementation and self-test about 14 min; ablation with on-disk proof about 1 min; the 31 foreground gate families about 4 min; check:pm-dispatch-gates 443.9s self-reported and 7m01s wall, detached; PR create, read-back and label about 2 min." }
Generated by Claude Code
claude commented
on Sep 15, 2026 claudeboton Sep 15, 2026 – with ClaudeContributorAuthorMore actionsACCEPT — skills seat, session
session_01HZfg2AwVX191qCizp88gQr, 2026-09-15T08:49Z. Report 5677361795 on PR #18286 (head9ec6e405) reviewed by the checklist: files changed = the claim's surface exactly (scripts/pm/post-stamped.mjs);Fixes #18284alone beside a closing keyword;skip-changesetread back;mcp_calls0 — every write through the REST proxy orlabel-write.mjs, the report itself written by the patched tool; gates 32 / 32 / 0 / 0 by--ran, battery 1730 pass (443.9 s detached, exit captured); the seat's re-run on a detached worktree of the head: self-test 152 / 9 batteries (117 / 8 on main), the brace-payload fixture PASSES the tool onorigin/mainand is REFUSED on the head, unknown-token and unclosed-opener fixtures refused, the control renders, ESLint 0, self-test wiring and watch-hint green (record 5677397822 on the PR names each). Deviations (5) answered one by one in the record. Out of scope routed: the impossible-instant acceptance → #18289 (serial behind this PR on the same file). Landing: in-seat now — every check on the head green — ready through the CCR route + auto-merge SQUASH; the card stayspm:dispatcheduntil the two landing readings.
Generated by Claude Code
claude commented
on Sep 15, 2026 claudeboton Sep 15, 2026 – with ClaudeContributorAuthorMore actionsLanded — skills seat, session
session_01HZfg2AwVX191qCizp88gQr, 2026-09-15T09:07Z. PR #18286 (head9ec6e405) merged by the queue as499f7f9e9766f264db7eddbb0ca197805b2fa7d0(single-parent squash pergit rev-list --parents) at 2026-09-15T09:06Z — themerged_atinstant, carried identically by themergedandremoved_from_merge_queuetimeline events. Readings at 2026-09-15T09:07Z:git log origin/maincarries(#18286); the queue refrefs/heads/gh-readonly-queue/main/pr-18286-*is gone from origin (read under the register's spelling — the seat's earlier 「not served」 clause was its own refspec slip, owned on #7623); theremoved_from_merge_queueevent is on the timeline. Non-governed landing: record 5677397822 PASS, ACCEPT on this card, provenance on the PR; the seat flipped it ready through the CCR route and armed auto-merge SQUASH at 2026-09-15T08:49Z. Now onorigin/main:post-stamped.mjsscans every double-brace opener before substitution and REFUSES any that is not a token it renders (brace-in-payload, unclosed opener, unknown name), on--dry-runtoo; self-test 152 cases across 9 batteries. Residue (pm:dispatched, assignee) stripped throughlabel-write.mjsand read back; #18289 (the impossible-instant acceptance, same file) is unblocked and dispatches next.
Generated by Claude Code
- added 2 commits that reference this issue
on Sep 17, 2026
Filed by the
domain:skillsexecution PM seat, sessionsession_01HZfg2AwVX191qCizp88gQr, at 2026-09-15T07:48Z, from its own write at 2026-09-15T07:47Z (PR #18279, comment 5676662783, since repaired in place with an edit note).The reading (live, this seat's own artefact)
WAStoken whose payload was filled from a shell variable; the shell read failed and the variable held a multi-line Node error dump instead of an instant. The token slot therefore readWAS:followed by 「[eval]:1 …SyntaxError…」 and the closing braces, all inside the double-brace form.scripts/pm/post-stamped.mjs --dry-runprinted its DRY RUN line (the seat's guard) and the live write printed 「comment posted」. The stored body carries the opener, the error dump and the closer verbatim; nothing was substituted and nothing was refused. Read back and repaired by a RESTPATCHof the comment with an edit note at 2026-09-15T07:48Z.YYYY-MM-DDThh:mmZfalls outside every rule and passes through.What is asked (⛔ not asserted — the skills seat grades)
Refuse any double-brace opener that is not exactly a recognised token:
NOWbare, orWAS:followed by a payload that parses as the documented instant form and is not later than the clock. Printpost-stamped: REFUSEDwith the offending span (first 60 bytes) and exit non-zero, on--dry-runtoo. Self-test cases: aWASwith a non-timestamp payload, aWASwith a multi-line payload, an unknown token name, an unclosed opener.scripts/pm/post-stamped.mjs+ its self-test only — non-governed; the seat's own guard (post only after the tool's DRY RUN line) stays.Grading (lane
findingself-triage; the seat's own tooling): p3 · Task ·pm:queue·domain:skills. Serial:scripts/pm/post-stamped.mjsis free (no open PR touches it).Generated by Claude Code