Skip to content

client realtime declared ≠ enforced:subscribeMetadata 回调把 RealtimeEventPayload 硬铸成 MetadataEvent,声明的顶层字段运行时是 undefined #4602

Description

@os-zhuang

#4587(spec 双源 C2)判真源扫描时的范围外发现,按第十条军规立案,unassigned。

现象

@objectstack/spec/apiMetadataEvent 声明顶层字段 id(uuid,必填)、metadataTypenamedefinition?userId?。但客户端 SDK 实际交付给订阅者的对象是 RealtimeEventPayload 信封,形状对不上:

  • 生产侧 packages/metadata/src/metadata-manager.ts(register/unregister 路径)发布的是:
    { 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-reactuseMetadataEvent 同源。

结果:订阅者写 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 行为)。

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions