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>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Aug 4, 2026
…e 关 (objectstack-ai#4721) (objectstack-ai#4922)

* feat(spec,metadata-protocol)!: reject a sort node spelling its direction `direction` (objectstack-ai#4721)

`SortNodeSchema` was a plain `z.object`, so zod's `.strip` default applied:

    SortNodeSchema.parse({ field: 'updated_at', direction: 'desc' })
      →  { field: 'updated_at', order: 'asc' }

The foreign key was discarded, `order` fell back to `asc`, and the sort ran the
OPPOSITE way under an ordinary success — with `limit`, a different set of rows,
with no signal anywhere in the response.

`direction` is not a typo: it is `IReportService.orderBy`'s live vocabulary,
which plugin-auth's objectql adapter already translates by hand. A translation
known to be necessary and enforced nowhere is the ADR-0049 shape.

Both doors onto that shape are closed here, in one change:

- `SortNodeSchema` (spec/src/data/query.zod.ts) → `strictObject` with
  `aliases: { direction: 'order' }`, so the rejection carries the translation.
  Edit distance can never bridge `direction` → `order`, so a bare
  "unrecognized key" would leave the caller where the silent strip did.
- `normalizeSortNodes` (metadata-protocol/src/protocol.ts) — the ingress every
  external `orderBy` funnels through — refuses `{ field, direction }` with
  400 INVALID_SORT naming `order` and quoting the corrected node.

Closing only the schema would repeat the objectstack-ai#1535/objectstack-ai#4522 door asymmetry:
`SortNodeSchema.parse` is reachable by three paths the REST normalizer never
sees, and the normalizer runs ahead of any QueryAST parse.

Deliberately NOT in scope: `QuerySchema`'s top level stays non-strict
(`QuerySchema.safeParse({object:'sales', nonsenseKey:1}).success === true`) —
tracked in the objectstack-ai#4001 campaign map for its own batch.

The `{field: direction}` map form is untouched: there `direction` is an ordinary
column name, and refusing it would be the mirror-image bug.

Strictness ledger: `query.zod.ts` keeps its `open` class for the four dialect
sites; `SortNodeSchema` is carved out as authorable (4 strip of 5), which also
resolves the recorded classification conflict — the FILE was the wrong unit.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ehu85kbvMcrNTUJjwxvLJ9

* docs(spec): keep SortNodeSchema's page description short; rationale moves to line comments

`build-docs.ts` takes the FIRST `/** */` block in a `.zod.ts` file as the
reference page's description, and joins every line of it with a blank line. The
long objectstack-ai#4721 rationale therefore rendered as a 56-line wall at the top of
`content/docs/references/data/query.mdx`, where a customer reads what a sort
node IS — not why one schema in the file is strict.

The prose is unchanged, it is just `//` instead of `/** */` so the generator
cannot pick it up, with a note at the top saying why it must stay that way.

Regenerated: query.mdx (two lines), and the two skill reference indexes, which
grew transitive entries because query.zod.ts now imports shared/strict-object.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ehu85kbvMcrNTUJjwxvLJ9

---------

Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Aug 6, 2026
 批 19/20) (objectstack-ai#5250)

* feat(spec)!: object 内层块拒绝未知键 —— 13 个站点收紧,IndexSchema 依据实测保留 (objectstack-ai#4001 批 19/20)

`data/object.zod.ts` 的顶层自 objectstack-ai#1535/objectstack-ai#4519/objectstack-ai#4522 起就会拒绝未知键,而它下面
的内层块一直是 zod 默认的 `.strip`。这个不对称正是本批的价值所在:一个已经
见过顶层拒绝拼写错误的作者,完全有理由把 `lifecycle: { maxAge: '30d' }` 的
干净解析读作"接受"。object 是本仓作者写入量最大的元数据类型。

批 20 —— 14 个站点关掉 13 个:
- `access`(ADR-0066 D2)、`lifecycle` 及其四个子块(retention/ttl/storage/
  archive)、`fieldGroups[]`(ADR-0085)、`external`(ADR-0015)、
  `userActions`、`systemFields`、`activityMilestones[]`、`publicSharing`、
  `objectExtensions[]`。
- 可达性是实测而非假定:从 24 个元数据类型根加 `ObjectStackSchema` 做 BFS
  (4810 节点 / 25 根),每一个都按**身份**判定 `direct`,没有一个依赖
  objectstack-ai#5056 发现的 `derived-clone` 桥;同一次运行里 `ObjectSchema`/`PageSchema`
  为正控制,批 14 的 `EmbedConfigSchema` 为负控制(unreachable,重叠 0.00)。
- parse 门单独探针、逐路径验证:今天顶层 REJECT、13 条嵌套路径全部静默
  STRIP,所以每一次收紧转化的都是实测到的静默丢弃,而不是猜测。

⚠️ `IndexSchema` 有意保留,这个"不动"才是本批的发现(objectstack-ai#5114 类,这次是在
发布**之前**抓到而不是之后)。console 为这个形状自带了一份手抄 JSON-Schema
(objectui `metadata-admin/EmbeddedItemEditor.tsx`)—— 因为 `index` 是框架
不发布 schema 的内嵌子类型 —— 而这份抄件已经漂移:它用 `where` 表示部分索引
谓词,spec 声明的是 `partial`。编辑器把表单结果拼回 `object.indexes[]` 后
PUT 整个对象,`saveMetaItem` 逐字保留 body 并做校验,所以单独关掉这个形状会
让 console 自己渲染的控件 422。该能力两个方向上其实都已经死了 ——
`driver-sql` 的 `syncDeclaredIndexes` 只读 `name`/`fields`/`unique` ——
这恰恰是必须先修生产者、并给 `type`/`partial` 一个 ADR-0049 结论的原因:
今天把作者指向 `partial`,是一条声称超出平台实际交付的 guidance(finding 18)。

批 19 —— `ui/app.zod.ts` 的 `BaseNavItemSchema`:`verify` 检查结论为否,
不改姿态。台账假定成员 `.extend()` 该基底(finding 16 的陷阱),实际它们
spread `...shape`;两者机制不同,并且双向实测过。九个分支各自已经 `.strict()`,
基底是模块私有且从不 parse,关掉它是保证的 no-op —— 而 objectstack-ai#4583 明确指出
no-op 收紧并非中性。`Class` 单元格未改动:它是机读的,而八个既有判定词
没有一个对"既不是门也不是死物"的形状是诚实的,加第九个属于改机读契约。

每条 alias/guidance 都是对 schema 的断言(finding 18),因此逐条锚定到具名
的兄弟契约并在测试中断言;`fieldGroups[]` 三个已弃用的 collapse 别名保持
**接受** —— 收紧一个形状不该把有文档的弃用变成拒绝。

已知并如实钉住的一处:`systemFields` 是 `false | {…}` 联合,拒绝信息是
`invalid_union` 的裸 "Invalid input"(objectstack-ai#5014 摊平);批 18 的
`discriminatedUnion` 解法在这里不可用(一个分支是字面量,没有判别键)。

三处记录齐备:schema JSDoc + `object-strictness-batch20.test.ts` /
`app-strictness-batch19.test.ts` + 台账散文行;计数由 os-regen 重新生成。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ehu85kbvMcrNTUJjwxvLJ9

* build(spec): 合并 origin/main 后重算 strictness 计数(os-regen)

merge 带入 objectstack-ai#5221 的 chart 站点(482 → 483),计数文件按 merge=os-regen 的
约定从合并后的树重新生成,而不是任何一边的算术。authorable 仍为 16。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ehu85kbvMcrNTUJjwxvLJ9

* docs(spec): 把批 19/20 的两个保留判定接到已归档的 issue 上(objectstack-ai#5247/objectstack-ai#5248/objectstack-ai#5249)

IndexSchema 的保留(生产者漂移 + ADR-0049)与 BaseNavItemSchema 的 Class
词汇缺口都已单独归档,台账行与 schema JSDoc 现在指向 issue,而不是只说
'交给维护者'——一条指不出去的判定,下一个人只能重新测一遍。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ehu85kbvMcrNTUJjwxvLJ9

* build(spec): 合并 objectstack-ai#5237 后重算 strictness 计数(os-regen)—— authorable 16 + (-1) → 15

合并队列以 MERGE_CONFLICT 踢出后重新同步 origin/main(merge,非 rebase)。
objectstack-ai#5237(objectstack-ai#5073 allowAddTab)与本批从两个不同目录改这份台账,散文行不重叠,
git 干净合并;数字零手改,counts.md 按 merge=os-regen 从合并后的树整体重生成。

重算结果 15 = `ui/` 7 → 6(objectstack-ai#5237)+ `data/` 22 → 9(批 20),两边分支都没写过
这个数——正是 objectstack-ai#5107 拆分要达到的效果。这一条也补进 data/ 段落:它是第十二次
实例,也是第一次没让任何人付出代价。

objectstack-ai#5237 对 `ui/app.zod.ts` 的改动(`apis` 墓碑文案)与本批的 BaseNavItemSchema
判定互不相干,两侧都在;它对 scripts/strictness-ledger.test.ts 的 view 计数
5 也是它自己的领地。

门:spec 308 文件 / 7929 测试通过;typecheck 通过;check:generated 9/9;
check:strictness-ledger 通过(484 站点,authorable 剩 15)。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ehu85kbvMcrNTUJjwxvLJ9

---------

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