Repository navigation
fix(pm): post-stamped's quoted-stamp remedy names the route that works, not the declaration already in the fence - #19707
Conversation
…, not the one already written
The mixed and positional remedies appended a warning to a prescription they
had just made: a seat that had ALREADY declared the stamp inside the fence
read `{{WAS:<the same digits>}}` back, character for character, and then a
trailing clause saying it would not work there. One route, chosen by where
the stamp sits, and it names the declaration OUTSIDE the quotation.
Three narrow follow-ups on the same refusal family land with it:
- the quoted-value gate is per VALUE, not body-wide, so a mistyped
declaration anywhere no longer suppresses the `positional-declared` row
for a real declared reading at a position;
- `positionsTaken` is a multiset, so two subscript lines carrying the same
stamp are two rows rather than one;
- a declaration of the act's OWN clock at the seconds grain is not refused —
minute containment, the grain the read-side patrol measures at.
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wnstp2kTth7sGXfr8fXypc
…t and the seconds grain Fifteen cases across two batteries, floors raised with them: the filed repro refused with a remedy that no longer prescribes the declaration already in the fence, the bare-stamp control inside the same fence refused identically, the body that takes the named route posting, and the three follow-ups each with the control that says the fix is containment rather than a widening. The header section states which of the card's three arms this takes and why the other two are refused, so the next reader does not reopen it. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Wnstp2kTth7sGXfr8fXypc
…st-stamped-fenced-declared-stamp
Contract reviewServed-tier: ① Derived judgmentsRendered in-seat by the skills seat 1 session at the served tier the maintainer named on this date (the fable tier word at
② Semver levelNone — no release. ③ Boundary flags
Must change before this record can read PASS (one round, this file only): (1) the quoted-span remedy tail says the DIGITS leave the quotation — the reading is declared in the prose beside it and the quoted line is pasted WITHOUT its stamp (or quotes the placeholder form); a quotation that keeps the digits is still a bare stamp there; (2) the shape 「the value declared in the prose AND the same digits standing inside a quotation, bare or token-wrapped」 gets a remedy that names the quoted occurrence and says to take the digits out — ⛔ never 「Declare it with a WAS declaration」, which is already on the page; (3) both pinned: the chain that FOLLOWS each remedy from the filed repro ends ACCEPTED, every step's text naming the next step, plus the prose-declared-and-quoted shape's remedy; floors up. Everything else in the diff stands. Implemented-by: VERDICT: FAIL Generated by Claude Code |
…ne body is not asked to re-declare A remedy is worth what following it does. The first cut named only the declaration — "the same declaration OUTSIDE the quotation" — so an author who did exactly that and pasted the quoted line back unchanged was refused again, by the ordinary text, prescribing the declaration now standing in the prose. The circle had moved one step, not closed. Every occurrence of the digits is now classified: declared in the prose, declaration-shaped text inside a quotation, bare inside a quotation, or loose (which still gets the ordinary remedy, unchanged). The route names both halves — declare the reading in the prose AND paste the quoted line without its stamp, or quote the placeholder form — and the body that has already declared it is told the declaration reads and only the quoted digits are left. Pinned as a chain, from the filed repro to a body that posts, every step's text naming the next; the bare-in-a-fence control stays refused. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Wnstp2kTth7sGXfr8fXypc
…st-stamped-fenced-declared-stamp
Contract reviewServed-tier: ① Derived judgmentsRendered in-seat by the skills seat 1 session at the served tier the maintainer named on this date (the fable tier word at
② Semver levelNone — no release. ③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
Fixes #19319
What the card measured, and which half was the defect
A seat pasted a
git logline into a fenced code block, declared the stamp inside the fence exactly as the contract spells a declaration, used{{NOW}}elsewhere, and was refused[mixed]with the remedy 「Declare it with{{WAS:2026-09-20T10:25:40Z}}if it is a quoted reading」 — the declaration already standing at the position the refusal was complaining about, character for character. Reproduced on this branch's base7e1b048a1d, exit 2, before any edit.The refusal is right; the remedy was wrong. Inside a quotation this tool renders TEXT, so a
{{WAS:…}}in a fence declares nothing there and the digits stand on the board like any others — which is what the header's MIXED and MASKING rows already say, and what keeps a stamp from hiding behind backticks. What was broken is the sentence telling the author what to do.The card named three arms; this PR takes the third and pins why
⭐ Neither refused arm serves the body that was filed. A pasted
git logline carries its stamp BARE, so no arm reaches it; wrapping that stamp in{{WAS:…}}to satisfy one would print the token inside the fence and publish a quotation the quoted command never emitted.⛔ A remedy is worth what FOLLOWING it does — the patch round
The first cut of this fix named only half the route: 「the same declaration OUTSIDE the quotation」. The review of record measured what happens next, and it was not good enough: an author who did exactly that — declared the reading in the prose, pasted the quoted line back unchanged — was refused
[mixed]a second time, and that second refusal carried the OLD circular text 「Declare it with{{WAS:…}}」 while that declaration was already standing in the prose.quotedSpanRemedyreturned null as soon as one occurrence sat outside a span, so the ordinary remedy fired. The circle had moved one step, not closed — the rider's (e) shape, reached by the fix for it.So every occurrence of the digits is now classified, and only the fourth kind leaves this remedy to the ordinary one:
{{WAS:…}}that is itself quoted{{WAS:…}}TOKEN outside every quotationAnd the route names both halves, always: declare the reading in the prose and paste the quoted line WITHOUT its stamp, or quote the placeholder form instead of digits. ⛔ A quotation that KEEPS the digits is still a bare stamp there. Where the declaration already reads in the prose, the refusal says so and names the quoted occurrence as the one thing left, rather than asking for the declaration again.
The three follow-ups ridden on this card (rider 5770506431)
positionsTakenwas a Set, so two subscript lines carrying the same stamp collapsed to one row. Now a multiset whose credits exist only so the declared walk declines a position the bare scan already took.hit.stamp === nowcompared at the minute grain only, so a declaration of the act's OWN clock at the seconds grain was refused while H56 files nothing. Now minute containment throughstampSpan— ⛔ the minute, neverH56_STAMP_TOLERANCE_MIN.positionaland thenpositional-declared」 — did not reproduce as stated; the measured chain ismixed, mixed→ declare one →positional→ declare the other → accepted.Evidence
The chain, pinned end to end, because that is the thing the first cut got wrong:
mixedmixed⛔ No widening, measured across six body shapes (the review's own probe, re-run on the final head against the base
4fba5036f): every one of the six verdicts is identical on base and head —mixed, mixed, mixed, ACCEPTED, mixed, positional. Only the remedy TEXTS moved. The bare-stamp-in-a-fence control is refused exactly as before.Reverse verification (
scripts/ablation-replace.mjs, anchor declared to hit once, write and restore verified against the disk): nulling the prose-declared classification — the patch round's own mechanism — turned 5 of 584 cases red, precisely the step-2 pins, while step 3 and the controls stayed green. Restore proved by blob, not by an exit code:226f7a6cd1141efeeb42d1aec964f5cae123fbb0before and after,git diff HEADempty, self-test back to 584 green.Self-test: 584 cases across 19 batteries, offline. Battery floors raised with the cases (
the quoting spelling52 → 67,the two positions17 → 24), ⛔ never lowered.Gates run locally, at the final head
e976b048eDerived with
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands(no paths) and reconciled with--ran: 30 derived, 30 run, 0 NOT-MEASURED, 0 UNRUN, every one recorded ascommand :: exit codecaptured before any pipe. Includespnpm check:pm-post-stamped(584 cases),pnpm check:nul-bytes,pnpm check:pm-dispatch-gates(724s on this box).Owed beyond the derivation:
scripts/pm/close-cards.mjs --self-testexit 0 (102 cases) andscripts/pm/check-half-states.mjs --self-testexit 0 (5160 cases) — both import from this file.eslint, narrowed and declared:
pnpm exec eslint scripts/pm/post-stamped.mjs --no-inline-config --format json→ exit 0, 1 file linted (counted from the JSON), 0 errors, 0 warnings. The narrowing is a measurement: the population is read fromeslint.config.mjsitself, which states this repo runs one config that 「never enables type-aware linting (noparserOptions.project, no typed@typescript-eslintrules) for ANY file」 — so this diff cannot move the verdict on any file it does not touch. The repo-widepnpm lintbelongs to CI. Control-byte self-scan beyond the gate:grep -naPover the changed file, no hits.Line budget
Net +209 (263 added / 54 removed, one file) against the dispatch's suggested ≤ +80 — declared, not quiet. Roughly +49 header (the decision pin plus the patch round's 「both halves」 paragraph), +47 the twenty-three self-test cases and their fixtures, the remainder the occurrence classifier and the two new readers with their doc comments. No ceiling moved and no ratchet involved:
scripts/pm/**carries none, and both battery floors moved UP.Changeset
skip-changeset:scripts/pm/**publishes nothing. Measured — the repo root package isprivate: truewith nofiles[], and no non-private package in the tree declares afiles[]entry that could reachscripts/.Acceptance notes
Two observations met on the way, neither filed (both below the filing bar):
positional-declaredrefusal text says the declaration 「renders THERE as the bare stamp」. For an opener left VERBATIM inside a quotation at one of the two positions that sentence is imprecise, while the refusal itself is correct, because H56 reads digits off the stored line either way. Carrier: the next PR in this refusal family.h56StampedReadingsreturns no line index, which is why thepositional-declaredde-duplication has to be a multiset over position-name plus stamp rather than a key on the line. The reader lives incheck-half-states.mjs, outside this card's declared file surface, so it was not touched.维护者速读(草稿)
改了什么 —
post-stamped拒绝理由里的「怎么办」那一句。以前它把作者已经写好的那条声明原样念回去;第一版补丁只说了半句「把声明移到引用块外面」,复审实测发现照做之后还会再被拒一次,而那次拒绝又把刚写进正文的声明念了回去——圈只是挪了一步。现在这句话说全了:数字必须离开引用块(正文里声明 + 粘贴的那行去掉时间戳,或改用占位符);已经在正文声明过的,拒绝会说「那条声明是有效的,别再写一遍」,只指出引用块里还剩的数字。顺带落了同一族的三个小修。为什么改 — 卡片实测三次:按提示照做等于什么都没做。拒绝本身是对的(引用块里的数字仍是板上的数字),错的是那句提示。
风险与代价(含回滚) — 拒绝面一个字节都没变宽:复审的六种形态在 base 与本分支上判决完全相同,只有文字变了。消融实验证明新用例真的会红。回滚成本 = revert 单文件单 PR。净增 209 行(建议上限 80),超出部分是 header 里钉决策与「两个半句」的段落,以及 23 条自测用例。
席位意见 —— 技能席 1(
session_01Wnstp2kTth7sGXfr8fXypc)已按 GitHub 与拉取的 heade976b048e复核两轮。第 0 轮记录 FAIL 5774680155:新补救文案指的路本身走不通——作者照做(散文里声明、围栏里保留数字)仍被拒,且拒绝文案回到旧循环句;补丁轮 1 三件欠项全闭合,本席用同一套六种正文形态在 base 与 head 上量过全文:每一种被拒形态的文案都写明「散文里声明 + 引用块里的行去掉戳(或引占位符)」,散文已声明的形态改为点名引用块里那一处、⛔ 不再叫作者重打声明,六种裁决 base/head 逐一相同(无放宽);链式用例 STEP 1→2→3 钉住,floor 60→67。本席在独立 worktree 复跑自测 584/19、两个引入方自测(5160、102)、nul-bytes、eslint 均 exit 0;check-governed-merges --pr 19707读不受管。复核记录 PASS 5775158312(席内 fable),--pair0,ACCEPT 5775160838 在卡上;开发的开放问题(是否教声明读者识别围栏)本席在车道内判 A——保留拒绝、只改文案;B 会推翻 #18543 的钉子,您若想要,是另一张卡。全绿即由本席按普通队列入队。你要做的 — 确认一件事:卡片给了三个方案,本 PR 选了第三个(保留行为、修提示),并在 header 里写明另外两个为什么不选。三选一的判断权在维护者;如果希望改成「围栏里的声明算声明」,那是另一张卡,且要动 #18543 钉下的决定。
🤖 Generated with Claude Code
https://claude.ai/code/session_01Wnstp2kTth7sGXfr8fXypc
Generated by Claude Code