fix(spec): docs-gen 把 retiredKey() 墓碑渲染成 never,而不是 any (#5606) - #6058
Merged
Conversation
`retiredKey()` 是 `z.never()`,`z.toJSONSchema` 把它发成 `{ "not": {} }` ——
没有 `type`、没有 `$ref`、没有 `enum`。`formatType()` 没有对应分支,于是全仓
约 28 处墓碑一路落到函数末尾的 `return prop.type || 'any'`,reference 页把一个
**已删除**的键印成了 **`any`**。
这是退役能得到的最差渲染。这些页面是升级作者(很常是 AI 作者,ADR-0033)的主要
输入,`heading?: any` 读起来不是「这个键被删了」,而是「这个槽存在,而且不校验」
—— 比它替换掉的 `heading?: string` **更**鼓励去写。写了之后 parse 会带着
`[REMOVED]` 处方硬拒,但那已经是在一份错元数据产出之后了。
两处改动,都落在 `scripts/lib/format-type.ts`:
- `{ not: {} }` 现在渲染成 `never`。这既是准确的 TypeScript(该键的 `z.input`
类型本就是 `never`),也不像 `any` 那样需要旁边的散文来兜底。
- 内联 shape 摘要在计入 `INLINE_KEY_LIMIT` **之前**先剔除墓碑。摘要格只印前 4 个
声明键的 `k?: type`,根本没有描述列,所以嵌套的墓碑无处安放处方:
`ui/theme.mdx` 宣传着 `{ base?: string; heading?: any; mono?: any }`,而这两条
处方在整页**任何地方都不出现**。退役键已不再是可写面,因此不再占用四个槽位之
一,也不再把作者**必须**写的键挤到 `…` 后面。已知的「把墓碑挪到 shape 底部」
规避办法覆盖不了这一类:#5248 把 `IndexSchema` 退役到只剩 3 个活键,在上限为 4
时第一个墓碑**在数学上**必然进入摘要。
逐键表行不受影响,描述列仍然完整携带 `[REMOVED]` 处方,只是类型格从 `any`
改成了 `never`。
反向验证(实测,两半分别做,方向都是常规的「还原缺陷 → 新钉子变红」):
注释掉 `isNeverNode` 前置返回 → 3 failed | 24 passed,三条红全部报
`expected 'any' to be 'never'`;还原该返回、把摘要改回不过滤的
`Object.keys(prop.properties)` → 4 failed | 23 passed,四条红报出「只做第一半」
会发布的中间态(`{ base?: string; heading?: never; mono?: never }`)。
⚠️ `content/docs/references/**` 的整体重生成不在本 commit 内:该步需要先
`gen:schema` 物化 gitignore 掉的 `packages/spec/json-schema/` 树,而本座位的
权限系统拒绝执行 `gen:schema`。详见 PR 正文。
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014wsZeReNTqiceBfLb5Pyf5
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
This was referenced Aug 6, 2026
…mat-type-never-tombstone
…stone renderer (#5606) Generated by `pnpm --filter @objectstack/spec gen:schema && gen:docs`. Do not hand-edit — regenerate instead. 30 reference pages, 117 lines: - 101 per-key tombstone rows: type cell `any` -> `never`, [REMOVED] prescriptions unchanged. - 16 inline summary cells: tombstones dropped before INLINE_KEY_LIMIT, e.g. Theme.typography.fontFamily `{ base?: string; heading?: any; mono?: any }` -> `{ base?: string }`, ObjectSchema.indexes drops `type?: any` and its trailing ellipsis. The three sharded artifact dirs (authorable-surface/, json-schema.manifest/, api-surface/) are byte-identical after gen:schema — this change does not reach them. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014wsZeReNTqiceBfLb5Pyf5
Contributor
📓 Docs Drift CheckNo hand-written docs reference the 0 changed package(s). ✅ |
Contributor
Author
|
CI 红分诊(PM 座位,会话 Generated by Claude Code |
os-zhuang
marked this pull request as ready for review
August 7, 2026 01:14
qq9340100
pushed a commit
that referenced
this pull request
Aug 7, 2026
main 落了 6e82972(#5606/#6058):docs-gen 把 retiredKey() 墓碑从 any 渲染成 never。本分支的 FieldMapping.transform 墓碑正好出现在这三页上,故 merge ref 用新 渲染器重算与分支内旧产物不一致。按门自身处方 gen:schema + gen:docs 重生成。 三页 diff 即 any→never 形状(同页上先前就存在的 rateLimitConfig 墓碑 #4911 也一并 翻新,可证是渲染器变更而非本分支改动),外加同一渲染器变更的连带效果: fieldMappings 的内联形状摘要不再打印 never 化的已退役属性,腾出的位置显示真实键。 strictness ledger 的 cloud/ 82→83 来自本次 merge(本分支从未触及 cloud/)。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011M7UwH25Unfi73UHim7ajY
This was referenced Aug 7, 2026
This was referenced Aug 7, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #5606
前提复核(对 origin/main,非引用 issue 正文)
三条都自己量过,前提成立:
packages/spec/scripts/lib/format-type.ts的formatType()确实没有z.never()分支。用真实转换器探过墓碑节点,输出向与
io: 'input'回退向都是同一个形状:无
type/ 无$ref/ 无enum⇒ 一路落到return prop.type || 'any'。页面实例仍在 origin/main:
content/docs/references/ui/theme.mdx:130——| **fontFamily** | { base?: string; heading?: any; mono?: any } | optional | |,描述列空,两条处方整页不出现。content/docs/references/data/object.mdx:125与:199—— ADR-0049 enforce-or-remove:IndexSchema.type与IndexSchema.partial没有任何 DDL 消费者 #5248 的两处type?: any(ObjectSchema.indexes/ObjectExtensionSchema.indexes)。修法
两半都落在
scripts/lib/format-type.ts内,没有碰build-docs.ts——INLINE_KEY_LIMIT与内联摘要装配本来就住在 format-type.ts 里,所以派单里「可选第二半仅当落点仍在 format-type.ts 时才做」的条件成立(#5837 的 build-docs.ts 读点面未被触及)。{ not: {} }→never,置于formatType()最前:该节点什么都不接受,后面没有任何分支能比它更具体。准确的 TypeScript(该键z.input类型本就是never),而且不像any需要旁边的散文兜底。非空的not({ not: { type: 'string' } })是普通的否定约束,不匹配,保持原渲染。INLINE_KEY_LIMIT之前先剔除墓碑。 摘要格只印前 4 个声明键的k?: type,压根没有描述列 —— 嵌套的墓碑无处安放处方。退役键已不是可写面,不该占四个槽位之一,更不该把作者必须写的键挤到…后面。[spec] 退役 HookContext session.roles —— #4839 双删后零消费方零生产方(ADR-0049) #5050 的「把墓碑挪到 shape 底部」规避办法覆盖不了这一类:ADR-0049 enforce-or-remove:IndexSchema.type与IndexSchema.partial没有任何 DDL 消费者 #5248 把IndexSchema退役到只剩 3 个活键,上限为 4 时第一个墓碑在数学上必然进摘要。渲染变化(单测里钉住的,且已由本轮重生成逐字兑现):
Typography.fontFamily{ base?: string; heading?: any; mono?: any }{ base?: string }ObjectSchema.indexes{ name?: string; fields: string[]; unique?: …; type?: any; … }[]{ name?: string; fields: string[]; unique?: … }[](无…,摘要已完整)Theme.animation等自有表行的墓碑anynever(描述列[REMOVED]处方原样保留)测试与反向验证
新增
packages/spec/scripts/format-type.test.ts两个 describe 块,共 8 个 case。pnpm --filter @objectstack/spec test→ 325 files / 8316 tests passed。npx vitest run scripts/format-type.test.ts→ 1 passed / 27 tests passed。pnpm --filter @objectstack/spec typecheck→ 绿(check:test-typecheckOK;79 file / 691 error 的既有 debt 账本未增未减)。npx eslint两个改动文件 → 退出码 0。node scripts/check-nul-bytes.mjs→ OK(5779 个 tracked 文本文件);另按扩面自扫grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]'三个改动文件,无命中。反向验证(实测,两半分开做;方向都是常规的「还原缺陷 → 新钉子变红」,因为这些断言的是修复产出的肯定渲染,不是「某个发现消失」):
isNeverNode前置返回 →3 failed | 24 passed,三条红分别报expected 'any' to be 'never'与expected 'any[]' to be 'never[]'。第一块里第 4 个 case 故意保持绿:它断言的是非never的渲染(非空not的越界护栏),所以是护栏不是死钉。第二块整体保持绿 —— 这是两半彼此独立的诚实信号:把墓碑从摘要里滤掉,与它本来会渲染成什么无关。Object.keys(prop.properties)→4 failed | 23 passed,四条红全在第二块,报出只做第一半会发布的中间态:{ base?: string; heading?: never; mono?: never }、{ dead1?: never; a: string; dead2?: never; b?: string; … }、{ x?: never; y?: never }。比any安全,但仍在拿读者的四个槽位养没人能写的键。两次实测的数字与红行签名都写进了测试文件的块注释,不用重跑就能读到。
✅ 解除停放:
content/docs/references/**整体重生成(已完成,2026-08-07)停放期间挂着的那半件事,本轮补齐。序列严格按解除条件执行:
git merge origin/main—— 无冲突(本分支只碰 format-type.ts + 其单测 + changeset),merge 单独 commit 在先(fix(spec):--update-base只许锚点前移,MERGE 态与倒退一律拒绝 (#5370) #5851 MERGE-state 守卫 / os-regen 驱动指示的gen:schema在 merge 未 commit 时运行,会把 authorable-surface 锚点倒退回旧 merge-base —— 生成器写入、门全绿、静默撤销 main 的锚点推进 #5370 anchor 回归)。pnpm --filter @objectstack/spec gen:schema—— 本座位未被权限系统拒绝,正常跑完(✅ Successfully generated 1622 schemas)。它同时物化了 gitignore 掉的json-schema/树(1611 个文件)与分片产物。pnpm --filter @objectstack/spec gen:docs——✅ Generated 232 files。content/docs/references/**作为独立 commit 提交(⛔ 全程没有对生成页做任何文本合并 / 手改)。重生成 diff 的量与形(逐条实测):
any→never;其中带[REMOVED]处方的 = 101,即处方无一丢失(反向核对:never行里不带[REMOVED]的 = 0)INLINE_KEY_LIMIT前被剔除content/docs/references/之外的改动authorable-surface/、json-schema.manifest/、api-surface/)gen:schema跑完后git status对这三个目录全空,post-#6069 分片写入器无一片 CHANGED派单里最要紧的那条「分片产物必须干净」是独立核过的:
gen:schema之后整棵树git status --porcelain返回 0 行,gen:docs之后也只有 30 个 references 页。#6069 的分片与本单零交集,这点从实测面而不只是推理面成立。归因探针(先声明预期方向,再跑): 30 页里有多少是本单渲染器改动、有多少是 main 上本来就漂的?预期 —— 若
origin/main的生成页本就同步,则把format-type.ts换回origin/main版本再跑一次gen:docs,content/docs/references/**应当逐字节回到 origin/main(diff 全空)。实测正是如此:⇒ main 侧零漂移,30 页 / 117 行 100% 归因于本单的渲染器改动。探针跑完已把两边都还原回本分支状态(
git status干净)。兑现 PR 上文那张渲染变化表(从真实 diff 里摘的):
逐键表行的处方原样保留,例:
预期红(已清零 ✅)
TypeScript Type Check 作业内的Check generated reference docs are in sync with the spec(pnpm --filter @objectstack/spec check:docs,.github/workflows/lint.yml:616)content/docs/references/**与新渲染器的产出有差异✅ 232 generated files in sync with packages/spec,退出码 0停放期间这条红的成因(存档):CI 里
check:authorable-surface会先生成 json-schema 树,check:docs再拿它重渲染并逐字节比对;渲染器变了而生成页没跟着变,必红。初版正文把它挂在 ESLint 作业下是错的,check:docs落在typecheck:job 里(lint.yml第 616 行),已于停放期间自我更正。至此本 PR 不再有任何已声明的预期红。 除下节平台签名外的任何红都应当当成真问题处理。
本轮解除停放的完整验证(全部前台阻塞跑完,真实输出)
pnpm --filter @objectstack/spec check:docs232 generated files in sync with packages/spec(退出码 0)—— 预期红清零点pnpm --filter @objectstack/spec check:generatedAll 10 generated artifacts are up to date.(10/10 全绿,含check:api-surface与check:docs)npx vitest run scripts/format-type.test.tsTest Files 1 passed (1) / Tests 27 passed (27)pnpm --filter @objectstack/spec test(merge 后全量复跑)Test Files 326 passed (326) / Tests 8340 passed (8340)(较停放前 325/8316 增加的 1 file / 24 tests 来自 merge 进来的 #6069sharded-artifacts.test.ts)pnpm --filter @objectstack/spec typecheckcheck:test-typecheckOK,debt 账本 79 file / 691 error 未增未减npx eslint两个改动源文件node scripts/check-nul-bytes.mjs[\x00-\x08\x0b\x0c\x0e-\x1f\x7f],无命中一个值得记下的过程坑(非缺陷):新 worktree 里首次跑
check:generated,api-surface/报 stale;实际是gen:api-surface读packages/spec/dist/index.d.ts,而新 worktree 从未 build 过(Could not resolve module symbol for . … Is the package built?)。这正是 AGENTS.md §9 的陈旧产物陷阱。pnpm --filter @objectstack/spec build之后复跑即 10/10 全绿,不是 main 上的真漂移,也未产生任何 api-surface 改动。平台故障(停放期间的红,非本 PR;存档)
GitHub Actions 自 2026-08-06 15:42Z 起平台级故障(runner 引导 503)。停放期间本 PR 上除
check:docs外的全部红/取消都是这一签名,已由 PM 验签:Failed to resolve action download info. / Service Unavailable(annotation 逐字相同):Auto Label、Build Core、Check PR Size、Console Pin Gate、Flag docs affected by code changes。ESLint、Console Pin Freshness、Spec property liveness、filter。Check Changeset、No other open PR may claim the same issue、Dogfood Regression Gate (1/3)、(3/3)、Vercel Preview Comments。Auto Label被这场风暴打掉,导致本 PR 停放期间零标签;本轮 push 会重新触发。停放(已解除 ✅)
停放:等待 spec 生成物按 category 分片:拆掉三个单体 ratchet 文件的合并队列串行税(维护者 2026-08-06 已拍板) #5837(spec 生成物分片)落地且 spec 座位独占窗口解除→ 条件已满足:spec 生成物按 category 分片:拆掉三个单体 ratchet 文件的合并队列串行税(维护者 2026-08-06 已拍板) #5837 随 refactor(spec): 三个热点生成物按 category / entrypoint 分片,拆掉合并队列的串行税 (#5837) #6069 今日落 main,窗口解除。解除后最后一轮 merge origin/main(先 commit merge,再重跑→ 已执行,见上节。gen:docs整体重生成 —— ⛔ 生成页不做文本合并)关于 changeset
写了真 changeset(
.changeset/docs-gen-retired-key-never.md,@objectstack/specpatch),没有走skip-changeset。理由:本 PR 改的是读者可见的产物。reference 页是升级作者(尤其 AI 作者,ADR-0033)的主要输入面,heading?: any→ 键从页面上消失、type?: any→type从摘要里消失,是升级者会直接撞上的表述变化;而skip-changeset的适用面是「test-only / workflow-only /.claude/-only,什么都不发布」。本 PR 不属于那一类。改动虽然只落在scripts/,但它是生成器,产物是发布面的一部分 —— 本轮重生成把这 30 个页面实际提交进来,这条判断被进一步坐实。Check Changeset已绿。对 #5729 的定价(派单要求的回答)
答:变简单(easier),而且是同文件同函数的相邻分支。
字面量加引号发生在
format-type.ts,不在build-docs.ts:两处都无条件套单引号,不看
typeof e/typeof prop.const,所以z.literal(2)印成'2'、数值字面量联合印成字符串型 —— 这就是 #5729。build-docs.ts里唯一用到const的地方是 union options 段的**Type:** \${variant.properties.type.const}``(反引号,不加单引号),不参与本类缺陷。对 #5729 的具体影响:
format-type.test.ts现在有现成的 fixture 惯例与断言风格,参考文档生成器把数值字面量 z.literal(2) 渲染成带引号的 '2',数值型联合被记成字符串型 #5729 直接加一个 describe 块即可。content/docs/references/**,与本 PR 完全同一套流程。本轮已实测确认该流程在普通座位上可跑通(gen:schema未被拒),参考文档生成器把数值字面量 z.literal(2) 渲染成带引号的 '2',数值型联合被记成字符串型 #5729 不必再为这一步预留不确定性。Record<string, any>[],抹掉已声明键 —— #4001 战役每个 open 分类站点都会复发 #4912 / gen:docs 给「元素是联合类型」的数组少了括号,164 处参考页单元格声明了另一种类型(string | number[] ≠ (string | number)[]) #5338 当年分开落的理由),所以 参考文档生成器把数值字面量 z.literal(2) 渲染成带引号的 '2',数值型联合被记成字符串型 #5729 应当串行排在本单之后,而不是并发。⛔ 本 PR 不顺手修 #5729 —— 它是独立单,且正文明确要求墓碑这一单不搭车。