Skip to content

test(scripts): 给文档散文里的版本宣称落一道棘轮门禁(带历史叙述结构性豁免) - #3711

Merged
yinlianghui merged 1 commit into
mainfrom
claude/issue-3697-version-claim-gate
Aug 7, 2026
Merged

test(scripts): 给文档散文里的版本宣称落一道棘轮门禁(带历史叙述结构性豁免)#3711
yinlianghui merged 1 commit into
mainfrom
claude/issue-3697-version-claim-gate

Conversation

@yinlianghui

Copy link
Copy Markdown
Collaborator

Fixes #3697

给「文档散文里的版本宣称」落一道静态门禁。测试-only,一个新文件 scripts/__tests__/doc-version-claims.test.ts,不碰任何文档本体、不碰 check-doc-links.mjs、不碰在途 #3674/#3696 的文件。

前提复核:成立,但派发预设的「裸宣称为零」被测量证伪

issue 的核心断言 —— scripts/ 下没有任何门禁读版本字面量 —— 我逐条核过,成立。

但派发裁决预设「裸宣称应为零(#3688/#3698 刚清)」。不是零。 那两次 PR 清掉的是 ^3.3.0 / ≥ 18 这两个拼写在它们各自触碰的面上,而这一族的一般形状从未被测量过。据实报账,不清账(文档修复不在本单文件面内,已另立单)。

全量测量(切点 d46b40324,即 PR #3698 的合并点)

content/docs + 38 个包 README,共 221 个文件,命中 38 处版本字面量:

分类 数量 说明
历史叙述(结构性豁免) 11 全部在 release-notes.md## v3.3.0 — 2026-04-17 节下
工具链事实(有锚,已核为真) 1 ci-cd-pipeline.md:82Node 22.x ↔ 14 处 workflow node-version: '22.x'
清单重述(有锚,与本包 package.json 逐字一致) 9 9 个包 README 的 **Peer Dependencies:**
样例/模板(不是对本仓的宣称) 5 插件作者自己的 package.json 骨架、markdown 渲染器的假 changelog 演示数据
无锚宣称(今天为真,但无处可核) 2 theming.md:355 的 Tailwind 能力下限、troubleshooting.md:71 的 React 18+
失真宣称(今天可测为错) 9 见下

分类扩充:派发给的三分类装不下,实测需要五类

裁决第 5 条要求「若测量发现三分类装不下的第四类形状,停手报告分类扩充方案」。测量确实溢出,但溢出的两类都是沿裁决自身的逻辑自然外推,不涉及契约取舍,故按裁决第 2 条的精神落地并在此报告,未停手:

  • 清单重述 —— 裁决第 2 条已说「工具链事实类若保留需可核实锚」。这一类就是该规则的推广:锚从 workflow YAML 换成同包的 package.json。不是新类,是同一类的第二种锚源。
  • 样例/模板 —— 真正的新形状。围栏里的内容有些根本不是对本仓的断言(plugin-markdown.mdx:259 是喂给渲染器演示表格的假 changelog),它既不是历史、不是事实、也不是裸宣称。

另加 无锚宣称,把「今天为真但无处可核」与「今天可测为错」分开 —— 前者是门禁要收缩的面,后者是要清偿的债,处置不同。

9 处失真(记账,本单不清账)

位置 文档写的 仓库真相 已立单
architecture-overview.md:16(ASCII 图内) @objectstack/spec ^4.0.4 29 处清单全为 ^17.0.0-rc.5 #3708
architecture-overview.md:35(散文) @objectstack/spec ^4.0.0 同上 #3708
architecture.md:317 TypeScript 5.0+ 34 处均为 ^6.0.3 #3708
create-plugin.mdx:152 @object-ui/core^0.3.0 的 peer 实为 workspace:* 的 dependency #3709
create-plugin.mdx:153 @object-ui/components 同上 同上 #3709
create-plugin.mdx:154 react peer ^18.0.0 ^18.0.0 || ^19.0.0,且漏了 react-dom peer #3709
plugin-chatbot/README.md:58 @ai-sdk/react v3 ^4.0.47 #3710
layout/README.md:20 react peer >= 18.0.0 ^18.0.0 || ^19.0.0(README 过度承诺) #3710
plugin-report/README.md:24 react peer ^18.0.0 || ^19.0.0 清单只有 ^18.0.0 落在 #3690 完成范围内,已追评

一处也没在本单修:文件面限定在 scripts/__tests__/。9 处全部以 kind: 'stale' 进清单 —— 记账不清账,债务留在下一个读者会绊到的地方,棘轮同时阻止第十处悄悄加入。

门禁形态:棘轮,不是正确性检查

它判不了「Tailwind v3.3+」是否为真(多数宣称没有锚)。它判的是事件:某个面上出现了一个版本字面量而没人写下理由。每个字面量要么

  1. 结构性豁免 —— 落在版本标题节下(发布说明是历史,必须冻结);要么
  2. 进同文件清单 KNOWN_CLAIMS,带 kind 与理由。

两个方向都棘轮:无条目的宣称变红,条目所指的宣称已消失也变红 —— 所以清干净一处文档就必须让清单缩短,清单不会烂成永久豁免。形状取自 ci-cd-pipeline-doc.test.tsDOCUMENTATION_EXEMPT(同目录先例,#3663 一族),清单进测试文件而非独立 JSON,以贴合本单的文件面。

两处取舍以实测定,不照搬

一、围栏扫描,与 check-doc-links.mjsstripCode() 相反

check-doc-links.mjs 抹掉围栏,其头注论证「不该拓宽」:围栏里合法地存在不是链接的 [..](..)。那条论证在那里成立,在这里反转,因为两个门禁为误报付的价钱不同 —— 它没有清单,误报即永久红;本门禁有清单,误报只值一行「这是演示数据」,而漏报要再赔一次 #3645 全族。

实测使这个取舍可量化:抹掉围栏会丢掉 38 处里的 12 处,其中两处正是最严重的活缺陷 —— architecture-overview.md:16 的三层架构 ASCII 图(差 13 个大版本)与 create-plugin.mdx:152 的脚手架产物(版本错且字段错)。

残留的洞据实写明而非暗示为全覆盖:两个扫描根之外的文件不在本门禁视野内

二、版本标题豁免必须写窄 —— 宽写法会让整份 RFC 静默逃逸

先想到的写法是「标题里含有像版本的东西」。实测:content/docs/rfcs/0001-clipboard-paste.md### 1.1 / ### 5.1 / ### 7.3 编号,18 个标题命中宽写法,整份 RFC 会掉出扫描面而门禁照报绿 —— 一个教科书式的空绿。

VERSION_HEADING 因此锚在标题开头,且两段式必须带 v 前缀:## v3.3.0 — 2026-04-17## [3.3.0] - 2026-04-17 是发布节,### 6.4 Quick-paste optimisation 是编号段落,继续被扫。这条区分由它自己的测试钉住,因为它是全文件唯一一处「regex 写懒一点就整体失效」的地方。

正则形状:覆盖与不覆盖

两个形状:@(objectstack|object-ui)/ + 包名 + 版本,以及 工具链名 + 版本(大小写不敏感 —— 包 README 的 peer 行把包名写成小写反引号,区分大小写只能量到 8 处,不区分能量到 27 处)。

版本 token 故意不接受「名字后面跟任意数字」:语料里有 | parseClipboard | Vitest | 100% branch |,裸整数规则会把它读成「Vitest 100」。字面量必须带点、带区间符(含排版符 )、带 v 前缀,或是 18+ 这样的显式下限。

代价写明:不带符号也不带点的裸大版本不被匹配 —— plugin-chatbot/README.md:9 的 "our Tailwind 4" 就漏了。放宽到能抓它,语料从 38 命中变成 37 处待审,多出来的绝大多数根本不是版本。而且只写大版本本就是最不易漂移的拼写,这份召回是值得丢的。

逆向验证:六个探针,方向都是先预测后跑

探针 预测 实测
0 基线 绿 Tests 6 passed (6)
1 植入裸宣称(quick-start.md 追加一行 spec ^9.9.9 + Node >= 16) ,点名文件与字面量 两条都点名:content/docs/guide/quick-start.md:177 的 spec ^9.9.9Node >= 16,1 failed
2 对照组(#3697 点名的 release-notes.md:41 / :58) 全程绿,一次都不被点名 输出里 release-notes 出现 0 次 ✅
3 删掉结构性豁免 ,11 处历史叙述失去豁免并点名 release-notes 点名 :16 :17 :43 :43…,2 failed
4 把 VERSION_HEADING 换成宽写法 ,编号标题测试报 RFC 会被误豁免 "1.1 Real-world scenarios" must stay SCANNED, not exempt
5 抽掉一条清单条目(plugin-report) ,恰好点名该条 packages/plugin-report/README.md:24
6 模拟文档被清干净(改 auth/README.md,清单条目留着) ,棘轮报「条目所指的宣称已不在树上」 KNOWN_CLAIMS names version claims that are no longer in the tree: packages/auth/README.md

探针 2 特别说明,免得读成空绿:release-notes.md:58| Node.js | ≥ 18 | 确实被正则匹配到了,是被结构性豁免放过的 —— 绿是「豁免生效」不是「没匹配上」,exemptClaims.length >= 8 那条断言就是钉这个的。:41^3.3.0根本不匹配(版本字面量离包名 15 个字符,超出 6 字符的 SEP 窗口),它的绿是「未检出」而非「被豁免」—— 两者性质不同,据实分开写,不合并成一句「对照组全绿」。

反空绿下限

files > 150allClaims > 25exemptClaims >= 8、豁免必须触及 release-notes.md、RFC 必须在豁免集合里、清单不得有重复键、每条理由长度 > 25 字符。全是下限不是等值 —— 文档天天增删,这个文件不该变成文件数快照。

门禁与测试

$ pnpm exec vitest run scripts/__tests__/doc-version-claims.test.ts
   Test Files  1 passed (1) ;  Tests  6 passed (6)

$ pnpm exec vitest run scripts/            # 整个 scripts 测试面,确认没打断邻居
   Test Files  19 passed (19) ;  Tests  345 passed (345)

$ pnpm run type-check:scripts              # tsconfig.scripts.json 正是覆盖 scripts/__tests__ 的项目
   exit=0

$ pnpm exec eslint scripts/__tests__/doc-version-claims.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]' scripts/__tests__/doc-version-claims.test.ts
   0 命中

node scripts/check-lint-coverage.mjs45/45 packages linted;node scripts/check-type-check-coverage.mjs → 无新增债务。

施工中付掉的一个陷阱,已写进文件头注

块注释的散文里写包路径通配符(段通配符后跟一个斜杠)会提前闭合该块注释,解析器随后在十几行之外的另一行注释上报一个毫无关系的错误。与本仓「写关于控制字节的规则时把控制字节写了进去」是同一形状 —— 写关于分隔符的文字,正是分隔符被具现化的时刻。注释里的包路径因此一律不带通配符,理由写在 TICK 常量的头注里。

changeset:不加

测试-only,无 API/行为变更,不产生用户可见的包变更。与 #3663 一族(scripts/__tests__/ 新增门禁)先例一致。

越界发现(只记录、不顺手修)

以上四条修好后都需同步更新本文件的 KNOWN_CLAIMS,否则 ratchet 变红 —— 这正是它的设计。


🤖 Generated with Claude Code

https://claude.ai/code/session_01GTRjn8xBqp75dk7kFupVRt


Generated by Claude Code

objectui#3697: no gate in this repository reads a version number, so the
`@objectstack/spec ^3.3.0` / `Node >= 18` family that #3645 and #3689 cleaned
up was found by a human census and nothing stops it coming back.

Adds a static ratchet over the two surfaces the issue names (`content/docs`
and the package READMEs). Every version literal must be either structurally
exempt — inside a `## vX.Y.Z` release section, which is history and must stay
frozen — or listed in an in-file inventory with a classification and a reason.
The inventory ratchets both ways: a claim with no entry fails, and an entry
whose claim is gone fails too, so cleaning a doc shrinks the list.

The census that set the design contradicted the dispatch's premise: the bare
claim count is not zero. 221 files, 38 literals, 11 exempt, 27 inventoried,
of which nine are measurably wrong today (`@objectstack/spec ^4.0.4` in the
architecture overview is thirteen majors stale). None is fixed here — this is
a test-only change and each repair is a docs edit; they are filed separately
and recorded as `kind: 'stale'`.

Two design points measured rather than assumed:

- Fences are SCANNED, inverting `check-doc-links.mjs`'s `stripCode()`. That
  gate has no inventory so a false positive is permanent red; this one absorbs
  a sample in a single line, while stripping fences would drop 12 of 38 hits
  including two of the worst live defects.
- `VERSION_HEADING` anchors at the start and demands a `v` prefix or a full
  three-part version. The naive spelling matches `### 1.1` / `### 6.4` in
  `rfcs/0001-clipboard-paste.md` and would silently exempt the whole RFC — a
  vacuous green, pinned by its own test.

No changeset: test-only, no user-visible change.

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

vercel Bot commented Aug 7, 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)
objectui Ignored Ignored Aug 7, 2026 11:47pm

Request Review

@github-actions github-actions Bot added the tests label Aug 7, 2026
@yinlianghui
yinlianghui marked this pull request as ready for review August 7, 2026 23:58
@yinlianghui
yinlianghui added this pull request to the merge queue Aug 7, 2026
Merged via the queue into main with commit 36bf202 Aug 7, 2026
17 checks passed
@yinlianghui
yinlianghui deleted the claude/issue-3697-version-claim-gate branch August 7, 2026 23:59
Sign up for free to 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.

观察记录:文档散文里的版本宣称无门禁可守 —— #3688 + #3689 把它清到零之后,没有任何东西阻止下一个作者写回去

2 participants