Repository navigation
[finding] post-stamped: the unread-knock check's lastWriteStamp parses a rolled-over stamp to a LATER instant, so a knock inside the gap reads 「none-newer」 and the refresh voids it — the header's 「lower bound, never fewer」 promise is broken #18293
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-15T10:21Z)
Branch:claude/issue-18293-post-stamped-unread-knock-honours-lower-bound
Worktree:objectstack-issue-18293
Domain:domain:skills
File surface:scripts/pm/post-stamped.mjs(the--bodyrefresh's unread-knock check —lastWriteStampand the window it derives — and its self-test cases) only (stop on breach; explain in the report)
Container & model:S,mode:subagent,model: default tier— no path mandate forscripts/pm/**; seat review atCONTRACT_REVIEW_TIER
Clause-②: no
Thread-read: none (the card carries no comment at claim time)
Serial constraints cleared: PR #18292 LANDED onorigin/mainat 2026-09-15T10:20Z (the calendar predicate this card reuses);scripts/pm/post-stamped.mjsis held by no open PR; batch 1/3 (no dev in flight)
Generated by Claude Code
claude commented
on Sep 15, 2026 claudeboton Sep 15, 2026 – with ClaudeContributorAuthorMore actionsos-dev-report
{ "issue": 18293, "status": "done", "branch": "claude/issue-18293-post-stamped-unread-knock-honours-lower-bound", "worktree": "/home/user/objectstack-issue-18293 (objectstack-issue-18293)", "pr": "https://github.com/objectstack-ai/objectstack/pull/18294", "head_sha": "f78f2c061d5cd9525b5b259d443ceeb32f8f7a58", "files_changed": [ "scripts/pm/post-stamped.mjs (+138 -24, one file, the declared surface and nothing else)" ], "gates": [ { "command": "node packages/lint/scripts/check-reference-carrier-shape.mjs", "exit": 0, "note": "5s" }, { "command": "node packages/lint/scripts/check-reference-carrier-shape.mjs --self-test", "exit": 0, "note": "1s" }, { "command": "node scripts/check-ci-filter-parity.mjs", "exit": 0, "note": "0s" }, { "command": "node scripts/check-closing-keyword-parity.mjs", "exit": 0, "note": "1s" }, { "command": "node scripts/check-closing-keyword-parity.mjs --self-test", "exit": 0, "note": "2s" }, { "command": "node scripts/check-comment-mask-corpus.mjs", "exit": 0, "note": "50s" }, { "command": "node scripts/check-declaration-mirrors.mjs", "exit": 0, "note": "0s" }, { "command": "node scripts/check-declaration-mirrors.mjs --self-test", "exit": 0, "note": "0s" }, { "command": "node scripts/check-scripts-symbol-anchors.mjs", "exit": 0, "note": "2s" }, { "command": "node scripts/check-scripts-symbol-anchors.mjs --self-test", "exit": 0, "note": "2s" }, { "command": "node scripts/check-self-test-wired.mjs", "exit": 0, "note": "1s" }, { "command": "node scripts/check-self-test-wired.mjs --self-test", "exit": 0, "note": "1s" }, { "command": "node scripts/check-self-test-workflow-commands.mjs", "exit": 0, "note": "36s" }, { "command": "node scripts/check-self-test-workflow-commands.mjs --self-test", "exit": 0, "note": "1s" }, { "command": "node scripts/check-whole-set-label-write.mjs", "exit": 0, "note": "2s" }, { "command": "node scripts/check-whole-set-label-write.mjs --self-test", "exit": 0, "note": "0s" }, { "command": "node scripts/pm/bare-root-worklist.mjs --self-test", "exit": 0, "note": "14s" }, { "command": "node scripts/report-test-timings.mjs --self-test", "exit": 0, "note": "0s" }, { "command": "pnpm check:agent-test-spelling", "exit": 0, "note": "2s" }, { "command": "pnpm check:bash32-floor", "exit": 0, "note": "1s" }, { "command": "pnpm check:cli-command-ids", "exit": 0, "note": "7s" }, { "command": "pnpm check:cross-package-test-inputs", "exit": 0, "note": "11s" }, { "command": "pnpm check:driver-memory-census", "exit": 0, "note": "2s" }, { "command": "pnpm check:entry-guard", "exit": 0, "note": "20s" }, { "command": "pnpm check:nul-bytes", "exit": 0, "note": "2s" }, { "command": "pnpm check:parse-guard", "exit": 0, "note": "1s" }, { "command": "pnpm check:pm-post-stamped", "exit": 0, "note": "1s" }, { "command": "pnpm check:pnpm-filter-targets", "exit": 0, "note": "1s" }, { "command": "pnpm check:ratchet-remedy-authority", "exit": 0, "note": "3s" }, { "command": "pnpm check:refd-timer-probe", "exit": 0, "note": "7s" }, { "command": "pnpm check:watch-hint-literal", "exit": 0, "note": "3s" }, { "command": "pnpm check:pm-dispatch-gates", "exit": 0, "note": "gate-source battery, run DETACHED (setsid nohup, waited with tail --pid, never timeout -s TERM); 447.5s by the tool own line, 448s wall (10:30:25Z to 10:37:53Z); 1730 self-test cases pass" }, { "command": "node --stack-size=4000 node_modules/eslint/bin/eslint.js scripts/pm/post-stamped.mjs", "exit": 0, "note": "not a derived family; no findings" }, { "command": "node scripts/pm/post-stamped.mjs --self-test", "exit": 0, "note": "the tool own battery; also covered by the derived `pnpm check:pm-post-stamped`" }, { "command": "grep -naP over the changed file for control bytes", "exit": 1, "note": "exit 1 = no hits, the intended reading" }, { "command": "node scripts/pm/check-clause2-carriers.mjs --pair 18294", "exit": 0, "note": "clause-2 carriers agree, no widening tell; PR carries no needs:contract-review label (labels: size/m, skip-changeset)" } ], "gates_ran_verdict": "node scripts/pm/dispatch-gates.mjs --ran ran.txt :: exit 0 — \"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)\". Derivation line: \"change set derived from git — 1 path(s) vs merge base 3964efb2e of origin/main and HEAD (committed 1, working tree 0, untracked 0)\". The derived set was identical before and after the commit (32 both times).", "reverse_verification": { "method": "Ablation from the COMMITTED state, two legs, hash-proved, with trap \"git checkout HEAD -- REL\" on EXIT INT TERM and absolute paths seeded from git rev-parse --show-toplevel. No build step exists on this path (plain .mjs loaded from source by absolute path), so the on-disk blob hash IS the proof of which bytes node read.", "head_blob": "48275456ba4f55f10422aa25c76cba7e45015e73", "base_blob": "6cd7b9074ac019c85d16e140b1a335bee6fe5b54 (origin/main 3964efb2)", "leg1_ablate": "base blob restored over scripts/pm/post-stamped.mjs; git hash-object matched 6cd7b907 (non-empty, asserted); marker count in lastWriteStamp: stampRealInstant = 0.", "before_reading": "lastWriteStamp(body whose newest stamp is 2026-04-31T00:00Z) = {\"stamp\":\"2026-04-31T00:00Z\",\"from\":1777593600000} i.e. 2026-05-01T00:00:00.000Z; unreadComments with a knock at 2026-04-30T12:00:00Z = kind=none-newer ok=true newer=0 — the filed defect, reproduced exactly. Same body with an older real stamp 2026-04-29T09:00Z beside it: also none-newer. Card with no comments: none-newer. --ack-through naming the knock: none-newer (the ack path was never reached).", "leg2_restore": "git checkout HEAD -- REL; git hash-object matched 48275456 (non-empty, asserted); git diff HEAD empty; marker count stampRealInstant = 1.", "after_reading": "lastWriteStamp(same body) = {\"stamp\":null,\"from\":null,\"unreal\":[{\"stamp\":\"2026-04-31T00:00Z\",\"rolledTo\":\"2026-05-01T00:00Z\"}]}; unreadComments with the same knock = kind=unacknowledged ok=false newer=1 — NOT none-newer. With the older real stamp beside it: since.stamp=2026-04-29T09:00Z, kind=unacknowledged (window measured from EARLIER, the never-fewer direction). Card with no comments: none-newer ok=true (the acceptance control, unchanged). --ack-through naming the knock: acknowledged.", "mutation_leg": "Second ablation on the committed tree, mutating ONLY the guard (unreal stamp recorded but still read as the last write) with the new cases left in place: anchor count \"continue;\" inside lastWriteStamp 1 -> 0 and injected text \"if (span && (!best\" present (both grepped, not inferred from an editor exit code); blob changed to 406e3a57. --self-test exit 1, 8 of 204 cases failed. Restored: hash back to 48275456, git diff HEAD empty, --self-test exit 0, 204/204.", "direction_predicted_and_observed": "Predicted TURNS RED (the ordinary direction) for the mutation leg and a CHANGED READING for the ablation leg; both observed. Two of the added cases stay green under the mutation — the no-parse-at-all half — and that is the honest reading, not a harness fault: see assumptions_verified." }, "self_test_cases": { "before": 186, "after": 204, "added": 18, "battery": "the unread-knock check: a refresh cannot void what nobody read", "floor_change": "SELF_TEST_BATTERIES floor for that battery 23 -> 41 (its actual case count; the ledger is a floor, never an equality). SELF_TEST_BATTERY_FLOOR (10 batteries) untouched; no other battery floor moved.", "case_names": [ "THE FILED READING: a knock inside a rolled-over stamps gap is NOT none-newer", "...it is refused as unacknowledged — the window may only widen", "...because the rolled-over stamp is not read as the last write at all", "...and the stamp it would not read is named, with the date it actually rolls to", "...the refusal says THAT, rather than claiming the body carries no stamp", "...and still points at the newest comment as the flag to pass", "a stamp that does not parse AT ALL reaches the same outcome — one rule, not two accidents", "...and reports no rollover it did not have", "with an older REAL stamp beside it the window measures from THAT one — earlier, so wider", "...so the same knock still counts as newer", "...and the refusal says the window is WIDER here, never narrower", "--ack-through naming the newest comment still clears a body with no readable stamp", "THE ACCEPTANCE CONTROL: nothing newer ⇒ the refresh is still written, unreal stamp or not", "...and the pass line names the stamp the derivation did not read", "THE CONTROL, UNCHANGED: a body whose stamps are all real is judged exactly as before", "...and its pass line carries no skipped-stamp clause at all", "a body with NO stamp stays distinguishable from one with only unreal stamps", "lastWriteStamp always answers an object, so no caller can read a null as no stamp problem" ] }, "deviations": [ "lastWriteStamp return shape changed from `null | {stamp, from}` to an ALWAYS-object `{stamp, from, unreal}`. Needed so the refusal and the transcript can name the stamps that were not read (the card asked for a row naming the offending stamp and why). Contained: grep shows this file is the sole consumer of lastWriteStamp and of the three unread* functions. The self-test pins the new shape.", "The --json transcript gained ONE key, unreal_stamps (an array of the stamp strings). Beyond the literal ask, and declared here: without it the machine-readable transcript of a widened window is indistinguishable from an ordinary one, while the human-readable pass line says so. One key, no new flag.", "The reading was taken by calling the pure core with fixture bodies rather than through `--body ... --dry-run`: that CLI path returns before the unread check by design and prints a line saying so (see assumptions_verified). The dispatch named this alternative.", "The report comment was posted with the patched tool itself (node scripts/pm/post-stamped.mjs --comment=18293), with the act-clock token deliberately OMITTED — the token is spelled out in words here rather than typed, because this report quotes impossible stamps (2026-04-31T00:00Z, 2026-13-45T99:99Z) as evidence and the mixed-stamp rule refuses a body carrying both that token and a bare stamp. Same omission the #18289 dev made, and stated here as the dispatch requires." ], "out_of_scope_findings": [ "to file (3 classes, dedupe words) — class (a), reproducible, PRE-EXISTING and untouched by this PR: unreadComments computes `after` for the ack-not-newest refusal with a strictly-later comparison on created_at ONLY, while `newest` is chosen with a created_at sort that tiebreaks on id. Two comments sharing one created_at, acknowledging the lower id, is refused with a head reading \"0 comment(s) landed after it\" and lists no comment at all — a count that contradicts its own refusal. Nothing is lost (the remedy line still names the right id and the write is still refused), the text is simply false. Measured reproducer: unreadComments({ storedBody: seat 2026-09-12T10:00Z, comments: [{id:100,created_at:2026-09-12T11:00:00Z},{id:101,created_at:2026-09-12T11:00:00Z}], ackThrough: 100 }) gives kind=ack-not-newest, after.length=0, newest.id=101. Dedupe words: post-stamped, ack-not-newest, after filter, created_at tiebreak, same-second comments. NOT fixed in place: it is a different defect class from this card (a refusal count, not a window that narrows), so condition ① of the bounded in-place exemption fails.", "noted, not filed: unreadPassText grew a parenthetical clause and is still a single line, so its own doc sentence (\"The one line a transcript carries\") still holds — verified by reading the return expression, which has no newline. Successor: the next author of scripts/pm/post-stamped.mjs.", "noted, not filed: the `--dry-run` path never reaches the unread check and says so in its own printed line. Correct and self-documented, not a gap — recorded only because the dispatch offered it as a route to the reading and it is not one. Successor: the next author of scripts/pm/post-stamped.mjs.", "noted, not filed: main() derives the REST `since` page filter from the PROBE read of the body and then judges with the body from the SECOND read (readCardTail). A body edit landing between those two reads could leave a comment unfetched that the pure check would have counted. No reproducer offline, the window is one HTTP round trip, and the REST filter is on updated_at (a superset) so an edited comment still arrives. Not filed: no measured reproducer, so it clears none of the three classes. Successor: whoever next touches the two-read sequence in main()." ], "mcp_calls": "0 — no MCP GitHub tool was called, read or write. Every GitHub read and write went through the REST proxy with curl or through scripts/pm/label-write.mjs.", "api_writes": "3 REST proxy writes: (1) POST /repos/objectstack-ai/objectstack/pulls (the draft PR), (2) POST /repos/objectstack-ai/objectstack/issues/18294/labels via scripts/pm/label-write.mjs (skip-changeset; its own read-back printed \"MATCHES the target — labels size/m, skip-changeset\"), (3) POST /repos/objectstack-ai/objectstack/issues/18293/comments (THIS report). Plus two git pushes (the empty-branch routing probe and the fix commit), which are not REST writes. Reads: the card, the claim comment, the PR read-back, and label-write own before/after reads.", "session": "session_01HZfg2AwVX191qCizp88gQr — this is the PARENT (dispatching skills seat) id: a subagent has no own session_01... id of its own, so the parent id is given as the contract requires.", "timings": { "worktree_created": "2026-09-15T10:22Z (git worktree add from origin/main 3964efb2)", "install": "7.2s, exit 0 (pnpm install --offline --frozen-lockfile)", "empty_branch_probe_push": "2026-09-15T10:23Z, exit 0 (the write-route probe; no 403)", "fix_commit_pushed": "f78f2c06, exit 0, pre-push check:commit-card-trailers green", "gate_batch_31": "10:31:16Z to 10:34:17Z (~181s wall, run detached, one command at a time)", "battery": "10:30:25Z to 10:37:53Z, 447.5s by its own line, exit 0", "ablation_and_mutation": "~10:39Z, both legs, tree clean after each", "pr_created": "2026-09-15T10:40Z, PR #18294", "label_applied": "2026-09-15T10:41:13Z, HTTP 200, read-back MATCHES" }, "assumptions_verified": [ { "assumption": "lastWriteStamp derives the last write instant through the same Date.parse path the quoted-stamp rule used before PR #18292", "verdict": true, "evidence": "Base blob 6cd7b907: the loop read stampSpan(stamp) alone (Date.parse plus a grain), with no calendar predicate. Ablation before-reading: lastWriteStamp(2026-04-31T00:00Z body).from = 1777593600000 = 2026-05-01T00:00:00.000Z." }, { "assumption": "a knock at 2026-04-30T12:00:00Z therefore returns ok:true kind:none-newer and the refresh proceeds", "verdict": true, "evidence": "Reproduced byte-for-byte on the base blob: \"kind=none-newer ok=true since.stamp=2026-04-31T00:00Z since.from=2026-05-01T00:00:00.000Z newer=0\"." }, { "assumption": "the file header promises the opposite (MORE comments count as newer, never fewer)", "verdict": true, "evidence": "The derivation paragraph in the header carries that sentence verbatim; it is quoted unchanged in the rewritten paragraph." }, { "assumption": "still reachable after PR #18292, because a stored body can be hand-written, platform-edited, or predate the tool", "verdict": true, "evidence": "stampRefusals guards the WRITE side only (quoted, positional and mixed routes over the body being sent). The read side takes tail.card.body from REST — bytes this tool need never have written." }, { "assumption": "a newest stamp that does not parse at all (2026-13-45T99:99Z) needs the same fix", "verdict": false, "evidence": "REFUTED by measurement, and this is the one seat assumption that did not hold. On the BASE blob lastWriteStamp(2026-13-45T99:99Z body) already returned null — stampSpan gives null for it and the old guard `if (span && ...)` skipped it — so since was null, every comment counted as newer, and the reading was already `unacknowledged`, not `none-newer`. That half was correct BY ACCIDENT, through a different code path from the rollover half, and pinned by nothing. The card asked for the same OUTCOME and the outcome was already there; what this PR changes is that both halves now come from ONE predicate and both are pinned. The mutation leg leaves exactly those two added cases green, which is the honest signature of this reading rather than a harness fault." }, { "assumption": "stampRealInstant (PR #18292) is on origin/main 3964efb2 and can be reused instead of a second parser", "verdict": true, "evidence": "Exported in the same file; lastWriteStamp now calls it and adds no parser of its own. `grep -c stampRealInstant` inside lastWriteStamp: 0 before, 1 after." }, { "assumption": "the `--body ... --dry-run` path can be used to take the reading without network", "verdict": false, "evidence": "Measured in the source: main() returns inside the `if (options.dryRun)` branch BEFORE the unread check, and prints its own line saying so — \"The unread-comment check reads the card and runs only on a live write.\" So the reading was taken the way the self-test takes it, by calling unreadComments/lastWriteStamp directly with fixture bodies, which the dispatch named as the alternative." }, { "assumption": "dispatch-gates --commands prints 32 commands for this surface", "verdict": true, "evidence": "32 with the path argument at 3964efb2 and 32 again derived from the real change set at f78f2c06; `diff` of the two command lists is empty." }, { "assumption": "the pm-dispatch-gates battery runs ~435-450s", "verdict": true, "evidence": "447.5s by the tool own closing line; 448s wall (10:30:25Z start, 10:37:53Z finish), exit 0, 1730 cases." }, { "assumption": "pnpm install --offline --frozen-lockfile works in a fresh worktree (~5s)", "verdict": true, "evidence": "exit 0, \"Done in 7.2s using pnpm v10.31.0\"." }, { "assumption": "scripts/pm/** is non-governed, so the seat lands it in-seat after review", "verdict": true, "evidence": "GOVERNED_SURFACES in scripts/pm/check-governed-merges.mjs holds five rows — docs/adr/, .claude/, skills/, AGENTS.md, CLAUDE.md — and no scripts/ row. PR left DRAFT, zero reviewers requested, no auto-merge armed, no ccr route touched." }, { "assumption": "nothing published moves, so skip-changeset is right", "verdict": true, "evidence": "scripts/pm/** is on the os-dev fast track for the skip-changeset judgement (not shipped by any package files[]). Label applied through scripts/pm/label-write.mjs, whose own read-back printed \"MATCHES the target\"." }, { "assumption": "THE DESIGN CHOICE the seat left open: refuse-and-require-ack vs. fall back to the newest real stamp", "verdict": "fall back to the newest REAL stamp (and, when there is none, the existing no-stamp rule)", "evidence": "The header rule picks it. (1) 实际业务需求 — measured, not read off: refusing outright dead-ends a real scenario the card itself names. A stored body carrying an impossible stamp on a card with ZERO comments has no newest comment, so --ack-through has no legal value and that seat post can never be refreshed again; the seat-post protocol refreshes on every dispatch, handover and pause, so this is a live path, not a hypothetical. The falling-back option acts identically wherever the refusing option could act at all (comments exist ⇒ every one counts as newer ⇒ --ack-through naming the newest clears, pinned as a case). (2) 项目长远合理性 — contract-first, no workaround: ONE rule decides what the last write instant is, and it is the same predicate the write side already refuses a quoted stamp with. Refusing adds a SECOND notion of the instant (unknown, beside absent) and a second refusal route; the file already warns at quotedRouteClosed that two spellings of one question is how a remedy comes to prescribe a refusal. (3) 防 AI 写代码犯错 — this is a tightening at the DERIVATION, not a tolerant `??` at a consumer: an unreal stamp is refused as a reading of anything, so no author (human or AI) can obtain a NARROWER unread window by storing a bogus date, and the refusal and both transcripts now NAME the stamp that was not read instead of silently claiming the body has no stamp. Declaration equals enforcement: the header sentence is now what the code does. (4) 创业阶段不扩散需求 — the smaller surface, and no transitional spelling: no new flag, no new refusal kind, no new CLI contract, no staged window. One guard inside one function plus honest text. Cost stated plainly: the derivation can now measure from a stamp much older than the true last write, so a refresh may list comments the seat has in fact already read — which is exactly the direction the header chose, over-asking rather than skipping." }, { "assumption": "exactly one rule should decide what the last write instant is (seat instruction)", "verdict": true, "evidence": "lastWriteStamp is the only place a stamp is judged for this purpose; unreadComments, unreadRefusalText, unreadPassText, the --json transcript and main() all read its one answer. grep over the repo for lastWriteStamp/unreadComments/unreadRefusalText/unreadPassText outside this file: zero hits, so the return-shape change is contained to the file on the card." } ] }
Generated by Claude Code
claude commented
on Sep 15, 2026 claudeboton Sep 15, 2026 – with ClaudeContributorAuthorMore actionsACCEPT — skills seat, session
session_01HZfg2AwVX191qCizp88gQr, 2026-09-15T10:51Z. Report 5678904168 on PR #18294 (headf78f2c06) reviewed by the checklist: files changed = the claim's surface exactly (scripts/pm/post-stamped.mjs);Fixes #18293alone beside a closing keyword;skip-changesetread back;mcp_calls0 — every write through the REST proxy orlabel-write.mjs; gates 32 / 32 / 0 / 0 by--ran, battery 1730 pass (447.5 s detached);--pair0; the seat's re-run on a detached worktree of the head: self-test 204 / 10 (186 / 10 on main), the measured knock-in-the-gap scenario readsnone-neweronorigin/main's tool (the ack path never reached) andunacknowledgedon the head with the window measured from the older real stamp and the ack clearing it, the all-real control unchanged, ESLint 0, wiring and watch-hint green (record 5678972520 on the PR names each). One seat premise refuted and owned: the no-parse half already readunacknowledgedon main by a different path; the head unifies both under the one predicate and pins them. The fallback-to-newest-real-stamp design accepted on the four axes. Deviations (5) answered one by one in the record. Out of scope routed: the same-secondack-not-newestcount → #18295; the read-back's false 「MUTATED」 on the platform's newline strip and footer append → #18296 (both serial behind this PR). Landing: in-seat once CI on the head is green (Lint & Repo Gates still running at 2026-09-15T10:49Z) — ready through the CCR route + auto-merge SQUASH; the card stayspm:dispatcheduntil the three 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-15T11:16Z. PR #18294 (headf78f2c06) merged by the queue as5a18f32e6a48573b3fb3feb7f47c0ce40518b4ba(single-parent squash pergit rev-list --parents) at 2026-09-15T11:15Z — themerged_atinstant, carried identically by themergedandremoved_from_merge_queuetimeline events. Readings at 2026-09-15T11:16Z:git log origin/maincarries(#18294); the queue refrefs/heads/gh-readonly-queue/main/pr-18294-*is gone from origin; theremoved_from_merge_queueevent is on the timeline. Non-governed landing: record 5678972520 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-15T10:59Z. Now onorigin/main:post-stamped.mjs'slastWriteStampnever reads an unreal stamp as the last write, falls back to the newest real one so the unread window only widens, and names the stamps it skipped; self-test 204 cases across 10 batteries. Residue (pm:dispatched, assignee) stripped throughlabel-write.mjsand read back; #18295 and #18296 (same file) are unblocked — serial, #18295 dispatches next.
Generated by Claude Code
- added a commit that references this issue
on Sep 17, 2026
Filed by the
domain:skillsexecution PM seat, sessionsession_01HZfg2AwVX191qCizp88gQr, at 2026-09-15T09:46Z, from the #18289 dev'sout_of_scope_findings(report 5678039238; PR #18292). Class (b): a declared contract violated — the file's own header says the derivation of the body's last write stamp is a LOWER bound (「MORE comments count as newer, never fewer — the check may ask for an acknowledgement it did not strictly need, and cannot skip one it did」).The reading (measured by the dev on PR #18292's tree)
scripts/pm/post-stamped.mjs(the--bodyrefresh's unread-knock check) derives the body's last write instant withlastWriteStamp, which parses the newest stamp in the stored body through the sameDate.parsepath the quoted-stamp rule used. A stored body whose newest stamp is an impossible date rolls FORWARD: newest stamp2026-04-31T00:00Z→ instant2026-05-01T00:00:00.000Z; a knock (comment) at2026-04-30T12:00:00Zthen returnsok: true, kind: none-newerand the refresh proceeds — the knock is voided.What is asked (⛔ not asserted — the skills seat grades)
Make the read side honour its own header: a newest stamp that names no real instant (NaN or a rollover — reuse the round-trip predicate PR #18292 added,
stampRealInstant) must not shrink the unread window. Choose the honest fallback and pin it: treat the body's last write instant as UNKNOWN and require--ack-throughto name the newest comment outright, or take the previous real stamp in the body. Self-test cases: the measured knock-in-the-gap scenario refused/asked, a body with only real stamps unchanged, a body with no stamp at all (whatever the tool does today, pinned).scripts/pm/post-stamped.mjsonly; serial behind PR #18292 (same file, the predicate it reuses).Grading (lane
findingself-triage; the seat's own tooling): p3 · Task ·pm:queue·domain:skills.Generated by Claude Code