Skip to content

ci(dx): 两个发现型闸门补「发现数」非空断言 —— 语料/驱动轴静默蒸发即红 (#4932) - #5917

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-4932-gate-nonvacuity
Aug 6, 2026
Merged

ci(dx): 两个发现型闸门补「发现数」非空断言 —— 语料/驱动轴静默蒸发即红 (#4932)#5917
os-zhuang merged 1 commit into
mainfrom
claude/issue-4932-gate-nonvacuity

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Closes #4932

两个闸门都靠「扫描发现目标 → 逐个检查」工作,而空集合天然通过所有检查。本单补的是这一族里下限断言那一半。

先说前提核对:issue 的两条子断言在 origin/main 上都已不成立

按 origin/main(7357130)逐条实测,#4932 正文点名的两处 catch {} 都已经不在了:

issue 的子断言 实测
check-doc-authoring.mjs:45for (const r of ROOTS) { try { walk(r, files); } catch {} } 吞掉 ROOT 缺失 已修(#4916)。现在是 assertRootsResolvable() 先解析全部 ROOT,死根按名报红;collectFiles 里没有任何 catch
check-driver-conformance.mjs:150listDir 兜底空数组 → 发现 0 个 driver → 无事可做 → 通过 已修(#4930 + #4363)。listDir 现在是裸 readdirSync;而且 DISCOVERED 不变量本来就在(discoveredErrors),零 driver 直接报红,模块头也已经把「账本可空 ≠ 发现结果可空」这条区分写清楚了

所以本 PR 不是照抄 issue 的修法,而是修它们剩下的那一半——发现环节成功、发现结果为空/偏少,仍然报绿:

check-doc-authoring:每个 ROOT 至少产出一个 .md/.mdx

assertRootsResolvable 只看得见「根不是目录」。根还在、语料搬走了(子树迁出、SKIP_PATHS 收得更宽、扩展名换了)时它是满意的:walk 少收一批,打印的计数静默变小,而 ✓ 362 files clean✓ 0 files clean 对读者是同一句话、对 CI 是同一个退出码 —— 这就是 #4932 说的「计数被打印,但从未被断言」。

下限按根算而不是按总数:总数会被还有文件的那个根托住(.claude 一个就够),而「语料只被读了一部分」恰恰是这道闸不该往语料有利方向解释的判定。断言落在 collectFiles 里(不是 main),self-test 因此驱动不变量本身而不是它的代理。

没有加棘轮:阈值只有「每个声明的根 ≥ 1 个文件」,由本次 walk 现算。高水位棘轮需要有人维护,还会把每次正当删除变成一场跟数字的争论 —— 派发词倾向不加,这里也确实找不到可测不可拍脑袋的阈值来源。

check-driver-conformance:发现改为「完备」,而不只是「非零」

packages/drivers/driver-sql 改名成 sql,包照样构建、照样测试、照样发布 —— 只有这道闸丢了它,而且丢成「少一行的矩阵 + 绿」。所以发现现在是完备的:DRIVERS_DIR 下每个条目要么是发现到的 driver,要么不是目录,要么在这里按名报红(unnamed / manifestless),没有任何东西被静默过滤掉。

一个必须说清的细节:今天这类改名恰好会被 RECONCILED 抓到(台账里有 driver-sql 的条目),但那只在台账非空时成立,而空台账是本闸的预期稳态(#5590#5701 之间就是空的)。模块头自己就写着 RECONCILED 的反向只走 LEDGER —— 所以那是当前 FILTER_TEXT 五行带来的巧合,不是机制。

顺带收掉 #4930 漏下的一处 swallow:manifest 探测的 catch { return false; } 会把任何读取失败答成「那就不是 driver」。现在只有 ENOENT(即这个过滤器真正要问的问题)才这样解释,其他 errno 直接抛。

反向验证:方向先定后跑,四组,含一组颜色不翻的

场景 预测 实测(旧 = origin/main 的脚本副本,同一份夹具)
四个 ROOT 都在、语料全空 旧绿 → 新红 ✓ 0 files clean exit 0 → 新 exit 1
单根蒸发(content 只剩非 markdown) 旧绿 → 新红 ✓ 3 files clean exit 0 → 新 exit 1,只点名 content 并报出总数 3
ROOT 被改名(#4916 原案) 旧红、新红 两边都 exit 1,同一句 dead-root 文案 —— 这一半没被削弱
DRIVERS_DIR 整体移走(#4930 原案) 旧红、新红 两边都 exit 1
driver-sql 改名为 sql 颜色不翻,诊断改变 旧只报 RECONCILED: ledger entry for driver-sql, which is not a driver package.(怪台账,点错原因);新多报一条 DISCOVERED: packages/drivers/sql is a package … but is not named driver-*。两边都是 2/1 problem(s),都红

最后一行是本单唯一「预测就不是 before-green/after-red」的地方,如实记账:今天的台账非空,所以颜色本来就红,新断言改的是把原因点对。颜色真正会翻的是空台账稳态 —— 在同一份合成树上跑 origin/main 的发现实现,得到 discovered=[driver-a]DISCOVERED errors=0(绿),而 sql 是个带 package.json 的真包却根本不在矩阵里;这一条已在 self-test 里按 discoveredErrors 的判定钉死,不依赖台账当时是什么状态。

self-test 与门禁实测

两个脚本仓内都已自带 --self-test(package.jsoncheck:* 也早已是 --self-test && 的形式),所以是补用例,不是新建 self-test。

check-doc-authoring 新增:单根为空 → 红且只点名该根、并报出这次真扫到的总数 → 恢复后绿;全空 → 红、点名四个根、总数 0。
check-driver-conformance 新增:两种丢行形状各自报红且点名(即使 driver 已发现非零)、合成树上的三分类(discovered / unnamed / manifestless)、一个 stat 不到的条目是报错而不是更小的轴(dangling symlink,ENOENT)、去掉破坏后判定复原,以及活树上两种丢行数都为 0。

实跑(worktree,origin/main 之上):

check:doc-authoring        exit=0   ✓ 362 files clean
check:driver-conformance   exit=0   5 drivers x 6 case-sets,25 covered / 5 DEBT / 0 exempt
check:role-word            exit=0   check:org-identifier   exit=0
check:adr-anchors          exit=0   check:docs-audit-scope exit=0
check:nul-bytes            exit=0   (5704 tracked text file(s))
eslint(两个改动文件)      exit=0

node scripts/check-nul-bytes.mjs 之外另做了越界自扫(grep -naP 覆盖 0x010x1f/0x7f),无命中。

发布物

tooling-only,scripts/** 两个文件,不发布任何包 ⇒ 无 changeset,PR 带 skip-changeset 标签(标签由本座位落,回读结果写在报告里)。CI 挂载点未动(.github/workflows/lint.yml 的两处 run: 原样)。

范围与另行立单


Generated by Claude Code

两处都靠「扫描发现目标 → 逐个检查」工作,而空集合天然通过所有检查。#4932
正文点的两处 `catch {}` 在 origin/main 上已经不在了(#4916 / #4930),留下的
是同一族里下限断言这一半:发现环节成功、发现结果为空,仍然报绿。

check-doc-authoring:每个 ROOT 必须至少产出一个 .md/.mdx。#4916 的
assertRootsResolvable 只看得见「根不是目录」;根还在、语料搬走了(子树迁出、
SKIP_PATHS 收得更宽、扩展名换了)时它是满意的,walk 少收一批,打印的计数
静默变小 —— `✓ 362 files clean` 与 `✓ 0 files clean` 对读者是同一句话,对 CI
是同一个退出码。下限按根算而不是按总数:总数会被还有文件的那个根托住
(`.claude` 一个就够),而「语料被读了一部分」正是这道闸不该往语料有利方向
解释的判定。断言放在 collectFiles 里,self-test 因此驱动不变量本身而不是它的
代理。不引入需要维护的高水位棘轮 —— 阈值只有「每个声明的根 ≥ 1 个文件」,
由本次 walk 现算。

check-driver-conformance:DISCOVERED 的零下限本来就在(#4363),补的是它看不见
的那一半 —— 整根蒸发会红,单行蒸发不会。把 packages/drivers/driver-sql 改名成
sql,包照样构建、照样测试、照样发布,只有这道闸丢了它,而且丢成「少一行的
矩阵 + 绿」。所以发现改为**完备**:DRIVERS_DIR 下每个条目要么是发现到的
driver,要么不是目录,要么在这里按名报红(unnamed / manifestless)。今天这
一类改名恰好会被 RECONCILED 抓到,但只在台账非空时成立,而空台账是本闸的
预期稳态(#5590#5701 之间就是空的)—— 那是巧合,不是机制。顺带收掉
#4930 漏下的一处 swallow:manifest 探测的 `catch { return false; }` 会把任何
读取失败答成「那就不是 driver」,现在只有 ENOENT 才是该过滤器要问的问题。

反向验证方向先定后跑,三种方向都实测:
- 语料整体消失(四个根都在、都空):旧 `✓ 0 files clean` exit 0 → 新 exit 1。
- 单根蒸发(content 只剩非 markdown):旧 `✓ 3 files clean` exit 0 → 新 exit 1,
  只点名 content 并报出总数 3。
- 根被改名(#4916 原案):旧红、新红 —— 方向不变,这一半没有被削弱。
- driver-sql 改名为 sql:今天台账非空,所以**颜色不翻**,变的是诊断 ——
  旧只报 RECONCILED(怪台账,点错原因),新多报一条 DISCOVERED 点名
  packages/drivers/sql。颜色真正会翻的是空台账稳态,已在 self-test 里按
  discoveredErrors 的判定钉死(旧实现在同一份合成树上 discovered=[driver-a]、
  DISCOVERED errors=0,绿)。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GX3sL71LFq8m2usg6VqTSE
@vercel

vercel Bot commented Aug 6, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 6, 2026 11:36am

Request Review

@os-zhuang os-zhuang added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 6, 2026 — with Claude
@github-actions github-actions Bot added the size/m label Aug 6, 2026
@os-zhuang
os-zhuang marked this pull request as ready for review August 6, 2026 11:42
@os-zhuang
os-zhuang added this pull request to the merge queue Aug 6, 2026
Merged via the queue into main with commit 5ddce62 Aug 6, 2026
24 of 25 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-4932-gate-nonvacuity branch August 6, 2026 11:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/m skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

发现型闸门在「什么都没发现」时静默通过:check-doc-authoring 与 check-driver-conformance 缺非空断言

2 participants