Skip to content

refactor(spec)!: remove DataEventType 'data.field.changed' — no producer (#4673) - #4685

Merged
os-zhuang merged 2 commits into
mainfrom
claude/issue-4673-retire-data-field-changed
Aug 2, 2026
Merged

os-zhuang merged 2 commits into
mainfrom
claude/issue-4673-retire-data-field-changed

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Fixes #4673

按维护者裁决走 ADR-0049 enforce-or-remove 的路线 3(移除)。

为什么移除

data.field.changed 声明在 DataEventType 里,但全仓没有任何生产者:engine 的 publishDataEvent 只发 data.record.{created,updated,deleted},以及(自 #4639 起)data.records.{updated,deleted}。订阅方 switch 到这个值上的分支永远不会执行,而 switch 本身照样编译通过——这正是 ADR-0078 说的"静默失效的声明",只不过发生在事件词表上。

更关键的是,按现有契约它根本无法被实现:DataEventSchema 是 record 形状的(recordId / changes / before / after),没有 field / oldValue / newValue 槽位可以承载逐字段语义。这个 enum 成员承诺了一个 payload 装不下的粒度。

FROM → TO

FROM TO
type: 'data.field.changed' type: 'data.record.updated',逐字段明细从 payload 的 changes 读(before / after 给出前后状态)

一行修复——删掉死分支,改从 update 事件读 changes:

// BEFORE —— 永远不会执行,没有生产者发过这个事件
if (event.type === 'data.field.changed') { onFieldChange(event); }

// AFTER —— 变更字段一直挂在 record 事件上
if (event.type === 'data.record.updated') {
  for (const [field, value] of Object.entries(event.changes ?? {})) onFieldChange(field, value);
}

信息没有丢失:逐字段明细本来就在 data.record.updated 上,而且是一次写入一个事件,而不是宽表上 N 个事件。删掉那个分支不改变任何可观测行为——它从来没跑过。

退役套件:哪些面适用、哪些不适用

这是本次实现前先做的判断。data.field.changed 是运行时事件类型的 enum 成员,不是作者在 stack 定义里书写的可授权(authorable)元数据属性,所以 .claude/skills/spec-property-retirement 的很多机件并不适用。逐条核对结果:

适用:

面 处理
Schema 从 DataEventType 删除成员,并在 schema 注释里写明移除了什么、活的机制是什么
ADR-0087 D3 semantic migration 新增 data-field-changed-event-retired 到 migrations/registry.ts 的 step 17,并扩写该 step 的 rationale
Pin 测试 api/events.test.ts:收窄后的 .options、退役名不再 parse、以及 FROM → TO 的正向 pin
生成物 spec-changes.json、docs/protocol-upgrade-guide.md、content/docs/references/api/events.mdx
Changeset @objectstack/spec major,含 FROM → TO 表与一行修复

不适用(逐条说明理由,避免下次重新审一遍):

  • retiredKey() 墓碑 —— 一个被删除的 enum 值无法承载 fix-it 提示,这和 step 17 里 sharing-rule full 退役时撞上的是同一个限制(该 step 的 rationale 已经写明这一点)。可强制执行的通道是 tsc(任何仍把该值写在 DataEventType 位置上的消费者编译失败)和 enum parse(现在直接拒绝,而不是接受一个永不到达的事件)。
  • ADR-0087 D2 conversion —— 这是运行时事件面,没有任何 stack / example / template 会书写事件名,所以没有源文件给 os migrate meta 改写。按 enhanced-api-error-field-errors-renamed 和 data-driver-find-stream-retired 的先例,登记为 semantic TODO 而非 conversion。(webhook 的订阅走的是另一个可授权 enum WebhookTriggerType,其词表早在 Webhook triggers undelete and api are declared but never fire #3196 就已经收敛到"真实存在的生产者"。)
  • liveness ledger —— ledger 治理的是可授权元数据类型(object / field / flow …),liveness/ 下没有 event 条目;DataEvent 是运行时 payload 契约。check:liveness、check:empty-state 均原样通过。
  • authorable-surface.json / gate (a)(b) —— 该棘轮追踪的是可授权键(api/DataEvent:type 这种),不是 enum 成员,所以键列表不变、两个 gate 正确地保持沉默(check:authorable-surface PASS 已验证)。
  • api-surface.json —— 快照记录导出的 name (kind),对 enum 成员级收窄是盲的;DataEventType (type) 条目不变(check:api-surface PASS 已验证)。
  • forms / i18n bundles / CLI advisory lint / examples / published skills —— 均以可授权键为前提,这里一个都没有:*.form.ts 没有对应输入,lint 是 ledger 驱动的,examples/ 与 skills/ 全仓 grep 无命中。
  • content/docs/references/api/realtime.mdx —— 里面的 field.changed 属于另一个 enum RealtimeEventType(不带 data. 前缀),归 Audit: several event/subscription/connector enums are schema-only (declared, no runtime consumer) #3197,本 PR 不动。

验证

全部在本地实跑,命令与结果如下:

pnpm --filter @objectstack/spec build                    OK
pnpm --filter @objectstack/spec check:generated          先 FAIL(证明 3 项过期)→ 精确重新生成后 PASS
  stale: spec-changes.json / docs/protocol-upgrade-guide.md / content/docs/references/**

重新生成后的 gate 全绿:

check:generated              PASS
check:liveness               PASS
check:empty-state            PASS
check:authorable-surface     PASS
check:api-surface            PASS
check:spec-changes           PASS
check:upgrade-guide          PASS
check:skill-examples         PASS
check:strictness-ledger      PASS
check:doc-authoring          PASS

测试与类型检查:

pnpm --filter @objectstack/spec typecheck                Done(exit 0)
pnpm --filter @objectstack/spec test                     Test Files 291 passed (291) / Tests 7315 passed (7315)
objectql + client + plugin-webhooks typecheck            三个全部 Done
objectql  src/engine-data-events.test.ts                 Test Files 1 passed / Tests 10 passed
plugin-webhooks src/auto-enqueuer.test.ts                Test Files 1 passed / Tests 16 passed
cli test/migrate-meta.e2e.test.ts (链路重放)              Test Files 1 passed / Tests 12 passed
eslint(三个改动源文件)                                   exit 0

说明:plugin-webhooks 首次 typecheck 报 Cannot find module '@objectstack/service-messaging',是 sibling 包 dist 未构建所致(退役 skill 里记录的 stale-dist 陷阱),turbo run build --filter=…... 补齐依赖闭包后三个包全部通过,与本次改动无关。

⚠️ 与在途 PR #4677 的文本冲突

#4677(分支 claude/bulk-write-missing-events-sm1i4b)向同一个文件 packages/spec/src/api/events.zod.ts 新增 BulkDataEventType / BulkDataEventSchema。本分支从 origin/main 切出(尚不含 #4677),diff 严格限定在 data.field.changed 的移除上,但两者大概率会在该文件产生文本冲突。

后合并的一方请注意:解决冲突后必须重新跑 spec 生成物门禁,否则 spec-changes.json / protocol-upgrade-guide.md / content/docs/references/** 会过期:

pnpm --filter @objectstack/spec build
pnpm --filter @objectstack/spec check:generated   # 再按它证明过期的项精确重新生成

两者语义上不冲突:#4677 新增一套批量写事件契约,本 PR 移除一个无生产者的成员,方向一致(都在让事件词表只保留真实存在的生产者)。

刻意没做


Generated by Claude Code

…cer (#4673)

`data.field.changed` was declared in `DataEventType` and emitted by nothing.
The engine's `publishDataEvent` sends `data.record.{created,updated,deleted}`
and (since #4639) `data.records.{updated,deleted}`; no other producer exists in
either repository. A subscriber switching on it held a branch that could never
run, and the surrounding `switch` still compiled — ADR-0078's silently-inert
declaration, on the event vocabulary.

It could not have been implemented against this contract as written either:
`DataEventSchema` is record-shaped (`recordId`, `changes`, `before`, `after`)
with no `field` / `oldValue` / `newValue` slot, so the member advertised a
granularity the payload has no room for.

FROM `type: 'data.field.changed'` TO `type: 'data.record.updated'`, reading the
per-field detail off the payload's `changes` map (with `before` / `after`).
Nothing is lost — that detail has always ridden on the record event, as one
event per write rather than N on a wide table.

Registered as an ADR-0087 D3 semantic migration
(`data-field-changed-event-retired`) rather than a D2 conversion: this is a
runtime EVENT surface, so there is no authorable source for `os migrate meta`
to rewrite. Deliberately no `retiredKey()` tombstone — a removed enum VALUE
cannot carry a fix-it prescription the way an authorable object key can (the
same limit the sharing-rule `full` retirement hit). The enforced channels are
tsc and the enum parse.

ADR-0049 enforce-or-remove, route 3.

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

vercel Bot commented Aug 2, 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 2, 2026 5:01pm

Request Review

…ire-data-field-changed

# Conflicts:
#	docs/protocol-upgrade-guide.md
#	packages/spec/src/migrations/registry.ts
@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling size/m labels Aug 2, 2026
@github-actions

github-actions Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @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 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/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/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/plugin-spec.mdx (via @objectstack/spec)
  • 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/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/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

已合入 main 并重新跑过门禁

开分支后 main 前进了两个提交(289c6f3、b8b3c64 — #4670 RetryPolicy 单源收敛),与本分支在两个文件上冲突。已用 merge(非 rebase,避免 force-push)解决:

冲突文件 解决方式
packages/spec/src/migrations/registry.ts 双方都在 step 17 的 rationale 末尾追加段落。两段都保留,按 main 的段落在前、本次移除的段落在后,conversionIds 与 semantic[] 数组本身自动合并无冲突。
docs/protocol-upgrade-guide.md 生成物,不手工解冲突:取 main 版本后重新跑 gen:upgrade-guide。

check:generated 在合并后精确指认出只有 docs/protocol-upgrade-guide.md 一项过期(spec-changes.json 与 content/docs/references/** 自动合并后即为最新),重新生成该项后门禁全绿:

check:generated              PASS      check:spec-changes           PASS
check:liveness               PASS      check:upgrade-guide          PASS
check:empty-state            PASS      check:skill-examples         PASS
check:authorable-surface     PASS      check:strictness-ledger      PASS
check:api-surface            PASS

合并后重跑 spec 测试:Test Files 292 passed (292) / Tests 7330 passed (7330),typecheck exit 0。

相对 main 的净 diff 仍严格限定在 7 个文件、+196/−5,未触碰 content/docs/releases/。

与 #4677 的潜在文本冲突提示依然有效(它尚未合入 main),见 PR 描述最后一节。


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/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

declared ≠ enforced:DataEventType 的 data.field.changed 全仓没有生产者(ADR-0049 第四态)

2 participants