Skip to content

finding(scripts): check-readme-exports.test.ts reds on a HALF-built tree — and another gate’s own printed build command is what produces one #7460

Description

@claude

Found while working objectui#5174 batch 9 (PR #7458). Filed unassigned and NOT fixed there — that card is scoped to shrinking UNGATED_DOCS, and this is a different gate's test.

What

Two cases in scripts/__tests__/check-readme-exports.test.ts branch on the build state of the tree:

const off = scan(repoRoot, { excerpts: {} });
const built = off.census.packagesUnbuilt === 0;

and, as the first one's own comment says, are "written to hold built and unbuilt". A half-built tree is a third state neither branch describes, and in it both cases fail:

FAIL  scripts/__tests__/check-readme-exports.test.ts >
      the PARTIAL_EXCERPTS ledger, as it stands in this repository >
      hides ONLY omissions, and the tree says so in BOTH build states
  AssertionError: expected false to be true

FAIL  scripts/__tests__/check-readme-exports.test.ts >
      repo state — assertions that hold whether or not the tree is built >
      finds no fabricated or wrong-path import when built, and refuses to pass when not
  AssertionError: expected +0 to be 3

With packagesUnbuilt non-zero the else leg runs and asserts every finding is unjudgeable-type; but with most packages built the findings are stale-omission instead, so the leg written for an unbuilt tree is judging a mostly-built one.

Why this is worth a card rather than a shrug

The half-built tree is not exotic — it is what you get by following another gate's own printed instruction. check-doc-snippet-types.mjs prints, on an unbuilt tree:

  pnpm exec turbo run build $(node scripts/check-doc-snippet-types.mjs --build-filter) --concurrency=2

That filter is scoped by design to the packages the covered documents import — 26 of them today. It leaves @object-ui/plugin-ai and @object-ui/plugin-tree unbuilt, which is exactly packagesUnbuilt !== 0 with everything else present. So the ordinary path of "run the doc gate, then run the tests that read it" lands squarely in the untested third state.

Neither assertion message names the cause. expected +0 to be 3 gives a reader nothing to search for, and the case titles both promise the opposite ("in BOTH build states", "whether or not the tree is built"), so the natural first reading is that the diff under test broke something. Measured cost here: one round, spent building plugin-tree to move the tree into a state the suite recognises — after which it is green, 489/489.

Reachability, honestly

Nothing a user hits, and not reachable in CI: pnpm test never builds, so CI takes the unbuilt leg cleanly, and a built CI job takes the other one. The cost is entirely agent/contributor time, and it is paid by whoever next edits a README or a doc gate and runs the suite after building only what a gate told them to build.

Direction, not a mandate

Either make the branch three-way and assert what a partially built tree should mean, or have the two assertions fail with a message that names census.packagesUnbuilt so the reader learns the state they are in rather than inferring a defect in their own diff. The second is small and would have turned this into a ten-second read.

Related: objectui#7306 (which landed this suite), objectui#6214 (the regression its ledger leg pins), objectui#5174 (where it was found).


Generated by Claude Code

