feat(spec)!: validation 的六个变体各自按自己的键集收紧(#4001 批 3b) - #4527
Merged
Conversation
…wn key set (#4001) `validation` was the registered metadata type this campaign kept deferring: a `z.lazy()` discriminated union whose six variants each `.extend()` a shared base, so the one-call `strictObject` conversion the other types took does not apply to it. WHY NOT JUST CLOSE THE BASE. `.extend()` inherits strictness, so closing `BaseValidationSchema` alone would have rejected unknown keys correctly. But an error map closes over the key list it was BUILT with, and for the base that is only the shared keys — so a typo of a variant's own key (`transtions` for `transitions`, `jsonschema` for `schema`) would have been rejected with no rename offered. That is the difference between a fixable error and a confusing one, and it is the whole reason the campaign insists rejections carry the fix. The union discriminates on `type`, so an author is always on exactly one variant and that variant's FULL key set is the right candidate list. The base is now a named shape spread into six `strictObject` calls, each with the aliases that fit its own vocabulary. No `BaseValidationSchema` object remains — nothing ever parsed the base alone. THE ENVELOPE. `validation` is registered, so the loader stamps `_packageId` / `_provenance` on it and `getMetaItemLayered` → `saveMetaItem` round-trips a body carrying them — and the schema could not represent them, so they were dropped on every parse. Declared once in the shared shape, inherited by all six. The type comes off the debt list in `kernel/metadata-type-schemas.test.ts`, which carries a reverse pin, so removing the entry was forced rather than optional. Five remain: action, field, mapping, page, translation. AND THE GATE LEARNED THE IDIOM AGAIN. Its strictness-claim check matched only a literal `.strict()`, so a file converted to `strictObject` read as NOT strict and the gate called a true claim a lie. Same blind spot the site count had one batch ago, for the same reason: a measuring tool has to learn the idiom whenever the idiom changes. Both now accept either spelling. Authoring impact: a key none of the variants declares is rejected instead of silently discarded — it was already being ignored, so no working behavior changes. The rejection names which variant it landed on ("this state-machine validation rule"), echoes the key, and suggests from that variant's own set. Verified: spec 283 files / 7159 tests, `tsc --noEmit` clean, all 8 generated artifacts current (the only regeneration is the six variants' envelope tables), all 15 `check:*` gates green. Probed directly: valid rule parses, unknown key rejected, a variant-specific typo gets its rename, envelope accepted. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WnqGjQFQMqd5k81LYV8SCY
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckThis PR changes 1 package(s): 107 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
This was referenced Aug 1, 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.
#4001 批 3 后半(清单)。批 3a = #4522,已合并。
validation是这场战役一直往后推的那个注册类型:z.lazy()判别联合,六个变体各自.extend()共享基类,其他类型用的「一次strictObject调用」在它身上不成立。为什么不能只收基类
.extend()会继承 strict,所以只收BaseValidationSchema也能正确拒绝未知键。但错误映射闭包捕获的是它被构造时的那份键列表,对基类而言只有共享键。于是变体自己的键拼错——
transtions之于transitions、jsonschema之于schema——会被拒绝,但给不出改名建议。那正是「可修的错误」和「令人困惑的错误」之间的差别,也正是这场战役坚持「拒绝必须带上修法」的全部理由。
联合按
type判别,作者永远只落在一个变体上,所以那个变体的完整键集才是正确的候选集。基类现在是一个命名 shape,展开进六个strictObject调用,每个配自己词汇表的别名。BaseValidationSchema这个对象已删除——从来没有任何东西单独解析基类。保护信封
validation是注册类型,加载器给它盖_packageId/_provenance,getMetaItemLayered→saveMetaItem往返时带着它们——而 schema 表达不了,所以每次解析都被丢弃。现在声明在共享 shape 里,六个变体一次继承。该类型从
kernel/metadata-type-schemas.test.ts的债务清单划掉——那个清单带反向钉,修好了不删条目测试就红,所以删除是被强制的而不是可选的。剩 5 个:
action·field·mapping·page·translation。闸门又得学一次新写法
账本闸门的「声称 strict 的行必须真的 strict」检查只匹配字面的
.strict(),于是改用strictObject的文件读起来像不 strict,闸门把一个真话判成了谎话。和上一批站点计数撞到的是同一个盲点,原因也一样:写法一变,测量工具就得跟着学一次。两处现在都接受两种拼写。
这是本战役第五次同形的事:测量工具跟不上被测量的东西。
作者可见的影响
六个变体都没声明的键,现在被拒绝而不是静默丢弃——它本来就在被忽略,所以没有任何正在工作的行为改变。
拒绝信息会点名它落在哪个变体上("this state-machine validation rule")、回显键、并从那个变体自己的键集里给出最近的建议。
验证
@objectstack/spec:284 文件 / 7187 用例通过(对合并后的 main),tsc --noEmit干净check:*闸门全绿参考
🤖 Generated with Claude Code
https://claude.ai/code/session_01WnqGjQFQMqd5k81LYV8SCY
Generated by Claude Code