From d41e862b0b67396af38b87c84982f7d96a4813ca Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 19 Aug 2026 04:12:12 +0000 Subject: [PATCH] feat(pm): H13 age-alarm for the domain-without-pm-state half-state; heal-backlog oldest-first discipline MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - scripts/pm/check-half-states.mjs: new H13 detector — a card carrying domain:* with no pm-state label, aged past one sweep cycle (2h; the triage Routine fires hourly), is reported as a defect of the healing loop, not inventory. Louder line when the card's own title/body self-declares P0/data-integrity: the emergency-triage channel is the mandated move. Needs the sweep's only unscoped listing — the shape is invisible to every label-scoped page by construction (it was not detected at all before this change). Report-only, exit conventions unchanged; 22 new self-test cases. - dispatch-runbook.md: 半状态治愈积压排序 — sweep half-state disjuncts ②③ process oldest-first, P0-suspect first; 优先最新 applies to fresh intake only. Paid at the 223-line ceiling by deduplicating text restated from SKILL.md/os-dev (replay-identity triple restatement, claim-spelling restatement, principle restatements, self-referential section enumeration). - SKILL.md: sweep line now scopes 优先最新 to fresh intake; 状态变更不 过夜 gains the waiting-is-a-state clause (who is awaited, since when, never in session memory; a P0-suspect wait mandates the emergency channel). Net-zero lines, paid by pointing two restated details at their runbook homes. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01AeA3nU1B5Q2pgxqxgUrexd --- .claude/skills/pm-dispatch/SKILL.md | 10 +- .../references/dispatch-runbook.md | 46 ++--- scripts/pm/check-half-states.mjs | 184 +++++++++++++++++- 3 files changed, 207 insertions(+), 33 deletions(-) diff --git a/.claude/skills/pm-dispatch/SKILL.md b/.claude/skills/pm-dispatch/SKILL.md index 0261999aa7..9a7c92432d 100644 --- a/.claude/skills/pm-dispatch/SKILL.md +++ b/.claude/skills/pm-dispatch/SKILL.md @@ -97,11 +97,11 @@ references);② 创建后手动 fire 一轮烟测,判据取**GitHub 上的产出 件**(维护者 2026-08-11 接受):hold 评论写触发文件清单,座位贴设「派发前必查」段,派发卡文件面与清单相交时点名该单、顺手活列为申报过的增项。 - **`Blocked-by:` 行是机器可 grep 的反向索引**,一遍读喂三个职责:上游关单时放回被解锁的、按解 锁扇出排序选择、**在合并后的 ref 上重验每张回队卡的文件面**(⛔ 只做第一件)。**一个标签存在 - ,当且仅当有具名读者**;2026-08-11 的「⛔ 不落存储标签」已被维护者 2026-08-13 意见取代(原话 - :「被依赖的卡片是不是应该通过label标注提高优先级」):`pm:blocking` = 分诊 sweep 自该索引推导写 + ,当且仅当有具名读者**;2026-08-11 的「⛔ 不落存储标签」已被维护者 2026-08-13 意见取代(原 + 话见 runbook 全序节):`pm:blocking` = 分诊 sweep 自该索引推导写 入/摘除的**缓存**,⛔ 不手工挂 —— 变的是缓存位置,推导本性与具名读者不变量未变(读者:选择优先级全序、列表页扫描)。 - **状态变更不过夜**:标签挂了评论没跟上、assignee 设了没认领评论、结论只在 chat ——都是半状态 - ,结束会话(含限流悬挂)前补齐成对或回滚半边(report-only 巡查见机械守卫索引)。**代执行他人指令 + ,结束会话(含限流悬挂)前补齐成对或回滚半边(report-only 巡查见机械守卫索引)。**等待他座位也是状态,写在卡上才存在**(等谁、自何时,⛔ 不留在会话记忆);P0 嫌疑的等待 ⇒ 紧急直接分诊通道是义务不是选项(维护者 2026-08-19)。**代执行他人指令 的关闭/作废,评论带出处三件(谁的指令、原话、在哪说的)** —— 无出处的关闭与误操作在证据上不可区 分,会被兄弟席当误扫重开;同理适用于摘标签、回收认领等不可反推理由的动作。 - **写后回读按风险定向;标签写恒为硬步骤 read-modify-write + 回读**(维护者 2026-08-18 裁定):① 先取现集 → ② 只增删目标标签 → ③ 写合并集 → ④ 写后回读核验 —— 整组 PUT 写的是本席快照,并发席落在你读与写之间的标签被静默剥掉,受害者常是你没打算碰的那枚;**承载闸门语义的标签**(如 `needs:contract-review`)挂与清两向同此四步,闸门被剥不是红灯是放行,「被剥」与「从未挂过」在证据上不可区分,回读是唯一察觉手段; @@ -258,7 +258,7 @@ of record)。**读侧闭环(维护者 2026-08-12 批准):接管与巡检只读 仓没有车道标签,这形状是它们队列卡的常态);③ 有 `domain:*` 无 pm-state。②③ 只取 `updated_at` 早于 ~2 分钟的卡且不是可选项 —— 半标注卡是协议自己按设计生产的(一次标签写入即把老卡打成半标 注),只带路由或状态机其一的卡对两个视图同时不可见。排除:`tracking`、`status:parked`(其正常形 -状恰是「带域标签无 pm-state」)、全部 `pm:seat` 贴、`qa-run` 记录(协议载体非工作);存量大时每轮限量、优先最新。**紧急卡直接分诊** +状恰是「带域标签无 pm-state」)、全部 `pm:seat` 贴、`qa-run` 记录(协议载体非工作);存量大时每轮限量 —— 新入卡①优先最新,半状态治愈积压②③**最老优先、P0 嫌疑先行**,⛔ 优先最新不适用(细则见 runbook)。**紧急卡直接分诊** (维护者 2026-08-13):维护者点名或 p0 嫌疑 ⇒ 立即起 `claude-fable-5` 分诊子代理,不等 Routine 班 次;授权面 = 分诊本身(定级/路由/标签/既有评论格式),⛔ 不写码不认领;产出落卡,与 Routine 分诊同格式同效力(细则见 `references/dispatch-runbook.md`)。 **跨仓 pin 链的窗口级兜底也在本 sweep**(联动单第一产者仍是接受座位;⛔ 只立单不执行 bump): @@ -467,7 +467,7 @@ issue 评论;④ draft PR 一存在立即 `subscribe_pr_activity` 硬步骤)全 - **停摆永不自愈**:携带任务中途状态的完成通知本身就是停摆信号,立刻 SendMessage 附前台执行姿 态句,⛔ 不等任何静默阈值(阈值是给「没有回答」的);复位走梯度,每次比上一次更具体,第三次停摆 判 unreliable 按接手协议重派。这里是消费侧兜底,⛔ 不能写成「派发词写全了就可以不探」。 -- **通知重放先算身份再读内容**:去重三元组 `(issue, 分支, PR head sha)`,与已验收那份相同 ⇒ 记 +- **通知重放先算身份再读内容**:与已验收那份同身份(三元组定义见 runbook)⇒ 记 「重放」即结束,⛔ 不重新验收不重复 ACCEPT;⛔ 到达不读作「还活着」,不到达也不读作「已死」。 - **直接验收兜底(报告丢失 ≠ 验收停摆)**:(a) draft PR 在且 CI 全绿 + (b) 探活确认已死或 ≥2h 无 推送 + (c) 报告未达 ⇒ 直接按 PR 验收(复核判据不减,细则见 runbook);先探活后翻 ready;舰队级 diff --git a/.claude/skills/pm-dispatch/references/dispatch-runbook.md b/.claude/skills/pm-dispatch/references/dispatch-runbook.md index 3c3e476d3a..b1c079e159 100644 --- a/.claude/skills/pm-dispatch/references/dispatch-runbook.md +++ b/.claude/skills/pm-dispatch/references/dispatch-runbook.md @@ -1,8 +1,7 @@ # 派发与收集运行细则(references —— 按需加载) -出处:主文件。原则与条款主文住主文件;本表只放细则增量 —— 原话、参数、原文模板、 -执行步骤 —— 在选卡、分诊、落卡、云卡、接手与处置停摆/重放/失报那一刻查阅。⛔ 不引 -用 issue 编号。 +出处:主文件(原则与条款住那里);本表只放细则增量 —— 原话、参数、模板、步骤 —— +在对应操作那一刻查阅;⛔ 不引用 issue 编号。 ## 车道取卡全序与 `pm:blocking`(维护者 2026-08-13) @@ -17,7 +16,12 @@ 出处(逐字,未译):「对于比较紧急的卡片,是否应该使用 fable 5 子agent直接分诊」。 触发、授权面与同格式同效力在主文件;补遗:授权面不含派发(⛔ 不写码、不认领、不派 发);单一生产者纪律照旧(与「代扫」同界)—— 分诊座位在班且已在处理同卡即让行; -紧急通道改的是节奏,不是生产者数目。 +紧急通道改的是节奏,不是生产者数目。**半状态治愈积压排序**(维护者 2026-08-19, +原话:「项目经理等分诊,但是没有切换 label,导致挂了很久。」「我刚和他说了他才处 +理。」):sweep 半状态析取②③**最老优先、P0 嫌疑先行**,「优先最新」只适用新入卡 +①,⛔ 永不适用治愈积压 —— 限量 + 最新优先令最老半状态结构性饿死,活过一个 sweep +周期的半状态是治愈环自身的缺陷,不是库存;正文自报 P0/data-integrity 的命中即走 +本节紧急通道;机械年龄告警在半状态巡查脚本(细节以脚本头为权威)。 ## 发现分诊轮细则(维护者 2026-08-13) @@ -149,36 +153,32 @@ subagent 在 PM 容器内运行,agent 定义与技能文本读**本地检出**, ## 跨车道简单阻塞项直接接手(维护者 2026-08-13) 出处(逐字,未译):「如果当前任务依赖其他赛道的任务,如果判断简单,是否可以直接 -接手而不必持续等待」。判据与认领纪律在主文件;补遗:只覆盖「等一周 vs 做十分钟」 -的形状,认领评论 `Claim:` 首行拼写照旧;`packages/spec` 恒归 spec 座位,本条不豁 -免(唯一所有者规则更硬);**⛔ 不是「借调」回归**(借调已议已删)—— 判据是依赖形 -状,只做被本车道开卡 `Blocked-by:` 点名的那一个阻塞项,做完即回,⛔ 不顺手接第二 -张。 +接手而不必持续等待」。判据与认领纪律(含 `Claim:` 拼写)在主文件;补遗:只覆盖 +「等一周 vs 做十分钟」的形状;`packages/spec` 恒归 spec 座位,本条不豁免(唯一所 +有者规则更硬);**⛔ 不是「借调」回归** —— 判据是依赖形状,只做被本车道开卡 +`Blocked-by:` 点名的那一个阻塞项,做完即回,⛔ 不顺手接第二张。 ## 停摆复位梯度 -原则三条(停摆永不自愈、完成通知即停摆信号立刻 SendMessage、消费侧兜底)在主文 -件;本节是执行细则。姿态句全文:「前台(阻塞)同步执行全部步骤,中途不停止、不把 -构建/测试挂到后台等唤醒」。梯度逐级(原样重发同一句不算一次复位):① 复述执行姿 -态 → ② 点名下一个工具调用 + 明令禁止后台等待 → ③ 第三次停摆判 unreliable,按接手 -协议重派到该分支。自己挂的定时器不会唤醒自己;长验证管线的派发/接力词写明前台姿 -态;生产侧条款在 os-dev 定义。 +原则三条在主文件;本节是执行细则。姿态句全文:「前台(阻塞)同步执行全部步骤,中 +途不停止、不把构建/测试挂到后台等唤醒」。梯度逐级(原样重发同一句不算一次复位): +① 复述执行姿态 → ② 点名下一个工具调用 + 明令禁止后台等待 → ③ 第三次停摆判 +unreliable,按接手协议重派到该分支。自己挂的定时器不会唤醒自己;长验证管线的派发/ +接力词写明前台姿态;生产侧条款在 os-dev 定义。 ## 通知重放去重 -原则(先算身份再读内容;⛔ 不重新验收、不重读 diff、不重复 ACCEPT;到达与不到达都 -不构成生死证据)在主文件;细则:重放形态可以完全正常、报告完整正确 —— monitor 按 -自己的 deadline 触发;身份 = 三元组 `(issue, 分支, PR head sha)` + 通知自报的守护 -对象(自报是「变便宜」不是前提,没自报就用三元组自己算);判定重放 ⇒ 台账记「重 -放,首达时间 T」即结束。 +原则在主文件;细则:重放形态可以完全正常、报告完整正确 —— monitor 按自己的 +deadline 触发;身份 = 三元组 `(issue, 分支, PR head sha)` + 通知自报的守护对象 +(自报是「变便宜」不是前提,没自报就用三元组自己算);判定重放 ⇒ 台账记「重放, +首达时间 T」即结束。 ## 直接验收兜底(报告丢失 ≠ 验收停摆) 触发三条件、舰队级例外与「先探活后翻 ready」在主文件;验收动作:逐文件核对 diff 与认领申报的文件面,对照 `origin/main` 复核 PR 正文的前提声明与验证叙述,复核清单 -其余判据不减;抢先翻 ready 会撞上 agent 的收尾推送(竞态保护)。「推分支 → 开 -draft PR → 立即交报告」是保险不是效率优化,住在 os-dev 定义的 Definition of -done,派发令只带增量。 +其余判据不减;抢先翻 ready 会撞上 agent 的收尾推送(竞态保护);「推分支 → 开 +draft PR → 立即交报告」契约(保险,非效率优化)住 os-dev 定义,派发令只带增量。 ## 派发词构造细则 diff --git a/scripts/pm/check-half-states.mjs b/scripts/pm/check-half-states.mjs index 9336c713a5..94cfed4c50 100644 --- a/scripts/pm/check-half-states.mjs +++ b/scripts/pm/check-half-states.mjs @@ -116,6 +116,22 @@ * review ACCEPT, so ready = reviewed by construction; a reviewed PR * that left the merge queue (or never entered it) with nobody handling * it would otherwise wait silently forever. Patrol input, not a gate. + * H13 a card carrying `domain:*` with NO pm-state label, aged past one + * sweep cycle — the half-annotated shape the protocol's own + * single-label writes produce by design, which the triage sweep's + * disjunct ③ ("有 domain:* 无 pm-state") exists to heal hourly. In that + * shape the card is invisible to every seat's candidate query (routing + * landed, the state machine never did), and it is ALSO invisible to + * every label-scoped listing in this sweep — the shape is defined by + * the absence of the labels the other listings key on — so H13 is the + * one item that needs an UNSCOPED listing. Aged past a cycle it is a + * defect of the HEALING LOOP, not inventory (maintainer, 2026-08-19, + * verbatim: 「项目经理等分诊,但是没有切换 label,导致挂了很久。」「我 + * 刚和他说了他才处理。」— the measured specimen, its body self-declaring + * P0/data-integrity, sat ~26h until poked by hand). A louder line fires + * when the card's own title/body self-declares P0/data-integrity: for + * that class the emergency-triage channel (immediate triage subagent) + * is the mandated move, never the hourly Routine. * * ## The close mechanism, measured (#8293) * @@ -678,6 +694,96 @@ export function h12OrphanLanding(pr, nowMs = Date.now()) { ); } +// --------------------------------------------------------------------------- +// H13 — domain:* without any pm-state label, aged past one sweep cycle +// (maintainer-reported incident, 2026-08-19). +// --------------------------------------------------------------------------- + +/** + * The label vocabulary that counts as "a pm-state" for H13, mirroring the + * triage sweep's disjunct ③: any of these makes the card visible to a named + * reader (queue view, lane view, unlock scan, decision inbox, finding + * grading round, epic index, seat registry), so their absence — with routing + * already present — is the invisible half-annotated shape. `pm:blocking` is + * deliberately NOT here: it is a derived priority cache, not a state, and a + * card carrying only it is exactly as invisible to candidate queries. + */ +export const PM_STATE_LABELS = [ + 'pm:queue', + 'pm:dispatched', + 'pm:blocked', + 'pm:on-hold', + 'pm:epic', + 'pm:seat', + 'needs-user-decision', + 'finding', +]; + +/** + * Labels whose NORMAL shape is domain-without-pm-state, excluded by the + * sweep's own protocol text (SKILL.md, Backlog sweep): flagging them would + * report the protocol's design as a defect. + */ +export const H13_EXEMPT_LABELS = ['tracking', 'status:parked', 'qa-run']; + +/** + * H13 threshold — "one sweep cycle": the triage Routine fires HOURLY and its + * disjunct ③ heals exactly this shape every round, so a card still in it + * after 2h has survived at least one full healing round it should not have — + * the alarm reads a failure of the healing loop, never routine intake + * latency (a just-landed domain label sits here only for the sweep's own + * ~2-minute settle window, two orders of magnitude under the threshold). + * Age reads `updated_at`: it needs no timeline fetch, and every healing + * write would bump it, so a stale `updated_at` in this shape means nothing + * touched the card at all. The measured specimen sat ~26h; at 2h it would + * have been flagged a day earlier. + */ +export const DOMAIN_HALF_STATE_STALE_HOURS = 2; + +/** + * Whether the card's own title/body self-declares P0 / data-integrity — the + * incident card carried its emergency-triage trigger in its body while the + * seat that saw it "waited for triage" in session memory. Read through + * `stripMarkdownCode` (H7 reading 4's careful-author protection carries + * over: a body QUOTING `P0` in backticks is not a self-declaration). + */ +export function h13SelfDeclaredP0(issue) { + const text = stripMarkdownCode(`${issue?.title ?? ''}\n${issue?.body ?? ''}`); + return /\bp0\b/i.test(text) || /data[\s-]?integrity/i.test(text); +} + +/** + * H13 — null when clean, else the finding sentence (louder for a + * self-declared P0/data-integrity card, whose mandated route is the + * emergency-triage channel, not the next Routine fire). An unreadable + * `updated_at` flags rather than reads as fresh (#4690 direction, same as + * H10/H11/H12). + */ +export function h13DomainWithoutPmState(issue, nowMs = Date.now()) { + const labels = labelNames(issue); + if (!labels.some((l) => l.startsWith('domain:'))) return null; + if (labels.some((l) => PM_STATE_LABELS.includes(l))) return null; + if (labels.some((l) => H13_EXEMPT_LABELS.includes(l))) return null; + const updated = Date.parse(issue.updated_at ?? ''); + const ageHours = Number.isFinite(updated) ? (nowMs - updated) / 3_600_000 : null; + if (ageHours !== null && ageHours <= DOMAIN_HALF_STATE_STALE_HOURS) return null; + const reading = + ageHours === null + ? 'an unreadable `updated_at` (which must not read as fresh)' + : `~${Math.round(ageHours)}h without activity (threshold ${DOMAIN_HALF_STATE_STALE_HOURS}h)`; + const base = + `\`domain:*\` with no pm-state label and ${reading} — routing landed, the state machine ` + + `never did, so the card is invisible to every seat's candidate query. A half-state older ` + + `than one sweep cycle is a defect of the healing loop (triage sweep disjunct ③), not ` + + `inventory: pair the domain label with its pm-state in one write, oldest first.`; + if (!h13SelfDeclaredP0(issue)) return base; + return ( + `🚨 P0-SUSPECT: the card's own title/body self-declares P0/data-integrity, and for that ` + + `class the emergency-triage channel (immediate triage subagent) is the mandated move, ` + + `never the hourly Routine. ${base}` + ); +} + // --------------------------------------------------------------------------- // Transport prerequisite — the classifier (pure) and the probe that feeds it. // @@ -974,7 +1080,7 @@ function reportPrerequisiteNotMet(v, options = {}) { const nothing = swept === 0 ? [ - ` Nothing was swept: no issue was listed and no predicate (H1–H12) ran, so this`, + ` Nothing was swept: no issue was listed and no predicate (H1–H13) ran, so this`, ` result says NOTHING about whether the board carries half-states. It is not a`, ` clean board and it is not a dirty one — it is no reading at all.`, ] @@ -1041,10 +1147,11 @@ async function sweep() { const seen = new Map(); const seenPrs = new Map(); const seenMerged = new Map(); + const seenUnscoped = new Map(); try { - await sweepInto(findings, seen, seenPrs, seenMerged); + await sweepInto(findings, seen, seenPrs, seenMerged, seenUnscoped); } catch (err) { - err.sweptSoFar = seen.size + seenPrs.size + seenMerged.size; + err.sweptSoFar = seen.size + seenPrs.size + seenMerged.size + seenUnscoped.size; throw err; } @@ -1053,7 +1160,8 @@ async function sweep() { console.log(` ${code} #${issue.number} ${msg}\n ${issue.html_url}`); } console.log( - `check-half-states: swept ${seen.size} open pm-/p0-labeled issue(s), ${seenPrs.size} open PR(s) ` + + `check-half-states: swept ${seen.size} open pm-/p0-labeled issue(s), ${seenUnscoped.size} open ` + + `issue(s) in H13's unscoped pass, ${seenPrs.size} open PR(s) ` + `and ${seenMerged.size} recently-merged PR(s) in ${OWNER_REPO} — ${findings.length} half-state(s) found. ` + `Report-only: findings are patrol input, not a gate verdict.`, ); @@ -1089,7 +1197,26 @@ async function listRecentlyMergedPullRequests() { return out; } -async function sweepInto(findings, seen, seenPrs, seenMerged) { +/** + * The unscoped listing H13 needs: the domain-without-pm-state shape is + * DEFINED by the absence of every label the listings below key on, so no + * label page can ever return it — the very property that hides it from seat + * queries hides it from a label-scoped sweep too. Ten pages, the same cap as + * `listIssues`; an open backlog beyond the cap is invisible to H13 (stated + * boundary, same convention as H8's merged window — the finding clears when + * the paired write lands, not when the card ages out). + */ +async function listAllOpenIssues() { + const out = []; + for (let page = 1; page <= 10; page++) { + const batch = await rest(`/repos/${OWNER_REPO}/issues?state=open&per_page=100&page=${page}`); + out.push(...batch.filter((i) => !i.pull_request)); + if (batch.length < 100) break; + } + return out; +} + +async function sweepInto(findings, seen, seenPrs, seenMerged, seenUnscoped) { for (const label of ['pm:dispatched', 'pm:queue', 'pm:blocked', 'pm:seat', 'pm:on-hold', 'priority:p0']) { for (const issue of await listIssues(label)) seen.set(issue.number, issue); } @@ -1153,6 +1280,17 @@ async function sweepInto(findings, seen, seenPrs, seenMerged) { const stale = h8MergedPrStillDispatched(issue, mergedWindow); if (stale) findings.push([issue, 'H8', stale]); } + + // H13 — the one item whose population no label page can list (note at + // `listAllOpenIssues`). Kept out of `seen` so H1–H12 keep their exact + // inputs and the summary line stays honest about what each pass covered; + // the overlap with the label listings costs nothing (the predicate is + // label-gated and pure). + for (const issue of await listAllOpenIssues()) { + seenUnscoped.set(issue.number, issue); + const halfState = h13DomainWithoutPmState(issue); + if (halfState) findings.push([issue, 'H13', halfState]); + } } // --------------------------------------------------------------------------- @@ -1474,6 +1612,42 @@ function selfTest() { t('H12: missing draft field is out of scope', h12OrphanLanding({ auto_merge: null, updated_at: hoursAgo(50) }, NOW), null); t('H12: merged row is out of scope', h12OrphanLanding({ ...openPr({ updated: hoursAgo(50) }), merged_at: '2026-08-13T10:00:00Z' }, NOW), null); + // -- H13: domain:* without any pm-state label, aged (2026-08-19 incident) -- + const domainCard = (labels, updatedAt, extra = {}) => ({ + ...issue(labels), + updated_at: updatedAt, + ...extra, + }); + t('H13: aged domain card with no pm-state -> finding', typeof h13DomainWithoutPmState(domainCard(['domain:engine-core', 'bug', 'regression'], hoursAgo(26)), NOW), 'string'); + t('H13: …and the finding names the threshold', h13DomainWithoutPmState(domainCard(['domain:engine-core'], hoursAgo(26)), NOW).includes(`${DOMAIN_HALF_STATE_STALE_HOURS}h`), true); + t('H13: …and blames the healing loop, not inventory', h13DomainWithoutPmState(domainCard(['domain:engine-core'], hoursAgo(26)), NOW).includes('healing loop'), true); + t('H13: pm:queue pairs the domain label -> clean', h13DomainWithoutPmState(domainCard(['domain:engine-core', 'pm:queue'], hoursAgo(26)), NOW), null); + t('H13: needs-user-decision is a state (the inbox reads it) -> clean', h13DomainWithoutPmState(domainCard(['domain:spec', 'needs-user-decision'], hoursAgo(200)), NOW), null); + t('H13: finding is a state (the grading round reads it) -> clean', h13DomainWithoutPmState(domainCard(['domain:cli', 'finding'], hoursAgo(200)), NOW), null); + // `pm:blocking` is a derived priority cache, not a state — a card carrying + // only it is exactly as invisible to candidate queries, so it still flags. + t('H13: pm:blocking alone is NOT a state -> still a finding', typeof h13DomainWithoutPmState(domainCard(['domain:services', 'pm:blocking'], hoursAgo(26)), NOW), 'string'); + t('H13: status:parked exemption (its normal shape IS this one)', h13DomainWithoutPmState(domainCard(['domain:services', 'status:parked'], hoursAgo(200)), NOW), null); + t('H13: tracking exemption', h13DomainWithoutPmState(domainCard(['domain:devx', 'tracking'], hoursAgo(200)), NOW), null); + t('H13: qa-run exemption', h13DomainWithoutPmState(domainCard(['domain:cli', 'qa-run'], hoursAgo(200)), NOW), null); + t('H13: no domain label is out of scope however bare', h13DomainWithoutPmState(domainCard(['bug'], hoursAgo(200)), NOW), null); + t('H13: fresh half-state is intake latency, not a finding', h13DomainWithoutPmState(domainCard(['domain:engine-core'], hoursAgo(1)), NOW), null); + t('H13: exactly at the threshold -> clean (strictly beyond fires)', h13DomainWithoutPmState(domainCard(['domain:engine-core'], hoursAgo(DOMAIN_HALF_STATE_STALE_HOURS)), NOW), null); + // #4690 in miniature, same as H10/H11/H12: unreadable must not read as fresh. + t('H13: unreadable updated_at -> finding, not fresh', typeof h13DomainWithoutPmState(domainCard(['domain:engine-core'], 'not-a-date'), NOW), 'string'); + t('H13: absent updated_at -> finding, not fresh', typeof h13DomainWithoutPmState(domainCard(['domain:engine-core'], undefined), NOW), 'string'); + // The louder line — the measured card carried its trigger in its own body. + const p0Body = { body: 'P0 checklist-item failure (data-integrity DELETE regression) — priority label is triage’s to set' }; + t('H13: body self-declaring P0 -> louder line', h13DomainWithoutPmState(domainCard(['domain:engine-core'], hoursAgo(26), p0Body), NOW).includes('P0-SUSPECT'), true); + t('H13: …which prescribes the emergency-triage channel', h13DomainWithoutPmState(domainCard(['domain:engine-core'], hoursAgo(26), p0Body), NOW).includes('emergency-triage'), true); + t('H13: data-integrity phrasing alone fires the louder line', h13SelfDeclaredP0({ title: '', body: 'a data integrity regression in DELETE' }), true); + t('H13: the title is scanned too', h13SelfDeclaredP0({ title: 'p0 suspect: rows vanish', body: '' }), true); + // Strip reuse (H7 reading 4): quoting the token in backticks is not a + // self-declaration, and `P0` inside a word is not the token. + t('H13: P0 only inside backticks is not a self-declaration', h13SelfDeclaredP0({ title: '', body: 'the card quotes `P0` in passing' }), false); + t('H13: P0 inside a word does not fire', h13SelfDeclaredP0({ title: '', body: 'the HTTP0 protocol note' }), false); + t('H13: a quiet body stays on the base line', h13DomainWithoutPmState(domainCard(['domain:engine-core'], hoursAgo(26), { body: 'ordinary defect' }), NOW).includes('P0-SUSPECT'), false); + // -- transport prerequisite (#7412) --------------------------------------- // The three container classes are REAL measurements, not invented fixtures; // each names where it was taken, so a future transport change can be checked