Skip to content

test(scripts): 棘轮陈旧消息按实际成因分句,覆盖全部三条退休路径 (#3674) - #3701

Merged
yinlianghui merged 2 commits into
mainfrom
claude/issue-3674-ratchet-message-both-paths
Aug 7, 2026
Merged

test(scripts): 棘轮陈旧消息按实际成因分句,覆盖全部三条退休路径 (#3674)#3701
yinlianghui merged 2 commits into
mainfrom
claude/issue-3674-ratchet-message-both-paths

Conversation

@yinlianghui

@yinlianghuiyinlianghui commented Aug 7, 2026

Copy link
Copy Markdown
Collaborator

Fixes#3674

单文件、两个 commit,只动 the objectui#3647 baseline only shrinks 这一条断言的消息stale 谓词与 expect(...).toEqual([]) 一字未动。

前提复核:成立,且比 issue 正文重一档 —— 是三条路径,不是两条

issue 点名的两条在 origin/main(切点 3e601773e,已含 PR #3687)上逐条命中:消息首行仍写死 A KNOWN_MISSING entry is stale — the path now resolves, so the defect is fixed.(:311),KNOWN_MISSING 已被 #3687 清空(:80)。

但复核 stale 的判据时量到第三条stale = KNOWN_MISSING 的键不在 stillMissing 里,而 missing = declared.filter((d) => !d.onDisk && !isDeclaredBuildOutput(d)) —— 谓词里有两个过滤条件,issue 只追踪了 declared 那一侧:

#一条基线条目离开 missing 的路径路径 resolve 了吗还在 files 里吗
1文件被建出来(onDisk 转真)✅ 是
2声明被删,条目不再进入 declared
3条目被 git-ignore 且其包有 build 脚本 → isDeclaredBuildOutput 转真

第 3 条是 issue 没有提到的:路径依然不存在、声明依然在,只是被豁免判据接管了。

这三条不是我编出来的分类 —— 同一个文件在 20 行之上已经把它们逐条列为「修复方向」了(存在性断言的消息,:290-294):

Fix it in whichever direction is true:
- the file should ship -> create it (that was the fix in PR #3662)
- the file is obsolete -> delete the entry from `files`
- it is build output -> git-ignore the path and give the package a `build` script,
which is how this guard recognises generated paths

三个被祝福的修法,一一对应三条退休路径。棘轮消息只描述了其中第一个。所以这不是「漏了一条」,是漏了三分之二,而正确答案本就写在同一个文件里。

取舍:成因可区分,故按实际成因分句(而非两因并列的中性表述)

派发单给了二选一:可区分则分句,不可区分则中性并列。实测可区分,且判据就在断言已经读的那份数据里 —— 对一条 stale 的 relPath,在 declared 里查一次即可:

  • 查不到 → 第 2 条(没有任何 files 声明它)
  • 查到且 onDisk → 第 1 条(路径存在了)
  • 查到且不 onDisk → 只可能是第 3 条(被豁免判据接管)

因此选了分句。中性并列写法的代价是它把「读者本可以被直接告知的事实」降级成「读者需要自己去 ls 一遍才能确定的事实」—— 而读者去 ls 恰恰就是 #3674 记录的那个失败现场:路径不存在,于是怀疑门禁自己坏了。

新增的极小逻辑(派发单要求写明)

一个 causeOf(relPath) 纯函数,只在构造失败消息时调用:

constcauseOf=(relPath: string): string=>{constentry=declared.find((d)=>d.relPath===relPath);if(!entry)return'no `files` entry declares it: the declaration was deleted, its package left the workspace, or this baseline key never matched one';if(entry.onDisk)return'the path now exists on disk';return'still declared and still absent, but now excused as build output (git-ignored, untracked, package has a `build` script)';};

从谓词读的同一份 declared / missing 数据里推导,不另建事实来源 —— 所以解释不可能与它所解释的判决漂移。这是刻意的:一份手写的成因说明会和谓词各走各的,正是本文件反对的形态(参见其对「白名单 vs 推导」的那段论证)。

第二个 commit:不宣称观察不到的历史

第 2 条最初写作「the files entry that declared it is gone」,自查时判为犯了本 PR 正要修的同一个错:谓词看到的事实只是「今天没有任何 files 条目声明它」,而这可能来自声明被删、包目录被删/改名/移出工作区,也可能来自这条基线键当初就拼错、从来没匹配过任何声明(键是手写字符串,完全可能)。说「is gone」是在断言一段它观察不到的历史。

故改为先陈述可观察事实(no \files` entry declares it`),再并列三种可能而不指定是哪一种。第 1、3 条本就是纯可观察表述,未动。

逆向验证:方向不是「红/绿翻转」,先说清楚

派发模板默认的「改前绿 / 改后红」在这里不成立,也不应该成立 —— 本 PR 不动谓词,红绿判决改前改后必须逐字节相同。若出现任何红绿差异,那才是缺陷(说明我不小心动了判断逻辑)。所以这里钉的是另一件事:同一组 fixture 下红绿不变,而归因从三分之一正确变为三分之三正确

三条路径在 fixture 层各演一遍(共用起手式:给 plugin-treefiles 加一条 "NOTICE",并把 packages/plugin-tree/NOTICE 挂进 KNOWN_MISSING),外加一个 F0 对照组:

扰动预测(动手前写下)实测
F0只做起手式(条目已声明、缺席、已挂账)5 绿 —— 条目在 missing 里,不 staleTests 5 passed (5)
F1建出 packages/plugin-tree/NOTICE棘轮红;归因应为「路径存在了」红 ✅
F2files 删掉 "NOTICE"棘轮红;归因应为「没有声明」红 ✅
F3把该路径写进 .gitignore(plugin-tree 有 vite build)棘轮红;归因应为「被豁免为构建产物」。这条是 issue 未记载的第三路径,若它返绿则我的三路径主张被证伪红 ✅,主张成立

四条预测全中,F0/F1/F2/F3 在改前改后红绿完全一致(F0 5 passed;F1/F2/F3 均 1 failed | 4 passed,失败的都是 the objectui#3647 baseline only shrinks)。

改前 —— 三条路径吐出逐字节相同的一句话

===== BEFORE F1 ===== / ===== BEFORE F2 ===== / ===== BEFORE F3 =====
AssertionError: A KNOWN_MISSING entry is stale — the path now resolves, so the defect is fixed.
Delete its line from KNOWN_MISSING in this file to bank the progress.
packages/plugin-tree/NOTICE (objectui#3674-fixture): expected [ 'packages/plugin-tree/NOTICE' ] to deeply equal []

F2 与 F3 下 packages/plugin-tree/NOTICE并没有 resolve —— 它依然不存在。F3 更刺眼:它连声明都还在。

改后 —— 同样三条路径,三句不同的归因

===== AFTER F1 =====
packages/plugin-tree/NOTICE (objectui#3674-fixture) — the path now exists on disk
===== AFTER F2 =====
packages/plugin-tree/NOTICE (objectui#3674-fixture) — no `files` entry declares it: the declaration was deleted, its package left the workspace, or this baseline key never matched one
===== AFTER F3 =====
packages/plugin-tree/NOTICE (objectui#3674-fixture) — still declared and still absent, but now excused as build output (git-ignored, untracked, package has a `build` script)

首行同时改为不再作因果宣称的 A KNOWN_MISSING entry no longer describes a live defect.,补救指令(删掉那一行)原样保留 —— 它在三条路径下都正确,这是 #3674 正文判定本条为观察类的依据,不应被改动。

fixture 全部还原(git checkout -- 定向还原 + rm 新建文件),git status 除本文件外干净。

门禁

pnpm exec vitest run scripts/__tests__/package-files-exist.test.ts → Test Files 1 passed / Tests 5 passed
pnpm exec vitest run scripts/__tests__/ --maxWorkers=2 → Test Files 18 passed (18) / Tests 339 passed (339)
pnpm type-check:scripts → exit 0
npx eslint scripts/__tests__/package-files-exist.test.ts → exit 0
node scripts/check-control-bytes.mjs → OK (scanned 3682 tracked text file(s); skipped 85 binary)
grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f]' 该文件 → 零命中
逐码点扫零宽/不可见字符(U+200B/U+FEFF/U+2028 等) → 0;全文件非 ASCII 字符仅一个破折号(本就在用)

scripts/__tests__/ 全量从 #3687 时的 17 文件 / 305 测试涨到 18 / 339,增量来自本 PR 之外的其它单,与本改动无关。

并行冲突面

与在途 #3696 同文件但文本无交叠:#3696 预计新增独立断言块,本 PR 只改 the objectui#3647 baseline only shrinks 内部的消息构造。本 PR 未新增/删除/重命名任何 it,KNOWN_MISSING(仍为空)与其上方文档注释一行未动 —— 那段注释是 #3687 写的历史记录,其「删声明与建文件同样使基线条目退休」一句在本 PR 之后依然为真,只是不再穷举全部路径;真正需要穷举的是失败消息,已就地解决,故不扩大文件面去改注释。

changeset:不加

纯测试文件、纯失败消息措辞,零生产代码、零发布产物、零运行时行为变化 —— 连 bug 修复都不是。与 #3667 / #3687 同判(AGENTS.md 第 151 行:功能改进需 changeset,本 PR 不属于)。

越界发现

无新开单。上文第 3 条路径不是独立缺陷,而是本单要修的那句消息所遗漏的成因之一,落在 #3674 的完成范围之内(该单主张「消息只覆盖了 stale 成因中的一条」,三条与两条只是同一主张的计数更正),故就地修完,未另立单。


🤖 Generated with Claude Code

https://claude.ai/code/session_01GTRjn8xBqp75dk7kFupVRt

`the objectui#3647 baseline only shrinks` 的失败消息首行写死
「the path now resolves, so the defect is fixed」,而一条 KNOWN_MISSING
条目离开 `missing` 有三条路径 —— 恰好就是同文件存在性断言已经列举的三个
修复方向:建出文件、删掉 `files` 声明、git-ignore 加 build 脚本使其成为
构建产物。只有第一条会让路径 resolve,另外两条下该归因与现场矛盾
(#3665 实测撞上第二条)。
改动限于该断言的消息:新增 causeOf(),从断言谓词读的同一份
declared/missing 数据里逐条推出成因。stale 谓词与 toEqual([]) 一字未动。
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GTRjn8xBqp75dk7kFupVRt
@vercel

vercelBot commented Aug 7, 2026

Copy link
Copy Markdown

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

1 Skipped Deployment
ProjectDeploymentActionsUpdated (UTC)
objectuiIgnoredIgnoredAug 7, 2026 10:07pm

Request Review

「声明没了」的措辞原为「the `files` entry that declared it is gone」,
但谓词分辨不出「声明被删」与「这条基线键从来就没匹配过任何声明」
(手写的 KNOWN_MISSING 键拼错时即为后者)。改为先陈述可观察事实
(no `files` entry declares it),再并列三种可能历史而不断言是哪一种 ——
与本 PR 反对的那种「宣称观察不到的因果」保持一致。
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GTRjn8xBqp75dk7kFupVRt
@yinlianghui
yinlianghui marked this pull request as ready for review August 7, 2026 22:12
@yinlianghui
yinlianghui added this pull request to the merge queueAug 7, 2026
Merged via the queue into main with commit 45cdd4cAug 7, 2026
17 checks passed
@yinlianghui
yinlianghui deleted the claude/issue-3674-ratchet-message-both-paths branch August 7, 2026 22:12
yinlianghui pushed a commit that referenced this pull request Aug 7, 2026
#3701(修 #3674)刚在同一文件里立了个约定:棘轮报陈旧时,成因要**逐条实测
上报**,不能由消息硬编码假定 —— 因为「离开基线」有多条路径,只有其中一条
意味着缺陷真被修好了,把假定写死会把读者引去核对一个并不成立的事实。
本 PR 的 license 棘轮有同样的结构,原消息虽然用的是「或」式并列(没有 #3674
那个断言错成因的毛病),但仍是让读者自己猜是哪一条。改成与 #3701 同形的
逐条成因,四条路径各自实测:
1. 现在有许可证文本了(并列出文件名)—— 只有这条意味着许可证真被补上
2. 改成了 private,不再分发,自然不欠
3. 不再声明 license 字段,没有主张要兑现
4. 该路径下已没有包(移走/删除,或这个基线键从来就没匹配上)
四条分支逐一实测,均按预测点名(证据见 PR 正文)。
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GTRjn8xBqp75dk7kFupVRt
akarma-synetal pushed a commit to akarma-synetal/objectui that referenced this pull request Aug 10, 2026
…禁封死该类 (objectstack-ai#3696, objectstack-ai#3702) (objectstack-ai#3703)
* fix(react-runtime,sdui-parser): 补上两包声明了 MIT 却从未随包发布的许可证文本 (objectstack-ai#3696)
两个包的 package.json 都写着 "license": "MIT",但仓库里没有对应的许可证文本,
所以每一个已发布的 tarball 都不含 MIT 要求随分发附带的许可证与版权声明。
这不是 files 声明问题:npm 有一份无视 files 恒定打包的清单(package.json、
README、LICENSE/LICENCE、COPYING、main 指向的文件),files: ["dist"] 并不会
把 LICENSE 排除在外。缺失的原因是磁盘上根本没有该文件,所以修法是补文件,
不是改 files —— 本 PR 未动任何 package.json。
两份 LICENSE 均为仓根 LICENSE 的逐字节副本,blob 与另外 37 份同为
cf2ca28。
同时给 package-files-exist 门禁加一道独立断言块:凡非 private 且声明了
license 字段的包,必须有许可证文本。判据从工作区扫描派生,不是硬编码包名单,
所以第 40 个包加进来的当天就被覆盖 —— objectstack-ai#3647objectstack-ai#3696 连着两次都是靠人肉逐包
普查才发现的。
该门禁同时点名了 apps/console(@object-ui/console,publishConfig.access:
public,同一缺陷),它超出本单文件面,已挂 objectstack-ai#3702 并写进双向 ratchet 基线。
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GTRjn8xBqp75dk7kFupVRt
* docs(scripts): 去掉门禁注释里一个不存在的 issue 交叉引用 (objectstack-ai#3696)
新块的注释里写了 "objectui#4984",但 objectui 的编号目前在 3700 一带,
该号不存在 —— 那是另一个仓的同族教训被误标成了本仓引用。悬空引用比没有引用
更坏:读者会去查一个查不到的单子。改为直接把道理写清楚(谓词的两肢今天在树上
没有标本,无人行经的逻辑可以被后来的改动反转而不转红),不再冒充交叉引用。
纯注释改动,断言与逻辑一字未动;11 passed 不变。
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GTRjn8xBqp75dk7kFupVRt
* test(scripts): license 棘轮的陈旧成因也按 objectstack-ai#3701 的约定分句上报 (objectstack-ai#3696)
objectstack-ai#3701(修 objectstack-ai#3674)刚在同一文件里立了个约定:棘轮报陈旧时,成因要**逐条实测
上报**,不能由消息硬编码假定 —— 因为「离开基线」有多条路径,只有其中一条
意味着缺陷真被修好了,把假定写死会把读者引去核对一个并不成立的事实。
本 PR 的 license 棘轮有同样的结构,原消息虽然用的是「或」式并列(没有 objectstack-ai#3674
那个断言错成因的毛病),但仍是让读者自己猜是哪一条。改成与 objectstack-ai#3701 同形的
逐条成因,四条路径各自实测:
1. 现在有许可证文本了(并列出文件名)—— 只有这条意味着许可证真被补上
2. 改成了 private,不再分发,自然不欠
3. 不再声明 license 字段,没有主张要兑现
4. 该路径下已没有包(移走/删除,或这个基线键从来就没匹配上)
四条分支逐一实测,均按预测点名(证据见 PR 正文)。
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GTRjn8xBqp75dk7kFupVRt
* fix(console): 补上 @object-ui/console 的许可证文本并把基线清空 (objectstack-ai#3702)
PM 分诊裁定把 objectstack-ai#3702 并入本 PR。@object-ui/console(apps/console)与 objectstack-ai#3696 的
两个包是同一个缺陷:非 private、声明 "license": "MIT"、无许可证文本,而它确是
发布物(publishConfig.access: public、版本 17.3.0 同线、在 changeset fixed 组、
有 prepublishOnly)。之前它超出 objectstack-ai#3696 的文件面,故只挂账不修。
改动两处:
1. apps/console/LICENSE —— 仓根 LICENSE 的逐字节副本,blob 同为
cf2ca28。未动 files 字段(npm 恒定收录
LICENSE),npm pack --dry-run 清单 3 → 4 个文件,LICENSE 出现。
2. KNOWN_LICENSE_TEXT_MISSING 清空。删除前先跑了一次套件让棘轮报陈旧,
逐条成因命中第一条(唯一意味着许可证真被补上的那条):
apps/console (objectui#3702) — it now ships license text (LICENSE)
删除后 11 passed。
同时:
- 基线上方的文档注释从「现在时描述一个条目」改写为历史记录。留着不动就会变成
一份描述并不存在的条目的声明 —— 与本 PR 正在消除的失真同类,只是搬进了注释
(objectstack-ai#3687 的同一处理)。棘轮机理段原文未动。
- 点名钉扎从两个包扩到三个,各带自己的 issue 号。这条比通用断言强:它不能靠
往基线里加一行来满足。删掉 apps/console/LICENSE 实测 2 红(通用断言 + 钉扎),
证明修完之后留下的是覆盖,不是沉默。
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GTRjn8xBqp75dk7kFupVRt
---------
Co-authored-by: Claude <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

package-files-exist 门禁的 ratchet 失败消息只描述了两条陈旧路径中的一条(「the path now resolves」)

2 participants

@yinlianghui@claude