From fd3f1f3ef3a7211bc436ffa1cc9482357c66133f Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 30 Oct 2026 10:05:18 +0000 Subject: [PATCH 1/2] fix(pm): read a record's Head-sha line only below its heading, refuse an unreadable one at write time, and name the line in the rules MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `contractReviewHeadSha` (check-half-states.mjs) now takes the first `Head-sha:` line BELOW the record's own `## Contract review` heading, outside any fenced code block, and `CONTRACT_REVIEW_HEAD_SHA_LINE` no longer admits a blockquoted or bulleted key: a `Head-sha:` line naming another head that sits above the record's own — before the heading, quoted, bulleted or fenced — was read as the record's head, so the record was found at the cited head and absent at its own. Every reader (H43, the locator, the owed list, the queue guard) reaches the head through this one function, so they move together; pins cover each shape in the H51 battery and the locator battery. `post-stamped.mjs` `recordKeyedLineRefusals` refuses a record that carries a `Head-sha` key which `contractReviewHeadSha` (imported, never re-spelled) reads as null, before the write; a record with no key is not re-judged. `contract-review.md` names the line the readers read: 「`Head-sha:` 行独占码段」, line-count neutral, 117 bytes. Measured on the 709 line-initial records posted 2026-10-23..30: 707 carry the line plain below the heading, 2 carry no key (the retired dialects), 0 read differently under the new rules. Co-authored-by: Claude Claude-Session: https://claude.ai/code/session_016WV4czYNVYrGQqbs51hpvv --- .../pm-dispatch/references/contract-review.md | 2 +- scripts/pm/check-half-states.mjs | 95 ++++++++++++++++--- scripts/pm/post-stamped.mjs | 63 +++++++++--- scripts/pm/record-recognisers.mjs | 13 ++- 4 files changed, 141 insertions(+), 32 deletions(-) diff --git a/.claude/skills/pm-dispatch/references/contract-review.md b/.claude/skills/pm-dispatch/references/contract-review.md index 2ee3a4dfdcd..b1029dbe2e7 100644 --- a/.claude/skills/pm-dispatch/references/contract-review.md +++ b/.claude/skills/pm-dispatch/references/contract-review.md @@ -30,7 +30,7 @@ - 新 head 即清;点名现 head 时 (b) 不起;条件满足由持有车道挂等复核标记或落交接行。 - 逾 24 小时条件未满的 hold 列入分诊轮次记录(`check-owed-reviews.mjs` 标出)。 - 复核记录 = 一条评论落 PR 或卡,席内与子代理同形;散文、dev 自评、`os-dev-report` 恒不算。 -- 同形 = `## Contract review` 题头、head sha 独占码段、①②③ 逐项、独立性对、PASS/FAIL。 +- 同形 = `## Contract review` 题头、`Head-sha:` 行独占码段、①②③ 逐项、独立性对、PASS/FAIL。 - 同形含首行 `Served-tier:`:值写该面所定档的常量名 `CONTRACT_REVIEW_TIER` 或 `TIER_DEFAULT`; 无此行不成裁决;模板 `node scripts/pm/record-recognisers.mjs --template`,申报面加 `--face declaration`。 - 同形带 `Local-runs:` 行:`none` 或 `probe — REASON` 具名唯一探针;有行必校,缺行旧记录不改判。 diff --git a/scripts/pm/check-half-states.mjs b/scripts/pm/check-half-states.mjs index db82b25ba99..7ee78ce11d5 100644 --- a/scripts/pm/check-half-states.mjs +++ b/scripts/pm/check-half-states.mjs @@ -12641,9 +12641,18 @@ export const H51_SHA_SPAN = /`([0-9a-fA-F]{7,40})`/g; * The `Head-sha:` key line — the ONE place a contract-review record names the * head it reviewed. * - * The key half is `record-recognisers.mjs`'s `keyLineRegex` convention, spelled - * here because that file imports this one: optional blockquote, bullet, bold - * and backticks around the key, then the colon. The VALUE is a hex code span of + * The key half is `record-recognisers.mjs`'s `keyLineRegex` convention WITHOUT + * its two positional tolerances, spelled here because that file imports this + * one: bold and backticks around the key are tolerated, then the colon. ⛔ A + * leading blockquote or list bullet is NOT — `keyLineRegex` admits both for + * `Served-tier:`, `Reviewed-by:` and `Local-runs:`, whose values describe the + * record itself, but this line decides WHICH HEAD a PASS clears, and a quoted + * or bulleted `Head-sha:` line is one the author CITED rather than wrote (a + * review quoting the seat's hand-off, a record quoting an earlier record), so + * a reader that took it read a quoted sha as reviewed. `contractReviewHeadSha` + * adds the two positional rules a single-line regex cannot carry: the line + * sits BELOW the record's own `## Contract review` heading and outside any + * fenced code block. The VALUE is a hex code span of * its OWN — the shape `--template` prints — and it is the FIRST thing after the * colon (spaces and bold may precede it, ⛔ nothing else); what follows the span * on that line is prose and decides nothing. A key folded into the span with @@ -12652,24 +12661,66 @@ export const H51_SHA_SPAN = /`([0-9a-fA-F]{7,40})`/g; * decoration and the value then opens with hex rather than a backtick, so the * record names no head — the accept set stays exactly one spelling. A bare hex * value with no span, a lowercase key and a span below the floor name no head - * either; the repair for every one of them is on the WRITE side. + * either; the repair for every one of them is on the WRITE side, where + * `post-stamped.mjs` refuses a record carrying a `Head-sha` key this reader + * cannot read BEFORE the write. */ export const CONTRACT_REVIEW_HEAD_SHA_LINE = new RegExp( - `^[ \\t]*(?:>[ \\t]*)?(?:[-*][ \\t]+)?(?:\\*\\*)?\`?Head-sha\`?(?:\\*\\*)?[ \\t]*:(?:[ \\t]|\\*\\*)*${H51_SHA_SPAN.source}`, + `^[ \\t]*(?:\\*\\*)?\`?Head-sha\`?(?:\\*\\*)?[ \\t]*:(?:[ \\t]|\\*\\*)*${H51_SHA_SPAN.source}`, ); /** - * The head a contract-review record names — the span on its first `Head-sha:` - * line, as written (case kept), or `null` when no line reads. - * - * First line wins, as `readServedTier` reads its key: a record is one comment - * and names one head, and a later `Head-sha:` line (a quoted record, say) is - * prose to this reader. Not judged against any head here — that is - * `contractReviewHeadMatch`'s half — so a caller can also ask which head a - * record is ABOUT, which the sentence a patrol prints needs. + * A fenced-code boundary, as CommonMark opens and closes one: three or more + * backticks or tildes at the line start (up to three spaces of indent). + * One toggle for both fence characters — the reader needs "inside a fence or + * not", never which fence; an unclosed fence runs to the end of the body, as + * it renders. + */ +const FENCE_LINE = /^[ ]{0,3}(?:`{3,}|~{3,})/; + +/** + * The head a contract-review record names — the span on the first `Head-sha:` + * line BELOW its `## Contract review` heading, as written (case kept), or + * `null` when no such line reads. + * + * Three positional rules, each measured in the review of the line-form reader + * (2026-10-30) as a way a sha the author never reviewed became the record's + * head: a `Head-sha:` line placed BEFORE the heading, a blockquoted or + * bulleted one, and one inside a fenced code block — each naming another head + * ABOVE the record's own line — read as the head, so the record was found at + * the cited head and absent at the real one. None is prose to a first-line + * reader, and all four shapes are what a quotation looks like. So: lines + * before the first heading-marker line are skipped (the heading is read with + * `CONTRACT_REVIEW_HEADING_MARKER`, blockquoted heading included, exactly as + * the locator finds the record); a fenced line is skipped, fence boundaries + * included; and `CONTRACT_REVIEW_HEAD_SHA_LINE` itself refuses the blockquote + * and the bullet. Among the lines that remain, the FIRST wins, as + * `readServedTier` reads its key: a record is one comment and names one head, + * and a later `Head-sha:` line (a quoted record, say) is prose to this reader. + * Measured on the 709 records with a line-initial heading posted to this + * repository in the week to 2026-10-30: 707 carry the line plain and below + * the heading, 2 carry no `Head-sha` key at all (the two retired-dialect + * records), and 0 read differently under these rules than under the + * first-line-anywhere reader they replace. + * + * Not judged against any head here — that is `contractReviewHeadMatch`'s half + * — so a caller can also ask which head a record is ABOUT, which the sentence + * a patrol prints needs, and which `post-stamped.mjs` asks before it writes a + * record: a body carrying a `Head-sha` key that reads `null` here is refused. */ export function contractReviewHeadSha(body) { + let fenced = false; + let belowHeading = false; for (const line of String(body ?? '').split(/\r?\n/)) { + if (FENCE_LINE.test(line)) { + fenced = !fenced; + continue; + } + if (fenced) continue; + if (!belowHeading) { + belowHeading = CONTRACT_REVIEW_HEADING_MARKER.test(line); + continue; + } const m = CONTRACT_REVIEW_HEAD_SHA_LINE.exec(line); if (m) return m[1]; } @@ -33452,8 +33503,22 @@ Doubles as the fire's **write self-check** (step 0). \`201\` is not the reading. const DELTA51 = RECORD51(OLDHEAD51, `\nThe PR has since moved to \`${HEAD51.slice(0, 8)}\` (seat comment 6515952505); this record judges \`${OLDHEAD51.slice(0, 8)}\` alone. Runs cancelled by the push of \`${HEAD51.slice(0, 8)}\`; landing waits on \`${HEAD51}\`.\n`); t('⛔ record reader: a delta record on head A whose PROSE names head B is NO review of B', latestContractReviewOnHead([cm51(1, DELTA51, T51)], HEAD51), null); t('⭐ record reader: …and IS the review of A — the `Head-sha:` line alone decides', latestContractReviewOnHead([cm51(1, DELTA51, T51)], OLDHEAD51)?.sha, OLDHEAD51); - t('record reader: `contractReviewHeadSha` reads the line\'s span — the first line wins, decoration and a trailing span tolerated', [contractReviewHeadSha(DELTA51), contractReviewHeadSha(`- **Head-sha:** \`${HEAD51}\` (moved to \`${OLDHEAD51}\`)\nHead-sha: \`${OLDHEAD51}\``), contractReviewHeadSha(`> Head-sha: \`${HEAD51}\``)].join(), [OLDHEAD51, HEAD51, HEAD51].join()); - t('⛔ record reader: the key folded INTO the span, a bare hex value, a lowercase key and a sub-floor span name no head', [contractReviewHeadSha(`\`Head-sha: ${HEAD51}\``), contractReviewHeadSha(`Head-sha: ${HEAD51}`), contractReviewHeadSha(`head-sha: \`${HEAD51}\``), contractReviewHeadSha('Head-sha: `ba3d95`')].join('|'), '|||'); + const UNDER51 = (...lines) => ['## Contract review', ...lines].join('\n'); + t('record reader: `contractReviewHeadSha` reads the line\'s span — the first line BELOW the heading wins; bold or backticks on the key, a trailing span and a blockquoted heading are tolerated', [contractReviewHeadSha(DELTA51), contractReviewHeadSha(UNDER51(`**Head-sha:** \`${HEAD51}\` (moved to \`${OLDHEAD51}\`)`, `Head-sha: \`${OLDHEAD51}\``)), contractReviewHeadSha(UNDER51(`\`Head-sha\`: \`${HEAD51}\``)), contractReviewHeadSha(`> ## Contract review\nHead-sha: \`${HEAD51}\``)].join(), [OLDHEAD51, HEAD51, HEAD51, HEAD51].join()); + t('⛔ record reader: the key folded INTO the span, a bare hex value, a lowercase key and a sub-floor span name no head', [contractReviewHeadSha(UNDER51(`\`Head-sha: ${HEAD51}\``)), contractReviewHeadSha(UNDER51(`Head-sha: ${HEAD51}`)), contractReviewHeadSha(UNDER51(`head-sha: \`${HEAD51}\``)), contractReviewHeadSha(UNDER51('Head-sha: `ba3d95`'))].join('|'), '|||'); + // ⛔ The shapes the review of the line-form reader probed (2026-10-30): a `Head-sha:` line naming ANOTHER head above the + // record's own — placed before the heading, blockquoted, bulleted or fenced. Each is a citation, not the record's line, + // so the record reads ABSENT at the cited head and FOUND at its own; the first-line-anywhere reader read all four as cited. + const CITED51 = { + preHeading: `Head-sha: \`${HEAD51}\`\n\n${RECORD51(OLDHEAD51)}`, + quoted: UNDER51('', `> Head-sha: \`${HEAD51}\``, `Head-sha: \`${OLDHEAD51}\``, '**VERDICT: PASS**'), + bulleted: UNDER51('', `- Head-sha: \`${HEAD51}\``, `Head-sha: \`${OLDHEAD51}\``, '**VERDICT: PASS**'), + fenced: UNDER51('', '```', `Head-sha: \`${HEAD51}\``, '```', `Head-sha: \`${OLDHEAD51}\``, '**VERDICT: PASS**'), + }; + t('⛔ record reader: a `Head-sha:` line before the heading, blockquoted, bulleted or fenced — naming another head ABOVE the record\'s own — is NO review of that head', Object.values(CITED51).map((body) => latestContractReviewOnHead([cm51(1, body, T51)], HEAD51)).join('|'), '|||'); + t('⭐ record reader: …and each IS the review of the head its own line names — the cited line is prose to the reader', Object.values(CITED51).map((body) => latestContractReviewOnHead([cm51(1, body, T51)], OLDHEAD51)?.sha).join(), [OLDHEAD51, OLDHEAD51, OLDHEAD51, OLDHEAD51].join()); + t('⛔ record reader: a quoted, bulleted or fenced line ALONE names no head, nor does a line with NO heading above it, nor one inside an unclosed fence', [contractReviewHeadSha(UNDER51(`> Head-sha: \`${HEAD51}\``)), contractReviewHeadSha(UNDER51(`- **Head-sha:** \`${HEAD51}\``)), contractReviewHeadSha(UNDER51('```md', `Head-sha: \`${HEAD51}\``, '```')), contractReviewHeadSha(`Head-sha: \`${HEAD51}\``), contractReviewHeadSha(UNDER51('```', `Head-sha: \`${HEAD51}\``))].join('|'), '||||'); + t('record reader: a tilde fence is a fence too, and CRLF line endings read', [contractReviewHeadSha(UNDER51('~~~', `Head-sha: \`${HEAD51}\``, '~~~', `Head-sha: \`${OLDHEAD51}\``)), contractReviewHeadSha(`## Contract review\r\nHead-sha: \`${HEAD51}\`\r\n`)].join(), [OLDHEAD51, HEAD51].join()); t('⛔ record reader: the RETIRED dialects — head in the heading (two spellings), on the first body line, in an adoption sentence — name no head', [DIALECT_A, DIALECT_B, DIALECT_C, DIALECT_D].map((d) => latestContractReviewOnHead([cm51(1, d(HEAD51), T51)], HEAD51)).join('|'), '|||'); // -- H54 — a `[Decision]`-titled card sitting in the DISPATCH pool (#16688) - diff --git a/scripts/pm/post-stamped.mjs b/scripts/pm/post-stamped.mjs index 65a2c531202..902660a24e2 100644 --- a/scripts/pm/post-stamped.mjs +++ b/scripts/pm/post-stamped.mjs @@ -947,6 +947,7 @@ import { ISSUE_BODY_LIMIT, PROXY_FLAG, claimSeatNumber, + contractReviewHeadSha, h56EstimatedStamp, h56StampedReadings, hasDecisionFacetMarker, @@ -2059,23 +2060,45 @@ export function keyedLineRefusalText(rows) { /** What the record's owner accepts, printed BY the refusal so the fix is one line away. */ export const RECORD_KEY_SPELLINGS = Object.freeze({ + 'Head-sha': '`Head-sha: ` then the reviewed head as 7 to 40 hex in a code span of its OWN, at the START of a line BELOW the `## Contract review` heading — not blockquoted, not bulleted, not inside a fence, not before the heading; the first such line is the head the record names, and a record written before the line existed may omit it', 'Local-runs': '`Local-runs: none` at the START of a line, or `Local-runs: probe — REASON` naming the ONE local run the read-only rule was broken for and why; a record written before the line existed may omit it', }); +/** PRESENCE only, case-blind: a `Head-sha` key anywhere in the body, however decorated or placed. Consulted solely to + * tell "the author wrote the key and the reader cannot read it" (refused) from "no key at all" (not re-judged). */ +const HEAD_SHA_KEY_ANYWHERE = /head-sha[ \t`*]*:/i; + +/** The first line carrying a `Head-sha` key, however spelled — a LOCATOR for the writer's eye, ⛔ never the judgement. */ +function headShaKeyLine(text) { + const hit = text.split(/\r?\n/).find((line) => HEAD_SHA_KEY_ANYWHERE.test(line)); + return hit === undefined ? '(no single line carries the key)' : offendingSpan(hit.trim(), 160); +} + /** - * The keyed-line problems in a `## Contract review` record about to be written, judged by the reader that OWNS the - * line — ⛔ never by a rule spelled here. One key today, `Local-runs:`, and ONE direction: a line that is PRESENT and - * cannot be graded is refused; an absent line is not re-judged, because the landed records carry none and this act - * must not re-open a verdict over a line nobody had been asked to write. Scoped by the record's own heading marker, - * on `--comment` alone — a record IS a comment. - * @returns {{ key: string, why: string, line: string }[]} — empty when the body is not a record, or when the line reads. + * The keyed-line problems in a `## Contract review` record about to be written, judged by the readers that OWN each + * line — ⛔ never by a rule spelled here. Two keys, ONE direction each: a line that is PRESENT and cannot be read is + * refused; an absent line is not re-judged, because the landed records carry none and this act must not re-open a + * verdict over a line nobody had been asked to write. Scoped by the record's own heading marker, on `--comment` + * alone — a record IS a comment. + * Head-sha `contractReviewHeadSha` (`check-half-states.mjs`) is the ONE reader every guard reaches the record's head + * through: the first plain `Head-sha:` line below the heading, outside any blockquote, bullet or fence. A + * body that carries the key somewhere and reads `null` there would be filed by every reader as a review of + * NO head — absent on the owed list, refused at the queue — so it is refused here, before the write. The + * template's null-oid placeholder reads as a head (it is 40 hex) and is not this row's: C6 refuses it. + * Local-runs `readLocalRuns` / `localRunsStand` (`record-recognisers.mjs`): `none` and `probe — REASON` are the + * closed set. + * @returns {{ key: string, why: string, line: string }[]} — empty when the body is not a record, or when every line reads. */ export function recordKeyedLineRefusals(body) { const text = String(body ?? ''); if (!CONTRACT_REVIEW_HEADING_MARKER.test(text)) return []; + const rows = []; + if (HEAD_SHA_KEY_ANYWHERE.test(text) && contractReviewHeadSha(text) === null) + rows.push({ key: 'Head-sha', line: headShaKeyLine(text), why: '`contractReviewHeadSha` reads NO head here — a `Head-sha` key is present, but no line below the `## Contract review` heading carries it plainly with a hex span of its own (a blockquoted, bulleted, fenced or pre-heading line, the key folded into the span, a bare hex value, a lowercase key and a sub-floor span all read as nothing). Every reader would file this record as a review of NO head.' }); const runs = readLocalRuns(text); - if (localRunsStand(runs)) return []; - return [{ key: 'Local-runs', line: runs.line, why: '`readLocalRuns` reads the line and cannot grade its value — `none` and `probe — REASON` are the closed set, and a probe with no reason names no deviation.' }]; + if (!localRunsStand(runs)) + rows.push({ key: 'Local-runs', line: runs.line, why: '`readLocalRuns` reads the line and cannot grade its value — `none` and `probe — REASON` are the closed set, and a probe with no reason names no deviation.' }); + return rows; } /** The refusal a caller reads, from `recordKeyedLineRefusals`' rows. */ @@ -2083,9 +2106,9 @@ export function recordLineRefusalText(rows) { return ( `post-stamped: REFUSED — ${rows.length} keyed line(s) in this \`## Contract review\` record cannot be read by the checker that owns them. Nothing was written.\n` + rows.map((r, i) => ` ${i + 1}. [${r.key}] ${r.why}\n line: ${r.line}\n write: ${RECORD_KEY_SPELLINGS[r.key]}`).join('\n') + - '\n\n `readLocalRuns` (`record-recognisers.mjs`) is imported HERE, so this IS the reading the record would get later —\n' + - ' on your own record, before the write. A record without the line is not re-judged; only a line that is present and\n' + - ' unreadable is refused. ⛔ No flag turns it off.' + '\n\n `contractReviewHeadSha` (`check-half-states.mjs`) and `readLocalRuns` (`record-recognisers.mjs`) are imported HERE,\n' + + ' so this IS the reading the record would get later — on your own record, before the write. A record without the\n' + + ' line is not re-judged; only a line that is present and unreadable is refused. ⛔ No flag turns it off.' ); } @@ -4064,7 +4087,7 @@ const SELF_TEST_BATTERIES = Object.freeze({ 'the two positions: a declaration renders as bare digits, and the patrol reads digits': 24, 'the shared rule: this tool and H56 cannot come to disagree': 6, 'the keyed lines: a claim\'s exact-value fields, judged by the readers that own them': 30, - 'the record line: a `## Contract review` record\'s `Local-runs:` value, judged when present — an absent line is not re-judged': 9, + 'the record line: a `## Contract review` record\'s `Head-sha:` and `Local-runs:` lines, judged when present — an absent line is not re-judged': 17, 'the vacant body: a VACANT seat post\'s ledger holds pointers, never obligations': 27, 'the decision facets: a decision analysis carries its marker, and a delta, a record or a quotation is not one': 22, 'the relay transport: the same act as one op, the comment found on the board, the exit register kept apart': 13, @@ -5274,7 +5297,7 @@ export function selfTest() { } // ── the record line ────────────────────────────────────────────────────── - battery('the record line: a `## Contract review` record\'s `Local-runs:` value, judged when present — an absent line is not re-judged'); + battery('the record line: a `## Contract review` record\'s `Head-sha:` and `Local-runs:` lines, judged when present — an absent line is not re-judged'); { const RECORD = (...lines) => ['## Contract review', '', `Served-tier: \`${CONTRACT_REVIEW_TIER_NAME}\``, 'Head-sha: `0123456789abcdef0123456789abcdef01234567`', ...lines, '', '**VERDICT: PASS**'].join('\n'); const rkeys = (body) => recordKeyedLineRefusals(body).map((r) => r.key).join(); @@ -5286,7 +5309,19 @@ export function selfTest() { t('the refusal prints the spelling that reads, names the owner, and says absence is not re-judged', (() => { const text = recordLineRefusalText(recordKeyedLineRefusals(RECORD('Local-runs: probe'))); return text.includes(RECORD_KEY_SPELLINGS['Local-runs']) && text.includes('readLocalRuns') && text.includes('not re-judged') && text.includes('Nothing was written'); })()); t('⛔ NOT a record: a claim or a report carrying a `Local-runs:` line is untouched — the reader runs on the record\'s heading only', rkeys('Claim: x\nLocal-runs: probe') === '' && rkeys('os-dev-report\n\nLocal-runs: probe') === ''); t('⛔ a `###` sub-heading is not the record marker, exactly as the locator reads it', rkeys('### Contract review\nLocal-runs: probe') === ''); - t('structural: the CLI runs this on `--comment` only, and the reader is imported from the record\'s own file, ⛔ never restated', /const record = options\.mode === 'comment' \? recordKeyedLineRefusals\(rendered\.body\) : \[\];/u.test(stampSource) && /import \{[^}]*\breadLocalRuns\b[^}]*\} from '\.\/record-recognisers\.mjs';/u.test(stampSource) && new RegExp('function\\s+(readLocalRuns|localRunsStand)\\b').test(stampSource) === false); + // The `Head-sha:` line — read by the one reader every guard reaches the head through, refused here when it is present and + // reads null. The shapes are the ones the line-form reader's review probed as a sha the author cited rather than reviewed. + const SHA_WITH = (headLines, ...lines) => ['## Contract review', '', `Served-tier: \`${CONTRACT_REVIEW_TIER_NAME}\``, ...headLines, ...lines, '', '**VERDICT: PASS**'].join('\n'); + const SHA_HEAD = '0123456789abcdef0123456789abcdef01234567'; + t('⛔ a `Head-sha:` line that is ONLY blockquoted is refused — the reader takes no quoted line, so this record would name no head', rkeys(SHA_WITH([`> Head-sha: \`${SHA_HEAD}\``])) === 'Head-sha'); + t('⛔ …and ONLY bulleted, ONLY fenced, or ONLY before the heading — each reads null and is refused', rkeys(SHA_WITH([`- Head-sha: \`${SHA_HEAD}\``])) === 'Head-sha' && rkeys(SHA_WITH(['```', `Head-sha: \`${SHA_HEAD}\``, '```'])) === 'Head-sha' && rkeys(`Head-sha: \`${SHA_HEAD}\`\n\n${SHA_WITH([])}`) === 'Head-sha'); + t('⛔ the key folded INTO the span, a bare hex value and a lowercase key are refused — each names no head to every reader', rkeys(SHA_WITH([`\`Head-sha: ${SHA_HEAD}\``])) === 'Head-sha' && rkeys(SHA_WITH([`Head-sha: ${SHA_HEAD}`])) === 'Head-sha' && rkeys(SHA_WITH([`head-sha: \`${SHA_HEAD}\``])) === 'Head-sha'); + t('⭐ a quoted line ABOVE the record\'s own passes — the reader skips the quotation and reads the line below it', rkeys(SHA_WITH([`> Head-sha: \`${'f'.repeat(40)}\``, `Head-sha: \`${SHA_HEAD}\``])) === ''); + t('⛔ ABSENT is NOT re-judged for this key either: a record with no `Head-sha` key at all passes untouched — the retired-dialect records carry none', rkeys(SHA_WITH([])) === ''); + t('both lines unreadable ⇒ both rows, `Head-sha` first', rkeys(SHA_WITH([`> Head-sha: \`${SHA_HEAD}\``], 'Local-runs: probe')) === 'Head-sha,Local-runs'); + t('the `Head-sha` refusal prints the spelling that reads, names the owner, and quotes the offending line', (() => { const rows = recordKeyedLineRefusals(SHA_WITH([`> Head-sha: \`${SHA_HEAD}\``])); const text = recordLineRefusalText(rows); return text.includes(RECORD_KEY_SPELLINGS['Head-sha']) && text.includes('contractReviewHeadSha') && rows[0].line.includes('> Head-sha'); })()); + t('⛔ NOT a record: a claim or a report carrying a quoted `Head-sha:` line is untouched — the reader runs on the record\'s heading only', rkeys(`Claim: x\n> Head-sha: \`${SHA_HEAD}\``) === '' && rkeys(`os-dev-report\n\n> Head-sha: \`${SHA_HEAD}\``) === ''); + t('structural: the CLI runs this on `--comment` only, and BOTH readers are imported from the files that own them, ⛔ never restated', /const record = options\.mode === 'comment' \? recordKeyedLineRefusals\(rendered\.body\) : \[\];/u.test(stampSource) && /import \{[^}]*\breadLocalRuns\b[^}]*\} from '\.\/record-recognisers\.mjs';/u.test(stampSource) && /import \{[^}]*\bcontractReviewHeadSha\b[^}]*\} from '\.\/check-half-states\.mjs';/u.test(stampSource) && new RegExp('function\\s+(readLocalRuns|localRunsStand|contractReviewHeadSha)\\b').test(stampSource) === false); } // ── the vacant seat body (#23396, ruling 6162084315 letter A) ────────────── diff --git a/scripts/pm/record-recognisers.mjs b/scripts/pm/record-recognisers.mjs index 8ed4979a2b9..004826d6cfe 100644 --- a/scripts/pm/record-recognisers.mjs +++ b/scripts/pm/record-recognisers.mjs @@ -2070,7 +2070,7 @@ export function locateReviewOfRecord(pair, carriedHead = null) { // stopped registering. const SELF_TEST_BATTERIES = Object.freeze({ 'the locator: the newest `## Contract review` on this head, on EITHER thread (#17302)': 19, - '#18141 · #27979: the head sha sits in a span of ITS OWN on the `Head-sha:` line — the key-in-span spelling and a span in the prose name no head': 10, + '#18141 · #27979: the head sha sits in a span of ITS OWN on the `Head-sha:` line — the key-in-span spelling and a span in the prose name no head': 13, 'the `Served-tier:` reading — the tier that SERVED the record (#17915 · #18060 · #18174)': 27, '⭐ the 2026-09-20 ruling: a pure-regeneration head move KEEPS the record, decided on the COMMITTED trees': 22, '#18042: the copyable record TEMPLATE — the one machine-read artefact with nothing to copy': 29, @@ -2232,7 +2232,16 @@ export async function selfTest() { const deltaOn = (headSha, where) => locateReviewOfRecord(pairOf({ headSha, ...(where === 'card' ? { cardComments: [DELTA] } : { prComments: [DELTA] }) })); t('⛔ #27979: a delta record on head A whose prose names head B is ABSENT for B — on the PR thread and on the card alike', deltaOn(DELTA_B, 'PR').state === 'absent' && deltaOn(DELTA_B, 'card').state === 'absent'); t('⭐ #27979: …and FOUND for A, naming the full sha its `Head-sha:` line carries', (() => { const r = deltaOn(DELTA_A, 'PR'); return r.state === 'found' && r.sha === DELTA_A && r.id === 6516042954; })()); - t('⛔ #27979: the FIRST `Head-sha:` line decides — a second one lower down (a quoted record) is prose to the reader', locateReviewOfRecord(pairOf({ headSha: DELTA_B, prComments: [{ ...DELTA, body: `${DELTA.body}\n\n> Head-sha: \`${DELTA_B}\`` }] })).state === 'absent' && contractReviewHeadMatch(`Head-sha: \`${DELTA_B}\`\nHead-sha: \`${DELTA_A}\``, DELTA_B) === DELTA_B); + t('⛔ #27979: the FIRST `Head-sha:` line below the heading decides — a second one lower down (a quoted record) is prose to the reader', locateReviewOfRecord(pairOf({ headSha: DELTA_B, prComments: [{ ...DELTA, body: `${DELTA.body}\n\n> Head-sha: \`${DELTA_B}\`` }] })).state === 'absent' && contractReviewHeadMatch(`## Contract review\nHead-sha: \`${DELTA_B}\`\nHead-sha: \`${DELTA_A}\``, DELTA_B) === DELTA_B); + // ⛔ #28024: the shapes the #28023 review probed — a `Head-sha:` line naming head B that the author CITED rather than wrote, + // sitting ABOVE the record's own line: blockquoted, bulleted, fenced, or placed before the heading. The first-line-anywhere + // reader took each as the record's head (found at B, absent at A); now each is prose and the record's own line decides. + const CITED_LEADS = [[`> Head-sha: \`${DELTA_B}\``], [`- Head-sha: \`${DELTA_B}\``], ['```', `Head-sha: \`${DELTA_B}\``, '```']]; + const QUOTED_ABOVE = (lead) => ({ id: 6516042955, created_at: '2026-10-30T05:53:38Z', body: ['## Contract review', '', ...lead, `Head-sha: \`${DELTA_A}\``, 'Local-runs: none', '', `Reviewed-by: \`${RECORD_SESSION}\``, '', '**VERDICT: PASS**'].join('\n') }); + t('⛔ #28024: a blockquoted, bulleted or fenced `Head-sha:` line ABOVE the record\'s own is ABSENT for the head it names — on the PR thread and on the card alike', CITED_LEADS.every((lead) => locateReviewOfRecord(pairOf({ headSha: DELTA_B, prComments: [QUOTED_ABOVE(lead)] })).state === 'absent' && locateReviewOfRecord(pairOf({ headSha: DELTA_B, cardComments: [QUOTED_ABOVE(lead)] })).state === 'absent')); + t('⭐ #28024: …and each record is FOUND for the head its OWN line names, with that line\'s full sha', CITED_LEADS.every((lead) => { const r = locateReviewOfRecord(pairOf({ headSha: DELTA_A, prComments: [QUOTED_ABOVE(lead)] })); return r.state === 'found' && r.sha === DELTA_A && r.id === 6516042955; })); + const PRE_HEADING = { ...QUOTED_ABOVE([]), body: `Head-sha: \`${DELTA_B}\`\n\n${QUOTED_ABOVE([]).body}` }; + t('⛔ #28024: a `Head-sha:` line BEFORE the `## Contract review` heading is not the record\'s — absent for the head it names, found for the one below the heading', locateReviewOfRecord(pairOf({ headSha: DELTA_B, prComments: [PRE_HEADING] })).state === 'absent' && locateReviewOfRecord(pairOf({ headSha: DELTA_A, prComments: [PRE_HEADING] })).state === 'found'); t('⭐ a correct record beside a refused one is FOUND, in either arrival order', locateReviewOfRecord(pairOf({ prComments: [KEYED_ROW, NEWER_RECORD] })).state === 'found' && locateReviewOfRecord(pairOf({ prComments: [RECORD(HEAD_9AF9, undefined, '2026-09-01T08:45:00Z', 3404), KEYED_ROW] })).state === 'found'); t('…and the locator chooses the CORRECT one — a refused spelling is never chosen over it, and never chosen at all', locateReviewOfRecord(pairOf({ prComments: [KEYED_ROW, NEWER_RECORD] })).id === 3403 && locateReviewOfRecord(pairOf({ prComments: [RECORD(HEAD_9AF9, undefined, '2026-09-01T08:45:00Z', 3404), KEYED_ROW] })).id === 3404); t('the template and the reader name ONE spelling — and the printed template still round-trips', contractReviewHeadMatch(contractReviewTemplateLines({ headSha: HEAD_9AF9 }).join('\n'), HEAD_9AF9) === HEAD_9AF9); From 2e75b19392b1acff5c999da52311e167042a11de Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 30 Oct 2026 10:40:59 +0000 Subject: [PATCH 2/2] fix(pm): the Head-sha reader opens scope only on an unquoted heading, refuses code-indented lines, and closes a fence only on its own character and length MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Contract-review should-fixes 1–3 on the first head of this branch: - A blockquoted `## Contract review` opens no head scope. A fully quoted signed record followed by a plain prose line opening `Head-sha:` read the plain line as the head, under the quoted signature; the locator still sees such a comment as a record (the marker keeps its `>` tolerance), and it now names no head. - `CONTRACT_REVIEW_HEAD_SHA_LINE` allows at most three spaces of indent (CommonMark's limit before an indented code block) and no tab, so a `Head-sha:` line rendered as code is skipped above the record's own line and names nothing alone. - `FENCE_LINE` captures the fence run; a fence closes only on the same character with a run at least as long, so a nested or mismatched fence keeps its content fenced. Pins for each shape in the H51 battery, the locator battery (floor 13 to 14; the blockquoted-heading pin flips to ABSENT with its reason) and the post-stamped record battery (floor 17 to 18). Co-authored-by: Claude Claude-Session: https://claude.ai/code/session_016WV4czYNVYrGQqbs51hpvv --- scripts/pm/check-half-states.mjs | 64 +++++++++++++++++++++---------- scripts/pm/post-stamped.mjs | 3 +- scripts/pm/record-recognisers.mjs | 8 +++- 3 files changed, 51 insertions(+), 24 deletions(-) diff --git a/scripts/pm/check-half-states.mjs b/scripts/pm/check-half-states.mjs index 7ee78ce11d5..9bc7476e2e6 100644 --- a/scripts/pm/check-half-states.mjs +++ b/scripts/pm/check-half-states.mjs @@ -12649,10 +12649,13 @@ export const H51_SHA_SPAN = /`([0-9a-fA-F]{7,40})`/g; * record itself, but this line decides WHICH HEAD a PASS clears, and a quoted * or bulleted `Head-sha:` line is one the author CITED rather than wrote (a * review quoting the seat's hand-off, a record quoting an earlier record), so - * a reader that took it read a quoted sha as reviewed. `contractReviewHeadSha` + * a reader that took it read a quoted sha as reviewed. The key may be indented + * by at most three spaces, CommonMark's limit before a line becomes an + * INDENTED CODE BLOCK: four spaces or a tab render the line as code, which is + * a quotation too, so the regex refuses them. `contractReviewHeadSha` * adds the two positional rules a single-line regex cannot carry: the line - * sits BELOW the record's own `## Contract review` heading and outside any - * fenced code block. The VALUE is a hex code span of + * sits BELOW the record's own UNQUOTED `## Contract review` heading and + * outside any fenced code block. The VALUE is a hex code span of * its OWN — the shape `--template` prints — and it is the FIRST thing after the * colon (spaces and bold may precede it, ⛔ nothing else); what follows the span * on that line is prose and decides nothing. A key folded into the span with @@ -12666,17 +12669,19 @@ export const H51_SHA_SPAN = /`([0-9a-fA-F]{7,40})`/g; * cannot read BEFORE the write. */ export const CONTRACT_REVIEW_HEAD_SHA_LINE = new RegExp( - `^[ \\t]*(?:\\*\\*)?\`?Head-sha\`?(?:\\*\\*)?[ \\t]*:(?:[ \\t]|\\*\\*)*${H51_SHA_SPAN.source}`, + `^[ ]{0,3}(?:\\*\\*)?\`?Head-sha\`?(?:\\*\\*)?[ \\t]*:(?:[ \\t]|\\*\\*)*${H51_SHA_SPAN.source}`, ); /** - * A fenced-code boundary, as CommonMark opens and closes one: three or more - * backticks or tildes at the line start (up to three spaces of indent). - * One toggle for both fence characters — the reader needs "inside a fence or - * not", never which fence; an unclosed fence runs to the end of the body, as - * it renders. + * A fence line, as CommonMark spells one: three or more backticks or tildes at + * the line start (up to three spaces of indent). The run is captured because a + * fence CLOSES only on the same character with a run at least as long as the + * one that opened it: a shorter run, or the other character, is content of the + * open fence (a nested ``` inside a ```` block, a ~~~ inside a ``` block), and + * reading past either would read fenced content as the record's line. An + * unclosed fence runs to the end of the body, as it renders. */ -const FENCE_LINE = /^[ ]{0,3}(?:`{3,}|~{3,})/; +const FENCE_LINE = /^[ ]{0,3}(`{3,}|~{3,})/; /** * The head a contract-review record names — the span on the first `Head-sha:` @@ -12690,11 +12695,16 @@ const FENCE_LINE = /^[ ]{0,3}(?:`{3,}|~{3,})/; * ABOVE the record's own line — read as the head, so the record was found at * the cited head and absent at the real one. None is prose to a first-line * reader, and all four shapes are what a quotation looks like. So: lines - * before the first heading-marker line are skipped (the heading is read with - * `CONTRACT_REVIEW_HEADING_MARKER`, blockquoted heading included, exactly as - * the locator finds the record); a fenced line is skipped, fence boundaries - * included; and `CONTRACT_REVIEW_HEAD_SHA_LINE` itself refuses the blockquote - * and the bullet. Among the lines that remain, the FIRST wins, as + * before the first UNQUOTED heading-marker line are skipped — the heading is + * read with `CONTRACT_REVIEW_HEADING_MARKER`, but a blockquoted heading opens + * nothing here, because a fully quoted record (heading, head line and + * signature all under `>`) followed by a plain line opening `Head-sha:` would + * otherwise hand the quoted record's signature to the plain line's head; the + * locator still finds such a comment as a record, and it then names no head. + * A fenced line is skipped, fence boundaries included, with the fence closed + * only by its own character and length (`FENCE_LINE`); and + * `CONTRACT_REVIEW_HEAD_SHA_LINE` itself refuses the blockquote, the bullet + * and an indented-code line. Among the lines that remain, the FIRST wins, as * `readServedTier` reads its key: a record is one comment and names one head, * and a later `Head-sha:` line (a quoted record, say) is prose to this reader. * Measured on the 709 records with a line-initial heading posted to this @@ -12709,16 +12719,18 @@ const FENCE_LINE = /^[ ]{0,3}(?:`{3,}|~{3,})/; * record: a body carrying a `Head-sha` key that reads `null` here is refused. */ export function contractReviewHeadSha(body) { - let fenced = false; + let fence = null; // the OPEN fence — its character and run length — or null outside one let belowHeading = false; for (const line of String(body ?? '').split(/\r?\n/)) { - if (FENCE_LINE.test(line)) { - fenced = !fenced; + const f = FENCE_LINE.exec(line); + if (f !== null) { + if (fence === null) fence = { ch: f[1][0], len: f[1].length }; + else if (f[1][0] === fence.ch && f[1].length >= fence.len) fence = null; continue; } - if (fenced) continue; + if (fence !== null) continue; if (!belowHeading) { - belowHeading = CONTRACT_REVIEW_HEADING_MARKER.test(line); + belowHeading = CONTRACT_REVIEW_HEADING_MARKER.test(line) && !/^\s*>/.test(line); continue; } const m = CONTRACT_REVIEW_HEAD_SHA_LINE.exec(line); @@ -33504,7 +33516,17 @@ Doubles as the fire's **write self-check** (step 0). \`201\` is not the reading. t('⛔ record reader: a delta record on head A whose PROSE names head B is NO review of B', latestContractReviewOnHead([cm51(1, DELTA51, T51)], HEAD51), null); t('⭐ record reader: …and IS the review of A — the `Head-sha:` line alone decides', latestContractReviewOnHead([cm51(1, DELTA51, T51)], OLDHEAD51)?.sha, OLDHEAD51); const UNDER51 = (...lines) => ['## Contract review', ...lines].join('\n'); - t('record reader: `contractReviewHeadSha` reads the line\'s span — the first line BELOW the heading wins; bold or backticks on the key, a trailing span and a blockquoted heading are tolerated', [contractReviewHeadSha(DELTA51), contractReviewHeadSha(UNDER51(`**Head-sha:** \`${HEAD51}\` (moved to \`${OLDHEAD51}\`)`, `Head-sha: \`${OLDHEAD51}\``)), contractReviewHeadSha(UNDER51(`\`Head-sha\`: \`${HEAD51}\``)), contractReviewHeadSha(`> ## Contract review\nHead-sha: \`${HEAD51}\``)].join(), [OLDHEAD51, HEAD51, HEAD51, HEAD51].join()); + t('record reader: `contractReviewHeadSha` reads the line\'s span — the first line BELOW the heading wins; bold or backticks on the key, a trailing span and up to three spaces of indent are tolerated', [contractReviewHeadSha(DELTA51), contractReviewHeadSha(UNDER51(`**Head-sha:** \`${HEAD51}\` (moved to \`${OLDHEAD51}\`)`, `Head-sha: \`${OLDHEAD51}\``)), contractReviewHeadSha(UNDER51(`\`Head-sha\`: \`${HEAD51}\``)), contractReviewHeadSha(UNDER51(` Head-sha: \`${HEAD51}\``))].join(), [OLDHEAD51, HEAD51, HEAD51, HEAD51].join()); + // ⛔ A BLOCKQUOTED heading opens no scope: a fully quoted signed record followed by a plain prose line opening + // `Head-sha:` would otherwise lend the quoted signature to the plain line's head (the locator still sees a record here, + // through the marker's `>` tolerance — it then names no head). Measured live: 0 of 709 records quote their heading. + const QUOTED_RECORD51 = `> ## Contract review\n> Head-sha: \`${OLDHEAD51}\`\n> Reviewed-by: \`session_01SEAT\`\n> **VERDICT: PASS**\n\nHead-sha: \`${HEAD51}\` is the head the PR has since moved to.`; + t('⛔ record reader: a QUOTED record followed by a plain line opening `Head-sha:` names no head — a blockquoted heading opens no scope, and nor does a quoted heading alone', [contractReviewHeadSha(QUOTED_RECORD51), contractReviewHeadSha(`> ## Contract review\nHead-sha: \`${HEAD51}\``), latestContractReviewOnHead([cm51(1, QUOTED_RECORD51, T51)], HEAD51)].join('|'), '||'); + // ⛔ An INDENTED CODE BLOCK (four spaces or a tab) is a quotation too: skipped above the record's own line, nothing alone. + t('⛔ record reader: a `Head-sha:` line indented as CODE — four spaces or a tab — above the record\'s own is skipped, and alone names no head', [contractReviewHeadSha(UNDER51(` Head-sha: \`${HEAD51}\``, `Head-sha: \`${OLDHEAD51}\``)), contractReviewHeadSha(UNDER51(`\tHead-sha: \`${HEAD51}\``, `Head-sha: \`${OLDHEAD51}\``)), contractReviewHeadSha(UNDER51(` Head-sha: \`${HEAD51}\``)), contractReviewHeadSha(UNDER51(`\tHead-sha: \`${HEAD51}\``))].join('|'), [OLDHEAD51, OLDHEAD51, '', ''].join('|')); + // ⛔ A fence closes only on its OWN character and a run at least as long: a nested ``` inside ````, a ~~~ inside ```, + // are content, and a `Head-sha:` line between them stays fenced; a longer closing run closes; a never-closed one runs out. + t('⛔ record reader: a nested or mismatched fence does not close the open one — the `Head-sha:` line inside stays fenced; a longer run closes; an unmatched run leaves it open to the end', [contractReviewHeadSha(UNDER51('````', '```', `Head-sha: \`${HEAD51}\``, '```', '````', `Head-sha: \`${OLDHEAD51}\``)), contractReviewHeadSha(UNDER51('```', '~~~', `Head-sha: \`${HEAD51}\``, '```', `Head-sha: \`${OLDHEAD51}\``)), contractReviewHeadSha(UNDER51('```', `Head-sha: \`${HEAD51}\``, '`````', `Head-sha: \`${OLDHEAD51}\``)), contractReviewHeadSha(UNDER51('```', '~~~', `Head-sha: \`${HEAD51}\``, '~~~', `Head-sha: \`${OLDHEAD51}\``))].join('|'), [OLDHEAD51, OLDHEAD51, OLDHEAD51, ''].join('|')); t('⛔ record reader: the key folded INTO the span, a bare hex value, a lowercase key and a sub-floor span name no head', [contractReviewHeadSha(UNDER51(`\`Head-sha: ${HEAD51}\``)), contractReviewHeadSha(UNDER51(`Head-sha: ${HEAD51}`)), contractReviewHeadSha(UNDER51(`head-sha: \`${HEAD51}\``)), contractReviewHeadSha(UNDER51('Head-sha: `ba3d95`'))].join('|'), '|||'); // ⛔ The shapes the review of the line-form reader probed (2026-10-30): a `Head-sha:` line naming ANOTHER head above the // record's own — placed before the heading, blockquoted, bulleted or fenced. Each is a citation, not the record's line, diff --git a/scripts/pm/post-stamped.mjs b/scripts/pm/post-stamped.mjs index 902660a24e2..93a4bcce686 100644 --- a/scripts/pm/post-stamped.mjs +++ b/scripts/pm/post-stamped.mjs @@ -4087,7 +4087,7 @@ const SELF_TEST_BATTERIES = Object.freeze({ 'the two positions: a declaration renders as bare digits, and the patrol reads digits': 24, 'the shared rule: this tool and H56 cannot come to disagree': 6, 'the keyed lines: a claim\'s exact-value fields, judged by the readers that own them': 30, - 'the record line: a `## Contract review` record\'s `Head-sha:` and `Local-runs:` lines, judged when present — an absent line is not re-judged': 17, + 'the record line: a `## Contract review` record\'s `Head-sha:` and `Local-runs:` lines, judged when present — an absent line is not re-judged': 18, 'the vacant body: a VACANT seat post\'s ledger holds pointers, never obligations': 27, 'the decision facets: a decision analysis carries its marker, and a delta, a record or a quotation is not one': 22, 'the relay transport: the same act as one op, the comment found on the board, the exit register kept apart': 13, @@ -5318,6 +5318,7 @@ export function selfTest() { t('⛔ the key folded INTO the span, a bare hex value and a lowercase key are refused — each names no head to every reader', rkeys(SHA_WITH([`\`Head-sha: ${SHA_HEAD}\``])) === 'Head-sha' && rkeys(SHA_WITH([`Head-sha: ${SHA_HEAD}`])) === 'Head-sha' && rkeys(SHA_WITH([`head-sha: \`${SHA_HEAD}\``])) === 'Head-sha'); t('⭐ a quoted line ABOVE the record\'s own passes — the reader skips the quotation and reads the line below it', rkeys(SHA_WITH([`> Head-sha: \`${'f'.repeat(40)}\``, `Head-sha: \`${SHA_HEAD}\``])) === ''); t('⛔ ABSENT is NOT re-judged for this key either: a record with no `Head-sha` key at all passes untouched — the retired-dialect records carry none', rkeys(SHA_WITH([])) === ''); + t('⛔ a fully QUOTED record, and a code-indented (four spaces or tab) line alone, are refused — a quoted heading opens no scope and an indented line is code', rkeys(SHA_WITH([`Head-sha: \`${SHA_HEAD}\``]).split('\n').map((l) => `> ${l}`).join('\n')) === 'Head-sha' && rkeys(SHA_WITH([` Head-sha: \`${SHA_HEAD}\``])) === 'Head-sha' && rkeys(SHA_WITH([`\tHead-sha: \`${SHA_HEAD}\``])) === 'Head-sha'); t('both lines unreadable ⇒ both rows, `Head-sha` first', rkeys(SHA_WITH([`> Head-sha: \`${SHA_HEAD}\``], 'Local-runs: probe')) === 'Head-sha,Local-runs'); t('the `Head-sha` refusal prints the spelling that reads, names the owner, and quotes the offending line', (() => { const rows = recordKeyedLineRefusals(SHA_WITH([`> Head-sha: \`${SHA_HEAD}\``])); const text = recordLineRefusalText(rows); return text.includes(RECORD_KEY_SPELLINGS['Head-sha']) && text.includes('contractReviewHeadSha') && rows[0].line.includes('> Head-sha'); })()); t('⛔ NOT a record: a claim or a report carrying a quoted `Head-sha:` line is untouched — the reader runs on the record\'s heading only', rkeys(`Claim: x\n> Head-sha: \`${SHA_HEAD}\``) === '' && rkeys(`os-dev-report\n\n> Head-sha: \`${SHA_HEAD}\``) === ''); diff --git a/scripts/pm/record-recognisers.mjs b/scripts/pm/record-recognisers.mjs index 004826d6cfe..8f8eb3d85e9 100644 --- a/scripts/pm/record-recognisers.mjs +++ b/scripts/pm/record-recognisers.mjs @@ -2070,7 +2070,7 @@ export function locateReviewOfRecord(pair, carriedHead = null) { // stopped registering. const SELF_TEST_BATTERIES = Object.freeze({ 'the locator: the newest `## Contract review` on this head, on EITHER thread (#17302)': 19, - '#18141 · #27979: the head sha sits in a span of ITS OWN on the `Head-sha:` line — the key-in-span spelling and a span in the prose name no head': 13, + '#18141 · #27979: the head sha sits in a span of ITS OWN on the `Head-sha:` line — the key-in-span spelling and a span in the prose name no head': 14, 'the `Served-tier:` reading — the tier that SERVED the record (#17915 · #18060 · #18174)': 27, '⭐ the 2026-09-20 ruling: a pure-regeneration head move KEEPS the record, decided on the COMMITTED trees': 22, '#18042: the copyable record TEMPLATE — the one machine-read artefact with nothing to copy': 29, @@ -2185,7 +2185,7 @@ export async function selfTest() { `**Director seat adoption record** — the verdict on \`${HEAD_9AF9}\` below is adopted verbatim.\n\n---\n\n## Contract review\n\n- **Reviewed-by:** \`${RECORD_SESSION}\``, ].map((body, i) => ({ id: 3310 + i, created_at: '2026-09-01T08:52:00Z', body })); t('⛔ the RETIRED dialects — the head in the heading (two spellings), on the first body line, in the adoption sentence — read ABSENT: a record names its head on the `Head-sha:` line or not at all', LEGACY.every((row) => locateReviewOfRecord(pairOf({ prComments: [row] })).state === 'absent')); - t('a blockquoted heading still reads — H51 tolerates the `>` a seat writes without meaning it', locateReviewOfRecord(pairOf({ prComments: [{ ...RECORD_ON_9AF9, body: RECORD_ON_9AF9.body.replace(/^## /, '> ## ') }] })).state === 'found'); + t('⛔ #28024: a blockquoted heading is still the record MARKER (H51 tolerates the `>`), but it opens no head scope — the record names no head and reads ABSENT', locateReviewOfRecord(pairOf({ prComments: [{ ...RECORD_ON_9AF9, body: RECORD_ON_9AF9.body.replace(/^## /, '> ## ') }] })).state === 'absent'); t('⛔ a `###` sub-heading is not the marker', locateReviewOfRecord(pairOf({ prComments: [{ ...RECORD_ON_9AF9, body: RECORD_ON_9AF9.body.replace(/^## /, '### ') }] })).state === 'absent'); t('⛔ the heading mentioned inside a paragraph is not the marker', locateReviewOfRecord(pairOf({ prComments: [{ ...RECORD_ON_9AF9, body: RECORD_ON_9AF9.body.replace(/^## /, 'see the ## ') }] })).state === 'absent'); const DEV_REPORT = { id: 3303, created_at: '2026-09-01T08:40:00Z', body: `os-dev-report\n\n\`\`\`json\n{ "issue": 13657, "pr": "https://github.com/o/r/pull/13864", "summary": "landed at \`${HEAD_9AF9}\`" }\n\`\`\`` }; @@ -2241,6 +2241,10 @@ export async function selfTest() { t('⛔ #28024: a blockquoted, bulleted or fenced `Head-sha:` line ABOVE the record\'s own is ABSENT for the head it names — on the PR thread and on the card alike', CITED_LEADS.every((lead) => locateReviewOfRecord(pairOf({ headSha: DELTA_B, prComments: [QUOTED_ABOVE(lead)] })).state === 'absent' && locateReviewOfRecord(pairOf({ headSha: DELTA_B, cardComments: [QUOTED_ABOVE(lead)] })).state === 'absent')); t('⭐ #28024: …and each record is FOUND for the head its OWN line names, with that line\'s full sha', CITED_LEADS.every((lead) => { const r = locateReviewOfRecord(pairOf({ headSha: DELTA_A, prComments: [QUOTED_ABOVE(lead)] })); return r.state === 'found' && r.sha === DELTA_A && r.id === 6516042955; })); const PRE_HEADING = { ...QUOTED_ABOVE([]), body: `Head-sha: \`${DELTA_B}\`\n\n${QUOTED_ABOVE([]).body}` }; + // ⛔ A fully QUOTED signed record followed by a plain prose line opening `Head-sha:`: the quoted heading opens no scope, + // so the plain line is not read against the quoted signature — absent for B, and absent for A (the quoted line is quoted). + const QUOTED_RECORD = { id: 6516042956, created_at: '2026-10-30T05:53:38Z', body: QUOTED_ABOVE([]).body.split('\n').map((l) => `> ${l}`).join('\n') + `\n\nHead-sha: \`${DELTA_B}\` is the head the PR has since moved to.` }; + t('⛔ #28024: a QUOTED record followed by a plain `Head-sha:` prose line is ABSENT for both heads — a blockquoted heading opens no scope', locateReviewOfRecord(pairOf({ headSha: DELTA_B, prComments: [QUOTED_RECORD] })).state === 'absent' && locateReviewOfRecord(pairOf({ headSha: DELTA_A, prComments: [QUOTED_RECORD] })).state === 'absent'); t('⛔ #28024: a `Head-sha:` line BEFORE the `## Contract review` heading is not the record\'s — absent for the head it names, found for the one below the heading', locateReviewOfRecord(pairOf({ headSha: DELTA_B, prComments: [PRE_HEADING] })).state === 'absent' && locateReviewOfRecord(pairOf({ headSha: DELTA_A, prComments: [PRE_HEADING] })).state === 'found'); t('⭐ a correct record beside a refused one is FOUND, in either arrival order', locateReviewOfRecord(pairOf({ prComments: [KEYED_ROW, NEWER_RECORD] })).state === 'found' && locateReviewOfRecord(pairOf({ prComments: [RECORD(HEAD_9AF9, undefined, '2026-09-01T08:45:00Z', 3404), KEYED_ROW] })).state === 'found'); t('…and the locator chooses the CORRECT one — a refused spelling is never chosen over it, and never chosen at all', locateReviewOfRecord(pairOf({ prComments: [KEYED_ROW, NEWER_RECORD] })).id === 3403 && locateReviewOfRecord(pairOf({ prComments: [RECORD(HEAD_9AF9, undefined, '2026-09-01T08:45:00Z', 3404), KEYED_ROW] })).id === 3404);