fix(spec): 别名表必须是对 schema 的真断言 —— 修 12 条存量 + 全仓闸门 (#5013) - #5486
Merged
Conversation
`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
…ry (#5013) Refs #5481, #5483. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018fxLGQdatPbBUvCgiVxg6D
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckThis PR changes 1 package(s): 109 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
This was referenced Aug 5, 2026
os-zhuang
marked this pull request as ready for review
August 5, 2026 14:11
This was referenced Aug 5, 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 #5013
前提复核(先做的事)
issue 的复现日期是 08-03,此后
main动过ui/*.zod.ts数次,所以先逐条对origin/main(ed0d2aa)取证。1 + 5 条全部仍如所述,而且
main上的ui/strictness-batch14.test.ts自己就把这六条钉成了引用#5013的反向债务清单(
KNOWN_DEFECTS),连带确认了 issue 的判断。前提成立。一张别名表是「对 schema 的两半断言」
unrecognized_keys路径查表,已声明的 key 根本到不了);两半都没人核对,而且两半都是假的。作者写
filter:照做之后第二次被拒,这次连建议都没有 ——
ReportSchema声明的是runtimeFilter。改了什么(没有增删任何可授权 key,原本能 parse 的都照旧 parse)
建议 1
ReportSchema的filter/filters/where/criteria一并指向runtimeFilter,与三十行之上的
JoinedReportBlockSchema逐字一致 —— 容器和子报表必须用同一套话纠正作者。建议 2 五条死条目删除(逐条已由运行时
.shape复核仍是死的):ReportSchema的columns/chart、DatasetSchema的measures/filter、ActionSchema的body—— 这些 key 本身都已被各自 schema 声明。(顺带确认
chartConfig是DashboardWidget的 key,不是 report 的:那条别名当年指错了层。)闸门额外挖出的 6 条,同一缺陷类,issue 的 AST 体检看不到:
system/email-template.zod.ts5 条 ——content/html→bodyHtml、text→bodyText、from/sender→fromOverride。原来全指向body/fromAddress,两个都不是这张 schema 的 key。content选bodyHtml是有理由的:它是必填那个 body 且接受任意字符串,省略
bodyText时服务端自己派生纯文本 —— 所以无论作者写的是标签还是散文,改名后都真的能渲染。ai/skill.zod.ts1 条 ——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。三件事在全仓尺度上把它打穿,而且都是实测不是推测:
...MetadataProtectionFields展开的 key 字面量读不到;aliases(或surface)不是字面量(
data/field.zod.ts、ui/theme.zod.ts、automation/etl.zod.ts等),AST 读成空表并报告干净;'this field group'同时是两张 schema 的 surface,字符串不是 key,手工映射会拿甲的表去judge乙的 shape。
所以改成
strictObject在构造期登记{ options, shape },表和 shape 出自同一个运行时节点,没有第二份真相,也没有 per-surface 注册可忘。
一个中途踩到的坑值得记:最初是把 options 用 symbol 挂在构建出的 schema 实例上,
结果
.superRefine()/.extend()的 clone 不带 symbol ——ReportSchema、DatasetSchema这些最要紧的当场静默失去看守,而闸门显示绿。改成构造期登记才对。
AST 保留,但只做覆盖:枚举源码里存在的调用点,走查必须每个都够到。
这是「absence must be loud」那一半 —— 走查够不到的表就是这道闸门没在judge的表,它必须红。
闸门另外钉了两条自己不judge 的边界:直接调
strictUnknownKeyError的 44 个调用点(只减不增棘轮,实测干净,#5483),以及同表内
aliasProbe撞车(#5481)。反向验证(先证红)
方向是跑之前定的,六次全部命中预测,每次都是预测的那一条断言红:
`where` -> `runtimeFilterz` — 未声明`columns` — 此处已声明`phrases` -> `triggerPhrases` — 墓碑,不接受任何值ui/report.zod.ts:408 (this unreachable surface)origin/mainui/report.zod.ts:248 `filter` -> `filters`第三行值得单独说:helper 自己的
knownKeys过滤掉了墓碑,但别名表在那个 fallback 之前就被查,所以「别名指向墓碑」是现有防线够不到的,这道闸门补上了。
第四行是这道闸门与批 14 那版的真正差别:后者对「表存在但没人judge」是静默通过的。
验证
生成物 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.ts的KNOWN_DEFECTS按它自己注释的承诺红了 ——「this list cannot outlive its debt」。它的 prescription-integrity 整节是删除而不是清空的:
留下来就是一份更弱的活检查副本(源码字面量读法,看不见 spread、拼装表、撞名 surface),
正是
strictObject要消灭的「两份真相」。它带的那条 parse 级断言(结构闸门做不到的)留下了。范围外发现(已单独立 issue,未指派)
aliasProbe归一化后两个 key 相同,后者静默覆盖前者(snap.grid被指向showGrid) #5481 ——aliasProbe归一化后同表两 key 撞车,后者静默覆盖前者。1 条活 defect:
these snap settings里grid_覆盖了grid: 'gridSize',作者写
grid: 24被指向showGrid(一个 boolean)。另有 3 条同因的休眠冗余。strictUnknownKeyError直接调用点的别名表在 #5013 闸门覆盖之外(实测干净,但无人看守) #5483 —— 44 个直接调strictUnknownKeyError的调用点在本闸门之外。临时加登记实测过,当前干净(唯一的报告是
ui/app.zod.tsnavigation 系列刻意的散文式 target),属观察类,已加只减不增棘轮看住。
Generated by Claude Code