Skip to content

feat(spec)!: ObjectSchema 在 parse 路径上收紧,而不只是 create() —— #1535 的奠基例子一直是活的(#4001 批 3a) - #4522

Merged
os-zhuang merged 2 commits into
mainfrom
claude/strict-schema-authz-surface-s8vnok
Aug 1, 2026
Merged

feat(spec)!: ObjectSchema 在 parse 路径上收紧,而不只是 create() —— #1535 的奠基例子一直是活的(#4001 批 3a)#4522
os-zhuang merged 2 commits into
mainfrom
claude/strict-schema-authz-surface-s8vnok

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

#4001 的批 3 前半(清单)。

#1535 的守卫没盖住真正要紧的那条路

ObjectSchema.parse ({ …, workflows: ['x'] })   →  静默丢弃
ObjectSchema.create({ …, workflows: ['x'] })   →  自 #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 / featuresenable)。毕业不该让作者少拿一条处方——这两个编辑距离够不着,只能显式命名。

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 那个把对作者不利的结局写得很直白:

"the retired key is STRIPPED, not aliased — an old-key author gets no highlightFields rather than silently working."

和 step 2 的 position.test.ts(断言一个不存在的层级)是同一类:测试把 strip 时代的假象固化成了正确行为。

验证

  • @objectstack/spec283 文件 / 7159 用例通过tsc --noEmit 干净
  • 8 个生成物闸门 up-to-date15 个 check:* 全绿
  • 嵌套告警存活性直接实测:object 收紧后 objects.acct.fields.amt.lenght 仍然报告;根层不再重复报告

参考

🤖 Generated with Claude Code

https://claude.ai/code/session_01WnqGjQFQMqd5k81LYV8SCY


Generated by Claude Code

…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
@vercel

vercel Bot commented Aug 1, 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 1, 2026 4:22pm

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation protocol:data tests tooling size/m labels Aug 1, 2026
@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec.

107 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 packages/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/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/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/kernel/runtime-capabilities.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/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.

@os-zhuang
os-zhuang marked this pull request as ready for review August 1, 2026 16:23
@os-zhuang
os-zhuang added this pull request to the merge queue Aug 1, 2026
Merged via the queue into main with commit 7bba90b Aug 1, 2026
21 checks passed
@os-zhuang
os-zhuang deleted the claude/strict-schema-authz-surface-s8vnok branch August 1, 2026 16:43
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation protocol:data size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants