Skip to content

Commit 99c19b3

Browse files
huangyiireneclaude
andauthored
fix(pm): post-stamped's quoted-stamp remedy names the route that works, not the declaration already in the fence (#19707)
Fixes #19319 ## What the card measured, and which half was the defect A seat pasted a `git log` line 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 base `7e1b048a1d`, 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 | arm | verdict | |:---|:---| | teach the declaration reader the fence | ⛔ refused — a third semantic for a quoted span (text for substitution, declaration for the contract). The rule above is worth what it is because it has exactly one. | | exempt fenced content from the MIXED scan | ⛔ refused — the one direction this file may not take, for the reason the MIXED and MASKING rows already give. | | keep the behaviour, fix the text | ⭐ taken, and the header section says so, so the next reader does not reopen it. | ⭐ **Neither refused arm serves the body that was filed.** A pasted `git log` line 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. `quotedSpanRemedy` returned 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: | the occurrence | reading | |:---|:---| | inside a `{{WAS:…}}` that is itself quoted | a PICTURE of a declaration, not one | | inside a quotation with no opener round it | digits, plainly | | inside a `{{WAS:…}}` TOKEN outside every quotation | a real declaration, and it READS | | anywhere else | a loose stamp in prose — the ordinary remedy, unchanged | And 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) - **(c) the quoted-value gate was body-wide** while its comment described a per-value rule. Now per value — and the refused values carry the stamps inside them, because the declared walk reads the RENDERED body. - **(c) `positionsTaken` was 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. - **(f) `hit.stamp === now` compared 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 through `stampSpan` — ⛔ the minute, never `H56_STAMP_TOLERANCE_MIN`. - **(e) the mixed remedy handing back the declared spelling** is the card's own defect, fixed above and then fixed properly in the patch round. Its second clause — 「on a position line carrying two stamps, following it lands on `positional` and then `positional-declared`」 — **did not reproduce** as stated; the measured chain is `mixed, 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: | step | body | verdict | what its text says next | |:---|:---|:---|:---| | 1 | the filed repro — token + declaration inside the fence | `mixed` | declare in the PROSE **and** paste the line without its stamp | | 2 | declared in the prose, line pasted unchanged (bare **or** token-wrapped) | `mixed` | the declaration ALREADY reads — ⛔ not asked for again; take the digits out | | 3 | declared in the prose, fence without the stamp | **ACCEPTED** | — | **⛔ 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: `226f7a6cd1141efeeb42d1aec964f5cae123fbb0` before and after, `git diff HEAD` empty, self-test back to 584 green. **Self-test**: 584 cases across 19 batteries, offline. Battery floors **raised** with the cases (`the quoting spelling` 52 → 67, `the two positions` 17 → 24), ⛔ never lowered. ## Gates run locally, at the final head `e976b048e` Derived 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 as `command :: exit code` captured before any pipe. Includes `pnpm 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-test` exit 0 (102 cases) and `scripts/pm/check-half-states.mjs --self-test` exit 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 from `eslint.config.mjs` itself, which states this repo runs one config that 「never enables type-aware linting (no `parserOptions.project`, no typed `@typescript-eslint` rules) for ANY file」 — so this diff cannot move the verdict on any file it does not touch. The repo-wide `pnpm lint` belongs to CI. Control-byte self-scan beyond the gate: `grep -naP` over 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 is `private: true` with no `files[]`, and no non-private package in the tree declares a `files[]` entry that could reach `scripts/`. ## Acceptance notes Two observations met on the way, neither filed (both below the filing bar): - The `positional-declared` refusal 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. - `h56StampedReadings` returns no line index, which is why the `positional-declared` de-duplication has to be a multiset over position-name plus stamp rather than a key on the line. The reader lives in `check-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 与拉取的 head `e976b048e` 复核两轮。第 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),`--pair` 0,ACCEPT 5775160838 在卡上;开发的开放问题(是否教声明读者识别围栏)本席在车道内判 A——保留拒绝、只改文案;B 会推翻 #18543 的钉子,您若想要,是另一张卡。全绿即由本席按普通队列入队。 **你要做的** — 确认一件事:卡片给了三个方案,本 PR 选了第三个(保留行为、修提示),并在 header 里写明另外两个为什么不选。三选一的判断权在维护者;如果希望改成「围栏里的声明算声明」,那是另一张卡,且要动 #18543 钉下的决定。 --- 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01Wnstp2kTth7sGXfr8fXypc --- _Generated by [Claude Code](https://claude.ai/code)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent bcb64fa commit 99c19b3

1 file changed

Lines changed: 263 additions & 54 deletions

File tree

0 commit comments

Comments
 (0)