Skip to content

fix(spec): 上一个 PR 里的保护信封检查是空的 —— 它跳过了 25 个类型里的 24 个(#4001) - #4519

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

fix(spec): 上一个 PR 里的保护信封检查是空的 —— 它跳过了 25 个类型里的 24 个(#4001)#4519
os-zhuang merged 1 commit into
mainfrom
claude/strict-schema-authz-surface-s8vnok

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

紧接 #4514。那个 PR 加的不变量测试断言了两件事:每个注册元数据类型不拒收加载器盖的 ADR-0010 信封(硬 422 那一类),以及不静默丢失它。

拒收那一半是有效的——它第一次运行就抓到了 hookdatasource

另一半是空的。

它怎么空的

它用一个通用探针 body 去 parse 每个 schema,再看 _packageId 有没有活下来。但一个 schema 如果有探针没提供的必填字段,就会因为无关原因解析失败,断言直接 early return:

if (!result.success) return;   // ← 25 个类型里 24 个走的是这条路

只有 field 真的被检查过。整个套件报绿。

这就是这场战役的主题——成功信号掩盖遗漏——出现在为了检测它而建的仪器里,而且就在账本刚刚记下闸门非递归扫描那同一课之后一个改动

一个会跳过的检查,和一个会通过的检查,在外部完全无法区分。

修法:结构式遍历,跳不过去

声明侧改成直接走 schema 结构——拆开 lazy / pipe / optional / default,展开 union——问「有没有哪个解析出的 object shape 声明了这个键」。

这个问题不需要构造合法实例,所以不存在「因无关原因失败」的早退路径。

两个护栏让它保持诚实:

护栏 防的是什么
遍历器解析不出的类型 = 硬失败 遍历器沉默的那一刻,正是测试会悄悄停止覆盖的那一刻
债务清单带反向钉(某项修好时测试变红) 防止清单活得比债务久,开始豁免不再需要豁免的类型

它接着找到了什么

不是 1 个,是 8 个未声明信封的注册类型:

actionbookfieldjobmappingpagetranslationvalidation

探针版本藏了其中 7 个。每一个今天都在往返中丢保护元数据,且在它的 schema 被收紧那天会变成硬 422。

本 PR 修掉 jobbook,清单剩 6 个。

一个开始重复的模式

这是同一个模式的第三次,出现在三个不同的仪器上:

仪器 它谎报的覆盖度 发现于
账本闸门 目录扫描不递归 → data/driver/ 九个站点隐形 #4412
严格性站点计数 strictObject( 不算站点 → 「解决了」和「删掉了」同数 #4514
信封探针 无关失败早退 → 24/25 静默跳过 本 PR

三个都是测量工具报告了自己并不具备的覆盖度。账本里现在写下了这条反复被重新推导出来的规则:

信一个绿色检查之前,先让它在一个你确知存在的东西上变红。

验证

  • @objectstack/spec282 文件 / 7141 用例通过tsc --noEmit 干净
  • 不变量测试从 51 项增到 77 项——差额就是此前被静默跳过的那些
  • 8 个生成物闸门 up-to-date(唯一的重新生成是 book / job 两张信封键表)
  • 15 个 check:* 闸门全绿

参考

🤖 Generated with Claude Code

https://claude.ai/code/session_01WnqGjQFQMqd5k81LYV8SCY


Generated by Claude Code

…f 25 types (#4001)

The invariant test added one change ago asserted two things about every
registered metadata type: that it does not REJECT the ADR-0010 envelope its
loader stamps (the hard-422 case), and that it does not silently lose it.

The reject half worked — it found `hook` and `datasource` on its first run.

The other half did not. It probed each schema with one generic body and asked
whether `_packageId` survived. A type whose required fields that body did not
supply failed for unrelated reasons, and the assertion returned early. TWENTY-
FOUR of the twenty-five types took that early return. Only `field` was ever
really checked, and the suite reported green.

That is this campaign's own subject matter — a success signal covering an
omission — reproduced inside the instrument built to detect it, one change after
the ledger recorded the identical lesson about the strictness gate's
non-recursive directory walk. A check that skips is indistinguishable from a
check that passes.

The declaration side is now STRUCTURAL: it walks the schema, unwrapping
lazy/pipe/optional/default and expanding unions, and asks whether any resolved
object shape declares the key. That needs no valid instance, so it cannot skip.
Two guards keep it honest:

  - a type the walker cannot resolve is a hard FAILURE, not a pass. The walker
    going quiet is exactly when this test would otherwise stop covering
    something.
  - the debt list carries a reverse pin that fails when an entry is fixed, so
    the list cannot outlive the debt it tracks.

What it found: 8 registered types do not declare the envelope, not 1 — `action`,
`book`, `field`, `job`, `mapping`, `page`, `translation`, `validation`. Each
loses protection metadata on every round-trip today and becomes a hard 422 the
day its schema closes. `job` and `book` are closed here; 6 remain listed.

Three occurrences now of one pattern, in three different instruments: the ledger
gate's non-recursive walk, `strictObject(` not matching the site count, and this
early return. Every one was a measuring tool reporting coverage it did not have.
The ledger now states the rule it keeps re-deriving: before trusting a green
check, make it go red on something you know is there.

Verified: spec 282 files / 7141 tests, `tsc --noEmit` clean, all 8 generated
artifacts current (the only regeneration is the two envelope key tables), 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 3:18pm

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation protocol:system 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 15:31
@os-zhuang
os-zhuang added this pull request to the merge queue Aug 1, 2026
Merged via the queue into main with commit 061406d Aug 1, 2026
21 checks passed
@os-zhuang
os-zhuang deleted the claude/strict-schema-authz-surface-s8vnok branch August 1, 2026 15:41
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:system size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants