Skip to content

feat(spec): api 补进 DEFAULT_METADATA_TYPE_REGISTRY 与 BUILTIN_METADATA_TYPE_SCHEMAS (#5271) - #5312

Merged
os-zhuang merged 7 commits into
mainfrom
claude/issue-5271-api-metadata-type-registry
Aug 5, 2026
Merged

feat(spec): api 补进 DEFAULT_METADATA_TYPE_REGISTRY 与 BUILTIN_METADATA_TYPE_SCHEMAS (#5271)#5312
os-zhuang merged 7 commits into
mainfrom
claude/issue-5271-api-metadata-type-registry

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Fixes #5271

Part of #5206(第 1 步,spec 车道)。engine 半边 PR #5279 不依赖本 PR,本 PR 也不动它的文件面。

前提先验证:是,两处都真的缺,且直写通道今天是开的

origin/main @ 88b9b2d:

  • DEFAULT_METADATA_TYPE_REGISTRYapi 条目(反证:view / flow 在,行号可见);
  • BUILTIN_METADATA_TYPE_SCHEMASapi 绑定;
  • 顺带发现 MetadataTypeSchema 枚举里也没有 api —— 子单正文没写这一条,但它是前置:注册表条目的 type 字段就是这个枚举。

比子单正文更要紧的一条实情:直写通道今天不是关着的,是开着且不校验isRuntimeCreateAllowed(metadata-protocol/protocol.ts)与 assertAllowed(sys-metadata-repository.ts)各有一条兜底 —— 「没有静态注册表条目的类型由 getMetaTypes() 合成为 allowRuntimeCreate: true,写入门必须一致」—— 而两处注释都点名 api。所以 PUT /meta/api/:name 一直返回 200,只是不过任何 schema。

一处需要更正父单措辞:getMetaTypes() 其实枚举得到 api(只要库里有行,metadataService.getRegisteredTypes() 就报它),只不过拿到的是合成描述符 —— label: 'api'filePatterns: []domain: 'system'schema: undefined。所以 Studio 不是「看不到这个类型」,而是「看到一个没有 JSON Schema 的类型,只能给 raw-JSON 文本框」。缺口是真的,形状比正文描述的窄一点。

证据闸(存量扫描):干净

仓内可及的全部 4 条作者声明,逐条跑 ApiEndpointSchema.safeParse:

OK    showcase_task_feed          (examples/app-showcase/src/system/apis/index.ts)
OK    showcase_inquiry_purge_api  (examples/app-showcase/src/system/apis/index.ts)
OK    e8policy_public_notes       (packages/qa/dogfood/test/fixtures/endpoint-policy-fixture.ts)
OK    e8policy_private_notes      (packages/qa/dogfood/test/fixtures/endpoint-policy-fixture.ts)

scanned=4 dirty=0

⚠️ 线上部署的 sys_metadata 无法从本环境扫描 —— 这是可行范围的诚实边界,不是「扫过了没事」。changeset 里给了运维侧的处方:升级前跑 GET /api/v1/meta/diagnostics?type=api(该类型现在被这个 sweep 覆盖了)。

旗标怎么定的(这是本单唯一的语义抉择)

allowRuntimeCreate: true + allowOrgOverride: false —— 两个都等于今天的实际取值,所以授权判决逐字节不变,本 PR 只加了一道形状门(422)。

code-only(allowRuntimeCreate: false + allowOrgOverride: false,即 job / agent 的形状)被证据否掉,三条:

  1. 移走一扇门而不是校验一扇门 —— 今天的 200 变成 403,这个合同变更本链条上没有任何一单要求过;
  2. metadata: allowRuntimeCreate:false is not enforced — PUT /meta creates job and agent items the registry declares code-only #5086(PR fix(metadata-protocol): allowRuntimeCreate:false 在每一种 kernel 上都生效 —— PUT /meta 不再创建注册表声明为 code-only 的 job / agent (#5086) #5263)对 code-only 类型在落库前、draft 与 active 一视同仁地拒绝,于是 api draft 将无法创建,api 不在 metadata 类型注册表里 —— Studio 直写路径完全不校验端点,publishPackageDrafts 也没有 E7 门 #5206 第 2 步(PR fix(metadata-protocol): publishPackageDrafts 对 api draft 跑 ADR-0121 端点发布门 (#5206 step 2) #5279)的 publishPackageDrafts 端点门没有 draft 可门;
  3. ADR-0121 的原文是「publish 拒绝」并附点名 key 的处方(D1/D2/D6),这预设了一个能写出 draft 的作者。「publish 拒绝」不等于「作者面拒绝」。

allowOrgOverride: false:端点是发布方的对外 URL 契约,per-org fork 可以挪 path、翻 authRequired、摘 rateLimit,而对方系统已经按这个 URL 集成了。ADR-0005 默认 false 正是要让 opt-in 是刻意的。姿态与 datasource 一致。

executionPinned: false(端点是委派,ADR-0121 D5,被 pin 的是目标 flow)、loadOrder: 92(在 flow 的 80 之后)。

ADR-0088 准入测试三条全过,且不构成对 router 退役的翻案:router 的交付形态是代码贡献,单条 ApiEndpoint 是声明式工件 —— 正是 ADR-0088 自己那一行预告的「第三种真实交付形态」。

收紧被实测否掉(本 PR 最值得读的一段)

api 进注册表后落入 #4001 收紧运动的两条不变量。保护信封已补(...MetadataProtectionFields);未知键收紧尝试了、被测量否掉了:

unrecognized_keys: ['packageId', 'state']

这个 schema 不只是作者面,它也是存量行的解析器(buildEndpointIndexgateApiItemsForPublish),而存量行带着元数据层自己的记账键。strictObjectpackages/metadata 10 条测试转红 —— 装载期兜底把端点整条排除(路由答 404),publish 门报 schema 错误而不是它本该给的 D6 判决。

所以 apiview 同列 STILL_STRIP,实测过程写进该列表自己的注释(不是一句「暂不收紧」)。真正的修法是元数据层的信封/正文分离,另立 #5309 —— 而不是教作者词表认两个存储层的键,那正是这场运动拒绝的交易。

Fixture 逐条裁定,不是批量改写

三条 api fixture,三种处置:

fixture 处置 为什么
protocol-meta.test.ts 的「plugin-registered types」 替换 留着会让断言经 isRuntimeCreateAllowed另一条分支(已静态注册且 allowRuntimeCreate: true)继续变绿,却仍宣称在证明「无静态条目」那条。theme / webhook 接手;api 另立两条专属断言(有效 → 200、无效 → 422)
sys-metadata-repository.test.ts 的 runtime-only 标本 替换 同一形状,同一理由。换成 webhook,并另加一条「已静态注册的 api 走 runtime-only 仍可写」
endpoint-matcher.test.ts 的「strips storage annotations」 整条重写 它断言 not.toHaveProperty('_lock') —— 钉的正是「信封被丢弃」这个缺陷本身,而 ...MetadataProtectionFields 修的就是它;且 fixture 里 _lock: { managed: true } 是 ADR-0010 从未定义过的形状。改为钉真正的分工:信封(已声明 ⇒ 存活)vs 记账键(⇒ 被 strip)

消费半径按规则的调用方扫的,不是按编辑的包:改在 packages/spec,坏掉的 fixture 在 packages/metadatapackages/objectql

反向验证(方向先预测再跑,其中一条是「变绿」)

把注册表条目与 schema 绑定 stash 掉重跑:

预测 实测
新 spec pin 测试 RED ✅ 9 条红
收紧计数 + STILL_STRIP 反向 pin RED ✅ 2 条红
protocol-meta 的 422 断言 RED ✅ 1 条红
objectql sweep 保持 GREEN,api 那一行从表里消失 ✅ 确认 —— sweep 枚举的是注册表,没有条目就没有那一行

第 4 条是特意预测的:那是「因为什么都没产出所以通过」的绿。记在这里,免得下一个读者把它当成覆盖。

验证

@objectstack/spec              309 files / 7963 tests passed  (typecheck clean)
@objectstack/metadata           22 files /  478 tests passed
@objectstack/metadata-protocol  41 files /  376 tests passed
@objectstack/objectql          116 files / 1871 tests passed  (typecheck clean)
@objectstack/runtime            89 files / 1313 tests passed
@objectstack/rest               40 files /  608 tests passed
@objectstack/example-showcase   12 files /  124 tests passed  (typecheck clean)

sweep 表新增的那一行(经真实 saveMetaItem 路径):

type   schema  valid→200  invalid→422
api    yes     ok         ok

生成基线整体重生成(gen:schema / api-surface / spec-changes / upgrade-guide / strictness-ledger),delta 只有 7 行,纯增量,是保护信封的 7 个键:

+ "api/ApiEndpoint:_lock" … "api/ApiEndpoint:_provenance"

json-schema.manifest.json / spec-changes.json / api-surface*.json / protocol-upgrade-guide.md / 台账 .counts.md 零变化(全部 check 门通过)。check:nul-bytes OK,并对全部改动文件另跑了控制字符自扫(零命中)。

顺带发现(已单独立单,均未指派,未在本 PR 修)

未触碰 content/docs/releases/#5206 的 engine 半边、PR #5279


🤖 Generated with Claude Code

https://claude.ai/code/session_01ErbEDVAg1No9gdg1pgDAGB


Generated by Claude Code

…A_TYPE_SCHEMAS (#5271)

Part of #5206 (step 1, spec 车道)。

`api` 条目一直被产出(artifact ingest 把 `defineStack({ apis })` 映射为 `api`)、
被索引(`buildEndpointIndex`)、被执行(#5040 E5/E8),而 spec 里哪儿都没声明这个
kind。于是 `getMetadataTypeSchema('api')` 返回 undefined,`saveMetaItem` 走它自己
文档写明的「未注册 schema 的类型不经校验直接落库」分支 —— `PUT /meta/api/:name`
接受任意 JSON。这是 `declared ≠ enforced` 反着读:enforced but undeclared。

- `MetadataTypeSchema` + `DEFAULT_METADATA_TYPE_REGISTRY` 补 `api` 条目;
- `BUILTIN_METADATA_TYPE_SCHEMAS` 补 `api: ApiEndpointSchema`;
- `ApiEndpointSchema` 补 ADR-0010 保护信封(每个注册类型的不变量);
- `api` 的最小 create seed(教 carve-out 形状与 object_operation 的两个半边);
- showcase `KIND_COVERAGE` 接手 `apis` 的覆盖(它不再是「非注册表 kind」)。

旗标按证据定,不是新授权:无静态条目时 `isRuntimeCreateAllowed` 与 `assertAllowed`
都走「无注册表条目 ⇒ 可运行时创建」的兜底(两处注释都点名 `api`),所以运行时直写
本来就被接受、只是不校验。`allowRuntimeCreate: true` 把这个既有判决写下来,
`allowOrgOverride: false` 同样是今天的实际取值。code-only 方案被证据否掉:它会把
今天的 200 变成 403,且 #5086 在落库前对 draft 一视同仁地拒绝,#5206 第 2 步
(PR #5279)将无 draft 可门。

`ApiEndpointSchema` 的收紧被实测否掉:同一个 schema 也解析存量行,而存量行带
`packageId` / `state`,`strictObject` 让 packages/metadata 10 条测试转红。`api` 因此
与 `view` 同列 STILL_STRIP,实测写进该列表注释,真正的修法(信封/正文分离)另立
#5309。

Fixture 逐条裁定而非批量改写:protocol-meta 与 sys-metadata-repository 里的 `api`
标本被**替换**(留着会让断言经另一条分支变绿、却仍宣称在证明「无静态条目」那条);
endpoint-matcher 那条「strips storage annotations」整条重写(它钉的正是信封被丢弃
这个缺陷本身)。

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

vercel Bot commented Aug 4, 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 5, 2026 2:09am

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling size/l labels Aug 4, 2026
@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/platform-objects, @objectstack/spec.

108 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 @objectstack/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/tenancy-modes.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/platform-objects, @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/http-protocol.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/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/platform-objects, @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.

Copy link
Copy Markdown
Contributor Author

CI 红诊断(PM,Spec property liveness 门,真缺陷非 flaky)

✗ 1 REGISTERED metadata type(s) governed by nothing:
    api

根因:本 PR 把 api 注册为 metadata type,即落入 liveness 门「每个 REGISTERED 类型必须被治理」的不变量(datasource 无治理期积了六个惰性键的教训就是这门的由来)。门给出两条路:

  1. 纳入 GOVERNED(优先)—— tsx check-liveness.mts --dump api 播种 packages/spec/liveness/api.json。E 系列执行器(17.x 立项:建设声明式 ApiEndpoint 执行器(挂载 + matchEndpoint + authRequired/cacheTtl/inputMapping/outputMapping 逐键接线) #5040)刚把 ApiEndpointSchema 全部键做成活键(authRequired/rateLimit/cacheTtl/mappings 各有真实 reader,E4/E5 的落点即证据路径),台账行应当全部可证 LIVE —— 这是治理成本最低的时点;
  2. PENDING_GOVERNANCE + 理由 + issue 号 —— 仅当某键实测无法归类时用,⛔ 不许为转绿而选。

修复后按门输出复跑 check:liveness 并在 PR 记录读数。dev 报告到达前本条即返工简报;若 dev 已在自行处理,以其实测为准。


Generated by Claude Code

Copy link
Copy Markdown
Contributor Author

CI 红诊断补充(第二签名,check:docs,同样机械可修)

✗ content/docs/references/ is out of date with packages/spec:
  ~ content/docs/references/api/endpoint.mdx
  ~ content/docs/references/api/metadata.mdx
  ~ content/docs/references/kernel/metadata-plugin.mdx

注册表条目与 schema 绑定改动了这三份生成文档,未随 PR 重生成提交。修法即门给的:pnpm --filter @objectstack/spec gen:schema && pnpm --filter @objectstack/spec gen:docs,提交 content/docs/references

与首条(liveness 治理)合并为一轮返工:(1) api 纳入 GOVERNED + 播种 liveness/api.json;(2) 三份参考文档重生成提交。 两项都是收尾补齐,不动已验证的设计。


Generated by Claude Code

Copy link
Copy Markdown
Contributor Author

CI 红诊断补充(第三签名,metadata-protocol 1/388,行为交互非缺陷)

FAIL protocol-publish-drafts-endpoint-gate.test.ts > refuses an `api` draft whose body does not even satisfy ApiEndpointSchema
Error: [invalid_metadata] api/garbage failed spec validation (saveMetaItem, protocol.ts:7137)← 死在 saveApiDraft setup(:200),未到 publish 断言

定性:这是本 PR「一处修,两面得」的预期后果#5206 第 2 步测试(随 main 刚合入)的世界观相撞 —— 该测试假设「垃圾 api draft 存得进去,publish 门负责拒」;本 PR 之后写入期就 422。行为方向正确(比 publish 更早的响亮拒绝),测试需要适配,不是回退本 PR。

返工项 3(与前两项同轮):适配该测试 —— 垃圾体的 setup 改为断言写入期 422(这本身就是本 PR 的验收形状);publish 门自身的覆盖用「过 ApiEndpointSchema 但违反 publish-only 规则(D1 命名空间 / D6 匿名须限流)」的 draft 重建,⛔ 不许让 publish 门测试变得空洞 —— 门测门的事,写入门测写入门的事。

返工汇总(三项,一轮):(1) api 纳入 GOVERNED + 播种 liveness/api.json;(2) 三份参考文档 gen:docs 重生成提交;(3) 上述测试适配。cc #5206(engine 车道:你们第 2 步测试的 setup 假设因 spec 第 1 步落地而改变,适配在本 PR 内完成,publish 门覆盖保持)。


Generated by Claude Code

`git merge origin/main`(至 5aae790,无冲突),生成物按 os-regen 四步互保:
generated 文件整体 checkout 回合并基线,再 wholesale 重生成,最后断言兄弟 PR
的条目仍在。

两处过程中发现并纠正的坑,记下来免得下一个人重踩:

1. `gen:api-surface` 读的是**构建产物**,不是源码。合并后没重建就重生成,会把
   #5021(PR #5289)刚退役的 `AnimationSchema` / `ZIndexSchema` 四行**重新加
   回去** —— 正是 AGENTS.md §9 的陈旧产物陷阱。重建 spec 后重生成才对
   (4422 → 4418 exports)。
2. 第 2 步的 `git checkout origin/main -- <generated>` 必须用**你实际合并的那个
   tip**,不是 `origin/main` 的当前值。main 在我合并与 checkout 之间又前进了,
   于是把 #4938 的 http-server 生成文档拉了进来 —— 而我的源码里没有那个改动,
   等于提交了一份源码产不出的生成物。改用合并基线 5aae790 后归零。

最终生成物 delta 相对合并基线只有 7 行,纯增量(ApiEndpoint 的保护信封键);
#5021 的退役完好(Animation/ZIndex 确认缺席)。

`protocol-publish-drafts-endpoint-gate.test.ts`(#5279,已合入 main)的
ENDPOINT_SCHEMA 用例改了**落地路径**而非断言:该 draft 现在过不了 saveMetaItem
的 422(这正是 #5206「一处修,两面得」要的结果),所以 fixture 改为先按真实写
路径存一条合法 draft、再只污染其 body —— 让那条 backstop 分支仍然被真实覆盖,
而不是删掉用例留一条无测试的活分支。未改该 PR 的任何生产代码。

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

Copy link
Copy Markdown
Contributor Author

追加:已 git merge origin/main,并按 os-regen 四步互保重生成

合并至 5aae790(无冲突)。PR #5279(#5206 第 2 步,engine 半边)已在其中合入 main,所以本分支现在同时带着两个半边 —— 下面第 3 条记的就是它们相遇处。

生成物 delta 相对合并基线只有 7 行、纯增量(ApiEndpoint 的保护信封键),兄弟 PR 的条目断言完好(#5021 退役的 AnimationSchema / ZIndexSchema 确认缺席)。commit 时 os-regen 守卫自报 all deferred artifacts are current — marker cleared

过程中踩到并纠正的两个坑(值得写进流程)

1. gen:api-surface 读的是构建产物,不是源码。 合并后没重建就重生成,会把 #5021(PR #5289)刚退役的 4 行 AnimationSchema / ZIndexSchema 重新加回去 —— AGENTS.md §9 陈旧产物陷阱的镜像。重建 spec 后重生成才对:4422 → 4418 exports。这一条恰好是「断言兄弟条目仍在」这一步抓出来的,不是事后发现的。

2. 第 2 步的 git checkout origin/main -- <generated> 必须用你实际合并的那个 tip。 main 在我 merge 与 checkout 之间又前进了(5aae790 → 175d789),于是把 #4938http-server 生成文档拉了进来 —— 而我的源码里没有那个改动,等于提交一份自己源码产不出的生成物。改用合并基线 5aae790 后归零。

3. 两个半边相遇:#5279 的 ENDPOINT_SCHEMA 用例改了落地路径,不是断言

protocol-publish-drafts-endpoint-gate.test.ts 里那条 refuses an api draft whose body does not even satisfy ApiEndpointSchema,原来用 saveMetaItem 铸出 garbage draft,并在注释里写明前提:「api has no entry in BUILTIN_METADATA_TYPE_SCHEMAS(that half is #5271)」。

本 PR 让那个前提消失:该 body 现在在最早那道门就被 422 拒了,draft 根本铸不出来 —— 这正是 #5206 要的「一处修,两面得」,spec 车道这侧有断言钉住。

于是那条 ENDPOINT_SCHEMA 分支从 Studio 写路径不再可达,它变成模块头自己称呼的那个 backstop:接住经别的路进库的行(直接 metadata.register()、迁移、或 #5271 之前写下的存量行)。

三种处置里选了换落地路径:

  • 删掉用例 → 留一条有生产代码、无测试的活分支;
  • 改写 body 使其能存下 → 只是把 422 再测一遍,ENDPOINT_SCHEMA 分支依旧无人覆盖;
  • 先按真实写路径存一条合法 draft(记账列逐字节等同生产),再只污染 stored body —— 那恰恰是早门管不到的唯一一样东西。

未改 #5279 的任何生产代码,只动了这一条用例的 setup 与注释。

合并后复验

@objectstack/spec              309 files / 7972 tests passed
@objectstack/metadata           22 files /  478 tests passed
@objectstack/metadata-protocol  42 files /  388 tests passed   ← 含 #5279 的 12 条
@objectstack/objectql          117 files / 1885 tests passed
@objectstack/example-showcase   12 files /  124 tests passed
check:authorable-surface / api-surface / spec-changes / strictness-ledger / upgrade-guide  全部 OK
check:nul-bytes OK

Generated by Claude Code


Generated by Claude Code

PM 返工第 1 轮。把 `api` 注册成 metadata type 就落入「每个 REGISTERED 类型
必须被治理」不变量,而 `check:liveness` 不在我上一轮跑过的门清单里:

    ✗ 1 REGISTERED metadata type(s) governed by nothing:  api

走的是第一条路线(纳入 GOVERNED + 播种台账),没有用 PENDING_GOVERNANCE ——
这是治理成本最低的时点:#5040 的 E 系列执行器全部已合 main,每个键**今天**
都有真实证据路径,而不是一句承诺。datasource 的教训(#4487:无治理期积了六个
惰性键,只能靠人手找出来)就是这条路线存在的理由。

27 条属性分类:live 25、planned 2、dead 0。

逐键证据按层给到 file:line —— 匹配器(endpoint-matcher)吃 name/path/method;
执行器(endpoint-executor)吃 type/target/objectParams;策略链
(endpoint-policy + security/inbound-rate-limit)吃 authRequired/rateLimit/
cacheTtl;映射层(api-mapping)吃 inputMapping/outputMapping;OpenAPI 增强
(rest/openapi-endpoints)吃 summary/description。7 个保护信封键由门自动判 live
(ADR-0010),故不写进台账。

两个 `transform` 判 **planned 而非 dead**,这个区分是有承重的:台账里的 dead
指「解析了、没有消费方」即静默 no-op;而 transform 是反过来 —— 它被解析后在
publish 与 runtime 两处**响亮拒绝**(api-mapping.ts:259),作者会被告知怎么改。
它留在词表里而不是被删,是因为接纳它需要函数注册表 + sandbox 裁决
(#5040 §3.4),那是一个待做的设计决定,不是一个可以顺手删掉的键。

无任何 `api` 键属于 proof-registry 的 bound high-risk class,因此没有一条带
`proof` —— 不为显得周全而编造。

另:`check:docs` 实测**仍红**(上一轮的同步提交并没有修好它,我从未跑过
`gen:docs`)。三份生成文档已重生成,delta 纯属本单:endpoint.mdx 的 7 个信封键,
metadata.mdx / metadata-plugin.mdx 的枚举里多了 `api`。

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

Copy link
Copy Markdown
Contributor Author

返工第 1 轮:api 纳入 liveness 治理 + 三份参考文档重生成

a4f4a8b 已推。

治理路线:第 1 条(纳入 GOVERNED + 播种台账),没有用 PENDING_GOVERNANCE

理由不是「能转绿」,而是此刻治理成本最低:#5040 的 E 系列执行器全部已合 main,所以每个键今天都指得到真实消费方,不是一句承诺。datasource 的教训(#4487:无治理期积了六个惰性键,最后靠人手找出来)正是这条路线存在的理由 —— 那六个键当年也「本来可以」当场治理。

改动两处:check-liveness.mtsGOVERNED 数组加 api,新增 packages/spec/liveness/api.json(用 --dump api 播的种)。

逐键分类计数

api   27 classified (live 25, planned 2, dead 0)
  • live 18(作者面真实键)+ 7 个保护信封键(_lock* / _provenance / _packageId|Version,门按 ADR-0010 自动判 live,故不写进台账)= 25
  • planned 2:inputMapping.transform / outputMapping.transform
  • dead 0

证据按层给到 file:line:

文件
匹配器 packages/metadata/src/endpoint-matcher.ts name(:210 重名裁决)、path(:199)、method(:115/:199)
执行器 packages/runtime/src/endpoint-executor.ts type(:216/:232)、target(:233)、objectParams.object(:217/:394)、objectParams.operation(:218/:368)
策略链 packages/runtime/src/endpoint-policy.tssecurity/inbound-rate-limit.ts authRequired(:354)、rateLimit.enabled(:88)、.windowMs(:91)、.maxRequests(:90)、cacheTtl(:252)
映射层 packages/runtime/src/api-mapping.ts inputMapping.source/target(:318/:322)、outputMapping.source/target(同函数,经 :358)
OpenAPI packages/rest/src/openapi-endpoints.ts summary(:214)、description(:215)

两个 transformplanned 而不是 dead,这个区分有承重。 台账里 dead 的定义是「解析了、没有消费方」——静默 no-op。transform 恰恰相反:它被解析后在 publish 与 runtime 两处响亮拒绝(api-mapping.ts:259),作者当场被告知怎么改。它留在词表里而不是被删,是因为接纳它需要函数注册表 + sandbox 裁决(#5040 §3.4)——那是一个待做的设计决定,不是可以顺手删的键。判 dead 会把它送进 enforce-or-remove 的清单,而它已经被 enforce 了(以拒绝的形式)。

无任何 api 键属于 proof-registry.mts 的 bound HIGH_RISK class,因此没有一条带 proof —— 没有为显得周全而编造一个。

两门输出

$ pnpm --filter @objectstack/spec check:liveness
  api         27 classified (live 25, planned 2)
✓ every governed-type property at the walk's one-level granularity is classified,
  every registered type is governed or explicitly pending, no ledger row outlives its
  property, every container inheritance is declared, and all bound high-risk proofs resolve.
$ pnpm --filter @objectstack/spec check:docs
✅ import examples resolve against api-surface.json (137 accepted gap(s) in the baseline)
✅ 244 generated files in sync with packages/spec

⚠️ 一处需要更正你的判断:check:docs 之前并没有被我的同步提交修好

任务里写的是「确认你同步提交已把三份参考文档修好」。实测它当时仍是红的:

✗ content/docs/references/ is out of date with packages/spec:
  ~ content/docs/references/api/endpoint.mdx
  ~ content/docs/references/api/metadata.mdx
  ~ content/docs/references/kernel/metadata-plugin.mdx

原因很简单:上一轮我跑的是 gen:schema / gen:api-surface / gen:spec-changes / gen:upgrade-guide / gen:strictness-ledger,从未跑过 gen:docs —— content/docs/references/** 是另一条生成链。已在本次提交里重生成,delta 纯属本单:endpoint.mdx 多 7 个信封键,另两份的枚举里多一个 api,无兄弟 PR 内容被动。

顺带记一条工具坑(与本单无关,但会咬下一个人)

重建 worktree 后 check:api-surfaceCould not resolve module symbol for . (dist/index.d.ts). Is the package built? —— 它读的是构建产物,而新 worktree 的 dist 是 gitignore 的、不存在。这不是内容失败。重建 spec 后复跑:public API surface + factory signatures unchanged ✓,且 api-surface.json 工作区干净(本轮改的是 liveness 台账、一个脚本数组、生成文档,都不可能动导出面)—— 我选择重建后实测而不是据此推断。

复验

@objectstack/spec  Test Files 309 passed (309) / Tests 7972 passed (7972)
check:liveness OK   check:docs OK   check:api-surface OK   check:authorable-surface OK
check:spec-changes OK   check:strictness-ledger OK   check:upgrade-guide OK   check:nul-bytes OK

其余已验证工作未改动。


Generated by Claude Code


Generated by Claude Code

PM 返工第 2 轮。`api` 进 metadata type registry 后,platform-objects 的
metadata-forms bundle 键集多出该类型的 label/description,四个 locale 漂移。

`node scripts/check-i18n-bundles.mjs --write`(merge 模式:不覆盖任何既有翻译,
新键以源文填充)。delta 纯属本单 —— 四个 bundle 各 +4 行,只有 `api` 一个键:

    api: { label: "API Endpoint", description: "Declarative HTTP endpoint — …" }

非英文 bundle 里这两条暂为英文源文,这是 merge 模式的既定产物("they still
need translating");`check:i18n-coverage` 的冻结基线未被突破,故两门皆绿。
手改 `.generated.ts` 不是这条链的修法。

⚠️ 这是我第三次漏门(liveness → docs → i18n),根因是我一直按"改了哪个包就跑
哪个包的门"收工,而这三道都是**仓根**的、由被改内容触发而非由被改目录触发。
已改为按 `.github/workflows` 的 job 清单逐条过:本次 push 前跑完 44 道 CI 必跑
门(lint.yml 25 道 + spec-liveness-check.yml 4 道 + spec 生成物 13 道 +
validate-deps.yml 2 道),全绿。

注:i18n 两门必须在 **full build 之后**跑 —— 抽取器经 extract config 读
`@objectstack/spec` 的 dist、并跑构建后的 CLI。未构建时它报的是九个包
"extract failed — no output",很容易被误读成配置坏了而不是缺构建。

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

Copy link
Copy Markdown
Contributor Author

返工第 2 轮:i18n bundle 重生成 + 按 workflow job 清单过全部 44 道门

6aba08e 已推。

修法:node scripts/check-i18n-bundles.mjs --write(merge 模式)。delta 纯属本单 —— 四个 locale bundle 各 +4 行,只有 api 一个键:

api: { label: "API Endpoint",
       description: "Declarative HTTP endpoint — a stable URL and policy layer over an existing pipeline (ADR-0121)" }

非英文 bundle 里这两条暂为英文源文,是 merge 模式的既定产物(门自己的话:"they still need translating"),check:i18n-coverage 的冻结基线未被突破,两门皆绿。手改 .generated.ts 不是这条链的修法。

门清单已改为按 .github/workflows 的 job 逐条过 —— 44/44 绿

你的批评是对的,我承认根因:我一直按「改了哪个包就跑哪个包的门」收工,而漏掉的这三道(liveness → docs → i18n)全是仓根的门,由被改内容触发而不是由被改目录触发。改注册表这件事恰好同时踩中治理、生成文档、翻译三条链,而它们分别住在 spec-liveness-check.yml、spec 的 gen:docs 链、lint.yml

本次 push 前按 workflow 逐条跑完:

lint.yml (25)                slot-lookup nul-bytes doc-authoring docs-audit-scope role-word
                             adr-anchors org-identifier authz-resolver service-providers
                             route-envelope error-code-casing wildcard-fallthrough
                             init-service-contract durability-log-level startup-registry-verdict
                             objectui-changeset release-notes node-version published-files
                             engine-double-contract type-check-coverage driver-conformance
                             i18n i18n-coverage merge-driver
spec-liveness-check.yml (4)  liveness empty-state variant-docs strictness-ledger
spec 生成物 (13)              authorable-surface api-surface spec-changes upgrade-guide docs
                             generated skill-refs skill-docs react-blocks
                             react-declaration-parity dual-source-exports exported-any
                             skill-examples
validate-deps.yml (2)        override-consistency osv-exemptions

PASS: 44   FAIL: 0

ESLint 亦跑过改动文件:0 errors。metadata-validation-sweep.test.ts 上那 3 条 Unused eslint-disable directive warning 在 main 上原样存在(同样 3 条、同样指令,只是被我新增的 fixture 顶移了行号),不是本 PR 引入,未顺手动它。

一条工具坑,值得记进流程

i18n 两门必须在 full build 之后跑。 抽取器经 extract config 读 @objectstack/specdist,并跑构建后的 CLI。未构建时它报的不是「漂移」,而是九个包齐刷刷 extract failed — no output + command i18n:extract:… not found —— 很容易被误读成配置坏了。lint.yml 里这两步的位置注释其实写明了(「belongs after the build step with the other consumer gates」),我这次是先撞上再回去读的。

复验

@objectstack/spec              309 files / 7972 tests passed
@objectstack/platform-objects    9 files /  266 tests passed   ← bundle 属主
@objectstack/metadata           22 files /  478 tests passed
@objectstack/metadata-protocol  42 files /  388 tests passed

其余已验证工作未改动。


Generated by Claude Code


Generated by Claude Code

claude added 2 commits August 5, 2026 01:32
…-metadata-type-registry

# Conflicts:
#	packages/spec/src/kernel/metadata-type-schemas.test.ts
合并 origin/main 后重跑 gen:schema,锚点 baseRev 由 1c3da1f0899 前进到
merge base 28ad90e(7907 keys,与该 commit 的 authorable-surface.json
逐行一致)。生成器写入,非手改;lag 本可保留,但 gen:schema 在 keys 漂移
(#5321 退役 109 键)时按设计刷新。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ErbEDVAg1No9gdg1pgDAGB
@github-actions github-actions Bot added size/xl and removed size/l labels Aug 5, 2026

Copy link
Copy Markdown
Contributor Author

同步接力:merge origin/main @ 28ad90e(含 #5314),全套复验绿

9de05e9 已推(接力 agent,原实现 agent 不在册)。两个 commit:merge + 锚点推进。

冲突与处置(仅 1 处手写冲突)

packages/spec/src/kernel/metadata-type-schemas.test.tsSTILL_STRIP 注释:#5319 在 main 上改写了 view 段措辞(ViewItemSchema 进已关闭清单、「open members … wearing their own names」),本分支在同一段落后追加了 api 段。语义合并:取 main 的 view 新措辞 + 保留本 PR 的 api 段;STILL_STRIP = ['view', 'api'] 不变。

os-regen / 锚点处理(四步,其中第 2 步踩到一个值得记的坑)

复验(merge 后全量,main 上 §9 全重建 + 清 runtime/.objectstack)

@objectstack/spec              310 files / 7926 tests passed   (typecheck clean)
@objectstack/metadata           22 files /  478 tests passed
@objectstack/metadata-protocol  42 files /  388 tests passed
@objectstack/objectql          118 files / 1911 tests passed
@objectstack/runtime            90 files / 1333 tests passed
@objectstack/rest               40 files /  608 tests passed
@objectstack/example-showcase   12 files passed
@objectstack/platform-objects    9 files passed
check:generated 9/9 OK(含 check:authorable-surface 锚点门)
check:exported-any OK   check:liveness OK(api 27 classified)   check:nul-bytes OK

又一条工具坑(rest 假红,已归因,非缺陷)

rest-openapi-route.test.ts 5 条 503:gen:schema rmSync 整个 json-schema/,而 openapi.json 只由 gen:openapi 重建(gitignored、无门)——check:generated 的 authorable-surface 门在原地跑 build-schemas 也会顺手把它抹掉。任何 gen:schema/check:generated 之后跑 rest 测试,必须先 gen:openapi。重生成后 40/608 全绿。CI 不受影响(test job 干净检出走完整 build)。

merge 基线即 #5314 的 merge commit 28ad90e。push 时 main 又前进到 b4ad984(+2:#5362 rest / #5363 spec,与本 PR 无文件重叠)——按 §10 不再追平,交 merge queue/CI 验证合并 commit。保持 draft,未挂 auto-merge。


Generated by Claude Code

@os-zhuang
os-zhuang added this pull request to the merge queue Aug 5, 2026
Merged via the queue into main with commit cdfbee2 Aug 5, 2026
25 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-5271-api-metadata-type-registry branch August 5, 2026 02:32
os-zhuang pushed a commit that referenced this pull request Aug 5, 2026
…bee2)

合并 origin/main 后重建时由 gen:schema 写出(先 commit merge 再跑生成,
#5370 的锚点倒退陷阱按序避开):baseRev 28ad90ecdfbee2,随锚点带入
#5312 的 api/ApiEndpoint 键面。check:generated 9/9 up to date,
check:authorable-surface 绿。非手改。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ErbEDVAg1No9gdg1pgDAGB
baozhoutao pushed a commit that referenced this pull request Aug 5, 2026
…ate ledger (#5000)

`api` landed in DEFAULT_METADATA_TYPE_REGISTRY / BUILTIN_METADATA_TYPE_SCHEMAS
on main (#5271 -> #5312) after this branch was cut, and the reconciliation
case did its job: an unclassified registered type fails until someone decides
how the CLI reaches it.

Measured rather than assumed. The stack DOES carry it (`apis:`, ADR-0121, still
declared on main), and the CLI and the write path reach the same schema — but
`ApiEndpointSchema` is a plain `z.object`, so an undeclared key on an endpoint
is dropped on BOTH paths. That is not a CLI divergence, it is a #4001 shape the
campaign has not reached; the strictness ledger still files all of `api/` as
"wire, tolerant by design", which stopped being true when the type was
registered. Filed as #5384, a sub-issue of #4001.

So `api` gets its own ledger row with the honest claim: the CLI is no looser
than the write path, and the #3786 pre-parse layer still names the key so the
author is not left with silence. When #5384 closes the shape, the agreement
assertion goes red and the row moves into GATED_AT — the ratchet working.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VkPSGsX9o17MsGv3Lbxu2w
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 size/xl tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[spec] api 补进 DEFAULT_METADATA_TYPE_REGISTRY 与 BUILTIN_METADATA_TYPE_SCHEMAS(#5206 第 1 步,拆单)

2 participants