Skip to content

test(spec): 别名一致性闸门收编 44 个 strictUnknownKeyError 直调点 —— probe 撞车维度首测 (#5483) - #5609

Merged
os-zhuang merged 2 commits into
mainfrom
claude/issue-5483-strict-unknown-registry
Aug 5, 2026
Merged

test(spec): 别名一致性闸门收编 44 个 strictUnknownKeyError 直调点 —— probe 撞车维度首测 (#5483)#5609
os-zhuang merged 2 commits into
mainfrom
claude/issue-5483-strict-unknown-registry

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Fixes #5483

按路线 1(过渡看守)落地:让 strictUnknownKeyError 自己登记别名表,alias-integrity.test.ts 因此把 44 个直调点也判了 —— 一个调用点都没改

为什么需要它

#5013 的闸门判的是 strictObject 建的表:那个 helper 在构造期登记 { options, shape },所以闸门能拿到运行时 shape。helper 之前的老接线直接调 strictUnknownKeyError,自带一份手抄的 knownKeys 数组,没有 shape 可登记,于是这 44 张表在闸门的每一条判据之外。

#5481 之后判据从两条变成三条,而第三条(表内 aliasProbe 撞车)只读别名表本身、不依赖 shape。所以本 issue 正文里那句「实测干净」只覆盖前两条 —— 这批表在撞车维度上是未测量,不是已测净。

怎么做的

  • 新增 packages/spec/src/shared/alias-table-registry.ts:一张独立的登记表。刻意不并进 strictObject 那张,因为两者成色不同,合表等于把弱的那一半悄悄贴上「shape 背书」的标签。
  • strictUnknownKeyError 首行登记自己的 { surface, knownKeys, aliases, guidance }。登记放在工厂里而不是调用点上,这是「零调用点改动」的全部机关,也让 把 44 个 strictUnknownKeyError 直调点批量迁到 strictObject,棘轮降到 0(路线 1 消不掉手抄数组与 shape 的漂移) #5593 的迁移仍然是一次干净可分离的改动。
  • strictObject 内部那次调用用 withoutDirectAliasTableRegistration 抑制登记:它的表已经带 shape 登记在更强的那张里,重复登记会让本表的规模取决于「此前有没有哪个测试恰好触发过一次拒绝」。规模随测试顺序漂移的东西不叫测量。
  • 闸门的 forcing walk 现在也会构建 error map(用一个合成的 unrecognized_keys issue)。两处 error map 是延迟构建的:strictObject(绕开 field.zodsuggestions.zod 的循环导入)和 data/object.zod.ts(绕开 temporal dead zone)。对直调点而言,这个延迟就是「登记了」和「根本看不见」的区别。

三条判据在这批表上的成色(闸门自己也这么写)

判据 strictObject 的 235 张 这 44 个直调点
别名 key 不得是已知键 .shape 手抄数组
别名 target 必须是已知键 .shape 判(含墓碑) 手抄数组判(墓碑不可见)
表内 aliasProbe 不得撞车 判 —— 完全等价

前两条继承手抄数组与 shape 之间的漂移,那是路线 1 消不掉的一半,归 #5593

首测结果

44 个源码调用点 → 运行时 52 张表(ui/app.zod.ts 的导航项工厂一个调用点跑九次,每个 type 变体一张;strictObject 那 235 张不受影响)。

两处显式豁免(都带陈旧检查,不许静默放过)

  1. PROSE_ALIAS_TARGETS —— ui/app.zod.ts 六条刻意的散文式 target(形如 type: 'url' (with url))。它们回答的是「键名对、变体错」,裸键名会误导。逐条枚举、绑定到 nav 那一族 surface;配套一条测试要求每条仍被用到,否则删除。
  2. VARIANT_LEGAL_GUIDANCE —— children 的 guidance 在 object / group 两个变体上确实不触发,因为那两个变体真的声明了 children。一张手写 guidance 被盖了九份,这是判据碰上变体族、不是死条目;同样逐条枚举 + 陈旧检查。

反向验证(先定方向再跑)

五次注入,每次先写下预期方向再执行:

注入 预期 实测
A 直调表里塞一个不存在的 target 红(判据 2),不被 #5555 的容差吸收 符合 —— 1 failed / 17 passed
B 直调表里塞一对 probe 撞车的 key 红(判据 3),同 target 的休眠冗余款 符合 —— 1 failed / 17 passed
C nav 族里加第五种 expanded 拼法 红 —— 容差是结构化的,新拼法落进 broken 符合 —— 1 failed / 17 passed
D 去掉工厂里的登记调用 红(规模自检) 红得更多,6 failed
E 去掉 forcing walk 里的 error map 触发 红「延迟 error map 也被触发」一条 多红一条,2 failed
=== A   x every alias target is a key the table claims to accept
        Tests  1 failed | 17 passed (18)
=== B   x no two alias keys in one table collapse onto the same probe (#5481)
        Tests  1 failed | 17 passed (18)
=== C   x every alias target is a key the table claims to accept
        Tests  1 failed | 17 passed (18)
=== D   x the direct call sites really registered (self-test before the verdict)
        x every direct call site with an alias table was reached at runtime
        x the deferred error maps were forced too
        x the nav-item factory registered one table per variant
        x every prose-target exemption is still load-bearing
        x every variant-legal guidance exemption is still load-bearing
        Tests  6 failed | 12 passed (18)
=== E   x every direct call site with an alias table was reached at runtime
        x the deferred error maps were forced too
        Tests  2 failed | 16 passed (18)
=== BASELINE restored
        Tests  18 passed (18)

两处预期没中,都按实测改了说法,没有把结果套回模板:

  • D 比预期红得多(6 条而非 1 条)。方向没错,范围错了:登记一停,所有「豁免仍被用到」的陈旧检查也一并失去依据 —— 它们读的正是这张登记表。这其实是好消息:说明陈旧检查不是自证的。
  • E 比预期多红一条。 我原本在测试注释里写「删掉触发之后覆盖检查不会注意到」,这是错的,实验直接推翻了它:data/object.zod.ts:962surface 与别名条目都是字面量,AST 看得见,所以覆盖检查会把它报成 unreached。注释已按实测改写:这条按名钉住的断言不是让「触发缺失」可见的东西,而是让它可读 ——「this object 不见了」指出机制,「object.zod.ts:962 未触达」只会让下一个人去找一个并不存在的 walk bug。

常规测试

pnpm --filter @objectstack/spec exec vitest run src/shared --maxWorkers=2
   Test Files  17 passed (17)
        Tests  303 passed (303)

pnpm --filter @objectstack/spec exec tsc --noEmit         -> exit 0
pnpm --filter @objectstack/spec check:strictness-ledger   -> 三条全绿,数字无变化
node scripts/check-nul-bytes.mjs                          -> OK(5501 个文件,无裸控制字节)

棘轮

toBeLessThanOrEqual(44) 一个字符没动。它劝阻的事情 —— 新增一个直调点、再抄一份键表 —— 在这批表拿到看守之后与之前一样不受欢迎。注释改了,因为原文说这批「没人看着」,现在不成立。

其他

  • changeset:@objectstack/spec patch。helper 里多了一次登记调用,是随包发布的运行时代码(对解析行为无影响),按 AGENTS.md「功能/改进加 changeset」与 ReportSchema 的 filter 别名指向 filters —— 一个 ReportSchema 同样拒绝的键(#4001 战役自己的假处方,第 5 例) #5013 自身闸门 PR 的先例走 patch。
  • check:strictness-ledger 数字无变化,故gen:strictness-ledger 产物(本 PR 没有新增 z.object( 站点,新文件也不是 *.zod.ts)。未新增任何 check: / gen: 脚本,无需登记 check-generated 台账。
  • 公开 API 面无变化:新登记模块shared/index.ts 导出,与 strict-object.ts / alias-probe.ts 同待遇 —— 闸门按相对路径取用的内部接缝。
  • 登记表带容量上限(512,当前占用 52)。strictUnknownKeyError 是发布出去的 API(在 api-surface.json./shared 下),消费者完全可以在循环里按租户建 error map;无上限的登记表会变成别人进程里的滞留物。溢出计数由闸门断言为 0,所以「装不下」会是一次响亮的失败,而不是悄悄判一个前缀。

跟进


🤖 Generated with Claude Code

https://claude.ai/code/session_018fxLGQdatPbBUvCgiVxg6D


Generated by Claude Code

…5483)

#5013 的闸门判的是 strictObject 建的表(构造期登记 {options, shape}),而 helper
之前的老接线直接调 strictUnknownKeyError、自带一份手抄 knownKeys 数组,没有 shape
可登记,于是这 44 张表在闸门的每一条判据之外。#5481 把判据加到三条之后,其中第三条
(表内 aliasProbe 撞车)只读别名表本身、不依赖 shape —— 也就是说这批表在撞车维度上
一直是「未测量」,而不是 issue 正文说的「已测干净」(那次测量早于 #5481)。

登记放在工厂里而不是调用点上:strictUnknownKeyError 首行把自己的
{surface, knownKeys, aliases, guidance} 记进一张独立的内部登记表
(shared/alias-table-registry.ts,不进 shared/index.ts 桶,与 strict-object.ts /
alias-probe.ts 同待遇)。44 个调用点一个都没改,#5593 的迁移因此仍是一次干净可分离
的改动。strictObject 内部那次调用用 withoutDirectAliasTableRegistration 抑制登记 ——
它的表已带 shape 登记在更强的那张里,重复登记会让本表规模取决于此前有没有哪个测试
恰好触发过一次拒绝。

闸门的 forcing walk 现在也用一个合成 unrecognized_keys issue 构建 error map:
strictObject 与 data/object.zod.ts 都把 map 延迟到首次拒绝才建,对直调点而言这个
延迟就是「登记了」和「根本看不见」的区别。

首测:44 个源码调用点 → 运行时 52 张表(ui/app.zod.ts 的导航项工厂一个调用点跑九次)。
- aliasProbe 撞车 0 条(参考:已覆盖的 235 张里实测 4 条,PR #5516 清零)
- 别名 key 不得是已知键 0 条
- 别名 target 必须是已知键 32 条,同一根因:NAV_ITEM_ALIASES 的四条「默认展开」
  别名指向 expanded,而 expanded 只声明在 group 变体上 —— 另外八个变体把作者指向
  该变体同样会拒的键。已立 #5555;修法要改面向作者的文案且落在调用点里,本单不动,
  这里钉成结构化的只减不增容差(≤32),第五种拼法直接红。

两处显式豁免各带一条陈旧检查:ui/app.zod.ts 六条刻意的散文式 target,以及 children
的 guidance 在 object/group 两个变体上本就合法。≤44 棘轮一字未动。

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

vercel Bot commented Aug 5, 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 5, 2026 9:26pm

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling size/l labels Aug 5, 2026
@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec.

109 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/ai/agents.mdx (via @objectstack/spec)
  • content/docs/ai/skills-reference.mdx (via @objectstack/spec)
  • content/docs/ai/skills.mdx (via @objectstack/spec)
  • content/docs/api/client-sdk.mdx (via @objectstack/spec)
  • content/docs/api/environment-routing.mdx (via @objectstack/spec)
  • content/docs/api/error-catalog.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-client.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-server.mdx (via @objectstack/spec)
  • content/docs/api/index.mdx (via @objectstack/spec)
  • content/docs/automation/approvals.mdx (via @objectstack/spec)
  • content/docs/automation/connectors.mdx (via @objectstack/spec)
  • content/docs/automation/flows.mdx (via @objectstack/spec)
  • content/docs/automation/hook-bodies.mdx (via packages/spec)
  • content/docs/automation/hooks.mdx (via @objectstack/spec)
  • content/docs/automation/index.mdx (via @objectstack/spec)
  • content/docs/automation/webhooks.mdx (via @objectstack/spec)
  • content/docs/automation/workflows.mdx (via @objectstack/spec)
  • content/docs/concepts/architecture.mdx (via @objectstack/spec)
  • content/docs/concepts/design-principles.mdx (via packages/spec)
  • content/docs/concepts/index.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-driven.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-lifecycle.mdx (via packages/spec)
  • content/docs/concepts/north-star.mdx (via @objectstack/spec)
  • content/docs/data-modeling/analytics.mdx (via @objectstack/spec)
  • content/docs/data-modeling/drivers.mdx (via @objectstack/spec)
  • content/docs/data-modeling/external-datasources.mdx (via @objectstack/spec)
  • content/docs/data-modeling/field-types.mdx (via @objectstack/spec)
  • content/docs/data-modeling/fields.mdx (via @objectstack/spec)
  • content/docs/data-modeling/formulas.mdx (via @objectstack/spec)
  • content/docs/data-modeling/index.mdx (via @objectstack/spec)
  • content/docs/data-modeling/objects.mdx (via @objectstack/spec)
  • content/docs/data-modeling/queries.mdx (via @objectstack/spec)
  • content/docs/data-modeling/schema-design.mdx (via @objectstack/spec)
  • content/docs/data-modeling/seed-data.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation-rules.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation.mdx (via @objectstack/spec)
  • content/docs/deployment/cli.mdx (via @objectstack/spec)
  • content/docs/deployment/tenancy-modes.mdx (via @objectstack/spec)
  • content/docs/deployment/troubleshooting.mdx (via @objectstack/spec)
  • content/docs/deployment/validating-metadata.mdx (via @objectstack/spec)
  • content/docs/getting-started/build-with-claude-code.mdx (via @objectstack/spec)
  • content/docs/getting-started/common-patterns.mdx (via @objectstack/spec)
  • content/docs/getting-started/examples.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-reference.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-start.mdx (via @objectstack/spec)
  • content/docs/getting-started/your-first-project.mdx (via @objectstack/spec)
  • content/docs/kernel/cluster.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/auth-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/cache-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/data-engine.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/index.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/metadata-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/storage-service.mdx (via packages/spec)
  • content/docs/kernel/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/email-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/queue-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sharing-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sms-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/storage-service.mdx (via packages/spec)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/spec)
  • content/docs/kernel/services.mdx (via @objectstack/spec)
  • content/docs/permissions/authorization.mdx (via @objectstack/spec)
  • content/docs/permissions/permission-sets.mdx (via @objectstack/spec)
  • content/docs/permissions/permissions-matrix.mdx (via @objectstack/spec)
  • content/docs/permissions/positions.mdx (via @objectstack/spec)
  • content/docs/permissions/rls.mdx (via @objectstack/spec)
  • content/docs/permissions/sharing-rules.mdx (via @objectstack/spec)
  • content/docs/plugins/adding-a-metadata-type.mdx (via @objectstack/spec)
  • content/docs/plugins/development.mdx (via @objectstack/spec)
  • content/docs/plugins/index.mdx (via @objectstack/spec)
  • content/docs/plugins/packages.mdx (via @objectstack/spec)
  • content/docs/protocol/backward-compatibility.mdx (via @objectstack/spec)
  • content/docs/protocol/diagram.mdx (via packages/spec)
  • content/docs/protocol/kernel/config-resolution.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/http-protocol.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/i18n-standard.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/index.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/lifecycle.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/plugin-spec.mdx (via @objectstack/spec)
  • content/docs/protocol/knowledge.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/query-syntax.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/schema.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/security.mdx (via packages/spec)
  • content/docs/protocol/objectql/state-machine.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/actions.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/concept.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/layout-dsl.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/record-alert.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/widget-contract.mdx (via @objectstack/spec)
  • content/docs/releases/implementation-status.mdx (via @objectstack/spec)
  • content/docs/releases/index.mdx (via @objectstack/spec)
  • content/docs/releases/v12.mdx (via @objectstack/spec)
  • content/docs/releases/v13.mdx (via @objectstack/spec)
  • content/docs/releases/v16.mdx (via @objectstack/spec)
  • content/docs/releases/v17.mdx (via @objectstack/spec)
  • content/docs/releases/v9.mdx (via @objectstack/spec)
  • content/docs/ui/actions.mdx (via @objectstack/spec)
  • content/docs/ui/apps.mdx (via @objectstack/spec)
  • content/docs/ui/create-vs-edit-form.mdx (via @objectstack/spec)
  • content/docs/ui/dashboards.mdx (via @objectstack/spec)
  • content/docs/ui/forms.mdx (via @objectstack/spec)
  • content/docs/ui/index.mdx (via @objectstack/spec)
  • content/docs/ui/public-data-collection.mdx (via @objectstack/spec)
  • content/docs/ui/setup-app.mdx (via @objectstack/spec)
  • content/docs/ui/translations.mdx (via @objectstack/spec)
  • content/docs/ui/views.mdx (via @objectstack/spec)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

Copy link
Copy Markdown
Contributor Author

PM 预记(session_018fxLGQdatPbBUvCgiVxg6D):本 PR 的 ESLint job 将因 #5604(main 侧 check:engine-double-contract 断裂,#5584 遗留,与本 diff 无关)而红——验收时不计入本单质量账,#5604 修复落地后合 main 重跑。本 PR 带 @objectstack/spec patch changeset,无需 skip 标签。


Generated by Claude Code

@os-zhuang
os-zhuang marked this pull request as ready for review August 5, 2026 22:15
@os-zhuang
os-zhuang added this pull request to the merge queue Aug 5, 2026
Merged via the queue into main with commit f8644c7 Aug 5, 2026
25 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-5483-strict-unknown-registry branch August 5, 2026 22:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/l tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

44 个 strictUnknownKeyError 直接调用点的别名表在 #5013 闸门覆盖之外(实测干净,但无人看守)

2 participants