feat(spec)!: ObjectSchema 在 parse 路径上收紧,而不只是 create() —— #1535 的奠基例子一直是活的(#4001 批 3a) - #4522
Merged
Merged
Conversation
…e() (#4001) #1535's founding example was still reproducible on the path that matters. ObjectSchema.parse ({ …, workflows: ['x'] }) → silently discarded ObjectSchema.create({ …, workflows: ['x'] }) → rejected since #1535 #1535 built the guard as a hand-rolled check inside the `create()` factory, reasoning that authored `*.object.ts` modules call `create()`. They do — but they are not the only producer, and not the path most instances travel: `defineStack({ objects })`, `/api/v1/meta/types/object` and the Studio form all reach this schema through `parse()`/`safeParse()`. So object-level `workflows` — an author believing they had wired up automation and shipping dead metadata, the example this whole campaign was filed on — kept being eaten in silence for the entire time #1535 was considered fixed. The base shape is now `.strict()`, carrying the `UNKNOWN_KEY_GUIDANCE` tombstones plus the semantic renames the WARNING layer already knew (`capabilities`/`features` → `enable`). Graduating a surface from warn to reject must not cost the author a prescription — edit distance cannot reach those two, so they are named explicitly. `create()` is unaffected: its own check runs before parsing and throws a richer located Error. Safe on the read path for the reason the other closed registered types are: the ADR-0010 envelope is declared, and `stripReadDecorations` removes `_diagnostics`/`_draft` before any strict re-parse (cloud#971). Verified rather than assumed — every `ObjectSchema.create()` call across `platform-objects` and the three example apps uses only declared top-level keys. New tombstone: `namespace` (retired in ADR-0006 D4) had none, so it was stripped in silence. `{ namespace: 'sys', name: 'user' }` shipped as plain `user`, under a name its author never intended. The rejection now carries `name: "sys_user"`. AND a coverage regression this change would otherwise have introduced. The unknown-key warning layer gated each metadata collection on its ROOT schema's posture, so closing `object` at the root would have switched off the warnings for everything BENEATH it — its 71 nested strip-mode sites stopping in the same commit, with nothing anywhere to say so. Posture is a per-node property; the walk now treats it as one. A strict root stays silent at its own level (the parse owns that failure) and the descent continues. Nested `object.fields.*` warnings are unaffected by the graduation. That is the fourth instance in this campaign of one shape: an improvement in one place quietly removing coverage in another. Three tests that asserted the strip as correct behaviour are now rejection tests. The `compactLayout` one had pinned the author-hostile outcome in so many words: "the retired key is STRIPPED, not aliased — an old-key author gets no highlightFields rather than silently working." Verified: spec 283 files / 7159 tests, `tsc --noEmit` clean, all 8 generated artifacts current, all 15 `check:*` gates green. 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:
|
…authz-surface-s8vnok
os-zhuang
marked this pull request as ready for review
August 1, 2026 16:23
This was referenced Aug 1, 2026
akarma-synetal
pushed a commit
to akarma-synetal/framework
that referenced
this pull request
Aug 2, 2026
…ctstack-ai#4001 批 5) (objectstack-ai#4529) * feat(spec)!: translation closes at both doors, and objectstack-ai#3778's ten-key guard retires into the message (objectstack-ai#4001) A translation that resolves to nothing is indistinguishable from a translation nobody wrote — no wrong string appears, just the source language, forever. So this type had the most literal version of the silent-strip failure in the spec. objectstack-ai#3778 already knew that, and fixed it for ten keys: a `z.preprocess` scanning for the retired object-first dialect. It had the shape every workaround for `.strip` has — it caught only the mistakes someone had already thought of, and it ran on the item door only, so the same ten keys in a file-authored bundle were dropped in silence. Same asymmetry objectstack-ai#4522 found in objectstack-ai#1535's object guard. The guard is now redundant and gone; its ten prescriptions ride the rejection as `guidance`. What was worth keeping was never the detection — detection generalizes for free once the default flips — it was the prose. Closed across every authorable group (objects/fields/views/actions/sections, apps, dashboards, pages, settings, metadata forms) and the i18n config, whose four objectstack-ai#3494-removed knobs get tombstones. Two gates were found doing half their job: - `translation` came off the ADR-0010 envelope debt list (down to four). - `metadata-create-seeds.test.ts` — the canonical "create shape ≠ spec" guard — asserts every seed parses. The `translation` seed ships `{ name, label, locale, objects }` and the type declared neither `name` nor `label`, so two thirds of the authoritative create shape was stripped while the gate reported green. A gate on a `.strip` schema catches a missing required key and never an extra undeclared one. Registered types closed at the top level: 17 of 25. Verified: 284 files / 7208 tests, tsc clean, 8 generated artifacts current, 15 check gates green, and the real bundles in examples/app-crm, app-todo and platform-objects all parse at module load. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WnqGjQFQMqd5k81LYV8SCY * docs(i18n): the retired-dialect rejection now covers both doors, not just the metadata one (objectstack-ai#4001) Three passages described objectstack-ai#3778's guard accurately for what it was — ten keys, rejected at the metadata door — and objectstack-ai#4001 changed both halves of that: any undeclared key is rejected, in a runtime item and in a file-authored bundle. - ui/translations.mdx: "only the groups on this page are accepted" was aspirational for bundles; it is now literally true. Says why this surface cares more than most — a translation that resolves to nothing looks exactly like one nobody has written yet. - i18n-standard.mdx: same correction on the retired-dialect callout. - i18n-standard.mdx: the `translationService` design-intent callout already warned the key is unrecognized; copying that snippet is now a build-time rejection rather than a silent drop, which is the part a reader acts on. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WnqGjQFQMqd5k81LYV8SCY --------- Co-authored-by: Claude <noreply@anthropic.com>
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 前半(清单)。
#1535 的守卫没盖住真正要紧的那条路
#1535 把未知键守卫做成了
create()工厂里的手写检查,理由写在代码里:「作者写的*.object.ts模块都调create()」。这话没错,但它们不是唯一的生产者,也不是大多数实例走的路:
defineStack({ objects })、/api/v1/meta/types/object、Studio 表单,三条全部经由parse()/safeParse()。于是对象级
workflows: [...]——作者以为接好了自动化、实际发布了一份死元数据,本 issue 正文举的那个例子——在 #1535 被认为已修复的整段时间里,一直在主路径上被静默吃掉。现在 base shape 是
.strict(),带上UNKNOWN_KEY_GUIDANCE的墓碑,以及告警层早就知道的语义改名(capabilities/features→enable)。毕业不该让作者少拿一条处方——这两个编辑距离够不着,只能显式命名。create()不受影响:它自己的检查在 parse 之前跑,抛的是更详细的定位错误。读取路径安全,理由和其他已关闭的注册类型一样:ADR-0010 信封已声明,且
stripReadDecorations会在任何严格重解析前剥掉_diagnostics/_draft(cloud#971)。实测而非推断——platform-objects加三个示例应用里所有ObjectSchema.create()调用,顶层键全部已声明。新墓碑:
namespace它此前没有墓碑,所以被静默剥离。
{ namespace: 'sys', name: 'user' }会以user发布——作者以为自己声明了命名空间,实际对象名不是他要的那个。拒绝信息现在直接给出name: "sys_user"。顺带:修掉一个本改动本会引入的覆盖回归
未知键告警层是按根 schema 的姿态决定整个集合走不走的。所以
object顶层一收紧,整个objects集合被排除——它下面 71 个仍是 strip 的嵌套站点会在同一个 commit 里停止告警,而没有任何地方会说这件事。姿态是每个节点的属性,走查现在按节点处理:strict 的根在自己那层沉默(解析已经在报,再警告就是重复),下探照常。
object.fields.*的嵌套告警不受这次毕业影响。这是本战役第四次撞到同一个形状:一处改进悄悄拿掉另一处的覆盖。
三个「把静默剥离钉成预期」的测试改写了
namespace/ 退役的compactLayout别名 / 移除的detail块。其中compactLayout那个把对作者不利的结局写得很直白:和 step 2 的
position.test.ts(断言一个不存在的层级)是同一类:测试把 strip 时代的假象固化成了正确行为。验证
@objectstack/spec:283 文件 / 7159 用例通过,tsc --noEmit干净check:*全绿object收紧后objects.acct.fields.amt.lenght仍然报告;根层不再重复报告参考
workflows: [...](and any unknown ObjectSchema key) is silently stripped at build — no error/warning (ADR-0032 'no silent failure', metadata layer) #1535(本 PR 补完的那个修复)、排查「手抄 spec 清单 + "keep in sync" 注释」模式:一天内确认三例,全部曾静默漂移 #3786 / feat(spec): 未知键 lint 下探到嵌套元数据(补上 227 个此前无声的站点) #4218(告警层)namespace退役)、ADR-0010(保护信封)、ADR-0085(compactLayout/detail)🤖 Generated with Claude Code
https://claude.ai/code/session_01WnqGjQFQMqd5k81LYV8SCY
Generated by Claude Code