Skip to content

fix(pm): post-stamped refuses a declared reading at the two positions H56 reads - #19632

Merged
huangyiirene merged 3 commits into
mainfrom
claude/issue-18995-positional-quoted-stamp-parity
Sep 22, 2026
Merged

huangyiirene merged 3 commits into
mainfrom
claude/issue-18995-positional-quoted-stamp-parity

Conversation

@huangyiirene

Copy link
Copy Markdown
Collaborator

Fixes #18995

scripts/pm/post-stamped.mjs claimed in its own header that its positional refusal "refuses exactly what the read-side patrol would file". The card falsified that twice, on live artefacts written by following the header's own prescription. This makes the claim true.

The defect, reproduced on the base

The positional walk ran the patrol's h56StampedReadings over maskQuotedStamps'd text, where a declared reading is blanked. So a declared-reading token (the WAS spelling) on the artefact's OPENING line, or on a subscript reading-time line, passed the write side — and the POSITIONAL refusal text offered that spelling there by name. substituteTokens then renders a declaration as its payload and nothing else, so the declaration is spent at render time and the board carries bare digits at a position H56 reads as the writing act's own clock.

Measured on 744a0a3 before the change, with the card's own two numbers:

body write side what renders at the position H56 drift vs the stored instant
opening line declaring a card's close time ACCEPTED, 0 refusals 2026-09-18T09:04Z 19 min — filed
subscript reading-time line declaring a CI-log reading ACCEPTED, 0 refusals 2026-09-19T07:58Z 22 min — filed

Both PM mechanism assumptions hold as stated. H1: substituteTokens emits the payload alone, so the declaration is erased at post time and no downstream reader can recover it. H2: the positional walk over masked text finds 0 readings on that opening line while the rendered body carries 1, on both positions.

The change

The positional judgement now runs over the body substituteTokens will actually send, through the same imported reader. A stamp standing at either position that is not this act's own clock is refused whichever spelling put it there — a new refusal kind positional-declared, whose remedy is the act-clock token at the position with the quoted reading moved into the body. The bare-stamp POSITIONAL remedy stops offering the declared route AT the position and prescribes it in the body instead, so no refusal text prescribes a refused remedy.

Two shapes are deliberately left alone, both because the patrol leaves them alone:

  • a position carrying MORE THAN ONE stamp once rendered (the act-clock token beside a declared reading on one line) — H56 holds that line out rather than guess, and so does this;
  • a declaration whose value IS this act's clock minute — it renders the bytes the act-clock token would have written, so refusing it would make the parity claim false the other way round.

The bare scan is untouched and stays value-free: a typed stamp equal to this act's own minute is still typed, and is still refused. A declaration the value rules already refused is not ALSO filed here — one typo, one refusal, the rule this file already states for the calendar and direction checks.

Parity pin

A new declared self-test battery, "the two positions: a declaration renders as bare digits, and the patrol reads digits", registers 17 cases and is pinned at 17 in SELF_TEST_BATTERIES (floor rules unchanged; the roster floor stays at 15). Every row asserts one body against BOTH halves: what stampRefusals does with it, and what h56EstimatedStamp — imported, not restated — would file on the bytes that body renders to. Declared reading on the opening line and on a subscript line: refused, and would be filed. Act-clock token at either position, the same declaration one line down, a rendered position carrying two stamps, and a declaration whose value is this act's clock: accepted, and the patrol is silent.

Fixture triage, per disposition rather than a batch respelling: the one case that pinned the acceptance itself ("a DECLARED quoted stamp is accepted even in the opening position") is replaced by the pair that reads in both directions; five one-line fixtures whose subject is the calendar or direction rule, and which put a declaration in the opening position only because they were one-liners, are moved off that line.

Reverse verification — one-time, nothing left in the tree

Both legs ran from the committed state through scripts/ablation-replace.mjs, so the mutation is proven on disk (anchor count and blob hash) rather than by an editor's exit code.

Leg A — revert the walk's input to the masked text, which is exactly the pre-fix behaviour:

ablation-replace: anchor "h56StampedReadings(substituteTokens(raw, now, spans).body)" x1 -> x0
ablation-replace: blob bd4827ab78bf -> dafbf9e6e0cf
ablation-replace: ok mutation landed
x post-stamped self-test: 7 of 562 case(s) failed, 0 floor problem(s).

The seven are the parity rows by name, including "THE FILED REPRO", "PARITY", and "the SECOND position the card measured". Direction observed: turned red, the ordinary direction.

Leg B — remove the act-clock exemption (hit.stamp === now). The suite goes red by CRASH, at an existing unrelated case whose body stops rendering, which is itself the evidence the exemption is load-bearing; it is reported as a crash, not as a row-level reading.

Both legs restored by the tool and re-checked by hand: worktree blob equals the HEAD blob and git diff HEAD is empty.

Gates, at d12550d

Derived with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands (no paths; the script takes its own change set from the merge base) and reconciled with --ran:

Run reconciliation - 30 derived, 30 run, 0 NOT-MEASURED, 0 UNRUN.
EXIT CODES - all 30 accounted famil(ies) carry one.

Every one exited 0, pnpm check:pm-post-stamped (the file's own --self-test, 562 cases across 19 batteries) and pnpm check:nul-bytes among them. Three families outside the derivation were run because this diff reaches them through imports: close-cards.mjs --self-test (102 cases, it imports renderBody), write-pace.mjs --self-test (69 cases) and check-half-states.mjs --self-test (5160 cases, the other half of this contract) — all exit 0.

The 55 artifact-roster families, the 11 wide-population families, the 14 pending-changeset families and the one path-scheduled CI job the derivation names are outside that total and are CI's.

Lint, narrowed and declared

pnpm lint is eslint . --no-inline-config over the whole tree and belongs to CI. What ran here is the same binary and the same flag over the one changed file, with the three readings that make a narrowing a measurement rather than a skip:

  1. the population is whatever eslint . reaches under eslint.config.mjs — read from that config, not guessed;
  2. --format json reports 1 file linted, 0 errors, 0 warnings, and git diff --name-only against the merge base names exactly that 1 file;
  3. that config enables no type-aware linting for ANY file. Its own header states it: "this repo runs one eslint.config.mjs, which never enables type-aware linting (no parserOptions.project, no typed @typescript-eslint rules) for ANY file, test or not." So nothing in this diff can move the verdict on a file it did not touch, and the narrowing excludes nothing.

turbo ls --affected is not cited: the repo-root package is not in the turbo graph at all, so this path is not on it.

Acceptance notes

  • The card's second same-family defect is no longer reachable on this base. The card reported 11 refusals while writing its own body, 8 of them quoted-not-a-stamp raised by quoting the token verbatim, plus UNKNOWN-opener refusals when the token was split across backticks. Measured here with the spellings the card lists: the placeholder form inside a code span beside an act-clock token is ACCEPTED, and so is the split-into-backticks workaround (0 openers flagged). The residue — a declared token carrying REAL DIGITS, quoted beside the act-clock token, still taking the MIXED refusal — is a deliberate decision recorded in the header of that file ("deliberately NOT made quote-aware"), and its fenced-block half belongs to post-stamped refuses a {{WAS:}}-declared stamp inside a fenced code block, and its refusal prescribes the declaration that is already there #19319, which remains open, is p3 and pm:queue, and is out of scope here. Nothing in this PR touches it.
  • Rejected alternative, recorded and not built: render a marker H56 recognises. It is a third spelling both halves must learn, visible on the board, bought to keep a reading in the one position where no reader can tell it from the act's own clock. The position is the defect; a marker makes it survivable instead of removing it. No legitimate seat artefact was found whose opening line MUST carry a quoted reading — the card's own remediation was to move the reading into the body, which is what the new refusal prescribes.
  • skip-changeset: scripts/pm/** is on the fast-track list and publishes nothing from any released package; no package files[] ships this path.
  • Line budget: net +124 (135 insertions, 11 deletions) against the dispatch's +120. Two trimming passes removed the table of what the rule buys and three lines of restated comment. The remaining 4 lines are the header's rejected-route paragraph, which the file's own convention keeps beside every other rejected route; it fits within budget only if that paragraph lives in this PR body alone, and that trade is the reviewer's to make, not this run's to take silently.
  • H56 semantics are untouched. check-half-states.mjs is imported only; h56EstimatedStamp is the one symbol this change adds to the import list, for the parity pin.

How a caller sees it

A body whose opening line declares a reading of something else is refused before any request, with the position named, the declaration quoted, the digits it would have rendered to shown, and the remedy spelled: the act-clock token there, the reading in the body.


Generated by Claude Code

… H56 reads

The positional walk ran over `maskQuotedStamps`'d text, where a declared
reading is blanked, so a quoted-reading token on the OPENING line or on a
subscript reading-time line passed the write side — and the header's refusal
text offered it there by name. `substituteTokens` renders a declaration as its
payload and nothing else, so the board carries bare digits at a position the
read-side patrol reads as the writing act's own clock, and files a row on it.

The positional judgement now runs over the body `substituteTokens` will
actually send, using the patrol's own imported reader: a stamp standing at
either position that is not this act's clock is refused whichever spelling put
it there. A stamp equal to this act's clock, and a position the render leaves
carrying more than one stamp, are both left alone — the first is the bytes the
act-clock token would have written, the second is the patrol's own holdout.

Claude-Session: https://claude.ai/code/session_01Wnstp2kTth7sGXfr8fXypc
Co-authored-by: Claude <noreply@anthropic.com>
Seventeen cases asserting that what post-stamped does with a body and what
H56 would file on the bytes that body renders to are the same answer: a
declared reading on the opening line and on a subscript reading-time line are
refused and would be filed; the act-clock token at either position, a
declaration one line down, a position the render leaves carrying two stamps,
and a declaration whose value is this act's own clock are accepted and leave
the patrol silent. The reader and the row are the patrol's own, imported.

The one-line fixtures that put a declaration in the opening position by
accident are moved off it; the one that pinned the acceptance itself is
replaced by the pair that reads in both directions.

Claude-Session: https://claude.ai/code/session_01Wnstp2kTth7sGXfr8fXypc
Co-authored-by: Claude <noreply@anthropic.com>
The table of what the rule buys becomes prose, and the comment at the walk
stops restating the two shapes the header already names.

Claude-Session: https://claude.ai/code/session_01Wnstp2kTth7sGXfr8fXypc
Co-authored-by: Claude <noreply@anthropic.com>

Copy link
Copy Markdown
Collaborator Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: d12550d799990a61099dc752c8b4b1d311259013

① Derived judgments

(a) The walk's input — RIGHT. renderBody computes one span map, calls stampRefusals(raw, nowMs, spans), then substituteTokens(raw, stampNow(nowMs), spans); inside the refusal now = stampNow(nowMs) (:1333) is that same string, so substituteTokens(raw, now, spans).body at :1409 is byte-identical to what writeArtefact POSTs — it sends rendered.body unchanged, no footer, no truncation, and reads it back. The reader is H56's own, imported at :810 and never re-defined (pinned structurally at :3965), so the walk sees exactly the two positions and the same hold-out rule. Rendering can neither create nor move a position: a token always renders non-empty, so the first non-blank line is the same line, and no substitution can emit a subscript opener.

(b) The two exemptions — parity-preserving, one declared asymmetry. The multi-stamp hold-out is not code at all: it falls out of the imported reader, so it cannot drift from H56. It is not a typing hole — a hand-typed bare stamp beside a declaration is still caught, because the bare walk runs on maskQuotedStamps'd text (:1382) where only the declaration is blanked (reproduced). hit.stamp === now (:1411) is load-bearing twice: it also exempts this act's own substituted token, which is why ablation leg B crashed rather than reddened. It does open one narrow spelling hole: a declaration typed equal to this act's minute is ACCEPTED at a position while the byte-identical bare stamp is REFUSED positional — I reproduced both. Judged acceptable: the bytes are the ones the act-clock token would write, the patrol is silent on both, and the header declares the asymmetry at :98-100. Using it requires knowing the minute exactly, i.e. reading a clock.

(c) The quotedValueProblems === 0 gate — safe, but incomplete and mis-described. The gate at :1408 is body-wide; its comment at :1377-1379 describes a per-VALUE rule. Reproduced: a shape-refused declaration anywhere suppresses the row for a real declared reading standing on the opening line, which the patrol would file. It is not a hole — renderBody refuses on any row, so nothing reaches the board — but the caller fixes one thing, re-runs, and is refused again. Same shape in positionsTaken, whose key is position-name plus stamp, so two different subscript lines carrying the same stamp collapse to one row. Both safe; both follow-ups, neither blocks.

(d) The flipped self-test (:3054) — the old assertion was the defect. It pinned as intended behaviour the exact body the card measured filed twice on live artefacts written by following this file's own prescription. No legitimate use lost: the remedy is the one the card's author already applied by hand, and no artefact was found whose opening line must carry a reading. Bonus fix: the old row passed no nowMs, so it judged direction against the wall clock; the replacement passes the fixed clock.

(e) The header rewrite — claim now true; one prescription still offers the declared token at a position. The parity sentence at :42-44 is now one-directional (patrol-could-file implies refused-before-the-write) and holds at the body level, including under the (c) gate. The POSITIONAL row (:46-50) and the refusal text (:1396-1398) no longer offer the declared spelling AT the position. But mixed still hands back the declared spelling for a bare stamp, and when that stamp sits on a position line carrying two stamps, following the mixed remedy lands on positional and then on positional-declared — reproduced end to end. Narrow, never accepts a bad body, but it is this file's own "a refusal prescribing a refused remedy" rule: follow-up.

(f) now and the grains — a real over-refusal. Confirmed from source: now is stampNow(nowMs), a MINUTE-grain string; hit.stamp is whatever the protocol regex matched, seconds optional. So hit.stamp === now compares like with like only at the minute grain. Reproduced: a declaration of this act's OWN clock at the SECONDS grain, at either position, is refused positional-declared while the patrol files nothing — the parity claim false the other way round, which is the very thing the exemption exists to prevent. The header hedges to "clock minute" (:94), so no prose is falsified and the direction is the safe one, and the remedy the refusal prints is correct and actionable. But the file pins the seconds grain elsewhere as an accepted grain, and the new battery's own-clock row is minute-only, so nothing pins the seconds arm. Follow-up: compare at minute containment (truncate hit.stamp to the minute, or reuse stampSpan) and add the seconds row.

(g) Nothing narrowed beyond the two positions. The walk iterates only the imported reader's hits. The five moved fixtures are one-liners whose declaration sat on the opening line only because they were one-liners, and each keeps its own subject. Two of them were genuine accepted-form narrowings (a reading of something else on the opening line, minute and seconds grain) and the patrol agrees with both, so the moves are right rather than a batch respelling. The one-liner left in place is the exemption pin. The only narrowing the patrol does not agree with is (f). Battery count checks out: 17 rows in the block at :3933, pinned at 17 at :2983.

② Semver level

skip-changeset is right, and there is no semver level to assign. One file, scripts/pm/post-stamped.mjs; the root package is private: true with no files[] and no published package ships scripts/**, so nothing user-visible moves. This repo routes such a PR through the label rather than an empty changeset, and "Check Changeset" reads skipped on the head, consistent. No content/docs/releases/ change, no file unrelated to the card. PR is draft, targets main, first line is the closing reference, and the change is the whole of route A, so the closing keyword is the right spelling.

③ Boundary flags

Implemented-by: claude/issue-18995-positional-quoted-stamp-parity
Reviewed-by: session_01Wnstp2kTth7sGXfr8fXypc

VERDICT: PASS


Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/m skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants