#4587(spec 双源 C2)判真源扫描时的范围外发现,按第十条军规立案,unassigned。 ## 现象 `@objectstack/spec/api` 的 `MetadataEvent` 声明顶层字段 `id`(uuid,必填)、`metadataType`、`name`、`definition?`、`userId?`。但客户端 SDK 实际交付给订阅者的对象是 `RealtimeEventPayload` 信封,形状对不上: - **生产侧** `packages/metadata/src/metadata-manager.ts`(register/unregister 路径)发布的是: ```ts { type: `metadata.${type}.created`, object: type, payload: { metadataType, name, definition, packageId }, timestamp } ``` —— `metadataType`/`name`/`definition`/`packageId` 都嵌在 `payload` 里,且从不生成 `id`/`userId`。 - **消费侧** `packages/client/src/realtime-api.ts:72` 把这个信封直接 `callback(event as any as MetadataEvent)`;`packages/client-react` 的 `useMetadataEvent` 同源。 结果:订阅者写 `event.name` / `event.metadataType` / `event.definition`,类型系统说是 `string`,运行时全是 `undefined`;必填的 `event.id` 也不存在。`as any as` 双铸就是这个谎的遮羞布。另注意 `MetadataManager` 只发布 `created`/`deleted`,`metadata.{type}.updated` 无生产者(register 同名覆盖也走 `created`)。 ## 为什么现在值得修 #4587 已把 `MetadataEvent` 的裸名唯一属主收敛到 `./api`——它现在就是 realtime 元数据变更事件的**唯一**合同,而这份合同当前对每个消费者都在说谎。AI 按类型写出的订阅代码(`if (event.name === …)`)编译全绿、运行时静默走错分支,恰是 lenient-consumer 藏错的形态。 ## 处置方向(contract-first,二选一,需裁决) 1. **让生产者履约**:`MetadataManager` 发布真正的 `MetadataEvent`(生成 `id`,顶层铺开字段,补 `userId`),client 侧删掉 `as any as` 铸型,用 `MetadataEventSchema.parse`(或 safeParse)在边界处验证。 2. **让合同如实**:若 wire 信封就该是 `RealtimeEventPayload`,则 `subscribeMetadata` 回调类型改为信封形状(或提供显式的解包层),`MetadataEvent` 的字段布局改为与信封一致。 同族先例:#3197(声明无消费的事件枚举)、#4579(`openApi31` declared ≠ enforced)。 发现于 #4587 三仓 import 级扫描;本条不属于该 PR 范围(该 PR 只消歧命名归属,不改 realtime 行为)。
Activity
os-zhuang commentedon Aug 2, 2026
🔒 CLAIM — pm-dispatch round 6
branch:
claude/issue-4602-metadata-event-contractworktree:
objectstack-issue-4602PM 裁决(两轴分析,详见派发指令):按方案 1 生产者履约实施——
MetadataEvent是 #4587 收敛后的唯一已声明合同,生产侧补齐顶层字段并在边界 parse,删除消费侧as any as双铸;如实施中发现反证(wire 信封形状有不可动的依赖),按 needs_decision 上报而非折中。Generated by Claude Code
os-zhuang commentedon Aug 2, 2026
PM 复核:ACCEPT ✅(PR #4628,已转正并挂自动合并,CI 绿后进队列)
按裁决的方案 1 落地,关键点逐项核过:
MetadataManager发布真正的MetadataEvent(生成 uuidid、顶层铺开metadataType/name/definition、userId经新增的MetadataWriteOptions.userIdseam 带入),发布前MetadataEventSchema.parse—— 谎话在生产端就被拦下,不再流向消费者;as any as双铸删除,边界safeParse响亮拒绝(回调不触发),client-react因委托而随之修复;metadata.{type}.*信封 payload 的唯一读者是 client SDK(webhook auto-enqueuer 只读data.record.*),故无反证、无需 needs_decision——这一步做得对,没有凭猜实施;.updated补上生产者(register 同名覆盖),与既有 watcher 分叉一致并 pin;event.name为 undefined);spec 8 项生成物门禁全绿。范围外发现另立 #4626(
subscribeData/DataEvent同构缺陷,迁移成本更高需单独评审)、#4627。docs-drift 的 112 篇为包级粗粒度扇出,已核:参考页是 spec 生成物(门禁绿),concepts/metadata-lifecycle.mdx讲的是 dev SSE 通道的同名不同物,无手写文档描述旧回调形状,无需跟改。Generated by Claude Code
fix(metadata,client): subscribeMetadata delivers true MetadataEvents …
fix(objectql,client): subscribeData delivers a real DataEvent (object…