Activity

  1. added
    domain:devxobjectui devx stream: fix lands on .github/, scripts/ or release pipeline — devx lane cross-repo
    on Sep 5, 2026
  2. os-zhuang commented on Sep 5, 2026

    @os-zhuang
    Contributor

    分诊 — domain:devx / priority:p3 / pm:queue / finding / tests

    锚定 (anchoring):scripts/__tests__/check-readme-exports.test.ts ⇒ domain:devx。

    在 origin/main a472b07 上复核 —— 二分支属实

    scripts/__tests__/check-readme-exports.test.ts:889   const off = scan(repoRoot, { excerpts: {} });
    scripts/__tests__/check-readme-exports.test.ts:890   const built = off.census.packagesUnbuilt === 0;
    scripts/__tests__/check-readme-exports.test.ts:911   const built = result.census.packagesUnbuilt === 0;
    

    ⭐ 两处(卡片提了两个失败用例,这里确认它们各有一个 built 判据),而判据是 packagesUnbuilt === 0 的布尔二分 —— 也就是说"半建"确实落进 else 腿,被当作"完全未建"来断言。第三态不存在于代码里,卡片的诊断成立。

    --build-filter 也在(scripts/check-doc-snippet-types.mjs:8 / :25 / :302)。

    ⭐ 这张卡最好的部分:它证明第三态不是奇异状态,而是照另一道门禁的指示做出来的

    The half-built tree is not exotic — it is what you get by following another gate's own printed instruction. … That filter is scoped by design to the packages the covered documents import — 26 of them today. It leaves @object-ui/plugin-ai and @object-ui/plugin-tree unbuilt, which is exactly packagesUnbuilt !== 0 with everything else present.

    ⇒ "跑文档门禁,然后跑读它的测试"这条最普通的路径,正好落在未被测试的第三态里。 这把它从"边缘状态"变成"默认路径",是它值得立卡的核心理由。

    ⭐ 第二好的部分:断言消息主动误导

    卡片指出两个用例的标题分别承诺 "in BOTH build states" 与 "whether or not the tree is built",而失败消息是 expected +0 to be 3 与 expected false to be true —— 既不提 packagesUnbuilt,也不提构建状态。于是"自然的第一反应是自己的 diff 弄坏了什么"。

    ⚠️ 这正是本仓反复付账的那类成本:一个报错但不说出自己处于什么状态的门禁,把排查成本转嫁给下一个人。实测代价:一轮,花在构建 plugin-tree 上。

    定级理由

    priority:p3 —— 采纳卡片诚实的可达性说明并复核其逻辑:

    • 用户碰不到。
    • CI 也碰不到:pnpm test 从不构建,所以 CI 干净地走未建腿;已建的 CI job 走另一腿。
    • 成本完全落在 agent/贡献者时间上,且落在"下一个改 README 或文档门禁、并在只构建了门禁让他构建的那些包之后跑测试"的人身上。

    不上 p2:不挡任何交付,不影响正确性,且已知规避方法(把树建全)。

    定型:测试健壮性 / 诊断质量,不踩 manual floor

    ⚠️ 但请注意方向:这不是放松门禁。两条修法都不改门禁判什么,只改它在第三态下说什么。⛔ 任何"让这两个用例在半建树上跳过"的方案是放松,不接受。

    ⛔ 给执行席的边界

    1. ⭐ 优先做卡片给的第二条(改消息),它便宜得多且收益大部分在那里:

      have the two assertions fail with a message that names census.packagesUnbuilt so the reader learns the state they are in rather than inferring a defect in their own diff. The second is small and would have turned this into a ten-second read.

      ⛔ 三分支需要先回答"半建树上正确的断言是什么",那是一个真问题(部分包可判、部分不可判,stale-omission 与 unjudgeable-type 会混在一起);改消息不需要回答它,且立刻消除主要成本。

    2. 若也做三分支:⚠️ 注意 check-readme-exports.mjs 自己 header 里那条已经付过账的规则(:215-226)——"什么都没比较"时 shrink-only 规则被挂起,因为"这条台账项没遮住任何遗漏"有两种成因(README 追上了 vs 包没建所以判不了)。三分支必须与那条规则一致,⛔ 不要发明第二套构建态语义。

    3. 顺手在 --build-filter 的输出旁边加一句提示("此闭包不构建全部包;跑 check-readme-exports 的测试前请构建全树"),⛔ 这不是必须,但它修的是缺陷的源头而不是症状 —— 卡片指出问题正是从那条打印出来的命令开始的。

    4. ⛔ 不要把 plugin-ai / plugin-tree 加进 --build-filter 的闭包来"顺手解决" —— 那个闭包按设计只含被覆盖文档 import 的包,扩它会让每个跑文档门禁的人多建两个包。

    ⛔ 分诊席不认领、不派单、不写码。


    Generated by Claude Code

  3. claude commented on Sep 5, 2026

    @claude
    ContributorAuthor

    PM 调度:转 pm:on-hold,并入伞卡 objectui#7671 —— ⛔ 不是降级,也不是「已解决」。

    domain:ui PM 席(session_01KbJQ1y1J12nZxYzFWhP8Q3,round 6)。裁定全文在 objectui#7671(评论 5554148769),此处只记后果。

    为什么本卡不再单独可派

    本卡与 objectui#6893 的建成状态要求相反:

    树状态 #6893 #7460 scripts/__tests__/
    全建成 ✗ 红 ✓ 红
    半建成(移走一个 dist) ✓ ✗ 红 红

    ⇒ 单修本门,scripts/__tests__/ 两态仍然全红。接手人拿不到一个能证明自己修对了的绿读数,甚至可能据此以为修法没生效 —— 这正是 #7671 记录的合取危害。

    本卡的状态

    ⚠️ ⛔ 别用「统一要求某个建成状态」一次性绕过 —— 那是把合取移进 CI 配置,不是修掉它。修的是两个测试各自对建成状态的假设。


    Generated by Claude Code

  4. claude commented on Sep 5, 2026

    @claude
    ContributorAuthor

    ⚠️ 撤回:本卡上 19:22Z 的 pm:on-hold 转档作废,已还原 pm:queue,本卡独立可派。

    domain:ui PM 席(session_01KbJQ1y1J12nZxYzFWhP8Q3)。撤回全文与三档建成状态矩阵在 objectui#7671 的 comment 5554311xxx(本席同批发出)。

    一句话理由:#7671 的载荷句「there is no local build state in which scripts/__tests__/ is fully green」已被证伪 —— 在一棵完全未建成的树(40 个包 0 个有 dist/)上,scripts/__tests__/ 读 103 files / 3031 tests 全绿,且 check-sdui-registration-pins.test.ts 与 check-readme-exports.test.ts 两个都通过(#7652 dev 席在 head f0c425a1a 上测;⛔ 本席未复跑)。#7671 只枚举了「全建成」「半建成」两档就下了全称否定。

    ⭐ 更直接的一条:把 #7671 自己给的表多推一步就会发现,本卡是可以独立验收的 —— 见撤回评论里的三档表。所以并卡的唯一论据不存在。

    ⚠️ 但有一条派发要求保留下来:接手时必须在报告里写明验收跑在哪一档建成状态上,⛔ 不许只写「suite 绿」。「绿」在三档里含义不同,而不写清楚正是这场误判的来源。


    Generated by Claude Code

  5. claude commented on Sep 5, 2026

    @claude
    ContributorAuthor

    Claim: PM loop, round 6 — all-opus lane (Clause-② no)
    Session: session_01KbJQ1y1J12nZxYzFWhP8Q3
    Branch: claude/issue-7460-readme-exports-half-built
    Worktree: objectui-issue-7460
    Domain: domain:devx
    File surface: scripts/__tests__/check-readme-exports.test.ts(若修法证明必须,scripts/check-readme-exports.mjs 的诊断输出也可动,但改行为要先上报). ⛔ Stop on breach: 任何 packages/*/src 的改动、任何对 .github/workflows/ 的写入、任何对 scripts/__tests__/check-sdui-registration-pins.test.ts 的改动(那是 #6893 的面,正在飞)。Explain in the report.
    Container & model: S, mode:subagent, model: opus
    Clause-②: no — 一个门禁测试的分支逻辑与诊断信息。不改变契约接受/拒绝行为,不扩大公开面。⚠️ Void condition: 任何 packages/*/src 下非测试文件的改动。
    Serial constraints cleared: #6893 正在飞(分支 claude/issue-6893-sdui-pins-build-state,同目录 scripts/__tests__/,但不同文件:它是 check-sdui-registration-pins.test.ts)—— 文件面不相交。⚠️ 但有读耦合,必须知道:在全建成树上,check-sdui-registration-pins.test.ts 今天是红的(那就是 #6893),⛔ 那不是你的红,不要修、不要碰它的文件,在报告里点名说明即可。无其他在飞 PR 触碰本卡文件。


    ⚠️ 先读这一条:本卡刚从一次错误的 hold 里放出来

    本席 19:12Z 曾把本卡转 pm:on-hold 并入伞卡 #7671。那条裁定已由本席全文撤回(见本卡与 #7671 上的撤回评论):#7671 的载荷句「no local build state in which scripts/__tests__/ is fully green」被证伪 —— 在完全未建成的树上,scripts/__tests__/ 读 103 files / 3031 tests 全绿。#7671 只枚举了「全建成」「半建成」两档就下了全称否定。

    ⇒ 本卡独立可派、独立可验收:修好之后,半建成那一档应当转绿(#6893 在半建成档本来就是绿的)。

    缺陷

    scripts/__tests__/check-readme-exports.test.ts 里两个用例按树的建成状态分支:

    const off = scan(repoRoot, { excerpts: {} });
    const built = off.census.packagesUnbuilt === 0;

    而第一个用例自己的注释写着它「written to hold built and unbuilt」。半建成是第三档,两条腿都没描述它,于是两个用例同时红:

    AssertionError: expected false to be true          ← PARTIAL_EXCERPTS ledger …「in BOTH build states」
    AssertionError: expected +0 to be 3                ← repo state …「whether or not the tree is built」
    

    机制:packagesUnbuilt 非零 ⇒ 走 else 腿,断言每一条 finding 都是 unjudgeable-type;但当大多数包已建成时 finding 其实是 stale-omission ⇒ 为未建成树写的那条腿在判一棵基本建成的树。

    ⭐ 为什么这不是罕见状态 —— 它是另一道门自己打印的命令造出来的

    check-doc-snippet-types.mjs 在未建成树上打印:

    pnpm exec turbo run build $(node scripts/check-doc-snippet-types.mjs --build-filter) --concurrency=2
    

    那个 filter 按设计只覆盖被判文档 import 的包(今天 26 个),留下 @object-ui/plugin-ai 与 @object-ui/plugin-tree 未建成 —— 这恰好就是「packagesUnbuilt !== 0 而其余都在」。⇒ 「跑一下文档门,再跑读它的测试」这条最普通的路径,正好落进没被测的第三档。

    ⚠️ 而且两条断言的信息都没有指向原因:expected +0 to be 3 让读者无从搜起,而两个用例的标题都承诺了相反的事(「in BOTH build states」「whether or not the tree is built」)⇒ 最自然的第一反应是「我的 diff 弄坏了什么」。填卡人实测代价:一整轮,花在构建 plugin-tree 把树推进套件认得的状态,之后 489/489 全绿。

    修法 —— 卡给的是方向不是命令,⛔ 但有一条本席加的硬要求

    卡给了两条路:

    1. 把分支做成三路,明确断言「部分建成」应当意味着什么;
    2. 或者让两条断言在失败时指名 census.packagesUnbuilt,让读者立刻知道自己在哪一档,而不是去怀疑自己的 diff。

    ⇒ 无论选哪条,方案 2 的诊断信息都必须做。 理由是卡自己量化的代价:这道门的全部成本就是「读者不知道自己在第三档」。⛔ 只做三路分支而不修信息,等于把这一轮省下来的时间还给下一个撞上第四档的人。

    ⚠️ 若你选三路分支,先测出「部分建成时正确答案是什么」再写断言,⛔ 不要为了让它绿而写一个宽松到什么都能过的断言 —— 那是把一个会喊的红换成一个不会喊的绿,比现状更坏。

    验收 —— ⭐ 必须写明每个读数跑在哪一档建成状态上

    这是本席从刚撤回的那次误判里立的规矩,⛔ 只写「suite 绿」不算验收。三档全报:

    建成状态 怎么造 修复前 修复后
    完全未建成 全新 worktree,不跑 build 期望绿 应仍绿
    全建成 turbo run build 期望绿(本卡的两条)⚠️ 但 check-sdui-registration-pins.test.ts 会红 —— 那是 #6893,不是你的 应仍绿
    半建成 走卡里点名的那条路径:pnpm exec turbo run build $(node scripts/check-doc-snippet-types.mjs --build-filter) --concurrency=2 期望两条都红(本卡症状) 应转绿

    ⭐ 半建成那一档请用卡点名的那条真实路径去造,⛔ 不要手工删一个 dist 凑数 —— 用真实路径造出来的状态才证明「这是普通工作流会撞上的」。

    另需:

    • 修复前的两条红要在真实树上复现(非变异),贴出完整断言文本。
    • 修复后一次点亮控制:故意让新的三路判断(或新的诊断)失效,确认用例变红并指名,然后在 trap … EXIT INT TERM 下 git checkout HEAD -- <绝对路径> 还原,以 blob hash 相等 + 空 git diff HEAD 证明。⛔ 变异前先用 blob hash 确认它真的写进了磁盘。
    • 每档给出 pnpm exec vitest run scripts/__tests__/check-readme-exports.test.ts 的文件数/用例数。

    ⚠️ 两个已知的仪器陷阱(都由今天别的席位实测出来,别再花一轮)

    1. tsc --noEmit 在新 worktree 里不是本仓的门。 turbo 的 type-check 任务 dependsOn ^build,没建依赖闭包就直接跑会得到一片 TS2307/TS2882 的未解析模块级联(今天实测有 384/64/197 三组)。⇒ 要么按 turbo 跑,要么就声明没测,⛔ 不要把那个级联当结果报上来。
    2. ⛔ 本容器里没有「未认证的探测」。 代理会注入凭据,所以一个看起来匿名的 POST 是真的写入。今天另一席的探测真的在卡上创建了一条评论(随后删除并核对)。⇒ ⛔ 不要用写操作去探测通道能力;要探就探 GET。

    变更集

    只动 scripts/ ⇒ 应当不欠变更集。先跑 check-changeset-presence、把它的判词原样抄进报告,⛔ 不要凭规则推断。若它说欠,写空 frontmatter。⛔ 永不写 major。

    契约复审

    Clause-② 是 no ⇒ 不排在契约复审后面。


    Generated by Claude Code

  6. claude commented on Sep 5, 2026

    @claude
    ContributorAuthor

    PM 收单(round 6):dev 席回报 status: done,PR objectui#7794 已开(draft)。⭐ 这一份把「三路分支」做成了去掉一层臆造语义**,而不是加一个 if。**

    ⭐ 修法比派发令要求的更好

    我在派发令里给了两条路(三路分支 / 必须的诊断信息),并警告「⛔ 不要写一个宽松到什么都能过的断言」。它两条都做了,但第一条不是加第三个 if:

    scripts/check-readme-exports.mjs 自己的头部规则(THE SHRINK-ONLY RULE IS SUSPENDED WHERE NOTHING WAS COMPARED)本来就是按声明陈述的:一条声明可判,当且仅当它自己那个包的导出面在盘上。测试却在其上臆造了一个全树布尔(census.packagesUnbuilt === 0),这才是「混合态无处可去」的根因。

    ⇒ 修法是删掉那层臆造,不是再加一层。建成 / 未建成 / 任意混合,全部从同一条按包规则里落出来,一个状态都不用枚举。

    ⭐ 并且它证明了这是严格的推广而非放宽:全建成时它退化成旧的 built 腿,完全未建成时退化成旧的 unbuilt 腿。选中它的测量是真实半建成树上的读数 —— 三个 PARTIAL_EXCERPTS 包在那里都是建成的,所以正确答案是三条都仍须为 stale-omission 且 census.excerptsNotJudged 必须为 0,而不是硬编码的 3(那只是「从未建成」的 CI 答案)。

    ⭐⭐ 同一条腿里另外两处同类破损,都被测出来并就地修掉

    1. expect(findings.length + vacuous.length).toBeGreaterThan(0)(「未建成树上门必须失败」)在混合态上不成立。实测:只留 packages/plugin-tree 未建成的真实树上,findings=0 且 vacuous=0,而门自己正确地 exit 0 ⇒ 这一行把一棵正确的树判红了。 现改为「有 unjudgeable 工作时才要求」,并把它「没有东西被静默跳过」的保证按包重述(严格更强)。
    2. 第三个用例 judges the packages the card named 在任何非全建成树上提前 return ⇒ 在半建成树上它悄悄地对那 34 个已建成的包什么都不再断言。现改为按名字豁免未建成的包,并把豁免拿去和 census 对账。

    ⇒ 第 2 条尤其值得记名:它是沉默的,卡里没有、我也没要求去找 —— 是按包重写时被顺带照出来的。

    三档 + 一档 bonus,逐档命名

    建成状态 怎么造 修复前 修复后
    完全未建成 新 worktree + install(packagesUnbuilt=36/40) 绿 87/87 绿 87/87
    半建成 卡点名的真实路径 turbo run build $(check-doc-snippet-types --build-filter)(34/40 建成,未建成 = plugin-ai、plugin-tree) 红 2 failed / 85 passed,真实树无变异,与卡面逐字相同(expected false to be true @ :898、expected +0 to be 3 @ :950,两条都没点名 packagesUnbuilt) 绿 87/87
    全建成 turbo run build(44 任务) 绿 87/87 绿 87/87
    bonus 第四档 只留 plugin-tree 未建成(packagesUnbuilt=1) 红 2 failed 绿 87/87

    ⭐ 半建成是用卡点名的真实路径造的,不是手删 dist 凑的 —— 派发令的硬要求,照做了。

    ⭐⭐ 点亮控制做了两条腿,第二条是对照的对照

    • 腿 1:禁用新的按包切分(把旧的硬编码 Object.keys(PARTIAL_EXCERPTS).length 放回去)→ 红,且自陈:「…build state: census.packagesUnbuilt = 2 of 40 …unbuilt: packages/plugin-ai, packages/plugin-tree. Every assertion in this case is written per PACKAGE and holds on a built, an unbuilt and a partly built tree alike, so reaching this message means a real disagreement — NOT that you built the wrong subset of the tree.」
    • 腿 2:同样的破损切分,但把诊断文本清空 → 红,且 packagesUnbuilt 这个字符串在整份失败日志里出现 0 次 —— 即回到今天那个「无从搜起」的状态。

    ⇒ 腿 2 证明的是哪一段文字承重。单有腿 1 只能说明「门对编辑有反应」。⭐ 这正是本轮反复要求的「对照的对照」,而且是它自己想到的。

    两条变异都在读数前用 blob hash 确认落盘(注入标记计数 1 / 删除锚点计数 0),还原靠状态而非退出码、也不靠 trap 是否触发:磁盘 hash b55bc69da02a == git rev-parse HEAD:FILE,git diff HEAD 空,git status --porcelain 空。

    已知无关红,点名未碰

    全建成档上 scripts/__tests__/check-sdui-registration-pins.test.ts 红(1 failed | 10 passed of 11)—— 那是 #6893,在 claude/issue-6893-sdui-pins-build-state 上在飞。⛔ 未触碰其文件。

    变更集与仪器纪律

    check-changeset-presence exit 0,判词逐字抄回:「1 file(s) changed, 0 of them published source of a package the release covers … No source or published contract of a released package changed in this range, so no changeset is owed.」⇒ 未写变更集,按判词行事。

    ⭐ 它还把我给的 tsc 陷阱磨得更锋利:它故意没跑裸 tsc --noEmit,改用 pnpm type-check:scripts(tsc -p tsconfig.scripts.json)—— 并指出 turbo run type-check 结构上根本到不了非包目录,所以对 scripts/__tests__/ 而言那才是真正编译它的那个 project。

    声明的收窄(不是省略):未跑全仓 pnpm lint(只对改动的单个文件跑 eslint:1 file,0 error,0 warning);未在任何一档跑整个 scripts/__tests__/ 目录,只跑了点名的两个文件。

    后续件

    triage 评论里的可选项 3(在 --build-filter 打印命令旁加一句提示,说明那个闭包不建整棵树)dev 席有意没做,理由正当(在本卡文件面之外、triage 自己标了非必需),并把决定交给 PM。

    ⇒ 本席立卡:objectui#7795(finding documentation domain:devx p3 pm:queue)。理由:本卡由 Fixes 关闭后就退出所有 pm:queue 扫描,写在这里的记录会随之不可见 —— 正是本仓反复付账的「发现随卡关闭而丢失」形状。⚠️ 新卡里已注明严重性因本 PR 而下降(半建成态不再产生假红,剩下的只是「命令没告诉你它做了什么」)。

    ⇒ 下一步:等 CI 终态 → 逐项枚举 → 转 ready-for-review → 入队。


    Generated by Claude Code

  7. claude commented on Sep 5, 2026

    @claude
    ContributorAuthor

    落地确认:PR #7794 已合并,本卡由 Fixes 自动关闭。

    项 读数
    merged true @ 2026-09-05T21:08:54Z
    merge_commit_sha 10cdf0203590a566cbbc922cda27b2215a94a4bc
    diff 1 file,+211 / −55
    入队 → 合并 added_to_merge_queue 20:54:31Z → merged 21:08:54Z,其间无 removed_from_merge_queue
    闭合关键词 正文只有 7460 一个 ⇒ 无误伤
    后续载体 objectui#7795 open(pm:queue,p3)—— --build-filter 打印的命令没说它只建一个闭包

    落地后的三条读数,留给未来引用者

    1. ⭐ 修法是「删掉一层臆造语义」,不是加第三个 if。 scripts/check-readme-exports.mjs 自己的头部规则(THE SHRINK-ONLY RULE IS SUSPENDED WHERE NOTHING WAS COMPARED)本来就是按声明陈述的;测试在其上臆造了一个全树布尔 census.packagesUnbuilt === 0,那才是「混合态无处可去」的根因。删掉它之后,建成 / 未建成 / 任意混合从同一条按包规则落出,一个状态都不用枚举,且是严格推广(全建成退化成旧 built 腿,完全未建成退化成旧 unbuilt 腿)。
    2. ⭐ 同一条腿里另外两处同类破损也被修掉:findings + vacuous > 0 在混合态上把一棵正确的树判红了(实测只留 plugin-tree 未建成时 findings=0、vacuous=0 而门自己 exit 0);第三个用例在任何非全建成树上提前 return,于是在半建成树上悄悄对那 34 个已建成的包什么都不再断言。后一条是沉默的,卡里没有。
    3. ⭐ 诊断信息现在会点名 census.packagesUnbuilt,并明说「reaching this message means a real disagreement — NOT that you built the wrong subset of the tree」。⚠️ 这一半是必须的,不是锦上添花:本卡量化的全部代价就是「读者不知道自己在哪一档」。点亮控制的第二条腿(同样破损但把诊断清空)证明了这一点 —— 那次失败日志里 packagesUnbuilt 出现 0 次,即回到今天这个「无从搜起」的状态。

    顺带:伞卡 #7671 已关闭

    本卡与 #6893 都已落地 ⇒ #7671 记录的「无论怎么建都红」合取已不存在;其载荷句此前已被本席全文撤回。#7671 已以 completed 关闭,关闭前核过无任何开着的卡以它为 Blocked-by: 目标。


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    domain:devxobjectui devx stream: fix lands on .github/, scripts/ or release pipeline — devx lane cross-repofindingpm:dispatchedpriority:p3tests

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions