fix(spec): docs-gen 把 retiredKey() 墓碑渲染成 never,而不是 any (#5606) - #6058
Draft
os-zhuang wants to merge 1 commit into
Draft
fix(spec): docs-gen 把 retiredKey() 墓碑渲染成 never,而不是 any (#5606)#6058os-zhuang wants to merge 1 commit into
os-zhuang wants to merge 1 commit into
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
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/**整体重生成不在本 PR 内派单要求「只跑
gen:docs,⛔ 不跑gen:schema」。这条在全新 worktree 里不可执行,实测:packages/spec/json-schema/被.gitignore:61整目录忽略,checkout 里不存在;build-docs.ts自 #4723 起把「树必须先存在」做成致命前置(b5bdf48 / #6000 今天刚落 main)。也就是说gen:docs单跑没有任何路径可以成立。而
gen:schema被本座位的权限系统拒绝(两次尝试均被 classifier 拦下)。权限系统的拒绝不是我可以绕的东西 —— 我没有去 shell 出底层脚本、也没有从别的 worktree 拷贝一棵 json-schema 树(后者会正好撞上那道前置守卫要防的「陈旧树假绿」)。顺带一个副作用值得记下:因为
gen:schema从未跑过,本分支对三个热点生成物(authorable-surface.json/json-schema.manifest.json/api-surface.json)的改动量是零,git status全程只有三个文件。派单里那条 ⛔ 的目的其实是完整达成的。预期红
Check generated reference docs are in sync with the spec(pnpm --filter @objectstack/spec check:docs,.github/workflows/lint.yml:616)content/docs/references/**与新渲染器的产出有差异check:authorable-surface会先生成 json-schema 树,check:docs再拿它重渲染并逐字节比对;渲染器变了而生成页没跟着变,必红。实测红行(第 19 步日志节选):
逐字命中本表 —— 这正是上面那节说的、本座位跑不了的那一步。
平台故障(非本 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。已对 lint 工作流rerun_failed_jobs重投一次(run 31118024384,attempt 2),ESLint仍是 0 步骤取消,故本 PR 尚无 ESLint 作业的真实判定。本地已单独跑过npx eslint两个改动文件(退出码 0);改动不涉及 fake engine / error code / route envelope,家族门的读点面未被触及。Check Changeset、No other open PR may claim the same issue、Dogfood Regression Gate (1/3)、(3/3)、Vercel Preview Comments。停放期间不为这些红推任何新提交;重投与落地由 PM 驱动。
修法只有一条命令,需要一个
gen:schema未被拒的座位:这一步本来就排在解除停放的动作里(见下,「再重跑
gen:docs整体重生成」),所以现在补一版也会在解除时被重做一次;真正的成本是本 PR 在停放期间带着一条已知红。除上表与上述平台签名之外的任何其它签名都不是预期内的,应当当成真问题处理。停放
停放:等待 #5837(spec 生成物分片)落地且 spec 座位独占窗口解除;解除后最后一轮 merge origin/main(先 commit merge,再重跑
gen:docs整体重生成 —— ⛔ 生成页不做文本合并)→ 红清零 → 由 PM 转 ready关于 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/,但它是生成器,产物是发布面的一部分。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前置)。Record<string, any>[],抹掉已声明键 —— #4001 战役每个 open 分类站点都会复发 #4912 / gen:docs 给「元素是联合类型」的数组少了括号,164 处参考页单元格声明了另一种类型(string | number[] ≠ (string | number)[]) #5338 当年分开落的理由),所以 参考文档生成器把数值字面量 z.literal(2) 渲染成带引号的 '2',数值型联合被记成字符串型 #5729 应当串行排在本单之后,而不是并发。⛔ 本 PR 不顺手修 #5729 —— 它是独立单,且正文明确要求墓碑这一单不搭车。