Skip to content

Commit de9c319

Browse files
claude[bot]claude
andauthored
fix(pm): H44's timestamp leg accepts an ISO instant whose hour follows a T — the ruled minute spelling no longer reads as untimestamped (#18326)
Fixes #18323 `H44_READING_TIMESTAMP` is the leg that decides a paragraph already carries its 取数时刻. It anchored the hour with a bare word boundary, which needs a NON-word character in front of it — and the `T` of an ISO instant is a word character. So the leg read **false** on the very spelling the 2026-09-02 ruling gives, and H44 reported readings that DO carry their time as untimestamped: a false positive on the row's own subject. A seconds stamp passed only by accident, the boundary holding before its `mm:ssZ` tail, so the match began at the MINUTES. ```js // before /\b\d{2}:\d{2}(?::\d{2})?Z\b/ // after — the left anchor is a word boundary OR a lookbehind for the ISO T /(?:\b|(?<=T))\d{2}:\d{2}(?::\d{2})?Z\b/ ``` ## Reverse verification — probes on the module's own export A `file://` import of the script (a pure export; the module runs nothing on import and makes no request), against the unpatched file at `a46cd8c4` and against this branch: | probe | before | after | |---|---|---| | `2026-09-15T14:51Z` — the live R+236 marker's fire stamp | false | **true** | | `2026-03T00:21Z` — the spelling the ruling itself gives | false | **true** | | `2026-09-15T14:51:42Z` | true | true | | `14:51Z` — a bare clock | true | true | | `19:38` — a bare clock with no zone | false | false | | `12314:51Z` — a clock inside a longer digit run | false | false | The seconds probe is the one that shows the difference between the fix and the accident it replaces: `'2026-09-15T14:51:42Z'.match(...)[0]` was `51:42Z` before and is `14:51:42Z` now — the match starts at the HOUR, and that is pinned as a case rather than the bare `true` that held either way. ## The one design choice, on the four axes The choice is between **naming the left anchor** (start / whitespace / punctuation, which the word boundary already spells, plus the ISO `T`) and **dropping the left boundary altogether**. This PR names it, and looks behind rather than consuming, so a match is still exactly the stamp. - **实际业务需求** — measured, not supposed: the only predecessor that occurs on this board is the ISO `T`, in the R+236 round-open marker (`fire 2026-09-15T14:51Z`) and in the ruling's own `2026-03T00:21Z`. No artefact was measured writing a stamp behind any other letter. Dropping the boundary would serve a case nobody has: `12314:51Z` is a digit run that happens to contain a clock, never a reading's time. - **项目长远合理性** — the boundary is doing real work and the defect was its SCOPE, not its presence; keeping it and naming the one exception is the contract-shaped repair, where dropping it would be a workaround that trades one class of wrong answer for another. The row's banner already claimed that a paragraph carrying `HH:MM[:SS]Z` is clean — this makes the regex true to a statement the file has been making all along, rather than rewriting the statement down to the implementation. - **防 AI 写代码犯错** — a report-only patrol that fires on a well-formed artefact teaches the seat writing it that the ruled spelling is wrong, and the cheapest way to silence a false row is to write a MORE precise stamp than the ruling asks for (which is exactly what this file's own marker case had been reduced to doing). Removing the false positive removes that pressure. Looking behind rather than consuming keeps the match equal to the stamp, so a future reader that echoes it gets no stray designator. Admitting any letter — the lenient direction — would make the leg quieter in ways nobody declared; the three negatives are pinned as cases so the widening cannot creep. - **创业阶段不扩散** — exactly one predecessor is admitted, and only in the uppercase the ruling spells. RFC 3339's lowercase `t` is legal and absent from this corpus; it is pinned as NOT admitted, so taking it later is a measurement rather than a guess. ## Self-test `node scripts/pm/check-half-states.mjs --self-test`: **4235 → 4243 cases pass**, exit 0. Eight cases added, one rewritten: - the two ISO spellings that read false (`2026-09-15T14:51Z`, and the ruling's own `2026-03T00:21Z`); - where the seconds stamp's match STARTS; - three declared negatives — the digit run, any other letter, and the lowercase `t`; - an ISO instant with no zone (`2026-09-15T14:51`) still reads false, exactly as a bare clock does; - the H44 marker case re-pinned to MINUTES, with the SECONDS spelling of the same reading kept beside it so neither can regress alone. The comment above it said this file spelled that stamp to seconds because a minute stamp could not clean a paragraph — a statement that is now false, so it is rewritten (not deleted) to say the limit is gone. **Firing control (ablation):** with the one regex line reverted and every case kept, the self-test goes red on exactly four cases by name — the two ISO spellings, the match-start pin, and the marker case, which reports `{shape: 'a round-open marker', kind: 'tree', fragment: '827cacbf'}` where `null` is wanted. That row IS the false positive this PR removes. Restoring reproduced the committed blob hash exactly, and `git diff HEAD` and `git status --porcelain` are both empty. ## Scope `scripts/pm/check-half-states.mjs` only, and inside it only the constant at the H44 grammar, its doc comment, and its self-test cases. H44's population, its seat gate, its marker shapes (`h44RoundOpenShapes` / `h44IsRoundOpenMarker`), the fragment grammar and every other H row are untouched. `:9791` is the constant's only consumer (`grep` count: one declaration, one consumer, the self-test cases) and it calls `.test`, so nothing reads the match text today. The two sibling cards dispatched on this same file — #18324 and #18325 — work at other regions (`h44NeedsSeatComments`, the `seatMarkers` capture, `latestSeatMarker`) and also add self-test cases at the tail; whichever lands second merges `main` and re-runs the self-test. PR #18322 is the predecessor that landed the marker spelling this card's measurement came from. ## Acceptance notes - `PROTOCOL_STAMP_RE` (H56's stamp shape) carries the same `\b` opener and is NOT affected: its boundary sits in front of the four-digit YEAR, where a non-word character really does precede it. Checked, not assumed. - Noted, not filed: the leg validates the SHAPE of a clock and not its ranges, so `1234-56:78Z` reads as a stamp. It read that way before this change and still does; the direction is toward MORE paragraphs clean, so it can only make the row quieter, and no artefact on the board writes it. `skip-changeset`: nothing published moves. The diff is one file under `scripts/pm/**`, which no package's `files[]` ships. _Generated by [Claude Code](https://claude.ai/code/session_01HZfg2AwVX191qCizp88gQr)_ --- _Generated by [Claude Code](https://claude.ai/code/session_01HZfg2AwVX191qCizp88gQr)_ Co-authored-by: Claude <noreply@anthropic.com>
1 parent a46cd8c commit de9c319

