Skip to content

fix(spec): 别名表必须是对 schema 的真断言 —— 修 12 条存量 + 全仓闸门 (#5013) - #5486

Merged
os-zhuang merged 3 commits into
mainfrom
claude/issue-5013-alias-integrity
Aug 5, 2026
Merged

fix(spec): 别名表必须是对 schema 的真断言 —— 修 12 条存量 + 全仓闸门 (#5013)#5486
os-zhuang merged 3 commits into
mainfrom
claude/issue-5013-alias-integrity

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Fixes #5013

前提复核(先做的事)

issue 的复现日期是 08-03,此后 main 动过 ui/*.zod.ts 数次,所以先逐条对 origin/main
(ed0d2aa)取证。1 + 5 条全部仍如所述,而且 main 上的
ui/strictness-batch14.test.ts 自己就把这六条钉成了引用 #5013 的反向债务清单
(KNOWN_DEFECTS),连带确认了 issue 的判断。前提成立。

一张别名表是「对 schema 的两半断言」

  • 它挂的 key 是 shape 拒绝的(别名只从 unrecognized_keys 路径查表,已声明的 key 根本到不了);
  • 它开的 target 是 shape 接受的(账本 finding 12)。

两半都没人核对,而且两半都是假的。作者写 filter:

Unrecognized key(s) on this report: `filter`. … Did you mean `filter` -> `filters`?

照做之后第二次被拒,这次连建议都没有 —— ReportSchema 声明的是 runtimeFilter

改了什么(没有增删任何可授权 key,原本能 parse 的都照旧 parse)

建议 1 ReportSchemafilter/filters/where/criteria 一并指向 runtimeFilter,
与三十行之上的 JoinedReportBlockSchema 逐字一致 —— 容器和子报表必须用同一套话纠正作者。

建议 2 五条死条目删除(逐条已由运行时 .shape 复核仍是死的):
ReportSchemacolumns/chartDatasetSchemameasures/filter
ActionSchemabody —— 这些 key 本身都已被各自 schema 声明。
(顺带确认 chartConfigDashboardWidget 的 key,不是 report 的:那条别名当年指错了层。)

闸门额外挖出的 6 条,同一缺陷类,issue 的 AST 体检看不到:

  • system/email-template.zod.ts 5 条 —— content/htmlbodyHtmltextbodyText
    from/senderfromOverride。原来全指向 body / fromAddress,两个都不是这张 schema 的 key。
    contentbodyHtml 是有理由的:它是必填那个 body 且接受任意字符串,
    省略 bodyText 时服务端自己派生纯文本 —— 所以无论作者写的是标签还是散文,改名后都真的能渲染。
  • ai/skill.zod.ts 1 条 —— trigger 原指 triggers(从来不是 key)。
    没有改指 triggerConditions,因为正确答案是个分叉:路由意图进 triggerConditions,
    自然语言意图进 description/instructions(triggerPhrases 墓碑本身就是这么写的)。
    改名会把 trigger: 'create a case' 这样的短语塞进 condition 数组槽,变成值上的第二次拒绝 ——
    finding 7 原形。所以改成 guidance 条目,它不做任何假承诺。

建议 3 全仓闸门 packages/spec/src/shared/alias-integrity.test.ts

为什么判定必须走运行时,而不是读源码字面量

批 14 那版用 TS AST 读源码对象字面量、再把 surface 字符串手工映射到 schema。
三件事在全仓尺度上把它打穿,而且都是实测不是推测:

  1. spread —— ...MetadataProtectionFields 展开的 key 字面量读不到;
  2. 拼装的表 —— 10 个调用点的 aliases(或 surface)不是字面量
    (data/field.zod.tsui/theme.zod.tsautomation/etl.zod.ts 等),AST 读成空表并报告干净;
  3. 撞名的 surface —— 'this field group' 同时是两张 schema 的 surface,
    字符串不是 key,手工映射会拿甲的表去judge乙的 shape。

所以改成 strictObject构造期登记 { options, shape },表和 shape 出自同一个运行时节点,
没有第二份真相,也没有 per-surface 注册可忘。

一个中途踩到的坑值得记:最初是把 options 用 symbol 挂在构建出的 schema 实例上,
结果 .superRefine() / .extend() 的 clone 不带 symbol —— ReportSchemaDatasetSchema
这些最要紧的当场静默失去看守,而闸门显示绿。改成构造期登记才对。

AST 保留,但只做覆盖:枚举源码里存在的调用点,走查必须每个都够到。
这是「absence must be loud」那一半 —— 走查够不到的表就是这道闸门没在judge的表,它必须红。

闸门另外钉了两条自己judge 的边界:直接调 strictUnknownKeyError 的 44 个调用点
(只减不增棘轮,实测干净,#5483),以及同表内 aliasProbe 撞车(#5481)。

反向验证(先证红)

方向是跑之前定的,六次全部命中预测,每次都是预测的那一条断言红:

破坏 预测 实测
活别名改指不存在的 key target 断言红 红: `where` -> `runtimeFilterz` — 未声明
别名挂到已声明 key 上 死条目断言红 红: `columns` — 此处已声明
别名指向墓碑 key target 断言红 红: `phrases` -> `triggerPhrases` — 墓碑,不接受任何值
加一张谁也不构造的表 覆盖断言红,判定保持绿 红:ui/report.zod.ts:408 (this unreachable surface)
五个文件整体还原成 origin/main 全部 12 条被点名 红:12 条,含 ui/report.zod.ts:248 `filter` -> `filters`

第三行值得单独说:helper 自己的 knownKeys 过滤掉了墓碑,但别名表在那个 fallback 之前就被查,
所以「别名指向墓碑」是现有防线够不到的,这道闸门补上了。

第四行是这道闸门与批 14 那版的真正差别:后者对「表存在但没人judge」是静默通过的。

验证

pnpm --filter @objectstack/spec typecheck     # tsc --noEmit,干净
pnpm --filter @objectstack/spec test          # 311 files / 7945 tests 全绿
pnpm --filter @objectstack/spec check:generated  # 9 个生成物全部 up to date
pnpm --filter @objectstack/lint test          # 57 files / 1198 tests 全绿(消费侧抽查)

生成物 0 改动 —— 本 PR 不增删任何可授权 key、不动任何 .describe(),
所以 spec-changes.json / authorable-surface.json / 台账都没有落差,不存在手改生成物的问题。

packages/lint 首轮 10 个文件失败,是 AGENTS.md §9 的陈旧构建陷阱
(@objectstack/formula@objectstack/sdui-parser 未构建),
pnpm --filter '@objectstack/lint^...' build 后全绿,与本 PR 无关。

批 14 的反向钉子

ui/strictness-batch14.test.tsKNOWN_DEFECTS 按它自己注释的承诺红了 ——
「this list cannot outlive its debt」。它的 prescription-integrity 整节是删除而不是清空的:
留下来就是一份更弱的活检查副本(源码字面量读法,看不见 spread、拼装表、撞名 surface),
正是 strictObject 要消灭的「两份真相」。它带的那条 parse 级断言(结构闸门做不到的)留下了。

范围外发现(已单独立 issue,未指派)


Generated by Claude Code

claude added 3 commits August 5, 2026 12:34
`ReportSchema` answered `filter` with "Did you mean `filter` -> `filters`?"
and then rejected `filters` too, with no suggestion the second time — the
strictness campaign's own fix pointing authors into the failure mode it
exists to remove. Five more entries were filed under keys their schema
already declares, so they could never run at all.

Repointed the report's scope-filter aliases at `runtimeFilter` (matching
`JoinedReportBlockSchema` verbatim), deleted the dead entries, and fixed the
six further defects a repo-wide sweep found in `ai/skill.zod.ts` and
`system/email-template.zod.ts`.

`strictObject` now records each declaration so `alias-integrity.test.ts` can
judge every table in the package against the runtime `.shape` it makes claims
about.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018fxLGQdatPbBUvCgiVxg6D
…ion (#5013)

The 批 14 reverse pin fired exactly as its comment promised — "this list
cannot outlive its debt" — once all six defects it tracked were fixed. Its
prescription-integrity section is deleted rather than emptied: what would
remain is a second, weaker copy of a now-live package-wide check, built on
the source-literal reading that cannot see spreads, assembled tables, or
colliding surface strings. The parse-level assertion it carried, which no
structural gate can make, stays.

`report.test.ts` gains the author-visible half: all four scope-filter
spellings name `runtimeFilter`, the prescribed key parses, and `columns` /
`chart` still authoring cleanly proves those alias entries really were dead.

The gate also pins, shrink-only, the 44 tables that reach
`strictUnknownKeyError` directly and so sit outside it — measured clean, but
a boundary that must not grow unnoticed.

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 1:50pm

Request Review

@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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ReportSchema 的 filter 别名指向 filters —— 一个 ReportSchema 同样拒绝的键(#4001 战役自己的假处方,第 5 例)

2 participants