Skip to content

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

Draft
os-zhuang wants to merge 5 commits into
mainfrom
claude/issue-5271-api-metadata-type-registry
Draft

feat(spec): api 补进 DEFAULT_METADATA_TYPE_REGISTRY 与 BUILTIN_METADATA_TYPE_SCHEMAS (#5271)#5312
os-zhuang wants to merge 5 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 4, 2026 8:02pm

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.

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 @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/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

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/l 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