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

refactor(spec)!: remove DataEventType 'data.field.changed' — no producer (#4673)#4685
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-retiredmigrations/registry.ts 的 step 17,并扩写该 step 的 rationale
Pin 测试 api/events.test.ts:收窄后的 .options、退役名不再 parse、以及 FROM → TO 的正向 pin
生成物 spec-changes.jsondocs/protocol-upgrade-guide.mdcontent/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-renameddata-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:livenesscheck: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 前进了两个提交(289c6f3b8b3c64#4670 RetryPolicy 单源收敛),与本分支在两个文件上冲突。已用 merge(非 rebase,避免 force-push)解决:

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

check:generated 在合并后精确指认出只有 docs/protocol-upgrade-guide.md 一项过期(spec-changes.jsoncontent/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

@os-zhuang
os-zhuang marked this pull request as ready for review August 2, 2026 17:15
@os-zhuang
os-zhuang added this pull request to the merge queue Aug 2, 2026
Merged via the queue into main with commit 36030ff Aug 2, 2026
22 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-4673-retire-data-field-changed branch August 2, 2026 17:25
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