1 file changed

Lines changed: 50 additions & 6 deletions

File tree

‎scripts/pm/check-half-states.mjs‎

Lines changed: 50 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9636,8 +9636,34 @@ export function h43GovernedReviewRequestGap(pr, governed, approvers, reviewed =
96369636
/**
96379637
* The timestamp that makes a paragraph clean. See narrowing 1 in the banner —
96389638
* the seconds field is optional so a full ISO stamp counts as the rule obeyed.
9639+
*
9640+
* ⚠️ The left anchor is a word boundary OR a literal `T`, and that alternation
9641+
* is the whole of #18323. A bare `\b` needs a NON-word character before the
9642+
* hour and the `T` of an ISO instant is a word character, so
9643+
* `2026-09-15T14:51Z` read FALSE — and so did `2026-03T00:21Z`, the spelling
9644+
* the 2026-09-02 ruling itself gives. A reading that DID carry its 取数时刻 in
9645+
* the ruled minute form was therefore reported as untimestamped: a false
9646+
* positive on this row's own subject. A seconds stamp escaped only by
9647+
* ACCIDENT, its match starting at the minutes of the `mm:ssZ` tail, which is
9648+
* why the pin below reads where the match BEGINS and not merely that it exists.
9649+
*
9650+
* The `T` branch is the one predecessor the ISO form needs, it looks BEHIND
9651+
* rather than consuming — so a match is still exactly the stamp and a reader
9652+
* echoing one gets no stray designator — and it is the only thing admitted:
9653+
*
9654+
* • ⛔ The boundary is NOT dropped. `12314:51Z` is a digit run that merely
9655+
* contains a clock, never a stamp, and it still reads false — that is what
9656+
* the boundary was there for and it is kept.
9657+
* • ⛔ Nor widened to any letter: `abc14:51Z` stays false. The ISO
9658+
* designator is the only predecessor measured on this board.
9659+
* • ⛔ And only the UPPERCASE `T` the ruling spells. RFC 3339's lowercase
9660+
* `t` is legal and absent from this corpus; admitting it is one
9661+
* measurement away, not a guess.
9662+
*
9663+
* Like every narrowing in the banner this admits strictly MORE paragraphs as
9664+
* clean, so it can only make the row QUIETER — it cannot manufacture a finding.
96399665
*/
9640-
export const H44_READING_TIMESTAMP = /\b\d{2}:\d{2}(?::\d{2})?Z\b/;
9666+
export const H44_READING_TIMESTAMP = /(?:\b|(?<=T))\d{2}:\d{2}(?::\d{2})?Z\b/;
96419667

96429668
/**
96439669
* The reading grammar, as data so the self-test can drive every shape by name
@@ -28900,6 +28926,21 @@ Mutual exclusion: \`get_comments\` page 747 → \`[]\`, page 746 = my own R+117
2890028926
t('H44 grammar: the timestamp accepts the ruled HH:MMZ', H44_READING_TIMESTAMP.test('19:38Z'), true);
2890128927
t('H44 grammar: …and the seconds field, which is the same rule obeyed harder', H44_READING_TIMESTAMP.test('2026-09-03T10:37:51Z'), true);
2890228928
t('H44 grammar: …but not a bare clock with no zone', H44_READING_TIMESTAMP.test('19:38'), false);
28929+
// ⭐ The ISO instant (#18323). Both of these read FALSE before that change —
28930+
// the second is the spelling the 2026-09-02 ruling itself gives — because the
28931+
// hour sat behind a `T`, which is a word character and so no boundary.
28932+
t('H44 grammar: ⭐ …and an ISO instant whose hour follows the `T`', H44_READING_TIMESTAMP.test('2026-09-15T14:51Z'), true);
28933+
t('H44 grammar: …including the ruling\'s own spelling, which read false against its own rule', H44_READING_TIMESTAMP.test('2026-03T00:21Z'), true);
28934+
// ⛔ The seconds form always passed — by ACCIDENT, the boundary holding before
28935+
// its `mm:ssZ` tail so the match began at the MINUTES. Pinning where the match
28936+
// STARTS is what tells the fix apart from the accident it replaces.
28937+
t('H44 grammar: the seconds stamp now matches from its HOUR, not from its `mm:ssZ` tail', '2026-09-15T14:51:42Z'.match(H44_READING_TIMESTAMP)[0], '14:51:42Z');
28938+
// ⛔ THE DECLARED NEGATIVES of the new anchor: it names one predecessor and
28939+
// does not drop the boundary.
28940+
t('H44 grammar: ⛔ a clock inside a longer digit run is not a stamp', H44_READING_TIMESTAMP.test('12314:51Z'), false);
28941+
t('H44 grammar: ⛔ nor does any other letter open one — only the ISO `T`', H44_READING_TIMESTAMP.test('abc14:51Z'), false);
28942+
t('H44 grammar: ⛔ and only the UPPERCASE `T` the ruling spells', H44_READING_TIMESTAMP.test('2026-09-15t14:51Z'), false);
28943+
t('H44 grammar: an ISO instant still needs its zone, exactly as a bare clock does', H44_READING_TIMESTAMP.test('2026-09-15T14:51'), false);
2890328944
t('H44 fences: an unterminated fence swallows to the end rather than reopening', h44StripFences('a\n```\n92 open cards').trim(), 'a');
2890428945

2890528946
// ⑨ The row sentence: it names the comment, the fragment, and its posture.
@@ -28989,11 +29030,14 @@ Doubles as the fire's **write self-check** (step 0). \`201\` is not the reading.
2898929030
t('H44 marker: ⭐ what it reports on that body is the untimestamped CHARTER reading', h44kind(MARKER_R236_18312, true), 'tree');
2899029031
t('H44 marker: …echoed verbatim, so the remedy is one edit of a known artefact', h44frag(MARKER_R236_18312, true), '827cacbf');
2899129032
t('H44 marker: …under the marker\'s own name rather than the carrier\'s', h44hit(MARKER_R236_18312, true).shape, 'a round-open marker');
28992-
// ⚠️ The stamp here is spelled to SECONDS deliberately: `H44_READING_TIMESTAMP`
28993-
// requires a word boundary before the hour, which a preceding `T` does not
28994-
// give, so a minute-precision ISO stamp does not clean a paragraph. That is
28995-
// this row's own reading and ⛔ not this card's to change — it is filed.
28996-
t('H44 marker: ⛔ and it stays SILENT on the same marker once the reading carries its stamp', h44hit(MARKER_R236_18312.replace('tip `827cacbf`)', 'tip `827cacbf`, read 2026-09-15T14:51:42Z)'), true), null);
29033+
// ⭐ The stamp here is spelled to MINUTES — the ruled form — because the limit
29034+
// that forced SECONDS is gone (#18323). `H44_READING_TIMESTAMP` used to need a
29035+
// non-word character before the hour, which a preceding `T` is not, so a
29036+
// minute-precision ISO stamp did not clean a paragraph and this case bought its
29037+
// silence with a precision the ruling never asked for. The seconds spelling is
29038+
// pinned beside it, so neither reading can regress alone.
29039+
t('H44 marker: ⛔ and it stays SILENT on the same marker once the reading carries its stamp', h44hit(MARKER_R236_18312.replace('tip `827cacbf`)', 'tip `827cacbf`, read 2026-09-15T14:51Z)'), true), null);
29040+
t('H44 marker: …and the SECONDS spelling of the same reading is silent too', h44hit(MARKER_R236_18312.replace('tip `827cacbf`)', 'tip `827cacbf`, read 2026-09-15T14:51:42Z)'), true), null);
2899729041
t('H44 marker: ⛔ a comment that QUOTES a marker deeper down is not one — H65\'s headline control', h44IsRoundOpenMarker('A note about last round.\n\n**Round-open marker** · triage seat · **R+235**'), false);
2899829042
t('H44 marker: …and such a comment off a seat post stays out of scope entirely', h44hit('A note about last round.\n\n**Round-open marker** · triage seat · tip `827cacbf`'), null);
2899929043
// ⭐ ONE declaration of the spellings: H44 reads H65's grammar rather than

0 commit comments

Comments
 (0)