diff --git a/.claude/agents/os-dev.md b/.claude/agents/os-dev.md index b52b7fea39..f93e8db475 100644 --- a/.claude/agents/os-dev.md +++ b/.claude/agents/os-dev.md @@ -207,7 +207,7 @@ push,都在新 head 上重跑并集 —— 至少 ratchet 族 —— **然后** dispatch prompt 只携带每单增量(裁决引文、裁决 / PM-机制假设分区、单卡条款、当日变动)。实 测:prompt 与本文件冲突时以本文件为准——无条件条款住在这里,错了也在这里改;遇到冲突 就在报告里点明,而不是悄悄选边。下列条款无论 prompt 是否提及都生效——prompt 的沉默是 -常态, 不是许可: +常态,不是许可: - **判断任何事之前先 build。** 过期的 `dist/*.d.ts` 两个方向都撒谎:假红烧掉几轮,去追一个不 存在的问题;假绿让收窄后的导出类型读成「消费者都干净」,而消费者根本没见过新的 @@ -229,7 +229,7 @@ dispatch prompt 只携带每单增量(裁决引文、裁决 / PM-机制假设分 ⛔ 永不 `git stash`(共享一个 LIFO 栈,两个 agent 同时 stash 互换条目;机制与 hook 见 AGENTS.md);取 出修复用临时 commit 或 patch 文件,都在自己 worktree 内: `git commit -am wip` 再 `git reset --soft HEAD~1`;`git diff > /tmp/wip.patch && git checkout -- ` 再 `git apply /tmp/wip.patch`。⛔ - `origin/main` 是共享指针, 别的 agent 一次 fetch 就推进它:`git reset --soft origin/main` 把你分支点 + `origin/main` 是共享指针,别的 agent 一次 fetch 就推进它:`git reset --soft origin/main` 把你分支点 之后**他人已合并的文件**整批 stage 成你的(实测一次四个 agent 的合并文件,commit 前才逮 住)。「我从哪开始」的 reset/diff/log/rebase 一律锚基本规则 1 记录的 `"$BASE"`。 - **要做反向验证(「回退修复,看诊断变化」)?先 commit 修复。** 已 commit,恢复只是 @@ -250,7 +250,7 @@ dispatch prompt 只携带每单增量(裁决引文、裁决 / PM-机制假设分 顺序说死:trap 只是崩溃路径上的便利,字节/哈希比对才是证明**; trap 触发只证明 shell 跑了 一个函数,不证明那个函数的效果。④ **空哈希读作 FAILURE,不是「没得比」** —— `git hash-object` 对解析不到的路径输出的是**空**,不是另一个哈希。比对对象是该路径的 - **HEAD blob** 哈希:空或缺失 ⇒ 失败;不匹配 ⇒ 响亮地非零退出并停下, 别让本轮继续在变异 + **HEAD blob** 哈希:空或缺失 ⇒ 失败;不匹配 ⇒ 响亮地非零退出并停下,别让本轮继续在变异 树上测量。 - **拒收类用例断言信封,不断言 throw 本身。** 最小断言集:错误的 **`code` 与 `status`** (ADR-0112 信封)。单独的 `expect(...).toThrow()` 不是拒收测试 —— 实测两个方向都致盲:未修复的 driver @@ -260,7 +260,7 @@ dispatch prompt 只携带每单增量(裁决引文、裁决 / PM-机制假设分 `unrecognized_keys`;守「某个**值**的判定」→ 要求完整 `safeParse` 绿。对刻意跑在 parse 之前的 规则要求全 parse 绿,是删掉合法覆盖;在判值的规则上只满足于 `unrecognized_keys`,是纵容幻影 检查。拒键与拒值是两个不同的事实。 -- **Fixture triage —— 三种处置,不是一把批量改拼写。** 你的改动删掉一个 alias 分支时, 拼着 +- **Fixture triage —— 三种处置,不是一把批量改拼写。** 你的改动删掉一个 alias 分支时,拼着 它的每个 fixture 都要逐个重判:**改拼写**(它只是用了 alias);**补声明**(改拼写暴露它从来就 不是 spec 合法);**整个换掉**(它 pin 的恰是你删的那个分支 —— 它的断言持续通过,*正因为 什么都不再产出*)。**按规则的消费半径扫 fixture,不按被编辑的包** —— 其它包的 fixture @@ -334,7 +334,7 @@ dispatch prompt 只携带每单增量(裁决引文、裁决 / PM-机制假设分 - **报告在 draft PR 时点交付 —— CI 收敛等待归 PM,不归你**(维护者 2026-08-10 拍板)。分支一推 上、draft PR 一开出,立刻交报告;门禁状态如实记录 —— `in_progress` 是诚实值。⛔ draft PR 开 出后永不 sleep、定时等待或空转轮询 CI(实测:空转轮询烧掉的恰是一个红门禁需要的预 - 算);你报告之后才转红的门禁,会作为同一认领上的补丁轮回来。逐卡例外: 派发词明 + 算);你报告之后才转红的门禁,会作为同一认领上的补丁轮回来。逐卡例外:派发词明 示「本单等 CI」只为那一张卡恢复等待 —— 以前台轮询,永不用后台 watcher。**本派发契约 压过平台注入的 PR 订阅姿态**(维护者 2026-08-11 裁决):云会话被自动订阅到自己的 PR、注入 了驻留指令时,仍以本文件为准;只有涉及标准文本之外的内容,才在 `open_questions` 记一笔冲 @@ -373,7 +373,7 @@ dispatch prompt 只携带每单增量(裁决引文、裁决 / PM-机制假设分 敛,不是你本地的覆盖面。未申报的收窄不在此列,那是漏跑。 **反轮询与不停轮由同一个 形状同时满足,而且只有这一个:一次前台阻塞等待**(Monitor 带 until 条件,或干脆在前台把那 套件跑完)。⛔ **结束一轮永远不是反轮询的解法** —— 实测一个 agent 正是为了 - 守「别在 sleep 循环里轮询」才停的轮,把停轮当成了合规的替代路径: 它不是,它恰是本条 + 守「别在 sleep 循环里轮询」才停的轮,把停轮当成了合规的替代路径:它不是,它恰是本条 明令禁止的那个终点。「主动等」与「被动停」的分界不在你怎么形容这次等待,而在这 一轮还活着没有。⛔ 后台 monitor 不会唤醒你:完成通知是「已无活跃子任务」的声 明,它**作为**停轮触发,而不在被等的活儿之后 —— 一天之内两个座位上四次同形停摆都 diff --git a/.claude/skills/pm-dispatch/SKILL.md b/.claude/skills/pm-dispatch/SKILL.md index fed4e98899..a3bb50913a 100644 --- a/.claude/skills/pm-dispatch/SKILL.md +++ b/.claude/skills/pm-dispatch/SKILL.md @@ -124,7 +124,7 @@ fire 一轮烟测,判据取**GitHub 上的产出**;③ 每 fire 一轮:读座位 runbook 全序节)。 - **跨仓解锁的判据是「消费方可安装」,不是「上游已合并」**:消费仓以**发布包**消费该上 游(而非 git SHA pin)时,解锁扫描放回前先验证修复已进入消费方可安装的发行版(安装面探 - 针: 装上的包是否接受新键/新行为);未发版 ⇒ 转 `pm:on-hold` + `Restart-when:` 加消费方安装面 + 针:装上的包是否接受新键/新行为);未发版 ⇒ 转 `pm:on-hold` + `Restart-when:` 加消费方安装面 判据,⛔ 不回 `pm:queue` —— 回队烧掉的派发注定以 premise-false 收场;发行与依赖升级是人工 动作,hold 评论预写唤醒后的派发形状。pin 消费的仓不适用本条 —— pin 移动即可安装,照 旧走 pin 滞后读数(多仓协调 2)。 diff --git a/.claude/skills/pm-dispatch/references/platform-readings.md b/.claude/skills/pm-dispatch/references/platform-readings.md index 866aabe49b..1939567e42 100644 --- a/.claude/skills/pm-dispatch/references/platform-readings.md +++ b/.claude/skills/pm-dispatch/references/platform-readings.md @@ -1,7 +1,7 @@ # 平台读数事实表(references —— 按需加载) 出处:主文件「平台读数纪律」。本表 = GitHub API / 工具行为的**实测事实**,做对应操作的那 -一刻查阅; 原则住主文件。⛔ 不引用 issue 编号 —— 每条自含失效模式与边界。 +一刻查阅;原则住主文件。⛔ 不引用 issue 编号 —— 每条自含失效模式与边界。 ## 队列成员资格与 auto-merge @@ -52,7 +52,7 @@ 死假说:auto-merge **不会**在已绿 PR 上静默空转(恢复窗口里两个全绿 PR 挂上即合)。 - enable 后的验证序列:① 先验队列分支(给条目 ~20–30s 建出);② 分支在 ⇒ 结束,⛔ 不翻转; ③ 等待后仍缺席**且队列已见 churn**(更新的条目建出了分支而你的没有 —— 截断下单纯 - 缺席不充分; 首挂静默不入队实测存在,churn 后翻转即愈)⇒ 翻转一次(`disable` → `enable`),仍 + 缺席不充分;首挂静默不入队实测存在,churn 后翻转即愈)⇒ 翻转一次(`disable` → `enable`),仍 以 timeline 事件验证; ④ ⛔ enable 与它的队列验证之间永不插 `disable` ——「入队」webhook 可 能乱序迟到,armed 窗口里补的 disable 会撤掉已发生的真实入队。 - **队列踢出先认签名再决定重投**:已知 flaky 核对失败签名一致 ⇒ 原样重投;止血修复合入 @@ -195,7 +195,7 @@ - **读数五坑**:`cd X && cmd` 会短路(路径不存在时命令在当前仓继续执行,产出假读数)—— 跨 仓一律 `git -C `; `git grep -c | wc -l` 数文件数不是命中数;裸名 grep 被幸存家族当子 - 串命中 —— 退役核验带引号精确名, 更硬判据是查声明 + 串命中 —— 退役核验带引号精确名,更硬判据是查声明 式(`^(export )?(const|type|interface) \b`)而非查提及;浅检出上的历史读数不可信 (`merge-base --is-ancestor` 假「非祖先」、`rev-list --count` 截断、`branch -r --contains` 零输出)—— 先 `--deepen` 再判,或走 REST `compare`; **容器里没有 `gh`**(实测 `command -v gh` 退出 1,两个标准路 @@ -271,9 +271,9 @@ 两侧收据做算术;双方占同一编号是常态(各取当时 max+1),重编号后进侧。 - **容器重启杀死在飞 dev,现场三态判读**:① 分支已推 + PR 已开 ⇒ 只欠验收(CI 重跑 + 复 核,不动代码); ② 死在 regen 中途(未提交全是生成物、merge commit 已在)⇒ PM 直接续作 —— - build → 整链 regen → 生成物门禁全绿 → 提交推送, 恢复 commit 带 `Recovery commit:` 前缀留审 + build → 整链 regen → 生成物门禁全绿 → 提交推送,恢复 commit 带 `Recovery commit:` 前缀留审 计;⚠️ 有的现场 regen 一件没跑,推送前先跑生成物门禁别赌; ③ 死在源码编辑中途 ⇒ 先 - 读 diff 判完整性 —— docblock 写全动机/失效模式/判据的,PM 可代跑终验后提交, 写一半意图 + 读 diff 判完整性 —— docblock 写全动机/失效模式/判据的,PM 可代跑终验后提交,写一半意图 不明的 ⛔ 不代提交、记进交接;dev 临时目录(`.os-scratch/` 一类)是工作物不是交付物,清 掉,⛔ 不进 feature PR。 - **零提交的探针分支不是在飞工作**:容器发不出分支删除 refspec(实测 @@ -303,12 +303,12 @@ `out of scope: #N` / `#N remains open`)。实测边界三条:关键词只绑**同一行**的 `#N`;动名 词(closing/fixing)不是关键词;行内反引号里的关键词不触发(code span 实测不建闭合链接;围栏 块未独立实测,按同规则对待但留待复测)。 -- **PR body 与 squash commit message 是两个独立解析源**(commit 干净 ≠ body 干净, 只查 commit 会 +- **PR body 与 squash commit message 是两个独立解析源**(commit 干净 ≠ body 干净,只查 commit 会 漏);误关的卡以 `completed` 状态对一切「只看 open」的过滤与巡检隐身,无任何机械守卫覆盖 这条路径 —— 消费侧检查 = 合并后读 `closed_by_pull_requests`(在复核清单)。 ## 断粮检测与跨墙恢复细则(5 小时用量墙) 原则、定时器选型(⛔ 不用 send_later 链)与恢复 playbook 在主文件;事实补遗:`npx ccusage blocks` 容 -器内可用(读本地会话记录),报当前 5 小时窗口边界/剩余与燃烧率; 盲区:窗口起点是本地推 +器内可用(读本地会话记录),报当前 5 小时窗口边界/剩余与燃烧率;盲区:窗口起点是本地推 断的近似值;撞墙报文形如「limit reached, resets at HH:MM」(重置时刻只在此刻可得)。 diff --git a/scripts/pm/check-half-states.mjs b/scripts/pm/check-half-states.mjs index afdc536e14..9fe2d93a27 100644 --- a/scripts/pm/check-half-states.mjs +++ b/scripts/pm/check-half-states.mjs @@ -7714,6 +7714,55 @@ function selfTest() { title, }); + // -- Row-text wrappers: every message assertion goes through one ----------- + // + // Each `hNrow(...)` is `String(hN…(...) ?? '')`. The predicates here are + // three-valued BY DESIGN — `null` when the card is clean, a string when the + // row fires — so a bare `predicate(...).includes(needle)` throws + // `TypeError: Cannot read properties of null (reading 'includes')` the + // moment a change makes that predicate go clean. That throw happens while + // evaluating `t()`'s ARGUMENTS, before `t()` runs, so no harness-level + // catch can convert it into a case: the suite ABORTS at the first such + // line, every later case never runs, and the output names a TypeError + // instead of a row. Which line you land on depends on ordering, so the + // information you lose is arbitrary. + // + // The cost is paid exactly during ABLATION — mutate a predicate, read which + // cases go red — which is the discipline this file's own headers lean on to + // prove a new row is failable. Through a wrapper, a nulled row instead + // reports `(got false, want true)` under its own case name and the suite + // runs to completion. ⚠️ The predicates themselves are UNCHANGED and are + // still asserted three-valued directly: `typeof pred(...) === 'string'` and + // `pred(...) === null` sites deliberately do NOT go through a wrapper — + // wrapping those would make every one of them trivially true. + const h4row = (...args) => String(h4BlockedNoBlockedBy(...args) ?? ''); + const h7row = (...args) => String(h7PartOfWithClosingKeyword(...args) ?? ''); + const h8row = (...args) => String(h8MergedPrStillDispatched(...args) ?? ''); + const h9row = (...args) => String(h9OnHoldNoRestartWhen(...args) ?? ''); + const h10row = (...args) => String(h10StaleUnclaimedP0(...args) ?? ''); + const h11row = (...args) => String(h11ImportantParked(...args) ?? ''); + const h12row = (...args) => String(h12OrphanLanding(...args) ?? ''); + const h13row = (...args) => String(h13DomainWithoutPmState(...args) ?? ''); + const h14row = (...args) => String(h14BlockingCacheIncoherent(...args) ?? ''); + const h16row = (...args) => String(h16StuckMergeConflict(...args) ?? ''); + const h18row = (...args) => String(h18RetriageAged(...args) ?? ''); + const h19row = (...args) => String(h19BlockOutlivedBlocker(...args) ?? ''); + const h22row = (...args) => String(h22ClosedCardPmResidue(...args) ?? ''); + const h24row = (...args) => String(h24QueuedWithAssignee(...args) ?? ''); + const h25row = (...args) => String(h25AwaitingMaintainerExclusivity(...args) ?? ''); + const h26row = (...args) => String(h26BlockOnIndefiniteTarget(...args) ?? ''); + const h32row = (...args) => String(h32SeatIdleOverQueue(...args) ?? ''); + const h33row = (...args) => String(h33ClaimPredatesRuling(...args) ?? ''); + // H34's wrapper (the pattern this generalizes) stays beside its own block, + // as do H8's `halvesRow` and H27's `dead27Row` — all three wrap a helper + // that is itself declared locally, next to the fixtures it closes over. + // H29/H30/H31 use a different reader, `says()`, declared at those blocks: + // it returns a DESCRIBING string (`NO MESSAGE (null)`) rather than `''`, so + // it also distinguishes "row fired without the needle" from "row went + // silent" on a case whose expectation is `false`. Both shapes run to + // completion; `says()` is the more informative and the more invasive, and + // unifying on one of them is a diff of its own, not this one. + t('H1: dispatched + no assignee -> finding', h1DispatchedNoAssignee(issue(['pm:dispatched'])), true); t('H1: dispatched + assignee -> clean', h1DispatchedNoAssignee(issue(['pm:dispatched'], ['os-help'])), false); t('H2: assignee + no claim comment -> finding', h2AssigneeNoClaimComment(issue(['pm:dispatched'], ['os-help']), ['looks good', 'triage: routed']), true); @@ -7735,23 +7784,23 @@ function selfTest() { t('H4: blocked without body line -> finding', typeof h4BlockedNoBlockedBy(issue(['pm:blocked'], [], 'waiting on upstream')), 'string'); t('H4: blocked with Blocked-by line -> clean', h4BlockedNoBlockedBy(issue(['pm:blocked'], [], 'Blocked-by: #123')), null); t('H4: unblocked card is out of scope', h4BlockedNoBlockedBy(issue([], [], '')), null); - t('H4: …and an unconsulted comment channel is not claimed as empty', h4BlockedNoBlockedBy(issue(['pm:blocked'], [], 'waiting on upstream')).includes('NEITHER channel'), false); - t('H4: the body-only sentence still names the unlock sweep as the stake', h4BlockedNoBlockedBy(issue(['pm:blocked'], [], 'waiting on upstream')).includes('unlock sweep greps'), true); + t('H4: …and an unconsulted comment channel is not claimed as empty', h4row(issue(['pm:blocked'], [], 'waiting on upstream')).includes('NEITHER channel'), false); + t('H4: the body-only sentence still names the unlock sweep as the stake', h4row(issue(['pm:blocked'], [], 'waiting on upstream')).includes('unlock sweep greps'), true); // H4 — the COMMENT channel (#8941 / #10061). Four shapes, positive and // negative, plus the unreadable one that is neither. t('H4: body clean but a comment carries the line -> clean', h4BlockedNoBlockedBy(issue(['pm:blocked'], [], 'waiting on upstream'), ['triage note', 'Blocked-by: #9465']), null); t('H4: body line AND a comment line (the union shape) -> clean', h4BlockedNoBlockedBy(issue(['pm:blocked'], [], 'Blocked-by: #123'), ['Blocked-by: #9465']), null); t('H4: neither channel -> finding', typeof h4BlockedNoBlockedBy(issue(['pm:blocked'], [], 'waiting on upstream'), ['triage note', 'graded p2']), 'string'); - t('H4: …and the sentence names BOTH channels', h4BlockedNoBlockedBy(issue(['pm:blocked'], [], 'waiting'), ['nothing here']).includes('NEITHER channel'), true); - t('H4: …and says a comment discharges the duty too', h4BlockedNoBlockedBy(issue(['pm:blocked'], [], 'waiting'), ['nothing here']).includes('Either channel discharges'), true); - t('H4: an empty comment thread is a real reading, not an unconsulted one', h4BlockedNoBlockedBy(issue(['pm:blocked'], [], 'waiting'), []).includes('NEITHER channel'), true); + t('H4: …and the sentence names BOTH channels', h4row(issue(['pm:blocked'], [], 'waiting'), ['nothing here']).includes('NEITHER channel'), true); + t('H4: …and says a comment discharges the duty too', h4row(issue(['pm:blocked'], [], 'waiting'), ['nothing here']).includes('Either channel discharges'), true); + t('H4: an empty comment thread is a real reading, not an unconsulted one', h4row(issue(['pm:blocked'], [], 'waiting'), []).includes('NEITHER channel'), true); // Unreadable is neither of the two: the row FIRES (a transport failure must // not shrink the patrol below its pre-fallback reach) and says why. t('H4: an UNREADABLE comment thread still fires', typeof h4BlockedNoBlockedBy(issue(['pm:blocked'], [], 'waiting'), null), 'string'); - t('H4: …but never claims the second channel is empty', h4BlockedNoBlockedBy(issue(['pm:blocked'], [], 'waiting'), null).includes('NEITHER channel'), false); - t('H4: …and says the thread could not be read', h4BlockedNoBlockedBy(issue(['pm:blocked'], [], 'waiting'), null).includes('could'), true); - t('H4: …citing the unreadable-is-not-absent rule', h4BlockedNoBlockedBy(issue(['pm:blocked'], [], 'waiting'), null).includes('#4690'), true); + t('H4: …but never claims the second channel is empty', h4row(issue(['pm:blocked'], [], 'waiting'), null).includes('NEITHER channel'), false); + t('H4: …and says the thread could not be read', h4row(issue(['pm:blocked'], [], 'waiting'), null).includes('could'), true); + t('H4: …citing the unreadable-is-not-absent rule', h4row(issue(['pm:blocked'], [], 'waiting'), null).includes('#4690'), true); // A comment line clears H4 whatever the ref says: H4's question is "did the // author leave the machine anything", which a cross-repo blocker answers. t('H4: a cross-repo comment line still discharges the duty', h4BlockedNoBlockedBy(issue(['pm:blocked'], [], 'waiting'), ['Blocked-by: objectstack-ai/objectui#4356']), null); @@ -7941,11 +7990,11 @@ function selfTest() { 'the PM should close #8131 deliberately once #8136 lands.', ); t('H7: the #8277 specimen is a finding', typeof h7PartOfWithClosingKeyword(pr8277), 'string'); - t('H7: …and it names the card it will close', h7PartOfWithClosingKeyword(pr8277).includes('Part of #8131'), true); + t('H7: …and it names the card it will close', h7row(pr8277).includes('Part of #8131'), true); // The measurement that refutes the sidebar hypothesis: the SAME body names // #8136 one clause later with no keyword, and #8136 took no closing link. // The predicate must reproduce that asymmetry, not blanket-flag both numbers. - t('H7: …and does NOT implicate #8136 from the same sentence', h7PartOfWithClosingKeyword(pr8277).includes('#8136'), false); + t('H7: …and does NOT implicate #8136 from the same sentence', h7row(pr8277).includes('#8136'), false); // Specimen 2 — PR #8261 (`Part of #8103`), the same round's other partial // delivery, which stayed open. No keyword anywhere near its number. @@ -8056,11 +8105,12 @@ function selfTest() { 'measurement above shows is lost on the predicate path.', ); const fired21 = h21NegatedClosingKeyword(pr10241); + const fired21Row = String(fired21 ?? ''); t('H21: the #10241 specimen FIRES', typeof fired21, 'string'); - t('H21: …and names the card it will close', fired21.includes('`fixed #10240`'), true); - t('H21: …and quotes the offending sentence back', fired21.includes('Filed, not fixed: #10240'), true); - t('H21: …and says the parser ignores the negation', fired21.includes('negations included'), true); - t('H21: …and offers the safe rewordings', fired21.includes('#10240 is not addressed here'), true); + t('H21: …and names the card it will close', fired21Row.includes('`fixed #10240`'), true); + t('H21: …and quotes the offending sentence back', fired21Row.includes('Filed, not fixed: #10240'), true); + t('H21: …and says the parser ignores the negation', fired21Row.includes('negations included'), true); + t('H21: …and offers the safe rewordings', fired21Row.includes('#10240 is not addressed here'), true); // H7 is silent on this body — the gap that made the row necessary. If this // ever inverts, H21 is redundant rather than merely quiet. t('H21: …and H7 is silent on it (the gap this row exists for)', h7PartOfWithClosingKeyword(pr10241), null); @@ -8161,12 +8211,12 @@ function selfTest() { ); t( 'H8: …and the finding names the delivering PR', - h8MergedPrStillDispatched(dispatched(4321), [mergedPr(4400, 'Part of #4321')]).includes('#4400'), + h8row(dispatched(4321), [mergedPr(4400, 'Part of #4321')]).includes('#4400'), true, ); t( 'H8: …and prescribes the paired write, not just the fact', - h8MergedPrStillDispatched(dispatched(4321), [mergedPr(4400, 'Part of #4321')]).includes('pm:dispatched'), + h8row(dispatched(4321), [mergedPr(4400, 'Part of #4321')]).includes('pm:dispatched'), true, ); // The closing-keyword arm: an OPEN dispatched card named by a merged PR's @@ -8210,7 +8260,7 @@ function selfTest() { ); t( 'H8: two merged deliverers -> both named', - h8MergedPrStillDispatched(dispatched(4321), [mergedPr(4400, 'Part of #4321'), mergedPr(4500, 'Fixes #4321')]).includes('#4500'), + h8row(dispatched(4321), [mergedPr(4400, 'Part of #4321'), mergedPr(4500, 'Fixes #4321')]).includes('#4500'), true, ); t('H8: empty merged window -> clean', h8MergedPrStillDispatched(dispatched(4321), []), null); @@ -8314,38 +8364,43 @@ function selfTest() { const openHalf = (number, body, draft = false) => ({ number, body, draft, merged_at: null }); const halves = (openPrs) => h8MergedPrStillDispatched(dispatched(9834), [mergedPr(10004, 'Part of #9834')], openPrs); + // `halves` is itself three-valued, so it needs the same row-text wrapper as + // the predicate it closes over — and it must stay a SEPARATE binding rather + // than `halves` being stringified in place, because the `typeof halves(...)` + // cases below assert exactly the nullability a `String()` would erase. + const halvesRow = (...args) => String(halves(...args) ?? ''); t('H8 open: a half-delivered card still reports', typeof halves([openHalf(10226, 'Part of #9834', true)]), 'string'); // The whole point of the downgrade: the destructive prescription must not // fire on a card whose remaining half is open. t( 'H8 open: …and does NOT prescribe dropping the label', - halves([openHalf(10226, 'Part of #9834', true)]).includes('Drop `pm:dispatched`'), + halvesRow([openHalf(10226, 'Part of #9834', true)]).includes('Drop `pm:dispatched`'), false, ); t( 'H8 open: …and says the label is CORRECT here', - halves([openHalf(10226, 'Part of #9834', true)]).includes('must NOT be dropped'), + halvesRow([openHalf(10226, 'Part of #9834', true)]).includes('must NOT be dropped'), true, ); - t('H8 open: …and names the open half', halves([openHalf(10226, 'Part of #9834', true)]).includes('#10226'), true); - t('H8 open: …and the merged half too', halves([openHalf(10226, 'Part of #9834', true)]).includes('#10004'), true); - t('H8 open: …and counts them, N of M', halves([openHalf(10226, 'Part of #9834', true)]).includes('1 of 2'), true); + t('H8 open: …and names the open half', halvesRow([openHalf(10226, 'Part of #9834', true)]).includes('#10226'), true); + t('H8 open: …and the merged half too', halvesRow([openHalf(10226, 'Part of #9834', true)]).includes('#10004'), true); + t('H8 open: …and counts them, N of M', halvesRow([openHalf(10226, 'Part of #9834', true)]).includes('1 of 2'), true); // A draft open half is the specimen's own shape — never filtered out. - t('H8 open: …and marks the open half as a draft', halves([openHalf(10226, 'Part of #9834', true)]).includes('(draft)'), true); + t('H8 open: …and marks the open half as a draft', halvesRow([openHalf(10226, 'Part of #9834', true)]).includes('(draft)'), true); t('H8 open: a NON-draft open half counts identically', typeof halves([openHalf(10226, 'Part of #9834', false)]), 'string'); // …and the row it replaces is unchanged whenever every deliverer HAS merged — // the genuine #8683 case, which must keep its prescription. t( 'H8 open: no open deliverer -> the destructive prescription still fires', - halves([]).includes('Drop `pm:dispatched`'), + halvesRow([]).includes('Drop `pm:dispatched`'), true, ); - t('H8 open: a missing open list is the pre-#10468 reading', halves(undefined).includes('Drop `pm:dispatched`'), true); + t('H8 open: a missing open list is the pre-#10468 reading', halvesRow(undefined).includes('Drop `pm:dispatched`'), true); t( 'H8 open: an open PR delivering a DIFFERENT card does not downgrade the row', - halves([openHalf(10226, 'Part of #9999')]).includes('Drop `pm:dispatched`'), + halvesRow([openHalf(10226, 'Part of #9999')]).includes('Drop `pm:dispatched`'), true, ); // No merged deliverer at all is still clean — the open side never MANUFACTURES @@ -8359,19 +8414,19 @@ function selfTest() { // included — a `Refs #N` open half is as live as a `Part of #N` one. t( 'H8 open: the branch-name fallback applies to the open side too', - halves([{ number: 10226, body: 'Refs #9834', draft: false, merged_at: null, head: { ref: 'claude/issue-9834-error-counter' } }]).includes('must NOT be dropped'), + halvesRow([{ number: 10226, body: 'Refs #9834', draft: false, merged_at: null, head: { ref: 'claude/issue-9834-error-counter' } }]).includes('must NOT be dropped'), true, ); // …and its re-scope guard travels with it. t( 'H8 open: a re-scoped open branch does not soften the row', - halves([{ number: 10226, body: 'Part of #9999', draft: false, merged_at: null, head: { ref: 'claude/issue-9834-x' } }]).includes('Drop `pm:dispatched`'), + halvesRow([{ number: 10226, body: 'Part of #9999', draft: false, merged_at: null, head: { ref: 'claude/issue-9834-x' } }]).includes('Drop `pm:dispatched`'), true, ); // A merged row appearing in the open list is not an outstanding half. t( 'H8 open: a merged row in the open list is not an open half', - halves([{ number: 10226, body: 'Part of #9834', merged_at: '2026-08-20T00:00:00Z' }]).includes('Drop `pm:dispatched`'), + halvesRow([{ number: 10226, body: 'Part of #9834', merged_at: '2026-08-20T00:00:00Z' }]).includes('Drop `pm:dispatched`'), true, ); @@ -8384,21 +8439,21 @@ function selfTest() { }); t('H22: closed + pm:dispatched -> finding', typeof h22ClosedCardPmResidue(closedCard(['pm:dispatched'])), 'string'); - t('H22: …and names the residue label', h22ClosedCardPmResidue(closedCard(['pm:dispatched'])).includes('`pm:dispatched`'), true); - t('H22: …and names the close reason', h22ClosedCardPmResidue(closedCard(['pm:dispatched'])).includes('closed `completed`'), true); + t('H22: …and names the residue label', h22row(closedCard(['pm:dispatched'])).includes('`pm:dispatched`'), true); + t('H22: …and names the close reason', h22row(closedCard(['pm:dispatched'])).includes('closed `completed`'), true); t( 'H22: …and prescribes only the label strip, no other write', - h22ClosedCardPmResidue(closedCard(['pm:dispatched'])).includes('already closed'), + h22row(closedCard(['pm:dispatched'])).includes('already closed'), true, ); t('H22: a not_planned close is residue too', typeof h22ClosedCardPmResidue(closedCard(['pm:queue'], 'not_planned')), 'string'); t('H22: a missing state_reason still renders a sentence', typeof h22ClosedCardPmResidue({ ...closedCard(['pm:queue']), state_reason: null }), 'string'); t( 'H22: …and never prints the string undefined', - h22ClosedCardPmResidue({ ...closedCard(['pm:queue']), state_reason: null }).includes('undefined'), + h22row({ ...closedCard(['pm:queue']), state_reason: null }).includes('undefined'), false, ); - t('H22: several residue labels are all named', h22ClosedCardPmResidue(closedCard(['pm:blocked', 'pm:blocking'])).includes('`pm:blocking`'), true); + t('H22: several residue labels are all named', h22row(closedCard(['pm:blocked', 'pm:blocking'])).includes('`pm:blocking`'), true); // The gate that keeps this from restating H3: an OPEN card is never this // row's, whatever it carries — every other item here already reads it. @@ -8414,8 +8469,8 @@ function selfTest() { t('H22: `pm:epic` likewise', h22ClosedCardPmResidue(closedCard(['pm:epic'])), null); t('H22: `pm:retriage` is deliberately out of the measured set', h22ClosedCardPmResidue(closedCard(['pm:retriage'])), null); // …but a seat card ALSO carrying a state label is still residue. - t('H22: `pm:seat` + a state label is residue for the state label', h22ClosedCardPmResidue(closedCard(['pm:seat', 'pm:dispatched'])).includes('`pm:dispatched`'), true); - t('H22: …and does not name the identity sticker', h22ClosedCardPmResidue(closedCard(['pm:seat', 'pm:dispatched'])).includes('`pm:seat`'), false); + t('H22: `pm:seat` + a state label is residue for the state label', h22row(closedCard(['pm:seat', 'pm:dispatched'])).includes('`pm:dispatched`'), true); + t('H22: …and does not name the identity sticker', h22row(closedCard(['pm:seat', 'pm:dispatched'])).includes('`pm:seat`'), false); // The census's five plus the state ruled in on 2026-08-23, each pinned — the // set is the item's scope, so a silent edit to it should break a test rather @@ -8502,8 +8557,8 @@ function selfTest() { // across, so H7's own sentence is asserted to CARRY the clause this one must // never carry — a one-sided assertion would pass against a sentence that lost // both. - const fired23 = h23CommitMessageContradiction(squashOf('9320', '9478')); - const fired7 = h7PartOfWithClosingKeyword({ body: 'Part of #77\n\nFixes #77' }); + const fired23 = String(h23CommitMessageContradiction(squashOf('9320', '9478')) ?? ''); + const fired7 = String(h7PartOfWithClosingKeyword({ body: 'Part of #77\n\nFixes #77' }) ?? ''); t('H23: the sentence prescribes REWORDING', fired23.includes('REWORD'), true); t('H23: …and never the body-surface backtick remedy', fired23.includes('put the keyword in backticks'), false); t('H23: …nor any "in backticks" advice at all', fired23.includes('in backticks'), false); @@ -8548,14 +8603,14 @@ function selfTest() { // -- H9: `pm:on-hold` without a machine-fireable `Restart-when:` ------------ const hold = (body) => issue(['pm:on-hold'], [], body); t('H9: hold with no Restart-when line -> finding', typeof h9OnHoldNoRestartWhen(hold('parked until the train ships')), 'string'); - t('H9: …and the finding prescribes the close default', h9OnHoldNoRestartWhen(hold('parked')).includes('not planned'), true); + t('H9: …and the finding prescribes the close default', h9row(hold('parked')).includes('not planned'), true); t('H9: closed-upstream form -> clean', h9OnHoldNoRestartWhen(hold('Restart-when: closed acme/widgets#123')), null); t('H9: executable-predicate form -> clean', h9OnHoldNoRestartWhen(hold('Restart-when: npm view create-objectstack dist-tags reports >= 17.0.0')), null); t('H9: mid-body line -> clean', h9OnHoldNoRestartWhen(hold('Context first.\nRestart-when: closed acme/widgets#123\nMore prose.')), null); // `manual` is a hold trying to opt out of having an exit — it counts as // missing, or the one-word spelling defeats the invariant. t('H9: manual form -> finding', typeof h9OnHoldNoRestartWhen(hold('Restart-when: manual — first EE customer asking')), 'string'); - t('H9: …and the finding names the manual shape', h9OnHoldNoRestartWhen(hold('Restart-when: manual — reason')).includes('manual'), true); + t('H9: …and the finding names the manual shape', h9row(hold('Restart-when: manual — reason')).includes('manual'), true); t('H9: Manual case-insensitive as a VALUE -> finding', typeof h9OnHoldNoRestartWhen(hold('Restart-when: Manual — reason')), 'string'); t('H9: manual line + fireable line -> clean', h9OnHoldNoRestartWhen(hold('Restart-when: manual — x\nRestart-when: closed acme/widgets#9')), null); // The KEY is byte-stable like `Blocked-by:` — a lowercase key is a line the @@ -8588,10 +8643,10 @@ function selfTest() { // hold must still fire, or backticks would become the opt-out the bare // spelling is denied. t('H9: a decorated `manual` hold still fires', typeof h9OnHoldNoRestartWhen(hold('`Restart-when: manual — first EE customer asking`')), 'string'); - t('H9: …and still names the manual shape', h9OnHoldNoRestartWhen(hold('**Restart-when: manual — reason**')).includes('manual'), true); + t('H9: …and still names the manual shape', h9row(hold('**Restart-when: manual — reason**')).includes('manual'), true); // -- H9's remedy text: verify/unwrap first, close last (#10102) ------------ - const h9NoLine = h9OnHoldNoRestartWhen(hold('parked until the train ships')); + const h9NoLine = String(h9OnHoldNoRestartWhen(hold('parked until the train ships')) ?? ''); t('H9: the no-line row names the decorated/unparsed possibility', h9NoLine.includes('cannot parse'), true); t('H9: …and tells the seat to read the body before acting', h9NoLine.includes('READ THE BODY BEFORE ACTING'), true); t('H9: …and demotes closing to the last resort', h9NoLine.includes('Closing is the LAST resort'), true); @@ -8599,15 +8654,15 @@ function selfTest() { // The `manual` row is NOT a parse failure — a line was read — so it must not // carry the "maybe it is there" hedge, or the one row that really does mean // "this hold has no exit" starts reading as uncertain. - t('H9: the manual row carries no unparsed hedge', h9OnHoldNoRestartWhen(hold('Restart-when: manual — reason')).includes('cannot parse'), false); - t('H9: …but does still demote closing', h9OnHoldNoRestartWhen(hold('Restart-when: manual — reason')).includes('Closing is the LAST resort'), true); + t('H9: the manual row carries no unparsed hedge', h9row(hold('Restart-when: manual — reason')).includes('cannot parse'), false); + t('H9: …but does still demote closing', h9row(hold('Restart-when: manual — reason')).includes('Closing is the LAST resort'), true); // The channel contract, stated in the row itself — two channels since // #10403, symmetric with H4/H14. An undocumented difference between two // adjacent rules is how the last two half-states on that lane were made, // and an undocumented SAMENESS would repeat it in mirror image. t('H9: the row states the two-channel contract', h9NoLine.includes('body OR a comment'), true); t('H9: …and names the predicates it now matches', h9NoLine.includes('H4/H14'), true); - t('H9: …and the manual row states it too', h9OnHoldNoRestartWhen(hold('Restart-when: manual — x')).includes('body OR a comment'), true); + t('H9: …and the manual row states it too', h9row(hold('Restart-when: manual — x')).includes('body OR a comment'), true); // -- H9's COMMENT channel (#10403) ----------------------------------------- // The incident fixture: a machine-fireable exit parked in a comment — the @@ -8627,21 +8682,21 @@ function selfTest() { t('H9: a mid-sentence mention in a comment is not a line', typeof h9OnHoldNoRestartWhen(hold('parked'), ['someone should add a `Restart-when: closed acme/w#1` line']), 'string'); // Both channels read and empty: the sentence says EITHER, so the reader // knows both were judged — and an unconsulted channel is never claimed. - t('H9: neither channel -> the sentence names EITHER channel', h9OnHoldNoRestartWhen(hold('parked'), ['no directive here']).includes('EITHER channel'), true); - t('H9: an empty comment thread is a real reading', h9OnHoldNoRestartWhen(hold('parked'), []).includes('EITHER channel'), true); - t('H9: an unconsulted comment channel is not claimed as read', h9OnHoldNoRestartWhen(hold('parked')).includes('EITHER channel'), false); - t('H9: …and the both-channels hedge tells the seat to read the thread too', h9OnHoldNoRestartWhen(hold('parked'), []).includes('BODY AND THE THREAD'), true); + t('H9: neither channel -> the sentence names EITHER channel', h9row(hold('parked'), ['no directive here']).includes('EITHER channel'), true); + t('H9: an empty comment thread is a real reading', h9row(hold('parked'), []).includes('EITHER channel'), true); + t('H9: an unconsulted comment channel is not claimed as read', h9row(hold('parked')).includes('EITHER channel'), false); + t('H9: …and the both-channels hedge tells the seat to read the thread too', h9row(hold('parked'), []).includes('BODY AND THE THREAD'), true); // Unreadable is neither read nor absent (#4690): the row fires on the cheap // side, says the thread could not be read, and never claims EITHER. t('H9: an UNREADABLE comment thread still fires', typeof h9OnHoldNoRestartWhen(hold('parked'), null), 'string'); - t('H9: …but never claims the second channel is empty', h9OnHoldNoRestartWhen(hold('parked'), null).includes('EITHER channel'), false); - t('H9: …and says the thread could not be read', h9OnHoldNoRestartWhen(hold('parked'), null).includes('could NOT be read'), true); - t('H9: …citing the unreadable-is-not-absent rule', h9OnHoldNoRestartWhen(hold('parked'), null).includes('#4690'), true); + t('H9: …but never claims the second channel is empty', h9row(hold('parked'), null).includes('EITHER channel'), false); + t('H9: …and says the thread could not be read', h9row(hold('parked'), null).includes('could NOT be read'), true); + t('H9: …citing the unreadable-is-not-absent rule', h9row(hold('parked'), null).includes('#4690'), true); t('H9: a fireable BODY line clears even an unreadable thread', h9OnHoldNoRestartWhen(hold('Restart-when: closed acme/widgets#123'), null), null); // Manual across channels: a manual body line plus a fireable comment line is // the mixed shape a seat actually writes when upgrading a hold in place. t('H9: manual body line + fireable comment line -> clean', h9OnHoldNoRestartWhen(hold('Restart-when: manual — x'), ['Restart-when: closed acme/widgets#9']), null); - t('H9: manual lines in BOTH channels still name the manual shape', h9OnHoldNoRestartWhen(hold('Restart-when: manual — x'), ['Restart-when: manual — y']).includes('manual'), true); + t('H9: manual lines in BOTH channels still name the manual shape', h9row(hold('Restart-when: manual — x'), ['Restart-when: manual — y']).includes('manual'), true); // The gathering policy — what gets READ AT ALL (mirrors the H4 gate pins). t('gate: a body-clean pm:on-hold card is an H9 candidate', needsRestartWhenComments(hold('no line here')), true); @@ -8658,7 +8713,7 @@ function selfTest() { updated_at: updatedAt, }); t('H10: unassigned p0 past the threshold -> finding', typeof h10StaleUnclaimedP0(p0([], hoursAgo(36), ['pm:queue']), NOW), 'string'); - t('H10: …and the finding names the threshold', h10StaleUnclaimedP0(p0([], hoursAgo(36)), NOW).includes(`${P0_UNCLAIMED_STALE_HOURS}h`), true); + t('H10: …and the finding names the threshold', h10row(p0([], hoursAgo(36)), NOW).includes(`${P0_UNCLAIMED_STALE_HOURS}h`), true); t('H10: fresh unassigned p0 -> clean', h10StaleUnclaimedP0(p0([], hoursAgo(1)), NOW), null); t('H10: exactly at the threshold -> clean (strictly beyond fires)', h10StaleUnclaimedP0(p0([], hoursAgo(P0_UNCLAIMED_STALE_HOURS)), NOW), null); t('H10: assigned p0 is out of scope however old', h10StaleUnclaimedP0(p0(['os-help'], hoursAgo(200)), NOW), null); @@ -8682,8 +8737,8 @@ function selfTest() { t('H11: bug label + blocked -> finding', typeof h11ImportantParked(parkedCard(['bug', 'pm:blocked']), NOW), 'string'); t('H11: security label + on-hold -> finding', typeof h11ImportantParked(parkedCard(['security', 'pm:on-hold']), NOW), 'string'); t('H11: priority:p1 + blocked -> finding', typeof h11ImportantParked(parkedCard(['priority:p1', 'pm:blocked']), NOW), 'string'); - t('H11: …and the finding names the parked state', h11ImportantParked(parkedCard(['bug', 'pm:blocked']), NOW).includes('pm:blocked'), true); - t('H11: …and the threshold', h11ImportantParked(parkedCard(['bug', 'pm:blocked']), NOW).includes(`${IMPORTANT_PARKED_STALE_DAYS}d`), true); + t('H11: …and the finding names the parked state', h11row(parkedCard(['bug', 'pm:blocked']), NOW).includes('pm:blocked'), true); + t('H11: …and the threshold', h11row(parkedCard(['bug', 'pm:blocked']), NOW).includes(`${IMPORTANT_PARKED_STALE_DAYS}d`), true); t('H11: fresh park is clean', h11ImportantParked(parkedCard(['bug', 'pm:on-hold'], { created: daysAgo(2) }), NOW), null); t('H11: exactly at the threshold is clean (strictly beyond fires)', h11ImportantParked(parkedCard(['bug', 'pm:on-hold'], { created: daysAgo(IMPORTANT_PARKED_STALE_DAYS) }), NOW), null); t('H11: important but not parked is out of scope', h11ImportantParked(parkedCard(['bug', 'pm:queue']), NOW), null); @@ -8704,8 +8759,8 @@ function selfTest() { merged_at: null, }); t('H12: ready + unarmed + stale -> finding', typeof h12OrphanLanding(openPr(), NOW), 'string'); - t('H12: …and the finding names the threshold', h12OrphanLanding(openPr(), NOW).includes(`${ORPHAN_LANDING_STALE_HOURS}h`), true); - t('H12: …and prescribes the landing-window re-read, not just the fact', h12OrphanLanding(openPr(), NOW).includes('landing window'), true); + t('H12: …and the finding names the threshold', h12row(openPr(), NOW).includes(`${ORPHAN_LANDING_STALE_HOURS}h`), true); + t('H12: …and prescribes the landing-window re-read, not just the fact', h12row(openPr(), NOW).includes('landing window'), true); t('H12: draft is out of scope however old (parked deliberately)', h12OrphanLanding(openPr({ draft: true, updated: hoursAgo(200) }), NOW), null); t('H12: armed auto-merge -> clean (queue machinery holds it)', h12OrphanLanding(openPr({ auto_merge: { merge_method: 'squash' } }), NOW), null); t('H12: fresh ready PR -> clean', h12OrphanLanding(openPr({ updated: hoursAgo(1) }), NOW), null); @@ -8726,8 +8781,8 @@ function selfTest() { ...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: …and the finding names the threshold', h13row(domainCard(['domain:engine-core'], hoursAgo(26)), NOW).includes(`${DOMAIN_HALF_STATE_STALE_HOURS}h`), true); + t('H13: …and blames the healing loop, not inventory', h13row(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); @@ -8745,23 +8800,23 @@ function selfTest() { 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: body self-declaring P0 -> louder line', h13row(domainCard(['domain:engine-core'], hoursAgo(26), p0Body), NOW).includes('P0-SUSPECT'), true); + t('H13: …which prescribes the emergency-triage channel', h13row(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); + t('H13: a quiet body stays on the base line', h13row(domainCard(['domain:engine-core'], hoursAgo(26), { body: 'ordinary defect' }), NOW).includes('P0-SUSPECT'), false); // -- H18: `pm:retriage` aged past one triage cycle (2026-08-19/20 ruling) -- // Reuses `domainCard` — a generic (labels, updated_at, extra) issue builder, // not a domain-specific one despite the name. t('H18: retriage past the threshold, coexisting pm:queue -> finding', typeof h18RetriageAged(domainCard(['pm:retriage', 'pm:queue'], hoursAgo(3)), NOW), 'string'); - t('H18: …and the finding names the threshold', h18RetriageAged(domainCard(['pm:retriage', 'pm:queue'], hoursAgo(3)), NOW).includes(`${RETRIAGE_STALE_HOURS}h`), true); - t('H18: …and names the coexisting standing label', h18RetriageAged(domainCard(['pm:retriage', 'pm:queue'], hoursAgo(3)), NOW).includes('`pm:queue`'), true); - t('H18: multiple coexisting labels are all named', h18RetriageAged(domainCard(['pm:retriage', 'pm:blocked', 'pm:blocking'], hoursAgo(3)), NOW).includes('`pm:blocked`') && h18RetriageAged(domainCard(['pm:retriage', 'pm:blocked', 'pm:blocking'], hoursAgo(3)), NOW).includes('`pm:blocking`'), true); + t('H18: …and the finding names the threshold', h18row(domainCard(['pm:retriage', 'pm:queue'], hoursAgo(3)), NOW).includes(`${RETRIAGE_STALE_HOURS}h`), true); + t('H18: …and names the coexisting standing label', h18row(domainCard(['pm:retriage', 'pm:queue'], hoursAgo(3)), NOW).includes('`pm:queue`'), true); + t('H18: multiple coexisting labels are all named', h18row(domainCard(['pm:retriage', 'pm:blocked', 'pm:blocking'], hoursAgo(3)), NOW).includes('`pm:blocked`') && h18row(domainCard(['pm:retriage', 'pm:blocked', 'pm:blocking'], hoursAgo(3)), NOW).includes('`pm:blocking`'), true); // Under-threshold: fresh objection is normal intake latency, not a finding. t('H18: retriage under the threshold -> clean', h18RetriageAged(domainCard(['pm:retriage', 'pm:queue'], hoursAgo(1)), NOW), null); t('H18: exactly at the threshold -> clean (strictly beyond fires)', h18RetriageAged(domainCard(['pm:retriage', 'pm:queue'], hoursAgo(RETRIAGE_STALE_HOURS)), NOW), null); @@ -8769,10 +8824,10 @@ function selfTest() { t('H18: no pm:retriage label -> out of scope however old', h18RetriageAged(domainCard(['pm:queue'], hoursAgo(200)), NOW), null); // The disputed-target variant: `pm:retriage` alone, no coexisting `pm:*`. t('H18: retriage ALONE (no coexisting pm:* label) -> finding', typeof h18RetriageAged(domainCard(['pm:retriage'], hoursAgo(3)), NOW), 'string'); - t('H18: …and names the disputed-target note', h18RetriageAged(domainCard(['pm:retriage'], hoursAgo(3)), NOW).includes('异议对象不明'), true); - t('H18: …and does not claim a coexisting label it does not have', h18RetriageAged(domainCard(['pm:retriage'], hoursAgo(3)), NOW).includes('alongside its standing'), false); + t('H18: …and names the disputed-target note', h18row(domainCard(['pm:retriage'], hoursAgo(3)), NOW).includes('异议对象不明'), true); + t('H18: …and does not claim a coexisting label it does not have', h18row(domainCard(['pm:retriage'], hoursAgo(3)), NOW).includes('alongside its standing'), false); // A non-`pm:*` label (e.g. `domain:*`) never counts as the coexisting label. - t('H18: a domain: label is not counted as a coexisting pm:* label', h18RetriageAged(domainCard(['pm:retriage', 'domain:skills'], hoursAgo(3)), NOW).includes('异议对象不明'), true); + t('H18: a domain: label is not counted as a coexisting pm:* label', h18row(domainCard(['pm:retriage', 'domain:skills'], hoursAgo(3)), NOW).includes('异议对象不明'), true); // #4690 in miniature, same as H10–H13: unreadable must not read as fresh. t('H18: unreadable updated_at -> finding, not fresh', typeof h18RetriageAged(domainCard(['pm:retriage'], 'not-a-date'), NOW), 'string'); t('H18: absent updated_at -> finding, not fresh', typeof h18RetriageAged(domainCard(['pm:retriage'], undefined), NOW), 'string'); @@ -8866,19 +8921,19 @@ function selfTest() { // Direction A — the label carried with nothing targeting it. t('H14-A: pm:blocking with nothing targeting it -> finding', typeof h14BlockingCacheIncoherent(carded(7276, ['pm:queue', 'pm:blocking']), idx([])), 'string'); - t('H14-A: …and it names the stale-cache reading', h14BlockingCacheIncoherent(carded(7276, ['pm:blocking']), idx([])).includes('stale derived cache'), true); - t('H14-A: …and prescribes the derivation pass, never a label from here', h14BlockingCacheIncoherent(carded(7276, ['pm:blocking']), idx([])).includes('derivation pass'), true); - t('H14-A: …and says why stale is worse than absent', h14BlockingCacheIncoherent(carded(7276, ['pm:blocking']), idx([])).includes('with authority'), true); + t('H14-A: …and it names the stale-cache reading', h14row(carded(7276, ['pm:blocking']), idx([])).includes('stale derived cache'), true); + t('H14-A: …and prescribes the derivation pass, never a label from here', h14row(carded(7276, ['pm:blocking']), idx([])).includes('derivation pass'), true); + t('H14-A: …and says why stale is worse than absent', h14row(carded(7276, ['pm:blocking']), idx([])).includes('with authority'), true); // The repo-boundary wording (#10139): STALE reads as "no dependent in this // repo", never as exhaustive over the population, and the remedy is // conditional on a cross-repo check rather than an outright drop. - t('H14-A: …names the repo boundary', h14BlockingCacheIncoherent(carded(7276, ['pm:blocking']), idx([])).includes('no dependent found in this repo'), true); - t('H14-A: …and says cross-repo dependents are not swept', h14BlockingCacheIncoherent(carded(7276, ['pm:blocking']), idx([])).includes('cross-repo dependents are not swept'), true); - t('H14-A: …and the remedy is conditional on verifying cross-repo dependents', h14BlockingCacheIncoherent(carded(7276, ['pm:blocking']), idx([])).includes('verify cross-repo dependents before'), true); + t('H14-A: …names the repo boundary', h14row(carded(7276, ['pm:blocking']), idx([])).includes('no dependent found in this repo'), true); + t('H14-A: …and says cross-repo dependents are not swept', h14row(carded(7276, ['pm:blocking']), idx([])).includes('cross-repo dependents are not swept'), true); + t('H14-A: …and the remedy is conditional on verifying cross-repo dependents', h14row(carded(7276, ['pm:blocking']), idx([])).includes('verify cross-repo dependents before'), true); // The negative: the old exhaustive phrasing ("the full two-channel index", // instructing an unconditional drop) must be gone — it is what would have // told a reader to sever the live #7917 / objectui#4356 edge. - t('H14-A: …and the old exhaustive phrasing is GONE', h14BlockingCacheIncoherent(carded(7276, ['pm:blocking']), idx([])).includes('full two-channel index'), false); + t('H14-A: …and the old exhaustive phrasing is GONE', h14row(carded(7276, ['pm:blocking']), idx([])).includes('full two-channel index'), false); // The negative for direction A: the label is EARNED, so nothing to report. t( 'H14-A: pm:blocking with a real dependent -> clean', @@ -8889,8 +8944,8 @@ function selfTest() { // Direction B — targeted, but the cache never landed. const missingIdx = idx([carded(9650, ['pm:queue'], 'Blocked-by: #9832')]); t('H14-B: targeted without pm:blocking -> finding', typeof h14BlockingCacheIncoherent(carded(9832, ['bug', 'pm:dispatched', 'domain:cli']), missingIdx), 'string'); - t('H14-B: …and it names the waiting card', h14BlockingCacheIncoherent(carded(9832, ['pm:dispatched']), missingIdx).includes('#9650'), true); - t('H14-B: …and calls it an invisible unblocker', h14BlockingCacheIncoherent(carded(9832, ['pm:dispatched']), missingIdx).includes('selection order cannot see'), true); + t('H14-B: …and it names the waiting card', h14row(carded(9832, ['pm:dispatched']), missingIdx).includes('#9650'), true); + t('H14-B: …and calls it an invisible unblocker', h14row(carded(9832, ['pm:dispatched']), missingIdx).includes('selection order cannot see'), true); // The negative for direction B: no label and nobody waiting is the ordinary // shape of ~230 of this board's ~234 open cards. It must be silent, or the // row means nothing. @@ -8904,8 +8959,8 @@ function selfTest() { ); // Fan-out cap: named, then counted. const manyDeps = idx(Array.from({ length: 7 }, (_, i) => carded(100 + i, [], 'Blocked-by: #5'))); - t('H14-B: a large fan-out names the cap and counts the rest', h14BlockingCacheIncoherent(carded(5, ['pm:queue']), manyDeps).includes(`+${7 - BLOCKING_DEPENDENT_LIST_CAP} more`), true); - t('H14-B: …and reports the true total, not the capped one', h14BlockingCacheIncoherent(carded(5, ['pm:queue']), manyDeps).includes('targeted by 7 open card(s)'), true); + t('H14-B: a large fan-out names the cap and counts the rest', h14row(carded(5, ['pm:queue']), manyDeps).includes(`+${7 - BLOCKING_DEPENDENT_LIST_CAP} more`), true); + t('H14-B: …and reports the true total, not the capped one', h14row(carded(5, ['pm:queue']), manyDeps).includes('targeted by 7 open card(s)'), true); t('H14: a missing index does not crash and reads as untargeted', h14BlockingCacheIncoherent(carded(5, ['pm:blocking']), undefined) !== null, true); // Reverse verification against the LIVE board, 2026-08-19 (234 open cards). @@ -8925,7 +8980,7 @@ function selfTest() { ]; const liveIdx = idx(liveBodies); t('H14 reverse-verify: #7276 (the board\'s only pm:blocking card) -> stale finding', typeof h14BlockingCacheIncoherent(carded(7276, ['pm:queue', 'domain:devx', 'pm:blocking']), liveIdx), 'string'); - t('H14 reverse-verify: #9832 (targeted by #9650, unlabeled) -> missing finding naming #9650', h14BlockingCacheIncoherent(carded(9832, ['bug', 'pm:dispatched', 'domain:cli']), liveIdx).includes('#9650'), true); + t('H14 reverse-verify: #9832 (targeted by #9650, unlabeled) -> missing finding naming #9650', h14row(carded(9832, ['bug', 'pm:dispatched', 'domain:cli']), liveIdx).includes('#9650'), true); t('H14 reverse-verify: #9919 (targeted by #9249, unlabeled) -> missing finding', typeof h14BlockingCacheIncoherent(carded(9919, ['pm:queue', 'repo:cloud']), liveIdx), 'string'); // …and the four measured NON-findings from the same reading, which is what // makes the six above readable as signal rather than as a predicate that @@ -8965,8 +9020,8 @@ function selfTest() { const epic9465 = carded(9465, ['domain:devx', 'pm:epic', 'pm:blocking']); const decision9968 = carded(9968, ['pm:decision', 'pm:blocking']); // The defect, pinned: this is what the body-only index reported. - t('H14 false-stale: #9465 reads STALE against a body-only index', h14BlockingCacheIncoherent(epic9465, bodyOnlyIdx)?.includes('stale derived cache'), true); - t('H14 false-stale: #9968 reads STALE against a body-only index', h14BlockingCacheIncoherent(decision9968, bodyOnlyIdx)?.includes('stale derived cache'), true); + t('H14 false-stale: #9465 reads STALE against a body-only index', h14row(epic9465, bodyOnlyIdx).includes('stale derived cache'), true); + t('H14 false-stale: #9968 reads STALE against a body-only index', h14row(decision9968, bodyOnlyIdx).includes('stale derived cache'), true); // The fix: the same two cards against the two-channel index. t('H14 false-stale: #9465 is CLEAN once comment edges are read', h14BlockingCacheIncoherent(epic9465, unionIdx), null); t('H14 false-stale: #9968 is CLEAN once comment edges are read', h14BlockingCacheIncoherent(decision9968, unionIdx), null); @@ -8974,9 +9029,9 @@ function selfTest() { t('H14 false-stale: …and #9969 and #9652 both point at #9968', unionIdx.get(9968).join(','), '9969,9652'); // Direction B rides the same union: a comment-only edge is enough to call a // card an invisible unblocker. - t('H14-B: a comment-only edge produces a missing-cache row', h14BlockingCacheIncoherent(carded(9465, ['domain:devx']), unionIdx)?.includes('#9709'), true); - t('H14-B: …and the sentence names the channel pair', h14BlockingCacheIncoherent(carded(9465, ['domain:devx']), unionIdx).includes('body or comment'), true); - t('H14-A: …the stale sentence names both channels too', h14BlockingCacheIncoherent(epic9465, bodyOnlyIdx).includes('body OR comment'), true); + t('H14-B: a comment-only edge produces a missing-cache row', h14row(carded(9465, ['domain:devx']), unionIdx).includes('#9709'), true); + t('H14-B: …and the sentence names the channel pair', h14row(carded(9465, ['domain:devx']), unionIdx).includes('body or comment'), true); + t('H14-A: …the stale sentence names both channels too', h14row(epic9465, bodyOnlyIdx).includes('body OR comment'), true); // -- H14 under an INCOMPLETE index (a gated comment fetch failed) ---------- // @@ -8986,7 +9041,7 @@ function selfTest() { t('H14-A: stale is SUSPENDED when the index is known incomplete', h14BlockingCacheIncoherent(epic9465, bodyOnlyIdx, { indexComplete: false }), null); t('H14-A: …and still fires when the index is complete', typeof h14BlockingCacheIncoherent(epic9465, bodyOnlyIdx, { indexComplete: true }), 'string'); t('H14-A: …and completeness defaults to true for body-only callers', typeof h14BlockingCacheIncoherent(epic9465, bodyOnlyIdx), 'string'); - t('H14-B: missing SURVIVES an incomplete index', h14BlockingCacheIncoherent(carded(9465, ['domain:devx']), unionIdx, { indexComplete: false })?.includes('#9709'), true); + t('H14-B: missing SURVIVES an incomplete index', h14row(carded(9465, ['domain:devx']), unionIdx, { indexComplete: false }).includes('#9709'), true); t('H14-B: …and an earned label stays clean either way', h14BlockingCacheIncoherent(epic9465, unionIdx, { indexComplete: false }), null); // The summary line carries the third `read X of Y` pair, and says out loud @@ -9111,17 +9166,18 @@ function selfTest() { // POSITIVE — a closed target fires. const expired10112 = h19BlockOutlivedBlocker(blockedCard(10112), [target(10126, 'closed', { closedAt: '2026-08-20T09:03:37Z' })]); + const expired10112Row = String(expired10112 ?? ''); t('H19: a CLOSED target fires', typeof expired10112, 'string'); - t('H19: …and names the target', expired10112.includes('`#10126`'), true); - t('H19: …with the close timestamp, so the latency is readable off the row', expired10112.includes('closed 2026-08-20T09:03:37Z'), true); - t('H19: …and says the block outlived its blocker', expired10112.includes('outlived its blocker'), true); - t('H19: …and says nothing else here asks this question', expired10112.includes('H4 asks whether the line EXISTS'), true); - t('H19: …and hands the release to the unlock sweep\'s double-checks', expired10112.includes('放行双查'), true); - t('H19: …naming double-check ① (most recent conversion comment)', expired10112.includes('MOST RECENT conversion comment'), true); - t('H19: …and double-check ② (a newer merged PR refuses release)', expired10112.includes('MERGED PR newer than that conversion comment'), true); - t('H19: …and forbids a label written from this script', expired10112.includes('never a label written from this script'), true); - t('H19: a fully discharged block says every target is closed', expired10112.includes('Every target it names is closed'), true); - t('H19: …and does not claim a partial discharge', expired10112.includes('PARTIAL'), false); + t('H19: …and names the target', expired10112Row.includes('`#10126`'), true); + t('H19: …with the close timestamp, so the latency is readable off the row', expired10112Row.includes('closed 2026-08-20T09:03:37Z'), true); + t('H19: …and says the block outlived its blocker', expired10112Row.includes('outlived its blocker'), true); + t('H19: …and says nothing else here asks this question', expired10112Row.includes('H4 asks whether the line EXISTS'), true); + t('H19: …and hands the release to the unlock sweep\'s double-checks', expired10112Row.includes('放行双查'), true); + t('H19: …naming double-check ① (most recent conversion comment)', expired10112Row.includes('MOST RECENT conversion comment'), true); + t('H19: …and double-check ② (a newer merged PR refuses release)', expired10112Row.includes('MERGED PR newer than that conversion comment'), true); + t('H19: …and forbids a label written from this script', expired10112Row.includes('never a label written from this script'), true); + t('H19: a fully discharged block says every target is closed', expired10112Row.includes('Every target it names is closed'), true); + t('H19: …and does not claim a partial discharge', expired10112Row.includes('PARTIAL'), false); // NEGATIVE — an open target is clean, and silence here is a real reading. t('H19: an OPEN target -> clean', h19BlockOutlivedBlocker(blockedCard(1), [target(2, 'open')]), null); @@ -9132,38 +9188,40 @@ function selfTest() { // PARTIAL — one of two closed. Fires, and says it is partial. const partial = h19BlockOutlivedBlocker(blockedCard(1), [target(2, 'closed', { closedAt: '2026-08-20T07:58:08Z' }), target(3, 'open')]); + const partialRow = String(partial ?? ''); t('H19: one closed of two still fires', typeof partial, 'string'); - t('H19: …and reports the count as 1 of 2', partial.includes('1 of 2 `Blocked-by:` target(s)'), true); - t('H19: …names it a PARTIAL discharge', partial.includes('PARTIAL'), true); - t('H19: …names the target that is still open', partial.includes('`#3`'), true); - t('H19: …and does not decide the card is unblocked', partial.includes('it does not decide it'), true); - t('H19: two closed of two reads as 2 of 2', h19BlockOutlivedBlocker(blockedCard(1), [target(2, 'closed'), target(3, 'closed')]).includes('2 of 2'), true); + t('H19: …and reports the count as 1 of 2', partialRow.includes('1 of 2 `Blocked-by:` target(s)'), true); + t('H19: …names it a PARTIAL discharge', partialRow.includes('PARTIAL'), true); + t('H19: …names the target that is still open', partialRow.includes('`#3`'), true); + t('H19: …and does not decide the card is unblocked', partialRow.includes('it does not decide it'), true); + t('H19: two closed of two reads as 2 of 2', h19row(blockedCard(1), [target(2, 'closed'), target(3, 'closed')]).includes('2 of 2'), true); // UNRESOLVED — never reads as clean, and never reads as closed either. const unresolvedOnly = h19BlockOutlivedBlocker(blockedCard(1), [foreign('objectstack-ai/cloud', 88, 'unresolved', { detail: 'HTTP 404' })]); + const unresolvedOnlyRow = String(unresolvedOnly ?? ''); t('H19: an UNRESOLVED target fires rather than reading clean', typeof unresolvedOnly, 'string'); - t('H19: …saying the liveness is UNJUDGED', unresolvedOnly.includes('UNJUDGED, not confirmed'), true); - t('H19: …and never claims the block is expired', unresolvedOnly.includes('outlived its blocker. Nothing else here'), false); - t('H19: …citing the unreadable-is-not-absent rule', unresolvedOnly.includes('#4690'), true); - t('H19: …naming the cross-repo target in full owner/repo#N form', unresolvedOnly.includes('`objectstack-ai/cloud#88`'), true); - t('H19: …with the observed status', unresolvedOnly.includes('HTTP 404'), true); - t('H19: …and still routes the release through the unlock sweep', unresolvedOnly.includes('放行双查'), true); + t('H19: …saying the liveness is UNJUDGED', unresolvedOnlyRow.includes('UNJUDGED, not confirmed'), true); + t('H19: …and never claims the block is expired', unresolvedOnlyRow.includes('outlived its blocker. Nothing else here'), false); + t('H19: …citing the unreadable-is-not-absent rule', unresolvedOnlyRow.includes('#4690'), true); + t('H19: …naming the cross-repo target in full owner/repo#N form', unresolvedOnlyRow.includes('`objectstack-ai/cloud#88`'), true); + t('H19: …with the observed status', unresolvedOnlyRow.includes('HTTP 404'), true); + t('H19: …and still routes the release through the unlock sweep', unresolvedOnlyRow.includes('放行双查'), true); // UNJUDGED must not read at judged-row weight (#11218 half 2). The premise // re-verification asked for exactly this check against the live report. - t('H19: …and says UNJUDGED is not a quiet row', unresolvedOnly.includes('must not be skimmed'), true); - t('H19: …and equates it with having read nothing at all', unresolvedOnly.includes('exactly as unverified as if nothing had been read'), true); + t('H19: …and says UNJUDGED is not a quiet row', unresolvedOnlyRow.includes('must not be skimmed'), true); + t('H19: …and equates it with having read nothing at all', unresolvedOnlyRow.includes('exactly as unverified as if nothing had been read'), true); // With NO repo probe taken, the wording stays undiagnosed — the pre-#11218 // posture, preserved rather than silently upgraded. - t('H19: an unprobed cross-repo target claims no cause', unresolvedOnly.includes('resolves only when its repo answers'), true); - t('H19: …and asserts nothing about the repo either way', unresolvedOnly.includes('is NOT readable') || unresolvedOnly.includes('IS readable'), false); + t('H19: an unprobed cross-repo target claims no cause', unresolvedOnlyRow.includes('resolves only when its repo answers'), true); + t('H19: …and asserts nothing about the repo either way', unresolvedOnlyRow.includes('is NOT readable') || unresolvedOnlyRow.includes('IS readable'), false); // -- The MEASURED cause (#11218 half 1, the half that can land) ------------ // // A cross-repo 404 is ambiguous; `GET /repos//` disambiguates // it. The row reports the measurement, never an inference from the issue 404. - const scopeGap = h19BlockOutlivedBlocker(blockedCard(10938), [ + const scopeGap = String(h19BlockOutlivedBlocker(blockedCard(10938), [ foreign('objectstack-ai/cloud', 944, 'unresolved', { detail: 'HTTP 404', repoReadable: false }), - ]); + ]) ?? ''); t('H19 cause: an unreadable REPO is named per target', scopeGap.includes("`objectstack-ai/cloud` is NOT readable to this sweep's credential"), true); t('H19 cause: …and the observed status is still carried', scopeGap.includes('HTTP 404'), true); t('H19 cause: …and it is declared measured, not inferred', scopeGap.includes('measured directly'), true); @@ -9172,39 +9230,40 @@ function selfTest() { t('H19 cause: …and the credential call is routed to routing/security', scopeGap.includes('routing/security'), true); t('H19 cause: …and ⛔ the card is not the place to fix it', scopeGap.includes('Do not "fix" it on the card'), true); // The OTHER leg of the same probe: repo readable, so the number is not there. - const missingNumber = h19BlockOutlivedBlocker(blockedCard(1), [ + const missingNumber = String(h19BlockOutlivedBlocker(blockedCard(1), [ foreign('objectstack-ai/objectui', 999999, 'unresolved', { detail: 'HTTP 404', repoReadable: true }), - ]); + ]) ?? ''); t('H19 cause: a READABLE repo means the number is not there', missingNumber.includes('IS readable, so that number is not there'), true); t('H19 cause: …and that is NOT reported as a scope gap', missingNumber.includes('is NOT readable'), false); t('H19 cause: …nor as an accepted cross-repo limit', missingNumber.includes('standing, ACCEPTED'), false); // A failed probe (`null`) picks NEITHER side — #4690 at the probe's own level. - const probeFailed = h19BlockOutlivedBlocker(blockedCard(1), [ + const probeFailed = String(h19BlockOutlivedBlocker(blockedCard(1), [ foreign('objectstack-ai/cloud', 88, 'unresolved', { detail: 'HTTP 500', repoReadable: null }), - ]); + ]) ?? ''); t('H19 cause: an unreadable PROBE names no cause at all', probeFailed.includes('IS readable') || probeFailed.includes('is NOT readable'), false); t('H19 cause: …but the target and status are still named', probeFailed.includes('`objectstack-ai/cloud#88`') && probeFailed.includes('HTTP 500'), true); // Mixed: only the scope-gapped ones are counted in the loud clause. - const mixedCause = h19BlockOutlivedBlocker(blockedCard(1), [ + const mixedCause = String(h19BlockOutlivedBlocker(blockedCard(1), [ foreign('objectstack-ai/cloud', 944, 'unresolved', { detail: 'HTTP 404', repoReadable: false }), foreign('objectstack-ai/objectui', 4356, 'unresolved', { detail: 'HTTP 404', repoReadable: true }), - ]); + ]) ?? ''); t('H19 cause: the scope-gap count is the unreadable-repo ones only', mixedCause.includes('1 of them are unjudgeable'), true); // A LOCAL target is never probed, and renders exactly as it always did. - const localUnresolved = h19BlockOutlivedBlocker(blockedCard(1), [target(77, 'unresolved', { detail: 'HTTP 404' })]); + const localUnresolved = String(h19BlockOutlivedBlocker(blockedCard(1), [target(77, 'unresolved', { detail: 'HTTP 404' })]) ?? ''); t('H19 cause: a LOCAL unresolved target claims no repo reading', localUnresolved.includes('readable'), false); t('H19 cause: …and is still named with its status', localUnresolved.includes('`#77` (HTTP 404)'), true); // An unresolved target alongside an open one still fires, and says which. const mixedUnresolved = h19BlockOutlivedBlocker(blockedCard(1), [target(2, 'open'), foreign('objectstack-ai/objectui', 4356, 'unresolved', { detail: 'HTTP 403' })]); + const mixedUnresolvedRow = String(mixedUnresolved ?? ''); t('H19: unresolved + open still fires', typeof mixedUnresolved, 'string'); - t('H19: …and reports the resolved remainder as open', mixedUnresolved.includes("The card's other 1 target(s) did resolve, and are still open."), true); + t('H19: …and reports the resolved remainder as open', mixedUnresolvedRow.includes("The card's other 1 target(s) did resolve, and are still open."), true); // Closed AND unresolved: the closed row leads, the gap is appended. - const closedAndUnresolved = h19BlockOutlivedBlocker(blockedCard(1), [target(2, 'closed'), foreign('objectstack-ai/cloud', 88, 'unresolved', { detail: 'HTTP 404' })]); + const closedAndUnresolved = String(h19BlockOutlivedBlocker(blockedCard(1), [target(2, 'closed'), foreign('objectstack-ai/cloud', 88, 'unresolved', { detail: 'HTTP 404' })]) ?? ''); t('H19: a closed target leads even when another is unresolved', closedAndUnresolved.includes('outlived its blocker'), true); t('H19: …and the unresolved one is still declared unjudged', closedAndUnresolved.includes('unjudged, not open'), true); // The render budget: many targets are capped and the row says it counted. - const manyClosed = h19BlockOutlivedBlocker(blockedCard(1), [2, 3, 4, 5, 6, 7, 8].map((n) => target(n, 'closed'))); + const manyClosed = String(h19BlockOutlivedBlocker(blockedCard(1), [2, 3, 4, 5, 6, 7, 8].map((n) => target(n, 'closed'))) ?? ''); t('H19: the target list is capped at the render budget', manyClosed.includes(`+${7 - H19_TARGET_LIST_CAP} more`), true); t('H19: …and the count is the full one, not the shown one', manyClosed.includes('7 of 7'), true); @@ -9224,7 +9283,7 @@ function selfTest() { t('H19 measured ①: …a body-only read would have found nothing', keysOf(blockedCard(10112, 'body carries no line')), ''); t( 'H19 measured ①: …and the card fires once its target is resolved closed', - h19BlockOutlivedBlocker(blockedCard(10112, 'body carries no line'), [target(10126, 'closed', { closedAt: '2026-08-20T09:03:37Z' })]).includes('`#10126` (closed 2026-08-20T09:03:37Z)'), + h19row(blockedCard(10112, 'body carries no line'), [target(10126, 'closed', { closedAt: '2026-08-20T09:03:37Z' })]).includes('`#10126` (closed 2026-08-20T09:03:37Z)'), true, ); @@ -9238,7 +9297,7 @@ function selfTest() { t('H19 measured ②: the decorated body line yields the target', keysOf(blockedCard(10063, liveDecoratedBody)), 'objectstack-ai/objectstack#9612'); t( 'H19 measured ②: …and the card fires once its target is resolved closed', - h19BlockOutlivedBlocker(blockedCard(10063, liveDecoratedBody), [target(9612, 'closed', { closedAt: '2026-08-20T07:58:08Z' })]).includes('closed 2026-08-20T07:58:08Z'), + h19row(blockedCard(10063, liveDecoratedBody), [target(9612, 'closed', { closedAt: '2026-08-20T07:58:08Z' })]).includes('closed 2026-08-20T07:58:08Z'), true, ); // The prose around the line names #9612 four more times; only the DIRECTIVE @@ -9336,18 +9395,19 @@ function selfTest() { const refState = (branch, state, detail = null) => ({ branch, state, detail }); const absentRef = [refState('claude/issue-8878-dispatch-latency', 'absent')]; const fired20 = h20DispatchedNoBranchRef(dispatchedCard(), gov(claim8878), absentRef, NOW_20); + const fired20Row = String(fired20 ?? ''); t('H20: the measured #8878 shape FIRES', typeof fired20, 'string'); - t('H20: …and names the branch that has no ref', fired20.includes('`claude/issue-8878-dispatch-latency`'), true); - t('H20: …and says NO SUCH REMOTE REF EXISTS', fired20.includes('NO SUCH REMOTE REF EXISTS'), true); - t('H20: …with the measured age and the threshold', fired20.includes(`~74 min after the claim was posted (threshold ${DISPATCHED_NO_REF_STALE_MINUTES} min)`), true); - t('H20: …and states the two-acts mechanism', fired20.includes('Claiming and dispatching are two acts with a gap between them'), true); - t('H20: …and that it is invisible from the card itself', fired20.includes('invisible from the card'), true); - t('H20: …and warns the symptom is identical to a DEAD agent', fired20.includes('IDENTICAL to a dev agent that died'), true); - t('H20: …naming the opposite remedies rather than diagnosing one', fired20.includes('a dead agent needs a probe, an undispatched claim needs a dispatch'), true); - t('H20: …and carries the ⛔ keying rule verbatim', fired20.includes('keys on NO REF AT ALL, never on "no PR yet"'), true); - t('H20: …with the reason a PR key would be wrong', fired20.includes('legitimately has a ref and no PR for over an hour'), true); - t('H20: …and routes an already-merged delivery to H8 instead', fired20.includes("the missing paired write is H8's"), true); - t('H20: …and forbids a label written from this script', fired20.includes('never a label written from this script'), true); + t('H20: …and names the branch that has no ref', fired20Row.includes('`claude/issue-8878-dispatch-latency`'), true); + t('H20: …and says NO SUCH REMOTE REF EXISTS', fired20Row.includes('NO SUCH REMOTE REF EXISTS'), true); + t('H20: …with the measured age and the threshold', fired20Row.includes(`~74 min after the claim was posted (threshold ${DISPATCHED_NO_REF_STALE_MINUTES} min)`), true); + t('H20: …and states the two-acts mechanism', fired20Row.includes('Claiming and dispatching are two acts with a gap between them'), true); + t('H20: …and that it is invisible from the card itself', fired20Row.includes('invisible from the card'), true); + t('H20: …and warns the symptom is identical to a DEAD agent', fired20Row.includes('IDENTICAL to a dev agent that died'), true); + t('H20: …naming the opposite remedies rather than diagnosing one', fired20Row.includes('a dead agent needs a probe, an undispatched claim needs a dispatch'), true); + t('H20: …and carries the ⛔ keying rule verbatim', fired20Row.includes('keys on NO REF AT ALL, never on "no PR yet"'), true); + t('H20: …with the reason a PR key would be wrong', fired20Row.includes('legitimately has a ref and no PR for over an hour'), true); + t('H20: …and routes an already-merged delivery to H8 instead', fired20Row.includes("the missing paired write is H8's"), true); + t('H20: …and forbids a label written from this script', fired20Row.includes('never a label written from this script'), true); t('H20: not a loud finding', isLoudFinding(fired20), false); // ★ The regression pin the filing card asked for by name: a dev inside a long @@ -9388,24 +9448,25 @@ function selfTest() { [refState('claude/issue-8878-dispatch-latency', 'unreadable', 'HTTP 500')], NOW_20, ); + const unread20Row = String(unread20 ?? ''); t('H20 unreadable: does NOT read as healthy', unread20 === null, false); t('H20 unreadable: fires its own row', typeof unread20, 'string'); - t('H20 unreadable: …which says the dispatch is UNJUDGED', unread20.includes('UNJUDGED, not confirmed'), true); - t('H20 unreadable: …and reports the observed status', unread20.includes('HTTP 500'), true); - t('H20 unreadable: …and does NOT assert the finding it did not measure', unread20.includes('NO SUCH REMOTE REF EXISTS'), false); - t('H20 unreadable: …citing the unread-is-not-absent rule', unread20.includes('#4690'), true); - t('H20 unreadable: …and refuses to guess WHY', unread20.includes('the cause is not guessed at'), true); + t('H20 unreadable: …which says the dispatch is UNJUDGED', unread20Row.includes('UNJUDGED, not confirmed'), true); + t('H20 unreadable: …and reports the observed status', unread20Row.includes('HTTP 500'), true); + t('H20 unreadable: …and does NOT assert the finding it did not measure', unread20Row.includes('NO SUCH REMOTE REF EXISTS'), false); + t('H20 unreadable: …citing the unread-is-not-absent rule', unread20Row.includes('#4690'), true); + t('H20 unreadable: …and refuses to guess WHY', unread20Row.includes('the cause is not guessed at'), true); t('H20 unreadable: not a loud finding either', isLoudFinding(unread20), false); // Mixed readings. "No ref at all" is a claim about EVERY branch the card // names, so one unread probe is enough to withhold it — and one existing ref // is enough to call the card healthy. - const mixedUnread20 = h20DispatchedNoBranchRef( + const mixedUnread20 = String(h20DispatchedNoBranchRef( dispatchedCard(), gov([claimRow(minsAgo20(74), 'Claim: seat.\nBranch: `claude/issue-1-a`\nBranch: `claude/issue-1-b`')]), [refState('claude/issue-1-a', 'absent'), refState('claude/issue-1-b', 'unreadable', 'HTTP 502')], NOW_20, - ); + ) ?? ''); t('H20 mixed: absent + unreadable takes the quieter row', mixedUnread20.includes('UNJUDGED, not confirmed'), true); t('H20 mixed: …and still names the branch that resolved absent', mixedUnread20.includes('`claude/issue-1-a`'), true); t('H20 mixed: …explaining why one unread probe withholds the finding', mixedUnread20.includes('one unread probe is enough to withhold it'), true); @@ -9427,15 +9488,16 @@ function selfTest() { t('H20: absent ref states -> no row', h20DispatchedNoBranchRef(dispatchedCard(), gov(claim8878), undefined, NOW_20), null); t('H20: a missing issue does not crash', h20DispatchedNoBranchRef(undefined, gov(claim8878), absentRef, NOW_20), null); const unstamped20 = h20DispatchedNoBranchRef(dispatchedCard(), gov([claimRow('not-a-date', claimBody8878)]), absentRef, NOW_20); + const unstamped20Row = String(unstamped20 ?? ''); t('H20: an unreadable claim timestamp fires rather than reading fresh', typeof unstamped20, 'string'); - t('H20: …and says so in place of an age', unstamped20.includes('an unreadable claim timestamp (which must not read as fresh)'), true); + t('H20: …and says so in place of an age', unstamped20Row.includes('an unreadable claim timestamp (which must not read as fresh)'), true); const many20 = Array.from({ length: 7 }, (_, i) => refState(`claude/issue-1-b${i}`, 'absent')); - const capped20 = h20DispatchedNoBranchRef( + const capped20 = String(h20DispatchedNoBranchRef( dispatchedCard(), gov([claimRow(minsAgo20(74), `Claim: seat.\n${many20.map((r) => `Branch: \`${r.branch}\``).join('\n')}`)]), many20, NOW_20, - ); + ) ?? ''); t('H20: the branch list is capped at the render budget', capped20.includes(`+${7 - H20_BRANCH_LIST_CAP} more`), true); // The summary line's fifth `read X of Y` pair — H19's shape, and owed for the @@ -9493,27 +9555,31 @@ function selfTest() { 'delivery' in over ? over.delivery : noDelivery, NOW_27, ); + // Same reason as H8's `halvesRow`: `dead27` is three-valued, and the + // `typeof dead27()` / `dead27(…) === null` cases below assert precisely the + // nullability that stringifying `dead27` itself would erase. + const dead27Row = (...args) => String(dead27(...args) ?? ''); // ★ The finding itself, and the facts the sentence must carry. t('H27: a frozen branch + no PR past 24h -> finding', typeof dead27(), 'string'); - t('H27: …and names the branch', dead27().includes(`\`${BR_27}\``), true); - t('H27: …and says the branch has not moved since the claim', dead27().includes('NOT MOVED SINCE IT WAS CLAIMED'), true); - t('H27: …and reports the age against the protocol threshold', dead27().includes(`threshold ${DEAD_CLAIM_STALE_HOURS}h`), true); - t('H27: …calling that threshold the protocol\'s own line, not a heuristic', dead27().includes("protocol's own stale-claim line"), true); - t('H27: …and states the measured ~26h age', dead27().includes('~26h after the claim was posted'), true); - t('H27: …and names the lane-block consequence, not just the silence', dead27().includes('mutual-exclusion read'), true); - t('H27: …and explains WHY H20 cannot see it', dead27().includes('pushing the empty branch the first action'), true); - t('H27: …and rules out the pre-window merged delivery first', dead27().includes(`${MERGED_WINDOW_PAGES} pages`), true); + t('H27: …and names the branch', dead27Row().includes(`\`${BR_27}\``), true); + t('H27: …and says the branch has not moved since the claim', dead27Row().includes('NOT MOVED SINCE IT WAS CLAIMED'), true); + t('H27: …and reports the age against the protocol threshold', dead27Row().includes(`threshold ${DEAD_CLAIM_STALE_HOURS}h`), true); + t('H27: …calling that threshold the protocol\'s own line, not a heuristic', dead27Row().includes("protocol's own stale-claim line"), true); + t('H27: …and states the measured ~26h age', dead27Row().includes('~26h after the claim was posted'), true); + t('H27: …and names the lane-block consequence, not just the silence', dead27Row().includes('mutual-exclusion read'), true); + t('H27: …and explains WHY H20 cannot see it', dead27Row().includes('pushing the empty branch the first action'), true); + t('H27: …and rules out the pre-window merged delivery first', dead27Row().includes(`${MERGED_WINDOW_PAGES} pages`), true); t('H27: not a loud finding', isLoudFinding(dead27()), false); // ★ Report-only, and specifically NOT a reclaim — the protocol protects a // claim whose branch carries commits, so this row must never read as // authority to drop an assignee. - t('H27: the remedy is the recovery inspection', dead27().includes('post-kill recovery'), true); - t('H27: …naming all three recovery states', dead27().includes('on the remote / on the container disk only / gone'), true); - t('H27: …and the UNVERIFIED hand-off', dead27().includes('flagged UNVERIFIED'), true); - t('H27: …quoting the protocol rule that forbids reclaiming this card', dead27().includes('有带提交活分支的认领永不回收'), true); - t('H27: …and never a label written from this script', dead27().includes('Never a label written from this script'), true); + t('H27: the remedy is the recovery inspection', dead27Row().includes('post-kill recovery'), true); + t('H27: …naming all three recovery states', dead27Row().includes('on the remote / on the container disk only / gone'), true); + t('H27: …and the UNVERIFIED hand-off', dead27Row().includes('flagged UNVERIFIED'), true); + t('H27: …quoting the protocol rule that forbids reclaiming this card', dead27Row().includes('有带提交活分支的认领永不回收'), true); + t('H27: …and never a label written from this script', dead27Row().includes('Never a label written from this script'), true); // ★ Disjoint from H20 BY CONSTRUCTION, in both directions, on one fixture. const absent27 = [{ branch: BR_27, state: 'absent', detail: null, headCommittedAt: null }]; @@ -9531,10 +9597,10 @@ function selfTest() { t('H27: activity is measured against the CLAIM, not the threshold', dead27({ refs: frozen('2026-08-23T05:47:00Z') }), null); // Three-valued, never two (#4690): an unreadable comparison is not a "no". t('H27: an unreadable head timestamp does NOT read as healthy', dead27({ refs: frozen(null) }) === null, false); - t('H27: …and fires the quieter UNJUDGED row instead', dead27({ refs: frozen(null) }).includes('UNJUDGED, not confirmed healthy'), true); - t('H27: …which does not assert the finding it did not measure', dead27({ refs: frozen(null) }).includes('NOT MOVED SINCE IT WAS CLAIMED'), false); - t('H27: …citing the unread-is-not-absent rule', dead27({ refs: frozen(null) }).includes('#4690'), true); - t('H27: an absent head field reads as unknown, not as an old date', dead27({ refs: [{ branch: BR_27, state: 'exists' }] }).includes('UNJUDGED'), true); + t('H27: …and fires the quieter UNJUDGED row instead', dead27Row({ refs: frozen(null) }).includes('UNJUDGED, not confirmed healthy'), true); + t('H27: …which does not assert the finding it did not measure', dead27Row({ refs: frozen(null) }).includes('NOT MOVED SINCE IT WAS CLAIMED'), false); + t('H27: …citing the unread-is-not-absent rule', dead27Row({ refs: frozen(null) }).includes('#4690'), true); + t('H27: an absent head field reads as unknown, not as an old date', dead27Row({ refs: [{ branch: BR_27, state: 'exists' }] }).includes('UNJUDGED'), true); t('H27: branchMovedSinceClaim is three-valued', [branchMovedSinceClaim(frozen()[0], claim27()), branchMovedSinceClaim(frozen('2026-08-23T09:00:00Z')[0], claim27()), branchMovedSinceClaim(frozen(null)[0], claim27())].join(','), 'false,true,'); // ★ The delivery term, through H8's own relation so the two cannot drift. @@ -9573,10 +9639,11 @@ function selfTest() { t('H27: a young claim -> no row even with a frozen branch', dead27({ claim: claim27('2026-08-24T04:00:00Z') }), null); t('H27: exactly AT the threshold is not past it', dead27({ claim: claim27(new Date(NOW_27 - DEAD_CLAIM_STALE_HOURS * 3_600_000).toISOString()) }), null); const unstamped27 = dead27({ claim: claim27('not-a-date') }); + const unstamped27Row = String(unstamped27 ?? ''); t('H27: an unreadable claim timestamp does not read as fresh', unstamped27 === null, false); - t('H27: …and yields the UNJUDGED row (the comparison is impossible)', unstamped27.includes('UNJUDGED'), true); + t('H27: …and yields the UNJUDGED row (the comparison is impossible)', unstamped27Row.includes('UNJUDGED'), true); const many27 = Array.from({ length: 7 }, (_, i) => ({ branch: `claude/issue-1-b${i}`, state: 'exists', headCommittedAt: '2026-08-22T09:00:00Z' })); - t('H27: the branch list is capped at the render budget', dead27({ refs: many27 }).includes(`+${7 - H20_BRANCH_LIST_CAP} more`), true); + t('H27: the branch list is capped at the render budget', dead27Row({ refs: many27 }).includes(`+${7 - H20_BRANCH_LIST_CAP} more`), true); // -- H16: open non-draft PR stuck in a merge conflict (2026-08-19 incident) -- // The single-PR payload shape, since `mergeable_state` is absent from the @@ -9592,12 +9659,12 @@ function selfTest() { } = {}) => ({ draft, mergeable_state, auto_merge, head, body, updated_at: updated, merged_at }); t('H16: dirty beyond the threshold -> finding', typeof h16StuckMergeConflict(conflictPr(), NOW), 'string'); - t('H16: …and the finding names the threshold', h16StuckMergeConflict(conflictPr(), NOW).includes(`${MERGE_CONFLICT_STALE_HOURS}h`), true); - t('H16: …and names the platform state it read', h16StuckMergeConflict(conflictPr(), NOW).includes('mergeable_state: dirty'), true); - t('H16: …and prescribes the merge-and-resolve remedy', h16StuckMergeConflict(conflictPr(), NOW).includes('merges `main` into the branch'), true); + t('H16: …and the finding names the threshold', h16row(conflictPr(), NOW).includes(`${MERGE_CONFLICT_STALE_HOURS}h`), true); + t('H16: …and names the platform state it read', h16row(conflictPr(), NOW).includes('mergeable_state: dirty'), true); + t('H16: …and prescribes the merge-and-resolve remedy', h16row(conflictPr(), NOW).includes('merges `main` into the branch'), true); // The proxy must be DECLARED in the row, not silently substituted: a reader // shown "~4h" has to know it is silence on the PR, not the conflict's age. - t('H16: …and declares the age is the PR\'s updated_at, not the conflict\'s', h16StuckMergeConflict(conflictPr(), NOW).includes("Age is the PR's `updated_at`, not the conflict's"), true); + t('H16: …and declares the age is the PR\'s updated_at, not the conflict\'s', h16row(conflictPr(), NOW).includes("Age is the PR's `updated_at`, not the conflict's"), true); t('H16: dirty within the threshold -> clean (a fresh push is mid-resolution)', h16StuckMergeConflict(conflictPr({ updated: hoursAgo(1) }), NOW), null); t('H16: exactly at the threshold -> clean (strictly beyond fires)', h16StuckMergeConflict(conflictPr({ updated: hoursAgo(MERGE_CONFLICT_STALE_HOURS) }), NOW), null); t('H16: draft is out of scope however old (parked deliberately)', h16StuckMergeConflict(conflictPr({ draft: true, updated: hoursAgo(200) }), NOW), null); @@ -9626,16 +9693,16 @@ function selfTest() { // `auto_merge` as finding-reducing and is right to; here the arming is what // made every proxy signal read healthy while the PR went nowhere. t('H16: armed auto-merge does NOT suppress the row', typeof h16StuckMergeConflict(conflictPr({ auto_merge: { merge_method: 'squash' } }), NOW), 'string'); - t('H16: …and the row says auto-merge does not resolve conflicts', h16StuckMergeConflict(conflictPr({ auto_merge: { merge_method: 'squash' } }), NOW).includes('does NOT resolve conflicts'), true); + t('H16: …and the row says auto-merge does not resolve conflicts', h16row(conflictPr({ auto_merge: { merge_method: 'squash' } }), NOW).includes('does NOT resolve conflicts'), true); // The contrast that makes the divergence deliberate rather than an oversight: // one PR row, two predicates, opposite readings of the same armed field. t('H16: …while H12 stays clean on that same armed PR (the divergence is by design)', h12OrphanLanding(conflictPr({ auto_merge: { merge_method: 'squash' }, updated: hoursAgo(50) }), NOW), null); // The held-card clause — the row names the delivery, not only the branch. - t('H16: a `Fixes #N` body names the card it is holding', h16StuckMergeConflict(conflictPr({ body: 'Fixes #9763\n\nsome prose' }), NOW).includes('holding card #9763'), true); - t('H16: `Part of #N` counts as held too (H8\'s reading of delivery)', h16StuckMergeConflict(conflictPr({ body: 'Part of #9652' }), NOW).includes('holding card #9652'), true); - t('H16: two cards are pluralised and listed in order', h16StuckMergeConflict(conflictPr({ body: 'Fixes #9961\nFixes #9936' }), NOW).includes('holding cards #9936, #9961'), true); - t('H16: a body with no card carries no holding clause', h16StuckMergeConflict(conflictPr({ body: 'no card here' }), NOW).includes('holding'), false); + t('H16: a `Fixes #N` body names the card it is holding', h16row(conflictPr({ body: 'Fixes #9763\n\nsome prose' }), NOW).includes('holding card #9763'), true); + t('H16: `Part of #N` counts as held too (H8\'s reading of delivery)', h16row(conflictPr({ body: 'Part of #9652' }), NOW).includes('holding card #9652'), true); + t('H16: two cards are pluralised and listed in order', h16row(conflictPr({ body: 'Fixes #9961\nFixes #9936' }), NOW).includes('holding cards #9936, #9961'), true); + t('H16: a body with no card carries no holding clause', h16row(conflictPr({ body: 'no card here' }), NOW).includes('holding'), false); // #8293 reading 4 carries over: a body QUOTING the spelling names nothing. t('H16: a backticked `Fixes #N` is not a held card', h16HeldCards('the dispatch asked for `Fixes #8284`').length, 0); t('H16: h16HeldCards de-duplicates and sorts', h16HeldCards('Fixes #30\nPart of #12\nFixes #30').join(','), '12,30'); @@ -9709,9 +9776,9 @@ function selfTest() { head: { ref: 'claude/issue-9763-literal-collector-spellings' }, }); t('H16 incident: the #9826 shape is a finding', typeof h16StuckMergeConflict(pr9826, NOW), 'string'); - t('H16 incident: …fires despite auto-merge being armed', h16StuckMergeConflict(pr9826, NOW).includes('MERGE CONFLICT'), true); - t('H16 incident: …and names the card it was holding', h16StuckMergeConflict(pr9826, NOW).includes('holding card #9763'), true); - t('H16 incident: …at its measured ~4h age', h16StuckMergeConflict(pr9826, NOW).includes('untouched for ~4h'), true); + t('H16 incident: …fires despite auto-merge being armed', h16row(pr9826, NOW).includes('MERGE CONFLICT'), true); + t('H16 incident: …and names the card it was holding', h16row(pr9826, NOW).includes('holding card #9763'), true); + t('H16 incident: …at its measured ~4h age', h16row(pr9826, NOW).includes('untouched for ~4h'), true); t('H16 incident: …and the sweep would have spent a request on it', h16NeedsDetail(pr9826, NOW), true); // The counterfactual that makes the fixture mean something: at the moment // the conflict appeared, the same PR was silent — the threshold is what @@ -10499,15 +10566,15 @@ function selfTest() { t('H24: assigned but not queued is out of scope (H1/H2 own it)', h24QueuedWithAssignee(queued(['pm:dispatched'], ['os-elon'])), null); t('H24: neither -> clean', h24QueuedWithAssignee(queued(['domain:skills'], [])), null); t('H24: a missing issue does not crash', h24QueuedWithAssignee(undefined), null); - t('H24: …and the row names the login so residue and ownership are separable', h24QueuedWithAssignee(queued(['pm:queue'], ['yinlianghui'])).includes('`yinlianghui`'), true); - t('H24: every assignee is named, not just the first', h24QueuedWithAssignee(queued(['pm:queue'], ['os-elon', 'qq9340100'])).includes('`qq9340100`'), true); + t('H24: …and the row names the login so residue and ownership are separable', h24row(queued(['pm:queue'], ['yinlianghui'])).includes('`yinlianghui`'), true); + t('H24: every assignee is named, not just the first', h24row(queued(['pm:queue'], ['os-elon', 'qq9340100'])).includes('`qq9340100`'), true); t('H24: assignees given as plain logins are read too', typeof h24QueuedWithAssignee({ ...queued(['pm:queue']), assignees: ['os-elon'] }), 'string'); // The ruling's ORDER, pinned: the rule fires on a human assignment too, and // the sentence carries the asymmetric remedy rather than an exemption. t('H24: a human assignment still fires (exemption is a later explicit marker)', typeof h24QueuedWithAssignee(queued(['pm:queue'], ['yinlianghui'])), 'string'); - t('H24: …and the row refuses the human-clearing write', h24QueuedWithAssignee(queued(['pm:queue'], ['yinlianghui'])).includes('never be cleared by an agent'), true); - t('H24: …and names the paired write it is owed', h24QueuedWithAssignee(queued(['pm:queue'], ['os-elon'])).includes('同笔摘 assignee'), true); - t('H24: …and names both contradicting readers', h24QueuedWithAssignee(queued(['pm:queue'], ['os-elon'])).includes('dispatchable NOW'), true); + t('H24: …and the row refuses the human-clearing write', h24row(queued(['pm:queue'], ['yinlianghui'])).includes('never be cleared by an agent'), true); + t('H24: …and names the paired write it is owed', h24row(queued(['pm:queue'], ['os-elon'])).includes('同笔摘 assignee'), true); + t('H24: …and names both contradicting readers', h24row(queued(['pm:queue'], ['os-elon'])).includes('dispatchable NOW'), true); // The closed gate, in mirror image to H22's open gate: one card, one row. t('H24: a CLOSED queued+assigned card is H22 residue, not this row', h24QueuedWithAssignee(queued(['pm:queue'], ['os-elon'], { state: 'closed' })), null); t('H24: …and H22 does fire on that same card', typeof h22ClosedCardPmResidue(queued(['pm:queue'], ['os-elon'], { state: 'closed', state_reason: 'completed' })), 'string'); @@ -10526,25 +10593,25 @@ function selfTest() { // half-delivered branch must NOT (there the label and the claim are CORRECT). const pairedMerged = [{ number: 900, merged_at: '2026-08-22T10:00:00Z', body: 'Fixes #10638', head: { ref: 'x' } }]; const pairedOpenHalf = [{ number: 901, merged_at: null, draft: true, body: 'Part of #10638', head: { ref: 'y' } }]; - t('H8: the full-delivery remedy names 同笔摘 assignee', h8MergedPrStillDispatched(queued(['pm:dispatched'], ['os-elon']), pairedMerged, []).includes('同笔摘 assignee'), true); - t('H8: …and points at H24 as the state it prevents', h8MergedPrStillDispatched(queued(['pm:dispatched'], ['os-elon']), pairedMerged, []).includes('H24'), true); - t('H8: …and keeps the human-assignment refusal', h8MergedPrStillDispatched(queued(['pm:dispatched'], ['os-elon']), pairedMerged, []).includes('never cleared by an agent'), true); - t('H8: the HALF-delivered branch prescribes no assignee drop', h8MergedPrStillDispatched(queued(['pm:dispatched'], ['os-elon']), pairedMerged, pairedOpenHalf).includes('同笔摘 assignee'), false); - t('H8: …and still says the label is correct there', h8MergedPrStillDispatched(queued(['pm:dispatched'], ['os-elon']), pairedMerged, pairedOpenHalf).includes('must NOT be dropped'), true); - t('H19: the release text names 同笔摘 assignee', h19BlockOutlivedBlocker(queued(['pm:blocked']), [{ key: 'objectstack-ai/objectstack#2', number: 2, local: true, state: 'closed' }]).includes('同笔摘 assignee'), true); - t('H19: …on the unresolved branch too (one release contract, one sentence)', h19BlockOutlivedBlocker(queued(['pm:blocked']), [{ key: 'objectstack-ai/cloud#2', number: 2, local: false, state: 'unresolved', detail: 'HTTP 404' }]).includes('同笔摘 assignee'), true); + t('H8: the full-delivery remedy names 同笔摘 assignee', h8row(queued(['pm:dispatched'], ['os-elon']), pairedMerged, []).includes('同笔摘 assignee'), true); + t('H8: …and points at H24 as the state it prevents', h8row(queued(['pm:dispatched'], ['os-elon']), pairedMerged, []).includes('H24'), true); + t('H8: …and keeps the human-assignment refusal', h8row(queued(['pm:dispatched'], ['os-elon']), pairedMerged, []).includes('never cleared by an agent'), true); + t('H8: the HALF-delivered branch prescribes no assignee drop', h8row(queued(['pm:dispatched'], ['os-elon']), pairedMerged, pairedOpenHalf).includes('同笔摘 assignee'), false); + t('H8: …and still says the label is correct there', h8row(queued(['pm:dispatched'], ['os-elon']), pairedMerged, pairedOpenHalf).includes('must NOT be dropped'), true); + t('H19: the release text names 同笔摘 assignee', h19row(queued(['pm:blocked']), [{ key: 'objectstack-ai/objectstack#2', number: 2, local: true, state: 'closed' }]).includes('同笔摘 assignee'), true); + t('H19: …on the unresolved branch too (one release contract, one sentence)', h19row(queued(['pm:blocked']), [{ key: 'objectstack-ai/cloud#2', number: 2, local: false, state: 'unresolved', detail: 'HTTP 404' }]).includes('同笔摘 assignee'), true); // -- H25 + the `pm:awaiting-maintainer` vocabulary (#11196 fix 5) ----------- t('the ruled spelling is pm:-prefixed', AWAITING_MAINTAINER_LABEL, 'pm:awaiting-maintainer'); t('H25: awaiting + pm:queue -> finding', typeof h25AwaitingMaintainerExclusivity(queued([AWAITING_MAINTAINER_LABEL, 'pm:queue'])), 'string'); - t('H25: …and the row names the coexisting label', h25AwaitingMaintainerExclusivity(queued([AWAITING_MAINTAINER_LABEL, 'pm:queue'])).includes('`pm:queue`'), true); - t('H25: …and the specific lie, not a tidiness complaint', h25AwaitingMaintainerExclusivity(queued([AWAITING_MAINTAINER_LABEL, 'pm:on-hold'])).includes('Restart-when'), true); + t('H25: …and the row names the coexisting label', h25row(queued([AWAITING_MAINTAINER_LABEL, 'pm:queue'])).includes('`pm:queue`'), true); + t('H25: …and the specific lie, not a tidiness complaint', h25row(queued([AWAITING_MAINTAINER_LABEL, 'pm:on-hold'])).includes('Restart-when'), true); t('H25: awaiting ALONE -> clean', h25AwaitingMaintainerExclusivity(queued([AWAITING_MAINTAINER_LABEL])), null); t('H25: awaiting + a non-state label -> clean', h25AwaitingMaintainerExclusivity(queued([AWAITING_MAINTAINER_LABEL, 'domain:skills', 'priority:p0', 'pm:blocking'])), null); t('H25: no awaiting label -> out of scope however many states', h25AwaitingMaintainerExclusivity(queued(['pm:queue', 'pm:dispatched'])), null); t('H25: a CLOSED card is H22 residue, not a live exclusivity breach', h25AwaitingMaintainerExclusivity(queued([AWAITING_MAINTAINER_LABEL, 'pm:queue'], [], { state: 'closed' })), null); t('H25: a missing issue does not crash', h25AwaitingMaintainerExclusivity(undefined), null); - t('H25: several conflicts are ALL named', h25AwaitingMaintainerExclusivity(queued([AWAITING_MAINTAINER_LABEL, 'pm:queue', 'needs-user-decision'])).includes('`needs-user-decision`'), true); + t('H25: several conflicts are ALL named', h25row(queued([AWAITING_MAINTAINER_LABEL, 'pm:queue', 'needs-user-decision'])).includes('`needs-user-decision`'), true); for (const conflicting of AWAITING_MAINTAINER_EXCLUSIVE_LABELS) { t(`H25: awaiting + \`${conflicting}\` -> finding`, typeof h25AwaitingMaintainerExclusivity(queued([AWAITING_MAINTAINER_LABEL, conflicting])), 'string'); } @@ -10552,13 +10619,13 @@ function selfTest() { // the vocabulary cannot be half-added (the defect class this family is about). t('vocabulary: H13 treats awaiting as a real state -> clean', h13DomainWithoutPmState(domainCard(['domain:skills', AWAITING_MAINTAINER_LABEL], hoursAgo(200)), NOW), null); t('vocabulary: …while the same card without it is still H13', typeof h13DomainWithoutPmState(domainCard(['domain:skills'], hoursAgo(200)), NOW), 'string'); - t('vocabulary: H22 counts awaiting as residue on a closed card', h22ClosedCardPmResidue(closedCard([AWAITING_MAINTAINER_LABEL])).includes(`\`${AWAITING_MAINTAINER_LABEL}\``), true); + t('vocabulary: H22 counts awaiting as residue on a closed card', h22row(closedCard([AWAITING_MAINTAINER_LABEL])).includes(`\`${AWAITING_MAINTAINER_LABEL}\``), true); t('vocabulary: …and an open card carrying it is not H22 residue', h22ClosedCardPmResidue(queued([AWAITING_MAINTAINER_LABEL])), null); t('vocabulary: H11 sees awaiting as a PARKED state', typeof h11ImportantParked(parkedCard(['bug', AWAITING_MAINTAINER_LABEL]), NOW), 'string'); - t('vocabulary: …and names it as the parked state', h11ImportantParked(parkedCard(['bug', AWAITING_MAINTAINER_LABEL]), NOW).includes(`\`${AWAITING_MAINTAINER_LABEL}\``), true); - t('vocabulary: …with the exit this state actually has (no Restart-when re-check)', h11ImportantParked(parkedCard(['bug', AWAITING_MAINTAINER_LABEL]), NOW).includes('Restart-when'), false); - t('vocabulary: …and it says the state has no machine exit', h11ImportantParked(parkedCard(['bug', AWAITING_MAINTAINER_LABEL]), NOW).includes('NO machine exit'), true); - t('vocabulary: H11 keeps the mechanical remedy for a BLOCKED card', h11ImportantParked(parkedCard(['bug', 'pm:blocked']), NOW).includes('Restart-when'), true); + t('vocabulary: …and names it as the parked state', h11row(parkedCard(['bug', AWAITING_MAINTAINER_LABEL]), NOW).includes(`\`${AWAITING_MAINTAINER_LABEL}\``), true); + t('vocabulary: …with the exit this state actually has (no Restart-when re-check)', h11row(parkedCard(['bug', AWAITING_MAINTAINER_LABEL]), NOW).includes('Restart-when'), false); + t('vocabulary: …and it says the state has no machine exit', h11row(parkedCard(['bug', AWAITING_MAINTAINER_LABEL]), NOW).includes('NO machine exit'), true); + t('vocabulary: H11 keeps the mechanical remedy for a BLOCKED card', h11row(parkedCard(['bug', 'pm:blocked']), NOW).includes('Restart-when'), true); t('vocabulary: a fresh awaiting park is still clean', h11ImportantParked(parkedCard(['bug', AWAITING_MAINTAINER_LABEL], { created: daysAgo(2) }), NOW), null); t('vocabulary: an UNimportant awaiting card is not inventory', h11ImportantParked(parkedCard([AWAITING_MAINTAINER_LABEL]), NOW), null); @@ -10792,10 +10859,10 @@ function selfTest() { ...extra, }); t('H26: target parked in pm:on-hold -> finding', typeof h26BlockOnIndefiniteTarget(waiting(), [tgt(987, ['pm:on-hold'])]), 'string'); - t('H26: …and the row says the block has no releasing mechanism', h26BlockOnIndefiniteTarget(waiting(), [tgt(987, ['pm:on-hold'])]).includes('NO MECHANISM THAT WILL EVER RELEASE IT'), true); - t('H26: …and names the target and its state', h26BlockOnIndefiniteTarget(waiting(), [tgt(987, ['pm:on-hold'])]).includes('`#987` (`pm:on-hold`)'), true); + t('H26: …and the row says the block has no releasing mechanism', h26row(waiting(), [tgt(987, ['pm:on-hold'])]).includes('NO MECHANISM THAT WILL EVER RELEASE IT'), true); + t('H26: …and names the target and its state', h26row(waiting(), [tgt(987, ['pm:on-hold'])]).includes('`#987` (`pm:on-hold`)'), true); t('H26: target parked in needs-user-decision -> finding', typeof h26BlockOnIndefiniteTarget(waiting(75), [tgt(68, ['needs-user-decision'])]), 'string'); - t('H26: a target carrying BOTH indefinite states names both', h26BlockOnIndefiniteTarget(waiting(), [tgt(987, ['pm:on-hold', 'needs-user-decision'])]).includes('`pm:on-hold` + `needs-user-decision`'), true); + t('H26: a target carrying BOTH indefinite states names both', h26row(waiting(), [tgt(987, ['pm:on-hold', 'needs-user-decision'])]).includes('`pm:on-hold` + `needs-user-decision`'), true); // The clean directions — an ordinary open target is not this row's business. t('H26: an ordinary open target -> clean', h26BlockOnIndefiniteTarget(waiting(), [tgt(987, ['pm:queue', 'domain:devx'])]), null); t('H26: an unlabelled open target -> clean', h26BlockOnIndefiniteTarget(waiting(), [tgt(987, [])]), null); @@ -10809,28 +10876,28 @@ function selfTest() { // An unresolved target is silent HERE and loud in H19 — one gap, one row. t('H26: an unresolved target is silent (H19 owns the unjudged sentence)', h26BlockOnIndefiniteTarget(waiting(), [{ ...tgt(987, null), state: 'unresolved', detail: 'HTTP 404' }]), null); t('H26: …and a labels-less open row cannot be judged either', h26BlockOnIndefiniteTarget(waiting(), [{ ...tgt(987, undefined) }]), null); - t('H26: …while H19 states that gap', h19BlockOutlivedBlocker(waiting(), [{ ...tgt(987, null), state: 'unresolved', detail: 'HTTP 404' }]).includes('UNJUDGED'), true); + t('H26: …while H19 states that gap', h19row(waiting(), [{ ...tgt(987, null), state: 'unresolved', detail: 'HTTP 404' }]).includes('UNJUDGED'), true); // The chain leg. t('H26: a target that is itself pm:blocked -> the transitive row', typeof h26BlockOnIndefiniteTarget(waiting(1395), [tgt(10101, ['pm:blocked'])]), 'string'); - t('H26: …and it says to look one level further', h26BlockOnIndefiniteTarget(waiting(1395), [tgt(10101, ['pm:blocked'])]).includes('TRANSITIVE'), true); - t('H26: …and does not claim the block can never release', h26BlockOnIndefiniteTarget(waiting(1395), [tgt(10101, ['pm:blocked'])]).includes('NO MECHANISM'), false); + t('H26: …and it says to look one level further', h26row(waiting(1395), [tgt(10101, ['pm:blocked'])]).includes('TRANSITIVE'), true); + t('H26: …and does not claim the block can never release', h26row(waiting(1395), [tgt(10101, ['pm:blocked'])]).includes('NO MECHANISM'), false); // Both legs at once, on two different targets, in one row. - const bothLegs = h26BlockOnIndefiniteTarget(waiting(), [tgt(987, ['pm:on-hold']), tgt(10101, ['pm:blocked'])]); + const bothLegs = String(h26BlockOnIndefiniteTarget(waiting(), [tgt(987, ['pm:on-hold']), tgt(10101, ['pm:blocked'])]) ?? ''); t('H26: both legs report together', bothLegs.includes('NO MECHANISM THAT WILL EVER RELEASE IT') && bothLegs.includes('TRANSITIVE'), true); // A target that is BOTH parked and blocked is named ONCE, under the reading // that ends the wait forever rather than the one that merely lengthens it. - const bothOnOne = h26BlockOnIndefiniteTarget(waiting(), [tgt(987, ['pm:on-hold', 'pm:blocked'])]); + const bothOnOne = String(h26BlockOnIndefiniteTarget(waiting(), [tgt(987, ['pm:on-hold', 'pm:blocked'])]) ?? ''); t('H26: a parked AND blocked target is named once, as indefinite', bothOnOne.includes('NO MECHANISM THAT WILL EVER RELEASE IT'), true); t('H26: …and not a second time as a chain', bothOnOne.includes('TRANSITIVE'), false); // A partially indefinite block still reports: one live blocker does not make // the indefinite one fireable. t('H26: one indefinite target among open ones still fires', typeof h26BlockOnIndefiniteTarget(waiting(), [tgt(900, ['pm:queue']), tgt(987, ['pm:on-hold'])]), 'string'); // The render cap, shared with H19 so one card cannot flood the anchor body. - const manyIndefinite = h26BlockOnIndefiniteTarget(waiting(), [1, 2, 3, 4, 5, 6, 7].map((n) => tgt(n, ['pm:on-hold']))); + const manyIndefinite = String(h26BlockOnIndefiniteTarget(waiting(), [1, 2, 3, 4, 5, 6, 7].map((n) => tgt(n, ['pm:on-hold']))) ?? ''); t('H26: the target list is capped like H19\'s', manyIndefinite.includes(`+${7 - H19_TARGET_LIST_CAP} more`), true); t('H26: …and still counts the full set', manyIndefinite.includes('on 7 target(s)'), true); // Cross-repo targets are addressed by full key, as in H19's rows. - t('H26: a cross-repo target is named owner/repo#N', h26BlockOnIndefiniteTarget(waiting(), [{ ...tgt(68, ['needs-user-decision']), local: false, key: 'objectstack-ai/objectos#68' }]).includes('`objectstack-ai/objectos#68`'), true); + t('H26: a cross-repo target is named owner/repo#N', h26row(waiting(), [{ ...tgt(68, ['needs-user-decision']), local: false, key: 'objectstack-ai/objectos#68' }]).includes('`objectstack-ai/objectos#68`'), true); // Both rows can fire on ONE card — different halves of one wait. const expiredAndIndefinite = [{ ...tgt(900, ['pm:queue']), state: 'closed' }, tgt(987, ['pm:on-hold'])]; t('H26 + H19: a partially expired, partially indefinite block fires both', Boolean(h19BlockOutlivedBlocker(waiting(), expiredAndIndefinite)) && Boolean(h26BlockOnIndefiniteTarget(waiting(), expiredAndIndefinite)), true); @@ -10865,14 +10932,14 @@ function selfTest() { // the liveness read, so the card resolved ONE target and it was closed. const asSwept = reparkKeys(undefined); t('H28 repro (OLD, gated): only the stale body target is resolved', asSwept, 'objectstack-ai/objectstack#9255'); - const falseCandidate = h19BlockOutlivedBlocker(reparked, [target(9255, 'closed', { closedAt: '2026-08-19T11:28:26Z' })]); + const falseCandidate = String(h19BlockOutlivedBlocker(reparked, [target(9255, 'closed', { closedAt: '2026-08-19T11:28:26Z' })]) ?? ''); t('H28 repro (OLD, gated): H19 publishes 1 of 1 CLOSED', falseCandidate.includes('1 of 1 `Blocked-by:` target(s)'), true); t('H28 repro (OLD, gated): …as a FULL discharge — the false unlock candidate', falseCandidate.includes('Every target it names is closed'), true); t('H28 repro (OLD, gated): …and never says PARTIAL', falseCandidate.includes('PARTIAL'), false); // NEW behaviour = ungated: both channels, so the live blocker is resolved too. const ungated = [target(9255, 'closed', { closedAt: '2026-08-19T11:28:26Z' }), target(11501, 'open')]; t('H28 repro (NEW, ungated): both channels are unioned', reparkKeys([REPARK_COMMENT]), 'objectstack-ai/objectstack#9255 objectstack-ai/objectstack#11501'); - const partialNow = h19BlockOutlivedBlocker(reparked, ungated); + const partialNow = String(h19BlockOutlivedBlocker(reparked, ungated) ?? ''); t('H28 repro (NEW, ungated): H19 reads 1 of 2', partialNow.includes('1 of 2 `Blocked-by:` target(s)'), true); t('H28 repro (NEW, ungated): …and calls it a PARTIAL discharge', partialNow.includes('PARTIAL'), true); t('H28 repro (NEW, ungated): …naming the live blocker as still open', partialNow.includes('`#11501`'), true); @@ -10880,14 +10947,15 @@ function selfTest() { // The PAIRED row — what ungating alone does not say. const stale9592 = h28StaleBodyBlockerLine(reparked, ungated, [REPARK_COMMENT], 'objectstack-ai/objectstack'); + const stale9592Row = String(stale9592 ?? ''); t('H28: the re-park shape fires', typeof stale9592, 'string'); - t('H28: …naming the spent BODY target', stale9592.includes('`#9255` (closed 2026-08-19T11:28:26Z)'), true); - t('H28: …and the live COMMENT target', stale9592.includes('`#11501`'), true); - t('H28: …calling the body line STALE', stale9592.includes('the body line is ') && stale9592.includes('STALE'), true); - t('H28: …and asking for the migration to the canonical home', stale9592.includes('rewrite the body line to name the live blocker'), true); - t('H28: …naming the re-park as the write that produced it', stale9592.includes('RE-PARK'), true); - t('H28: …and recording the false unlock candidate the gate used to publish', stale9592.includes('FALSE unlock candidate'), true); - t('H28: report-only, never a body written from this script', stale9592.includes('never a body or a label written from this script'), true); + t('H28: …naming the spent BODY target', stale9592Row.includes('`#9255` (closed 2026-08-19T11:28:26Z)'), true); + t('H28: …and the live COMMENT target', stale9592Row.includes('`#11501`'), true); + t('H28: …calling the body line STALE', stale9592Row.includes('the body line is ') && stale9592Row.includes('STALE'), true); + t('H28: …and asking for the migration to the canonical home', stale9592Row.includes('rewrite the body line to name the live blocker'), true); + t('H28: …naming the re-park as the write that produced it', stale9592Row.includes('RE-PARK'), true); + t('H28: …and recording the false unlock candidate the gate used to publish', stale9592Row.includes('FALSE unlock candidate'), true); + t('H28: report-only, never a body written from this script', stale9592Row.includes('never a body or a label written from this script'), true); // H19 and H28 fire TOGETHER on this card — different halves of one wait. t('H28 + H19: both rows fire on the re-parked card', Boolean(partialNow) && Boolean(stale9592), true); @@ -10918,12 +10986,12 @@ function selfTest() { t('H28 split: a mid-sentence prose mention is NOT a directive', [...blockerChannelKeys('the stated **Blocked-by: #9255** is discharged', reparked, 'objectstack-ai/objectstack')].join(' '), ''); t('H28 split: no text at all is an empty set', blockerChannelKeys(undefined, reparked, 'objectstack-ai/objectstack').size, 0); // Multiple stale/live targets are counted and capped like every other row. - const manyStale = h28StaleBodyBlockerLine( + const manyStale = String(h28StaleBodyBlockerLine( { ...issue(['pm:blocked'], [], 'Blocked-by: #1\nBlocked-by: #2'), number: 9592 }, [target(1, 'closed'), target(2, 'closed'), target(11501, 'open')], [REPARK_COMMENT], 'objectstack-ai/objectstack', - ); + ) ?? ''); t('H28: two stale body targets are counted', manyStale.includes('names 2 CLOSED'), true); t('H28: …and the live one is still named', manyStale.includes('`#11501`'), true); @@ -10965,8 +11033,8 @@ function selfTest() { // The board halves — both required. t('H32: held + idle lane + stale marker -> finding', typeof h32SeatIdleOverQueue(seat(HELD), marker('Round-start marker — R6.', 600), idleLane, NOW32), 'string'); - t('H32: …and it names the lane', h32SeatIdleOverQueue(seat(HELD), marker('Round-start marker — R6.', 600), idleLane, NOW32).includes('`domain:spec`'), true); - t('H32: …and the unclaimed count', h32SeatIdleOverQueue(seat(HELD), marker('Round-start marker — R6.', 600), idleLane, NOW32).includes('15 unclaimed'), true); + t('H32: …and it names the lane', h32row(seat(HELD), marker('Round-start marker — R6.', 600), idleLane, NOW32).includes('`domain:spec`'), true); + t('H32: …and the unclaimed count', h32row(seat(HELD), marker('Round-start marker — R6.', 600), idleLane, NOW32).includes('15 unclaimed'), true); t('H32: work IN FLIGHT is a working seat -> clean', h32SeatIdleOverQueue(seat(HELD), marker('Round-start marker — R6.', 600), busy, NOW32), null); t('H32: an EMPTY queue is a finished lane -> clean', h32SeatIdleOverQueue(seat(HELD), marker('Round-start marker — R6.', 600), { unclaimed: 0, inFlight: 0 }, NOW32), null); t('H32: a vacant seat is out of scope however deep the queue', h32SeatIdleOverQueue(seat('[PM seat] domain:spec — ⏳ vacant'), marker('收班', 6000), idleLane, NOW32), null); @@ -11004,7 +11072,7 @@ function selfTest() { // An unreadable STAMP still fires — that is the #4690 direction, and it // differs from an unreadable THREAD because the wait exemption was still read. t('H32: an unreadable marker stamp must not read as fresh', typeof h32SeatIdleOverQueue(seat(HELD), { body: 'Round-start marker', createdAt: 'not-a-date' }, idleLane, NOW32), 'string'); - t('H32: …and the row says so rather than printing a number', h32SeatIdleOverQueue(seat(HELD), { body: 'Round-start marker', createdAt: 'not-a-date' }, idleLane, NOW32).includes('unreadable marker timestamp'), true); + t('H32: …and the row says so rather than printing a number', h32row(seat(HELD), { body: 'Round-start marker', createdAt: 'not-a-date' }, idleLane, NOW32).includes('unreadable marker timestamp'), true); // latestSeatMarker — recency, with governingClaim's thread-order fallback. t('H32 marker: the NEWEST comment wins', latestSeatMarker([{ body: 'old', created_at: minsAgo(600) }, { body: 'new', created_at: minsAgo(10) }]).body, 'new'); @@ -11032,7 +11100,7 @@ function selfTest() { const rulingRow33 = row33(RULING_AT, 'Triage: lands in packages/rest — dispatch scope = option 1 as a sweep.'); t('H33: a claim predating a ruling -> finding', typeof h33ClaimPredatesRuling(dispatched33(), [claimRow33, rulingRow33]), 'string'); - t('H33: …and it names both stamps', h33ClaimPredatesRuling(dispatched33(), [claimRow33, rulingRow33]).includes(RULING_AT), true); + t('H33: …and it names both stamps', h33row(dispatched33(), [claimRow33, rulingRow33]).includes(RULING_AT), true); t('H33: a ruling BEFORE the claim -> clean (the order could carry it)', h33ClaimPredatesRuling(dispatched33(), [row33('2026-08-19T09:00:00Z', 'Triage: routed'), claimRow33]), null); t('H33: no ruling on the thread at all -> clean (the ordinary card)', h33ClaimPredatesRuling(dispatched33(), [claimRow33, row33(RULING_AT, 'ACCEPT — PR #12066 reviewed')]), null); t('H33: no claim at all -> clean', h33ClaimPredatesRuling(dispatched33(), [rulingRow33]), null); @@ -11046,7 +11114,7 @@ function selfTest() { t('H33: an unreadable CLAIM stamp declines', h33ClaimPredatesRuling(dispatched33(), [row33('nope', 'Claim: PM loop round R6'), rulingRow33]), null); t('H33: an unreadable RULING stamp declines', h33ClaimPredatesRuling(dispatched33(), [claimRow33, row33('nope', 'Triage: routed')]), null); // Multiple rulings are counted, and the NEWEST is the one named. - const twoRulings = h33ClaimPredatesRuling(dispatched33(), [claimRow33, rulingRow33, row33('2026-08-22T09:00:00Z', 'Maintainer ruling — option 1 stands')]); + const twoRulings = String(h33ClaimPredatesRuling(dispatched33(), [claimRow33, rulingRow33, row33('2026-08-22T09:00:00Z', 'Maintainer ruling — option 1 stands')]) ?? ''); t('H33: two later rulings are counted', twoRulings.includes('2 triage-ruling comment(s)'), true); t('H33: …and the NEWEST is the one quoted', twoRulings.includes('2026-08-22T09:00:00Z'), true);