Skip to content

fix(metadata,client): subscribeMetadata 交付真正的 MetadataEvent——生产者履约 + 边界校验 (#4602) - #4628

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-4602-metadata-event-contract
Aug 2, 2026
Merged

fix(metadata,client): subscribeMetadata 交付真正的 MetadataEvent——生产者履约 + 边界校验 (#4602)#4628
os-zhuang merged 1 commit into
mainfrom
claude/issue-4602-metadata-event-contract

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Fixes#4602

按 PM 裁决(方案 1,生产者履约)实施:MetadataEvent(@objectstack/spec/api)是 #4587 收敛后 realtime 元数据变更的唯一已声明合同,本 PR 让生产侧发布真正的 MetadataEvent,消费侧在边界处校验而不是硬铸。

生产侧(packages/metadata)

  • MetadataManager.register() / unregister() 构造完整的 MetadataEvent:生成 uuid id,metadataType/name/definition 顶层铺开,写路径声明了操作者时携带 userId,发布前用 MetadataEventSchema.parse 校验(malformed 在生产端响亮失败,不再把谎话送下游)。
  • 传输信封保持不变:RealtimeEventPayloadpayload 承载完整 MetadataEvent。仓内对 metadata.{type}.* 信封 payload 的唯一读者是 client SDK(webhook auto-enqueuer 只消费 data.record.*),因此无信封依赖被破坏——未发现需要 needs_decision 的反证。
  • register 同名覆盖现在发布 metadata.{type}.updated(与既有 added/changed watcher 分叉一致),此前 .updated 声明而无任何生产者。已 pin 测试。
  • MetadataEventType 是封闭枚举:枚举外的类型(如 translation)没有已声明的 realtime 事件合同,不发布(debug 日志)——发布一个每个合规消费者都必须拒绝的事件更糟(declared = enforced)。覆盖面是否扩枚举/收窄 subscribeMetadata 参数类型,已立案 MetadataEventType 是 13 类型的封闭枚举,但可注册的 metadata 类型远多于此——枚举外类型的 realtime 事件合同缺位,需裁决覆盖面 #4627 待裁决。

消费侧(packages/client,client-react 传递生效)

  • 删除 callback(event as any as MetadataEvent) 双铸;subscribeMetadata 在边界拆信封并 MetadataEventSchema.safeParse,不合合同的 payload 响亮拒绝(handler 抛错、回调不触发),绝不静默胁变或透传。
  • client-reactuseMetadataSubscription / useMetadataSubscriptionCallback 直接委托 client SDK,无自己的铸型,随之修复,无需改动。

合同 seam(packages/spec)

  • MetadataWriteOptions 新增可选 userId(操作者),让知道 actor 的写路径能把 userId 带进事件——否则 MetadataEvent.userId 永远是"声明了但没人能生产"。加法变更,现有调用方不受影响。spec 八项生成产物门禁全绿(check:generated 全过,无需重新生成)。

测试

  • packages/metadata/src/metadata-realtime-events.test.ts(9 例):信封 payload 用 spec schema 本体 parse(双方漂移即红);覆盖 created/updated/deleted、userId 携带/缺省、枚举外类型零发布、非 string packageId 剔除、id 唯一、publish 失败不影响写入。
  • packages/client/src/realtime-api.test.ts(5 例):订阅者收到顶层字段(pre-fix 树上必红——旧代码回调收到的是信封,event.nameundefined);pre-fix 生产者形状与错误字段类型均被响亮拒绝且回调不触发;packageId 过滤与退订不回归。
  • @objectstack/metadata test:14 files / 290 tests 全绿;@objectstack/client test:16 files / 209 tests 全绿;spec/client/client-react typecheck 全绿。

范围外发现(已立案,unassigned)

🤖 Generated with Claude Code

https://claude.ai/code/session_012C2cd7tL8QDoZ2QKN3djJ5


Generated by Claude Code

…— producer fulfils the declared contract (#4602)
MetadataManager now builds a schema-valid MetadataEvent (generated uuid id,
flattened top-level metadataType/name/definition, userId when the write
declares an actor via the new MetadataWriteOptions.userId seam), validates it
with MetadataEventSchema.parse before publishing, and carries it as the
RealtimeEventPayload envelope's payload. A register() overwrite now publishes
metadata.{type}.updated (mirroring the added/changed watcher split) instead of
a second .created. Types outside the closed MetadataEventType enum publish
nothing (declared = enforced) instead of an event every compliant consumer
must reject.
The client SDK's subscribeMetadata unwraps the envelope and validates with
MetadataEventSchema.safeParse at the boundary — off-contract payloads are
rejected loudly (callback never invoked), and the 'as any as MetadataEvent'
double-cast is deleted. client-react's metadata hooks delegate to it and are
fixed transitively.
Out-of-scope findings filed: #4626 (subscribeData/DataEvent twin defect),
#4627 (MetadataEventType enum coverage vs registrable types).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012C2cd7tL8QDoZ2QKN3djJ5
@vercel

vercelBot commented Aug 2, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
ProjectDeploymentActionsUpdated (UTC)
objectstackIgnoredIgnoredAug 2, 2026 12:10pm

Request Review

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation tests tooling size/l labels Aug 2, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 3 package(s): @objectstack/client, @objectstack/metadata, @objectstack/spec.

112 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 packages/client, @objectstack/spec)
  • content/docs/ai/skills.mdx(via @objectstack/spec)
  • content/docs/api/client-sdk.mdx(via @objectstack/client, @objectstack/spec)
  • content/docs/api/data-flow.mdx(via @objectstack/client)
  • content/docs/api/environment-routing.mdx(via @objectstack/client, @objectstack/spec)
  • content/docs/api/error-catalog.mdx(via @objectstack/client, @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 @objectstack/metadata, 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/client, @objectstack/spec)
  • content/docs/kernel/cluster.mdx(via packages/metadata, @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/data-service.mdx(via packages/client)
  • content/docs/kernel/runtime-services/email-service.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/index.mdx(via packages/client, 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/metadata, @objectstack/spec)
  • content/docs/kernel/services.mdx(via @objectstack/spec)
  • content/docs/permissions/authentication.mdx(via @objectstack/client)
  • 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/client, @objectstack/metadata, @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/metadata-service.mdx(via @objectstack/metadata)
  • content/docs/protocol/kernel/plugin-spec.mdx(via @objectstack/spec)
  • content/docs/protocol/kernel/realtime-protocol.mdx(via @objectstack/client)
  • 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/client, @objectstack/spec)
  • content/docs/releases/index.mdx(via @objectstack/spec)
  • content/docs/releases/v12.mdx(via @objectstack/metadata, @objectstack/spec)
  • content/docs/releases/v13.mdx(via @objectstack/spec)
  • content/docs/releases/v16.mdx(via @objectstack/client, @objectstack/spec)
  • content/docs/releases/v17.mdx(via @objectstack/spec)
  • content/docs/releases/v9.mdx(via @objectstack/metadata, @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.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/lteststooling

Projects

None yet

2 participants

@os-zhuang@claude