…y schemas (#5000)
#5000 measured that `os build` / `os validate` never parse `page` metadata
through `PageSchema`, so an undeclared page-component key shipped into the
artifact and #4001's "all three example apps validate clean" was empty
evidence on the page surface.
Re-measured on origin/main the claim does not hold: both commands parse the
whole stack through `ObjectStackDefinitionSchema`, whose `pages` element is
the schema `getMetadataTypeSchema('page')` returns. The issue's repro and its
own negative control (`responsiveStyles.large` -> `.lg`) both exit non-zero
today, and `os build` writes no artifact for a rejected stack.
Nothing pinned either half of that, so this adds the evidence the issue found
missing:
- per registered metadata type, one undeclared key gets the same verdict
from the write-path gate and from the schema the CLI parses through
(the three structurally-different carriers -- field, translation, view --
are named and asserted at their real positions), plus a reconciliation
that fails when a newly registered type is classified by nobody;
- the two commands act on that parse, run through the real binary, with a
control case (same stack, no planted key) that must exit 0.
Behavioural rather than schema-instance identity on purpose: spec ships one
bundle per entry point, so the root entry's embedded `PageSchema` and
`@objectstack/spec/kernel`'s registry copy are never `===` (filed as #5379).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VkPSGsX9o17MsGv3Lbxu2w
Fixes #5000
结论先说:前提不成立,所以本单没有实现新门
#5000 说
objectstack build/validate不会把 page 元数据按PageSchema解析,因而 ADR-0089 D3a 早该拒绝的键一路通过并写进产物。按 Prime Directive「issue 是线索不是规格」,先在origin/main(2f6516e)上复核 —— 复现不出来。两条命令都把整个 stack 过ObjectStackDefinitionSchema,而它的pages元素就是getMetadataTypeSchema('page')返回的那个 schema,和MetadataManager.validate/GET /api/v1/meta/ Studio 表单同一道门。PM 裁定里「让
os validate变成真门」的那件事,已经是现状,所以本 PR 不加门,只补 #5000 真正指出的那个缺口:没有任何东西钉住这件事。实测证据(均在
origin/main+ 本分支,2026-08-05)definePage的即时 parse 分开,配置写成纯对象字面量(不走defineStack/definePage):顺带说明 issue 当时为何会看到 exit 0 的一个可能:它改的是
examples/app-showcase,那里每个 page 都走definePage,而definePage会先抛;真要区分「命令闸没闸」必须绕开工厂。另一个更可能的机制是当时packages/spec/dist陈旧(AGENTS.md §9 那个陷阱)—— CLI 消费 dist,而它们的对照脚本走 src,同一份 schema 两个判决,正好长成 issue 描述的那个不对称。两者都无法事后证实,只作为记录。responsiveStyles.large写成.lg)现在也是红的,且带处方:逐类型测量(24 个已注册元数据类型):21 个在 stack 根部由同一个 schema 实例承载(
objects/pages/flows/ …);另外 3 个是结构性差异而非漏洞 ——field嵌在objects[].fields、translation在 stack 上是 locale 束(TranslationBundleSchema)而注册表是行形状TranslationItemSchema、view注册表是 view metadata type-schema is a no-op for runtime shapes: ViewItem/personalization bodies strip to {} under ViewSchema #3095 的四成员联合而 stack 承载其中的容器成员。三者的未声明键都被 CLI 这条路拒绝(objects.0.fields.title/translations.0.en-US/views.0)。三个示例应用(PM 要求的 build 侧测量):
app-showcase/app-crm/app-todo的validate全部 exit 0(warning 43 / 4 / 4 条,均为既有 author-time 建议)。也就是说 build 侧没有存量违规需要豁免,不需要降级成 warning,也不需要开硬化后续单 —— build 已经是硬门(拒绝即不出产物,上面第 1 条实测)。本 PR 做了什么
只加一个测试文件
packages/cli/test/metadata-type-schema-gate.test.ts(7 个用例,无生产代码改动、无 spec 改动):objectstack build/validate从不按 PageSchema 解析页面元数据:ADR-0089 D3a 早就该拒绝的键一路通过,#4001 的「三个示例应用 validate 全过」对 page 面是空证 #5000 的一般形式)。os build另断言没写产物。外加对照用例:同一个 stack 不种缺陷时必须 exit 0 —— 否则这三条会因为无关原因而红着通过,那正是objectstack build/validate从不按 PageSchema 解析页面元数据:ADR-0089 D3a 早就该拒绝的键一路通过,#4001 的「三个示例应用 validate 全过」对 page 面是空证 #5000 自己踩到的坑。A 与 B 分开写是因为它们独立失效:A 绿 B 红 = schema 严格但命令吞了判决(#3782 的形状);B 绿 A 红 = 命令在一个没人核对过是否规范的 schema 上闸(#4409 量到的 23/26)。
反向验证(方向事先声明)
预测:把
validate.ts里 parse 失败的分支临时短路,只有 B 的两条 validate 用例变红,A 的 schema 层用例与 build 用例保持绿。实测完全一致 —— 且值得记一笔:短路后命令仍以非零退出(下游result.data为 undefined 引发崩溃),光断言退出码是抓不住的,是那条「输出里必须出现那个键 / ADR-0089 D3a」的断言在干活。这就是这两条用例不只看 exit code 的原因。为什么用行为等价而不是实例同一性
@objectstack/spec按 entry 分别打包,root entry 内嵌的PageSchema和@objectstack/spec/kernel注册表里的那份在 dist 上永远不===(只有走 src 才成立)。这条约束已写进测试注释,并单独记为观察类 finding #5379,免得下一个人先写一遍 identity 断言再怀疑自己。未做 / 需维护者裁断
objectstack build/validate从不按 PageSchema 解析页面元数据:ADR-0089 D3a 早就该拒绝的键一路通过,#4001 的「三个示例应用 validate 全过」对 page 面是空证 #5000 应按「前提失效 + 证据补齐」关闭,而不是按「新门已实现」关闭 —— 方向 A/B/C 的取舍在当前代码上已无对象。测试
Generated by Claude Code