From 6b65ea136dc86759b04b67423c7c4b7713866057 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 15 Sep 2026 15:31:32 +0000 Subject: [PATCH 1/2] feat(pm): the half-state patrol reads the tier a triage round artefact names MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `.claude/skills/pm-dispatch/SKILL.md` 〈分诊座位职责〉 closes the two-tier rule with a forcing function — 「选层按 fire 时刻,⛔ 不用计数器;简报写明本轮跑的层」 — and nothing read it, so the clause was satisfied by silence. H65 is one report-only row: a triage round artefact (round-open marker, round close, stand-down brief) that carries no `Tier: hourly|daily` line at the start of a line is a half-state. It judges the NAMING only, never which tier was right — the selector stays the seat's and is pinned in the runbook. The row buys ONE comment page per triage seat post, the newest, located from the carrier's own `comments` count: the shared comment cache holds a thread's FIRST page and GitHub serves comments oldest-first, so on the 816-comment seat post that window is 2026-08-06..08-10 and both artefacts this row was written for sit five weeks outside it. The page is judged in place and never cached, so H44's, H56's and H64's corpus is unchanged. Co-authored-by: Claude Claude-Session: https://claude.ai/code/session_01HZfg2AwVX191qCizp88gQr --- scripts/pm/check-half-states.mjs | 545 +++++++++++++++++++++++++++++++ 1 file changed, 545 insertions(+) diff --git a/scripts/pm/check-half-states.mjs b/scripts/pm/check-half-states.mjs index ac93c610dc..38864b3f88 100644 --- a/scripts/pm/check-half-states.mjs +++ b/scripts/pm/check-half-states.mjs @@ -14805,6 +14805,290 @@ export function h64ExposureClause(counts = {}, cap = H64_LOGIN_ROSTER_CAP) { ); } +// --------------------------------------------------------------------------- +// H65 — a TRIAGE round artefact that names no tier (#18302, report-only). +// +// ## The rule, and the clause that had no reader +// +// `.claude/skills/pm-dispatch/SKILL.md` 〈分诊座位职责〉 makes the triage seat's +// inventory two-tiered and closes the rule with a forcing function: 「选层按 +// fire 时刻,⛔ 不用计数器;简报写明本轮跑的层」. The SELECTOR itself is pinned +// one hop away in `.claude/skills/pm-dispatch/references/dispatch-runbook.md` +// 〈分诊两级盘点细则〉 — 「每日层(当日首 fire)= 四仓全量对账 + 归集本就日频的 +// 职责」, the hourly tier reading a `since` window — and neither text is +// ambiguous. What had no reader is the LAST clause. The filing card's grep for +// it across `scripts/pm/` and `.claude/skills/pm-dispatch/`, excluding the two +// declaring files, returned ZERO, with two lit controls on THIS file (131 +// H-rows matched; 180 mentions of the triage seat matched). The measured cost, +// n=1 and stated as such: the seat ran a full-board enumeration on R+235 +// (correct — the day's first fire) and again on R+236 (wrong — a `since` window +// was due), and named the tier in neither artefact. The error was invisible +// until a maintainer asked about something else. +// +// ⛔ This row adds no rule and edits no skill text. A seat that must NAME its +// tier cannot skip CHOOSING one, which is the (a)-shaped hold the same skill +// ranks first — 先删掉容许出错的构造 → 让正确形态成为唯一拼写 → 最后才加检查. +// The construct removed here is the tier-LESS artefact shape, not the rule. +// +// ## What it judges, and what it deliberately does NOT +// +// It reads one thing: does a triage round artefact carry a `Tier:` line. It +// does ⛔ NOT judge whether the tier named was the RIGHT one. The selector is +// the seat's, it is pinned in the runbook, and deciding "was this fire the day's +// first" is a different reading over a different corpus. This row forces the +// NAMING, and the naming is what sends the seat to the selector. +// +// ## The one spelling, and why it is closed +// +// A line beginning `Tier:` followed by `hourly` or `daily`; everything after the +// tier word is free text, so both of these are clean: +// +// Tier: hourly (since 2026-09-15T13:45Z) +// Tier: daily (first fire of the day) +// +// ⛔ Any other word after the key is a FINDING rather than a tolerated dialect, +// and a `Tier:` that is not at the start of a line is a finding too. Both +// refusals are the same call the skill's own remedy order makes: 让正确形态成为 +// 唯一拼写. A lenient reader here would accept `Tier: full` / `Tier: 全量` / +// `**Tier:** hourly` and hand the next seat four spellings of one declaration, +// which is the state the rule exists to leave. The remedy text prints the two +// legal lines verbatim, so the row can only ever be cleared into the spelling +// it names. +// +// ⚠️ A blockquoted `> Tier: daily` is NOT accepted, and that is the deliberate +// half of the line-start rule: a quoted line is this artefact repeating ANOTHER +// one, and letting it clean the carrier would let a marker quoting last round's +// brief satisfy this round's duty. +// +// ## The artefact shapes, measured and named +// +// Four spellings, three of them measured on the triage seat post (#6015) and +// each judged on the comment's HEADLINE — its first non-empty line — because +// that is where all three announce themselves and because a comment that merely +// QUOTES a marker deeper in its body is not itself a round artefact: +// +// a round-open marker `**Round-open marker** · triage seat · `session_…` · +// **R+236** · fire 2026-09-15T14:51Z` (5682407231, +// 5681229485) +// a round close `分诊轮收尾 · R+234 · `session_…` · …` (5659560733) +// a stand-down brief `# 🔻 收班简报 · 分诊席 · `session_…` · **R+220 → +// R+234**` (5659616136) +// a round-open marker H44's `R+ open` spelling, reused BY REFERENCE +// (`H44_ROUND_OPEN_MARKER`) rather than re-spelled. +// +// ⚠️ That last entry is the reason H44 is untouched here. Its regex expects +// `R+ open` and does NOT match the current marker spelling — measured +// against all four bodies above — so widening H44 to reach them would change +// what an existing row reports about a corpus it has read for weeks. Reusing +// the constant costs nothing, keeps ONE spelling of that shape in the file, and +// leaves H44's own population exactly as it was. +// +// ## The window: the NEWEST comment page, bought, and why a cached one lies +// +// ⚠️ The obvious first draft is "read the threads `commentCache` already holds", +// which is what H44/H56/H64 do and what this row was dispatched to do. It was +// measured and abandoned. `commentRowsFor` fetches `?per_page=100` with no +// `page`, and GitHub serves issue comments OLDEST-FIRST: on #6015, 816 comments +// at the reading, that first page is 2026-08-06T16:01:10Z → 2026-08-10T17:29:28Z +// — five weeks stale. Both artefacts this card was filed about (5681229485, +// 5682407231, 2026-09-15) are outside it, while the page itself is ~81 August +// round-closes nobody will ever edit. A row on that window would fire forever on +// archive and never see a live round: a check that cannot observe its subject, +// which is the exact shape this card exists to remove. +// +// So the row BUYS one page — the LAST one — per triage seat post: page +// `ceil(comments / H65_COMMENTS_PAGE_SIZE)` computed from the carrier's own +// `comments` count, so there is no time constant to defend and no `since` +// window that can truncate away the newest rows. ONE request per post per run, +// one such post on this board. +// +// ⛔ The page is judged IN PLACE and never written to `commentCache`. H44's, +// H56's and H64's corpus is whatever the rows above them fetched, and each of +// those headers forbids a later row widening it; `prCommentCache` is the same +// refusal one row over. A reader can check this cheaply: nothing below calls +// `commentRowsFor`, and no `commentCache.set` exists in this pass. +// +// ## Population: the triage seat post, and ⛔ NOT via `seatIsHeld` +// +// `pm:seat` plus a title whose lane half begins `triage` — the execution seats +// have no two-tier rule, so they are out of scope by construction rather than by +// silence. The gate deliberately does NOT reuse `h44NeedsSeatComments`: that one +// requires `seatIsHeld`, which excludes a `🟢 Routine` holder BY NAME, and the +// triage seat is a Routine seat whenever no session has taken it over. Inheriting +// that gate would make this row silent exactly when the seat is running on its +// schedule, which is most of the time. +// +// ## Residuals, declared rather than implied +// +// • An artefact older than the newest page is outside this row. That is the +// patrol's subject stated honestly — the duty is per ROUND, and a brief from +// forty rounds ago is archive — but it means the rows are a LOWER BOUND and +// the clause says so on every run. +// • A page that fails or comes back empty leaves the post UNJUDGED, never +// clean (#4690), and the coverage pair is what says which happened. +// • ⛔ No dated floor. The file's one floor is H22's `PM_SWEEP_CLOSED_FLOOR`, +// which exists for a measured 87%-residue flood on a sibling board and is +// unset by default; it is that row's per-install adaptation, not this file's +// idiom for a new row. Here the newest page IS the bound, and the two live +// tier-less markers are not noise to floor away — they are the finding. +// --------------------------------------------------------------------------- + +/** The page size this row's one purchase uses — H50's, so the file has one. */ +export const H65_COMMENTS_PAGE_SIZE = H50_COMMENTS_PAGE_SIZE; + +/** The two legal tiers. ⛔ A closed set: anything else is a finding. */ +export const H65_TIER_WORDS = Object.freeze(['hourly', 'daily']); + +/** + * The declaration line. `\S*` rather than `\S+` on purpose: a bare `Tier:` with + * nothing after it must report as the wrong WORD (an empty one) rather than + * fall through to the not-at-line-start branch and be described as buried. + */ +export const H65_TIER_LINE = /^[ \t]*Tier:[ \t]*(\S*)/m; + +/** The key on its own, used only to tell "absent" from "present but misplaced". */ +export const H65_TIER_KEY = /Tier:/; + +/** + * The artefact grammar, as data so the self-test can drive every shape by name. + * ⛔ No `g` flag on any of them: a sticky `lastIndex` would make this row's + * answer depend on how many comments preceded it (H44's rule, same reason). + */ +export const H65_ROUND_ARTEFACT_SHAPES = Object.freeze([ + Object.freeze({ kind: 'a round-open marker', re: /Round-open marker/i }), + Object.freeze({ kind: 'a round-open marker', re: H44_ROUND_OPEN_MARKER }), + Object.freeze({ kind: 'a round close', re: /分诊轮收尾/ }), + Object.freeze({ kind: 'a stand-down brief', re: /收班简报/ }), +]); + +/** + * The comment's HEADLINE — its first non-empty line, fences already blanked. + * + * The headline is the whole false-positive control: all three measured shapes + * announce themselves on line one, and a comment that quotes a marker further + * down (a report ABOUT a round, this card's own body) is not a round artefact. + */ +export function h65Headline(body) { + for (const line of h44StripFences(body).split('\n')) { + if (line.trim()) return line; + } + return ''; +} + +/** + * Is this card the TRIAGE seat's post? + * + * `pm:seat` plus a title beginning `[PM seat] triage` — the execution seats have + * no two-tier rule, so they are out of scope BY CONSTRUCTION rather than by + * silence. ⛔ Read off the title directly rather than through `seatLane`, which + * returns a null lane for this seat on purpose (there is no `domain:*` label to + * count an inventory against), and ⛔ not through `h44NeedsSeatComments`, whose + * `seatIsHeld` leg excludes a `🟢 Routine` holder BY NAME — the triage seat is a + * Routine seat whenever no session has taken it over, which is most of the time. + */ +export function h65IsTriageSeatPost(issue) { + if (!labelNames(issue ?? {}).includes('pm:seat')) return false; + return /^\[PM seat\]\s*triage\b/iu.test(String(issue?.title ?? '')); +} + +/** + * Which comment page holds the NEWEST comments, from the carrier's own count. + * + * `counted: false` is the honest third state: with no readable `comments` field + * the newest page cannot be LOCATED, so the caller reads page 1 and must treat a + * FULL page as unjudged rather than clean — `completeCardThread`'s reading of a + * full first page, taken for the same reason one row over. + */ +export function h65NewestPagePlan(issue, pageSize = H65_COMMENTS_PAGE_SIZE) { + const total = Number(issue?.comments); + if (!Number.isFinite(total) || total < 0) return { page: 1, counted: false }; + return { page: Math.max(1, Math.ceil(total / pageSize)), counted: true }; +} + +/** The one request this row buys, spelled where the self-test can pin it. */ +export function h65CommentPagePath(ownerRepo, number, page, pageSize = H65_COMMENTS_PAGE_SIZE) { + return `/repos/${ownerRepo}/issues/${number}/comments?per_page=${pageSize}&page=${page}`; +} + +/** Which round artefact this comment is, or null for anything else. */ +export function h65RoundArtefactShape(body) { + const headline = h65Headline(body); + if (!headline) return null; + for (const shape of H65_ROUND_ARTEFACT_SHAPES) { + if (shape.re.test(headline)) return shape.kind; + } + return null; +} + +/** + * H65's predicate — null when the comment is clean or out of scope, else which + * way the tier declaration is missing. + * + * @param {string} body — the comment body. + * @returns {{ shape: string, reason: string, detail: string|null }|null} + */ +export function h65TierlessRoundArtefact(body) { + const shape = h65RoundArtefactShape(body); + if (!shape) return null; + const text = h44StripFences(body); + const declared = H65_TIER_LINE.exec(text); + if (declared) { + const word = declared[1]; + if (H65_TIER_WORDS.includes(word)) return null; + return { + shape, + reason: 'unknown-tier', + detail: word ? word.slice(0, H44_FRAGMENT_ECHO_CAP) : '(nothing)', + }; + } + if (H65_TIER_KEY.test(text)) return { shape, reason: 'not-at-line-start', detail: null }; + return { shape, reason: 'absent', detail: null }; +} + +/** The two legal lines, printed verbatim in every remedy. */ +export const H65_TIER_EXAMPLES = '`Tier: hourly (since )` or `Tier: daily (first fire of the day)`'; + +/** + * The row. Report-only, and it names the comment so the remedy is an EDIT of a + * known artefact rather than a hunt. + * + * ⚠️ It names the NEWEST offender rather than H44's oldest, and the reason is + * the duty's shape: the tier is a per-ROUND declaration, so the artefact worth + * a reader's attention is the current one. Every offender needs its own edit + * either way, and the count says how many there are. + * + * @param {object} hit — `h65TierlessRoundArtefact`'s result. + * @param {object} comment — the REST comment row (id, created_at). + * @param {number} total — how many artefacts on this carrier carry the defect. + */ +export function h65TierlessRoundArtefactRow(hit, comment, total = 1) { + if (!hit) return null; + const id = String(comment?.id ?? 'an unread id'); + const because = + hit.reason === 'unknown-tier' + ? `carries a \`Tier:\` line naming ${hit.detail === '(nothing)' ? 'NOTHING' : `\`${hit.detail}\``}, which is not one of \`${H65_TIER_WORDS.join('` / `')}\`` + : hit.reason === 'not-at-line-start' + ? 'spells `Tier:` somewhere that is NOT the start of a line — decorated or mid-paragraph, where neither a reader nor this row looks for a declaration' + : 'names NO tier at all'; + const more = + total > 1 + ? ` ${total - 1} further artefact(s) in this window carry the same defect; this is the NEWEST.` + : ''; + return ( + `${hit.shape} (comment \`${id}\`) ${because} — so the two-tier rule at ` + + '`.claude/skills/pm-dispatch/SKILL.md` 〈分诊座位职责〉 (「选层按 fire 时刻,⛔ 不用计数器;' + + '简报写明本轮跑的层」) is satisfied by SILENCE here. The clause is the rule\'s forcing ' + + 'function: a seat that must NAME its tier cannot skip choosing one, and the one measured ' + + 'instance of the skipped choice was a full-board enumeration on a fire that owed a `since` ' + + `window. Remedy: one line, at the start of a line — ${H65_TIER_EXAMPLES} — with the selector ` + + 'read from `.claude/skills/pm-dispatch/references/dispatch-runbook.md` 〈分诊两级盘点细则〉 ' + + '(「每日层(当日首 fire)= 四仓全量对账 + 归集本就日频的职责」). ⛔ This row does NOT judge ' + + 'whether the tier chosen was the RIGHT one — that reading is the seat\'s and is pinned in the ' + + 'runbook; this one only refuses the silence. Report-only: patrol input, never a gate verdict.' + + more + ); +} + // --------------------------------------------------------------------------- // Report rendering — pure over (findings, counts), so `--self-test` pins both // media offline. The live sweep below picks a renderer and prints it; nothing @@ -15157,6 +15441,21 @@ export const SWEEP_COUNT_KEYS = [ 'seatSignedUserPat', 'seatSignedUserUnreadChannel', 'seatSignedUnreadAuthor', + // H65's coverage set (#18302). `roundTierPosts`/`roundTierRead` is the + // ordinary pair — how many triage seat posts the row could speak about and how + // many had their newest comment page in hand — and it matters more here than + // for a cache-reading row, because this one BUYS that page: a failed request + // is the whole population unjudged, and `0 carrier(s) filed` would render + // identically to a seat that named its tier every round. `roundTierComments` + // is how many comments were on those pages and `roundTierArtefacts` how many + // of them were round artefacts at all — the pair that separates "the seat is + // naming its tier" from "this window held no round". `roundTierRows` is what + // was FILED, so a quiet run and a row-less one stay distinguishable. + 'roundTierPosts', + 'roundTierRead', + 'roundTierComments', + 'roundTierArtefacts', + 'roundTierRows', 'refBeyond', ]; @@ -15647,6 +15946,21 @@ export function summaryLine(counts, findingCount) { 'count is a lower bound. It fetches NOTHING of its own: the corpus is the open card and PR ' + 'bodies in hand plus the card threads H44 and H56 read, so a PULL-REQUEST comment thread is outside ' + 'it by construction and these numbers are a LOWER BOUND. ' + + // H65's coverage set (#18302). UNCONDITIONAL like every other window's, and + // it is the only place three things are visible: that this row BUYS a page + // rather than reading the shared cache, WHY (that cache holds a thread's + // oldest page), and that an artefact older than the bought page is outside + // the row by construction. + `Triage round tiers (H65): ${counts.roundTierArtefacts ?? 0} round artefact(s) judged on the ` + + `NEWEST comment page of ${counts.roundTierRead ?? 0} of ${counts.roundTierPosts ?? 0} triage seat ` + + `post(s) (${counts.roundTierComments ?? 0} comment(s) on those pages), ` + + `${counts.roundTierRows ?? 0} carrier(s) filed. It BUYS that page — ONE request per post, the page ` + + "computed from the carrier's own `comments` count — because the shared comment cache holds a " + + 'thread\'s FIRST page and GitHub serves comments OLDEST-FIRST: on an 800-comment seat thread that ' + + 'window is five weeks stale, so a cache-only read would judge artefacts nobody will edit and never ' + + 'see the current round. The page is judged in place and never cached, so no other row\'s corpus ' + + 'moves. An artefact OLDER than that page is outside this row by construction and a post whose page ' + + 'could not be read is UNJUDGED rather than clean, so the rows are a LOWER BOUND. ' + `Report-only: findings are patrol input, not a gate verdict.` ); } @@ -15710,6 +16024,7 @@ export const SUMMARY_CLAUSE_ANCHORS = [ ['h58RulingMarkers', 'Queued ruling markers (H58): '], ['h59Linkage', 'Merged-PR closing linkage (H59): '], ['h64SeatSigned', 'Unattributed seat content (H64): '], + ['h65RoundTier', 'Triage round tiers (H65): '], ['reportOnly', 'Report-only: '], ]; @@ -16350,6 +16665,29 @@ export const HALF_STATE_FAMILY_BAND = Object.freeze({ // contradict each other, the repair a re-post on the board. H64: 'state', + // H65 is a `state` (#18302), and the three refusals are each taken on the + // refused band's OWN criterion rather than on this subject's vocabulary. + // + // ⛔ NOT `gate`: that band exists for the row that can tell a STRIPPED gate + // from an ungated card — an ABSENCE reading as a green light on a check that + // decides whether something may LAND. Nothing here decides a landing, and + // reading `gate` off the second half alone would make the band mean 「anything + // protective」, which is H57's refusal taken for H57's reason. + // ⛔ NOT `stall`: nothing is stopped. The round ran, the board was read, the + // cards were graded; what is missing is the declaration that would have sent + // the seat to the selector. Whether a tier-less round DELAYS anything is + // unmeasured by this row and it claims no such thing. + // ⛔ NOT `inventory`: it alarms about ONE artefact on one carrier. The + // population reading — how many artefacts were judged, and the window they + // came from — is a summary clause and takes no band at all (H39's shape). + // + // What is left is `state`'s criterion exactly: a LIVE artefact whose face is + // half-written against a shape it owes, the repair one edit on the board by + // the seat that wrote it. And it is H44's and H56's band, the two rows this + // one sits beside — one reads a seat artefact whose reading cannot be dated, + // one whose date is wrong, and this one whose tier is unnamed. + H65: 'state', + // H57 is a `stall` (#17132), and the three refusals are each taken on the // refused band's own criterion rather than on this subject's vocabulary — // which is how every band call since H51 has been argued. @@ -21160,6 +21498,72 @@ async function sweepInto(findings, seen, seenPrs, seenMerged, seenUnscoped, seen ]); } + // H65 (#18302) — the triage round artefact that names no tier. At the FOOT + // beside H53 and H59, and for their reason: this row BUYS a page, so it is + // placed where every free read is already done and the purchase is provably + // the smallest thing left to buy. + // + // ⛔ It writes NOTHING into `commentCache`. H44, H56 and H64 have finished + // judging that map by the time this runs, and each of their headers forbids a + // later row widening the corpus they reported on; `prCommentCache` is the same + // refusal one row over. The page is read, judged and dropped. + // + // ⚠️ It must also not be moved ABOVE those rows on the theory that it is + // another seat-post reader: the cache it would have to share is exactly the + // one measured wrong for this subject — a thread's FIRST page, which GitHub + // serves oldest-first — and the whole argument for buying a page is in this + // row's header. + const h65Posts = new Map(); + for (const [number, issue] of seenUnscoped) { + if (h65IsTriageSeatPost(issue)) h65Posts.set(number, issue); + } + for (const [number, issue] of seen) { + if (h65IsTriageSeatPost(issue)) h65Posts.set(number, issue); + } + for (const issue of h65Posts.values()) { + stats.roundTierPosts = (stats.roundTierPosts ?? 0) + 1; + const plan = h65NewestPagePlan(issue); + let rows; + try { + rows = await rest(h65CommentPagePath(OWNER_REPO, issue.number, plan.page)); + } catch { + // ⛔ No retry on any status (#17374). An unread page leaves the post + // UNJUDGED and the coverage pair is the only thing that says so. + continue; + } + if (!Array.isArray(rows) || rows.length === 0) continue; + // A page reached WITHOUT a readable comment count may not be the last one, + // and a full page is exactly the shape that hides what follows it. + if (!plan.counted && rows.length >= H65_COMMENTS_PAGE_SIZE) continue; + stats.roundTierRead = (stats.roundTierRead ?? 0) + 1; + stats.roundTierComments = (stats.roundTierComments ?? 0) + rows.length; + let newest = null; + let total = 0; + rows.forEach((row, index) => { + const body = row?.body ?? ''; + if (!h65RoundArtefactShape(body)) return; + stats.roundTierArtefacts = (stats.roundTierArtefacts ?? 0) + 1; + const hit = h65TierlessRoundArtefact(body); + if (!hit) return; + total += 1; + // Newest wins, with a THREAD-ORDER fallback for an unreadable stamp — + // `latestSeatMarker`'s resolution, so an unparseable `created_at` can + // never silently promote an older artefact (#4690). + const stamp = Date.parse(row?.created_at ?? ''); + const at = Number.isFinite(stamp) ? stamp : null; + if ( + newest === null || + (at === null || newest.at === null ? index > newest.index : at >= newest.at) + ) { + newest = { hit, row, at, index }; + } + }); + if (newest) { + stats.roundTierRows = (stats.roundTierRows ?? 0) + 1; + findings.push([issue, 'H65', h65TierlessRoundArtefactRow(newest.hit, newest.row, total)]); + } + } + // H45's second half — the `pm:epic` index, read as its own population. // // LAST in the sweep, and that placement is the mechanism rather than a @@ -30414,6 +30818,147 @@ Mutual exclusion: \`get_comments\` page 747 → \`[]\`, page 746 = my own R+117 t('H59 adjacency: …and silent on the false-OPEN body too — no `Part of` at all', h7PartOfWithClosingKeyword(pr59FalseOpen), null); t('H59 adjacency: the one grammar is shared with H7/H8/H23 rather than re-spelled', closingKeywordTargets(pr59FalseOpen.body).has('1792'), true); + // -- H65 — a TRIAGE round artefact that names no tier (#18302) ------------- + // + // ⛔ The self-test never touches GitHub. Every fixture below is an OFFLINE + // body whose HEADLINE is copied verbatim from an artefact measured on the + // triage seat post (#6015): the two tier-less round-open markers this card was + // filed about (5682407231 R+236, 5681229485 R+235), the round close + // (5659560733 R+234) and the stand-down brief (5659616136, R+220 → R+234). + // None of the four carries a tier line — they ARE the finding. + const MARKER65 = + '**Round-open marker** · triage seat · `session_01VxjMEAhT53WHUCtP9WMrSU` · **R+236** · fire 2026-09-15T14:51Z · **objectui 轮**\n\n' + + 'Doubles as the fire\'s **write self-check** (step 0). `201` is not the reading.'; + const CLOSE65 = + '分诊轮收尾 · R+234 · `session_01PAMZt3owWHe7CMyTzrDkwF` · 2026-09-14T06:0xZ · **objectui 轮**\n\n' + + '## 第 0 步 写入自检 ✅'; + const BRIEF65 = + '# 🔻 收班简报 · 分诊席 · `session_01PAMZt3owWHe7CMyTzrDkwF` · **R+220 → R+234**\n\n' + + '**2026-09-13T15:33Z 维护者裁「接管座位,开跑」就座。**'; + const withTier65 = (body, line) => { + const [head, ...rest] = body.split('\n\n'); + return [head, line, ...rest].join('\n\n'); + }; + const seat65 = (title, labels = ['pm:seat'], extra = {}) => ({ + ...issue(labels, [], '', title), + number: 6015, + comments: 816, + ...extra, + }); + const TRIAGE65 = seat65('[PM seat] triage (objectstack-wide) — 🟢 `session_01VxjMEAhT53WHUCtP9WMrSU` · R+236'); + const h65 = (body) => h65TierlessRoundArtefact(body); + // ⚠️ Through a wrapper, like every other three-valued predicate here: a bare + // `h65(body).reason` would throw while evaluating `t()`'s ARGUMENTS the moment + // the predicate goes clean, aborting the suite instead of reporting a case. + // ⛔ And never `typeof … === 'object'` for the fires direction — `typeof null` + // is `'object'` too, so that spelling passes on exactly the regression it is + // written to catch. + const h65reason = (body) => String(h65(body)?.reason ?? 'clean'); + const h65shape = (body) => String(h65(body)?.shape ?? 'clean'); + const h65row = (body, comment = { id: 5682407231 }, total = 1) => + String(h65TierlessRoundArtefactRow(h65(body), comment, total) ?? ''); + + // ⭐ The three measured shapes, tier-less — the filing card's own specimens. + t('H65 fires: the measured round-open marker names no tier', h65reason(MARKER65), 'absent'); + t('H65 fires: …named as a round-open marker', h65shape(MARKER65), 'a round-open marker'); + t('H65 fires: the measured round close likewise', h65reason(CLOSE65), 'absent'); + t('H65 fires: …named as a round close', h65shape(CLOSE65), 'a round close'); + t('H65 fires: the measured stand-down brief likewise', h65reason(BRIEF65), 'absent'); + t('H65 fires: …named as a stand-down brief', h65shape(BRIEF65), 'a stand-down brief'); + + // ⭐ The one spelling clears every one of them. + t('H65 clean: the marker with `Tier: hourly …`', h65(withTier65(MARKER65, 'Tier: hourly (since 2026-09-15T13:45Z)')), null); + t('H65 clean: …with `Tier: daily …`', h65(withTier65(MARKER65, 'Tier: daily (first fire of the day)')), null); + t('H65 clean: the round close with a tier line', h65(withTier65(CLOSE65, 'Tier: hourly (since 2026-09-14T05:00Z)')), null); + t('H65 clean: the stand-down brief with a tier line', h65(withTier65(BRIEF65, 'Tier: daily (first fire of the day)')), null); + t('H65 clean: free text after the tier word is allowed — only the word is closed', h65(withTier65(MARKER65, 'Tier: hourly — anchor is the last brief on this thread')), null); + t('H65 clean: a bare `Tier: daily` with nothing after it', h65(withTier65(MARKER65, 'Tier: daily')), null); + t('H65 clean: leading indentation does not break the line-start rule', h65(withTier65(MARKER65, ' Tier: daily (first fire of the day)')), null); + + // ⭐ The spelling is CLOSED — a dialect is a finding, not a tolerated variant. + t('H65 fires: `Tier:` with another word', h65reason(withTier65(MARKER65, 'Tier: full')), 'unknown-tier'); + t('H65 fires: …and the row echoes the word it refused', h65row(withTier65(MARKER65, 'Tier: full')).includes('`full`'), true); + t('H65 fires: a Chinese spelling of the tier is a dialect too', h65reason(withTier65(MARKER65, 'Tier: 全量')), 'unknown-tier'); + t('H65 fires: a capitalised tier word is not the spelling', h65reason(withTier65(MARKER65, 'Tier: Daily')), 'unknown-tier'); + t('H65 fires: …and a bare `Tier:` naming nothing reports as naming nothing', h65row(withTier65(MARKER65, 'Tier:')).includes('naming NOTHING'), true); + + // ⭐ The LINE-START rule, both halves. + t('H65 fires: a `Tier:` buried mid-paragraph is not a declaration', h65reason(MARKER65 + '\n\nThe round ran with Tier: daily, as it happens.'), 'not-at-line-start'); + t('H65 fires: …a bold-decorated `**Tier:** daily` is the same defect', h65reason(withTier65(MARKER65, '**Tier:** daily')), 'not-at-line-start'); + t('H65 fires: …and a BLOCKQUOTED tier line is this artefact quoting another', h65reason(withTier65(MARKER65, '> Tier: daily (first fire of the day)')), 'not-at-line-start'); + t('H65 fires: …the row says where the line belongs rather than only that it is wrong', h65row(MARKER65 + '\n\nRan with Tier: daily.').includes('NOT the start of a line'), true); + + // ⭐ Out of scope — the two ways this row stays silent. + t('H65 scope: an ordinary comment on the triage post is not a round artefact', h65('Retriage answer for #18042: the grading stands.'), null); + t('H65 scope: …nor is a comment that QUOTES a marker further down', h65('A note about last round.\n\n**Round-open marker** · triage seat · **R+235**'), null); + t('H65 scope: a tier line is not owed by a comment that is not a round artefact', h65('Dispatch order for #18302.'), null); + t('H65 scope: the triage seat post IS the population', h65IsTriageSeatPost(TRIAGE65), true); + t('H65 scope: ⛔ a `domain:*` seat post is NOT — the execution seats have no two-tier rule', h65IsTriageSeatPost(seat65('[PM seat] domain:spec — 🟢 os-elon')), false); + t('H65 scope: ⛔ nor the skills seat', h65IsTriageSeatPost(seat65('[PM seat] skills — 🟢 os-warren (session_x)')), false); + t('H65 scope: ⛔ nor a `repo:*` seat', h65IsTriageSeatPost(seat65('[PM seat] repo:cloud — 🟢 os-zhuang')), false); + t('H65 scope: an ordinary card carrying no `pm:seat` is out', h65IsTriageSeatPost(seat65('[PM seat] triage (objectstack-wide) — 🟢 Routine', ['pm:queue'])), false); + t('H65 scope: a missing card does not crash', h65IsTriageSeatPost(undefined), false); + t('H65 scope: ⭐ a `🟢 Routine` triage seat IS in population — the seat runs as a Routine', h65IsTriageSeatPost(seat65('[PM seat] triage (objectstack-wide) — 🟢 Routine')), true); + t('H65 scope: …and that is exactly what `seatIsHeld` would have excluded by name', seatIsHeld(seat65('[PM seat] triage (objectstack-wide) — 🟢 Routine')), false); + t('H65 scope: …so inheriting H44\'s seat gate would have silenced this row', h44NeedsSeatComments(seat65('[PM seat] triage (objectstack-wide) — 🟢 Routine')), false); + + // ⭐ H44 is UNTOUCHED, and the measured reason this row could not lean on it. + t('H65 adjacency: ⛔ H44\'s round-open regex does NOT match the current marker spelling', H44_ROUND_OPEN_MARKER.test(MARKER65), false); + t('H65 adjacency: …and it still matches the `R+ open` spelling it owns', H44_ROUND_OPEN_MARKER.test('R+118 open'), true); + t('H65 adjacency: this row reads that spelling too, BY REFERENCE rather than by re-spelling it', h65RoundArtefactShape('R+118 open · triage seat'), 'a round-open marker'); + t('H65 adjacency: H44 is silent on a tier-less marker that carries its stamp — a different reading', h44UntimestampedReading(MARKER65, true), null); + t('H65 adjacency: …and this row is silent on H44\'s subject when the tier IS named', h65(withTier65('R+118 open\n\n92 open cards', 'Tier: daily')), null); + + // The window: ONE bought page, located from the carrier's own count. + t('H65 window: the newest page of an 816-comment thread is page 9', h65NewestPagePlan(TRIAGE65).page, 9); + t('H65 window: …and it is a COUNTED plan, so a full page is the real last one', h65NewestPagePlan(TRIAGE65).counted, true); + t('H65 window: a thread inside one page reads page 1', h65NewestPagePlan(seat65('[PM seat] triage — 🟢 Routine', ['pm:seat'], { comments: 40 })).page, 1); + t('H65 window: an exactly-full first page is still page 1', h65NewestPagePlan(seat65('[PM seat] triage — 🟢 Routine', ['pm:seat'], { comments: 100 })).page, 1); + t('H65 window: 101 comments move it to page 2', h65NewestPagePlan(seat65('[PM seat] triage — 🟢 Routine', ['pm:seat'], { comments: 101 })).page, 2); + t('H65 window: an empty thread still names a page rather than crashing', h65NewestPagePlan(seat65('[PM seat] triage — 🟢 Routine', ['pm:seat'], { comments: 0 })).page, 1); + t('H65 window: ⛔ an unreadable count is NOT counted — the caller must treat a full page as unjudged', h65NewestPagePlan(seat65('[PM seat] triage — 🟢 Routine', ['pm:seat'], { comments: undefined })).counted, false); + t('H65 window: the request is the last page at the shared page size', h65CommentPagePath('o/r', 6015, 9), '/repos/o/r/issues/6015/comments?per_page=100&page=9'); + t('H65 window: the page size is H50\'s, so the file has ONE', H65_COMMENTS_PAGE_SIZE, H50_COMMENTS_PAGE_SIZE); + + // The sentence carries its own contract. + t('H65 row: it names the comment, so the remedy is an edit rather than a hunt', h65row(MARKER65).includes('`5682407231`'), true); + t('H65 row: …quotes the clause it patrols', h65row(MARKER65).includes('简报写明本轮跑的层'), true); + t('H65 row: …says the rule is satisfied by SILENCE without it', h65row(MARKER65).includes('satisfied by SILENCE'), true); + t('H65 row: …prints BOTH legal spellings verbatim', h65row(MARKER65).includes('Tier: hourly') && h65row(MARKER65).includes('Tier: daily (first fire of the day)'), true); + t('H65 row: …points at the selector rather than re-deciding it', h65row(MARKER65).includes('每日层(当日首 fire)'), true); + t('H65 row: ⛔ …and refuses to judge WHICH tier was right', h65row(MARKER65).includes('does NOT judge'), true); + t('H65 row: a lone offender claims no others', h65row(MARKER65).includes('further artefact(s)'), false); + t('H65 row: …and a carrier with more says so, naming this one as the NEWEST', h65row(MARKER65, { id: 1 }, 3).includes('2 further artefact(s) in this window carry the same defect; this is the NEWEST.'), true); + t('H65 row: an unread comment id is named rather than faked', h65row(MARKER65, {}).includes('an unread id'), true); + t('H65 row: a clean artefact renders no row at all', h65TierlessRoundArtefactRow(null, { id: 1 }), null); + t('H65 row: not a loud finding', isLoudFinding(h65row(MARKER65)), false); + t('H65 row: report-only is on the face of it', h65row(MARKER65).includes('never a gate verdict'), true); + + // Band, counters and the clause. + t('H65 band: registered as a STATE row — the repair is one edit on the board', familyBand('H65'), 'state'); + t('H65 band: ⛔ NOT `gate` — nothing here decides whether something may land', familyBand('H65') === 'gate', false); + t('H65 band: ⛔ NOT `stall` — the round ran; the declaration is what is missing', familyBand('H65') === 'stall', false); + t('H65 band: ⛔ NOT `inventory` — it alarms about ONE artefact; the population is a clause', familyBand('H65') === 'inventory', false); + t('H65 band: …and it is H44\'s and H56\'s band, the two rows it sits beside', familyBand('H65') === familyBand('H44') && familyBand('H65') === familyBand('H56'), true); + t('H65 band: the sweep really pushes it, so the registry sees it', familyRegistryCoverage().emitted.includes('H65'), true); + t('H65 band: no code is left unregistered by this change', familyRegistryCoverage().missing.length, 0); + t('H65 band: …and no band names a family the sweep never emits', familyRegistryCoverage().extra.length, 0); + t('H65 band: the registry still fits inside the ledger ROW CAP', Object.keys(HALF_STATE_FAMILY_BAND).length <= FAMILY_LEDGER_ROW_CAP, true); + t('H65 band: a gate row still outranks it, so H31/H35 survive the trim longer', familyRank('H31') < familyRank('H65'), true); + t('H65: all five count keys ride the enumerated forwarding contract', ['roundTierPosts', 'roundTierRead', 'roundTierComments', 'roundTierArtefacts', 'roundTierRows'].every((k) => SWEEP_COUNT_KEYS.includes(k)), true); + const SUM65 = saidBy('h65RoundTier', summaryLine({ roundTierPosts: 1, roundTierRead: 1, roundTierComments: 16, roundTierArtefacts: 4, roundTierRows: 1 }, 0)); + t('H65 summary: the coverage pair is reported', SUM65.includes('NEWEST comment page of 1 of 1 triage seat post(s)'), true); + t('H65 summary: …with what was on those pages', SUM65.includes('16 comment(s) on those pages'), true); + t('H65 summary: …how many were round artefacts at all', SUM65.includes('4 round artefact(s) judged'), true); + t('H65 summary: …and how many carriers were filed', SUM65.includes('1 carrier(s) filed'), true); + t('H65 summary: it says the page is BOUGHT rather than read off the cache', SUM65.includes('It BUYS that page'), true); + t('H65 summary: …and WHY — the shared cache holds the OLDEST page', saidBy('h65RoundTier', summaryLine({}, 0)).includes('OLDEST-FIRST'), true); + t('H65 summary: …that no other row\'s corpus moves', saidBy('h65RoundTier', summaryLine({}, 0)).includes('never cached'), true); + t('H65 summary: …that an older artefact is outside the row', saidBy('h65RoundTier', summaryLine({}, 0)).includes('OLDER than that page is outside this row'), true); + t('H65 summary: …and that an unread page is UNJUDGED, never clean', saidBy('h65RoundTier', summaryLine({}, 0)).includes('UNJUDGED rather than clean'), true); + t('H65 summary: the clause is rendered on EVERY run, not just interesting ones', saidBy('h65RoundTier', summaryLine({}, 0)).includes('0 of 0'), true); + t('H65 summary: a bare line renders numbers, never `undefined`', saidBy('h65RoundTier', summaryLine({}, 0)).includes('undefined'), false); + // -- The `[::]` collapse (#12090): behaviour-preserving, asserted as such --- // The class held U+003A TWICE, never the fullwidth U+FF1A its shape implied. // These cases pin that the collapse changed nothing a reader could observe. From 8e48a84a9016a0379b6d6214976843cd9d0bda12 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 15 Sep 2026 15:40:34 +0000 Subject: [PATCH 2/2] fix(pm): H65's remedy carries a real stamp, not a bracket-shaped placeholder MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The row's sentence is rendered into a pinned issue body by the patrol workflow, and GitHub mutates body bytes around angle-bracket-shaped fragments — a remedy whose own example is eaten teaches nothing. Co-authored-by: Claude Claude-Session: https://claude.ai/code/session_01HZfg2AwVX191qCizp88gQr --- scripts/pm/check-half-states.mjs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/scripts/pm/check-half-states.mjs b/scripts/pm/check-half-states.mjs index 38864b3f88..4b17cafb80 100644 --- a/scripts/pm/check-half-states.mjs +++ b/scripts/pm/check-half-states.mjs @@ -15045,8 +15045,17 @@ export function h65TierlessRoundArtefact(body) { return { shape, reason: 'absent', detail: null }; } -/** The two legal lines, printed verbatim in every remedy. */ -export const H65_TIER_EXAMPLES = '`Tier: hourly (since )` or `Tier: daily (first fire of the day)`'; +/** + * The two legal lines, printed verbatim in every remedy. + * + * ⛔ The hourly example carries a REAL stamp rather than an angle-bracket + * placeholder, and that is not a style choice: this sentence is rendered into a + * pinned ISSUE BODY by the patrol workflow, and GitHub mutates body bytes around + * angle-bracket-shaped fragments (AGENTS.md, 「spell poison-shaped tokens out in + * words」). A remedy that arrives with its own example eaten teaches nothing. + */ +export const H65_TIER_EXAMPLES = + '`Tier: hourly (since 2026-09-15T13:45Z)` or `Tier: daily (first fire of the day)`'; /** * The row. Report-only, and it names the comment so the remedy is an EDIT of a