Skip to content

fix(scripts): 扫描根消失时判红并点名,不再在零个文件上报绿 (#4930) - #4941

Merged
xuyushun441-sys merged 2 commits into
mainfrom
claude/issue-4930-silent-scan-roots
Aug 3, 2026
Merged

fix(scripts): 扫描根消失时判红并点名,不再在零个文件上报绿 (#4930)#4941
xuyushun441-sys merged 2 commits into
mainfrom
claude/issue-4930-silent-scan-roots

Conversation

@xuyushun441-sys

@xuyushun441-sys xuyushun441-sys commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Fixes #4930

三个 scripts/check-*.mjs 的扫描根解析不到时静默返回空集,于是"没找到问题"和"根本没打开过"是同一条绿线。修法照抄 #4934(check-doc-authoring.mjsassertRootsResolvable):扫描前先校验每个声明的根存在且是目录,死根判红并点名,不留 optional 白名单

先说一件与议题正文不符的实况

议题说三个脚本在根改名后都会报绿。按当前 main 实测,只有第 1 个是真的端到端静默;另外两个已经会判红,但把原因说错了。我没有为了对齐议题描述而放宽任何东西,下面每一组都是真实输出。

脚本 改前(根改名) 判定
check-single-authz-resolver 红,但报的是 Delegator missing: packages/rest/src/rest-server.ts 重复 resolver 那条扫描确实是空转;之所以还能红,纯属两个 DELEGATORS 恰好也住在同一个根下面。把任一 delegator 挪出 packages/、或加第二个扫描根,这条就彻底静默了
check-startup-registry-verdict 红:nothing to scan — … does not exist 是文件existsSync 放行,然后落到 files.length === 0 去怪"没有源文件";更深一层,walk 中途 stat 不到的目录会悄悄缩小语料而 files.length 仍然非零,响亮的空语料闸门根本不会触发
check-driver-conformance 红:5 条 CASE_SETS names X, which …-conformance.ts no longer exports 报的是后果不是原因——一个目录被改名,读起来像五次蓄意删除。作者的下一步动作跟着报错信息走,所以信息必须是原因

三个都改成:先解析根,死根带原因(does not exist / exists but is not a directory)当场点名判红,并把 walk 里的吞掉一并删掉——扫描中途出错同样意味着语料只读了一半,不能当作"扫干净了"打印。

不加白名单、不加 optional: true:这些根全是 git 跟踪的目录且目录里有跟踪文件,任何能在仓库根跑这些闸门的 checkout 都不会合法地缺其中之一。加个"以防万一"的可选标记,等于给下一个作者一条受支持的途径去消音而不是去修改名——就是那个空 catch {},只是说得客气些(理由同 #4934)。


三组红/绿双向证明(真实输出)

1. check:authz-resolver

--- GREEN (roots intact) ---
✓ check-single-authz-resolver self-test: duplicate detection, delegation, and the dead-root hard error (red when the scan root is renamed, green when restored) all hold.
✓ check:authz-resolver: single shared authorization resolver intact; both entry points delegate.
exit=0
--- RED (packages/ renamed away) ---

✗ check:authz-resolver: declared scan root(s) do not resolve, so the duplicate-resolver
  scan would have concluded "none found" from zero files:

  packages — does not exist

Every entry in SCAN_ROOTS (scripts/check-single-authz-resolver.mjs) must be a directory in
the checkout, and this check runs from the repo root. If a directory was renamed or moved,
update SCAN_ROOTS to follow it; if it was deleted, remove the entry deliberately. Do NOT
restore a tolerant skip: this used to be `catch { return out; }`, and a dead root simply
made the scan read zero files while the gate kept printing green (#4930).

exit=1
--- RED (packages replaced by a file) ---

✗ check:authz-resolver: declared scan root(s) do not resolve, so the duplicate-resolver
  scan would have concluded "none found" from zero files:

  packages — exists but is not a directory

exit=1
--- GREEN again (restored) ---
✓ check:authz-resolver: single shared authorization resolver intact; both entry points delegate.
exit=0

改前同一次改名的输出(留档,说明红是蹭来的):

✗ check:authz-resolver failed:

Delegator missing: packages/rest/src/rest-server.ts

Delegator missing: packages/runtime/src/security/resolve-execution-context.ts

exit=1

2. check:startup-registry-verdict

--- GREEN (roots intact) ---
✓ startup registry verdicts: 47 startup/open-registry seam(s) across 1401 file(s), 41 read-only (legal), none recording a verdict the boot can contradict.
exit=0
--- RED (packages/ renamed away) ---

✗ startup-registry-verdict: the scan root does not resolve, so the audit would have
  reported a verdict over a corpus it never opened:

  /home/user/objectstack-4930/packages — does not exist

This check scans the workspace `packages/` tree (override with --packages-dir PATH). If the
directory was renamed or moved, point the check at it; if it was deleted, that is a
deliberate decision to record. Do NOT restore a tolerant skip: the walk used to open with
`catch { return out; }`, which turned an unreadable directory into a silently smaller
corpus (#4930).

exit=1
--- RED (packages replaced by a file — the case old existsSync waved through) ---

✗ startup-registry-verdict: the scan root does not resolve, so the audit would have
  reported a verdict over a corpus it never opened:

  /home/user/objectstack-4930/packages — exists but is not a directory
exit=1
--- GREEN again (restored) ---
✓ startup registry verdicts: 47 startup/open-registry seam(s) across 1401 file(s), 41 read-only (legal), none recording a verdict the boot can contradict.
exit=0

改前:✗ startup-registry-verdict: nothing to scan — /home/user/objectstack-4930/packages does not exist.(根是目录时对,根是文件时把原因说成"没有源文件")。

3. check:driver-conformance

--- GREEN (roots intact) ---
check-driver-conformance: OK — 20 covered cell(s), 0 in the DEBT ledger, 0 exempt.
exit=0
--- RED (packages/plugins renamed away) ---

  x check-driver-conformance: declared scan root(s) do not resolve, so the matrix would
    have been built from an axis nothing could read:

    packages/plugins — does not exist

  DRIVERS_DIR and CASE_SETS_DIR (scripts/check-driver-conformance.mjs) must both be
  directories in the checkout. If one was renamed or moved, point the constant at it; if it
  was deleted, that is a deliberate decision to record. Do NOT restore a tolerant skip: this
  used to be `catch { return []; }`, and a dead root produced an empty axis whose downstream
  errors named the wrong cause (#4930).

exit=1
--- RED (packages/spec/src/data renamed away — pre-fix this printed 5 wrong-cause errors) ---

  x check-driver-conformance: declared scan root(s) do not resolve, so the matrix would
    have been built from an axis nothing could read:

    packages/spec/src/data — does not exist
exit=1
--- GREEN again (restored) ---
check-driver-conformance: OK — 20 covered cell(s), 0 in the DEBT ledger, 0 exempt.
exit=0

改前同一次 packages/spec/src/data 改名:

  x CLASSIFIED: CASE_SETS names FILTER_LOGIC_CASES, which filter-logic-conformance.ts no longer exports.
  x CLASSIFIED: CASE_SETS names TEMPORAL_CASES, which temporal-conformance.ts no longer exports.
  x CLASSIFIED: CASE_SETS names TEMPORAL_TIME_CASES, which temporal-conformance.ts no longer exports.
  x CLASSIFIED: CASE_SETS names PAGINATION_CASES, which pagination-conformance.ts no longer exports.
  x CLASSIFIED: CASE_SETS names PAGINATION_UNORDERED_CASES, which pagination-conformance.ts no longer exports.

双向证明已折进常驻自检

观察绿对一个"失败模式就是少扫"的闸门毫无意义,所以每次运行都先观察红。三个 --self-test 现在都会:把一个根改名走开 → 要求红并点名该根、不牵连幸存的根 → 把另一个根换成文件 → 要求 exists but is not a directory → 在 walk 里挂一个悬空 symlink → 要求报错而不是语料变小 → 全部恢复 → 要求重新变绿。

check-single-authz-resolver.mjs 原本没有 --self-test,本 PR 补上,并把它原有的两条不变量(重复 resolver 检测、入口点仍然委派)也用真实 walker 在真实临时目录上驱动一遍。

⚠️ 动了根 package.json(一行)

仓内 10 个 check:* 条目都串了 --self-test &&,只有 check:authz-resolver 没有(因为它没有自检)。本 PR 补齐:

-    "check:authz-resolver": "node scripts/check-single-authz-resolver.mjs",
+    "check:authz-resolver": "node scripts/check-single-authz-resolver.mjs --self-test && node scripts/check-single-authz-resolver.mjs",

没有新增/删除脚本条目,不影响 check:generated 的 ledger 对账。未改 .github/workflows/

验证

  • pnpm check:authz-resolver / check:startup-registry-verdict / check:driver-conformance — 全绿(自检 + 实跑),即当前 main 的现存状态没有因为收紧被判红。
  • npx eslint 三个脚本 — 干净,exit 0。
  • pnpm check:changeset-fixed / check:changeset-no-major — 绿。

刻意没动的(议题已逐条排除的不同形状)

check-changeset-fixed.mjs / check-override-consistency.mjs 的 glob 展开中途 catch { continue; }check-nul-bytes.mjs 的记账式 skipped.unreadablecheck-changeset-no-major.mjs 声明过的无 .changeset 行为——这三类是对的,保持原样。discoverDrivers() 里那个 statSync(package.json).isFile() 的 try/catch 同理:它筛的是枚举出来的候选集,不是声明的根。

…tes (#4930)

`check-single-authz-resolver`, `check-startup-registry-verdict` and
`check-driver-conformance` each concluded from a walk that opened with
`try { readdirSync(dir) } catch { return out/[]; }`. A root that was renamed,
moved or made unreadable produced zero files, and zero files produce zero
findings — the same verdict as a clean workspace. Same shape as #4916/#4934.

Each script now resolves every declared root before drawing any conclusion
from the scan, and fails naming the root and the reason ("does not exist" /
"exists but is not a directory"). The swallows inside the walks are gone too:
an error mid-walk means the corpus was only partly read, which must not print
as a clean pass either. No whitelist and no `optional: true` flag — every root
is a git-tracked directory, so an optional marker would be a supported way to
silence the failure instead of fixing the rename.

Measured on this tree, pre-fix: only check-single-authz-resolver was genuinely
vacuous end to end (it went red by luck, because both DELEGATORS live under the
same root, reporting "Delegator missing" for the wrong cause). The other two
already failed, but named the wrong cause — startup-registry-verdict's
`existsSync` accepted a file and then blamed an empty corpus, and
driver-conformance answered a renamed `packages/spec/src/data` with five
"CASE_SETS names X, which <file> no longer exports" errors that read as five
deliberate deletions. Both now name the directory.

The proofs are bidirectional and permanent, not one-offs in the PR body: each
`--self-test` renames a root away mid-run and requires red naming that root,
replaces one with a file and requires the not-a-directory verdict, dangles a
symlink inside the walk and requires the error rather than a smaller corpus,
then restores everything and requires green again. check-single-authz-resolver
had no self-test and gained one covering both of its original invariants;
`package.json` chains `--self-test &&` into `check:authz-resolver` to match the
other ten `check:*` entries.

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

vercel Bot commented Aug 3, 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 3, 2026 4:59pm

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation dependencies Pull requests that update a dependency file tooling size/l labels Aug 3, 2026

Copy link
Copy Markdown
Contributor Author

CI 说明:Validate Package Dependencies 的红与本 PR 无关

本 PR 只改了三个 scripts/check-*.mjs 和根 package.json一行(给 check:authz-resolver 串上 --self-test &&)。那一行命中了 validate-deps.yml**/package.json paths 过滤,把这个 job 拉了起来,而它红在 pnpm-lock.yaml —— 本 PR 从未碰过的文件:

| https://osv.dev/GHSA-rgw5-rvv9-x895 | 7.5  | npm       | brace-expansion | 5.0.8   | 5.0.9         | pnpm-lock.yaml |

pnpm-lock.yaml:29 已有的 override brace-expansion@>=5.0.0 <5.0.8: ^5.0.8 是为上一次同包公告加的,上界正好停在 5.0.8,新公告把 5.0.8 本身划了进去,于是不再覆盖。

证据表明这是既有状态而非本 PR 引入:

已按 Prime Directive #10 单独归档为 #4946(未认领),在本 PR 里顺手改锁文件。


Generated by Claude Code

@xuyushun441-sys
xuyushun441-sys marked this pull request as ready for review August 3, 2026 17:03
@xuyushun441-sys
xuyushun441-sys added this pull request to the merge queue Aug 3, 2026
Merged via the queue into main with commit d5eae53 Aug 3, 2026
22 of 23 checks passed
@xuyushun441-sys
xuyushun441-sys deleted the claude/issue-4930-silent-scan-roots branch August 3, 2026 17:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file documentation Improvements or additions to documentation size/l tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

三个 check:* 脚本的扫描根消失时返回空数组而不是报错 —— 目录一改名,门禁在零个文件上报绿

2 participants