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

FROMTO
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]ofObject.entries(event.changes??{}))onFieldChange(field,value);}

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

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

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

适用:

处理
SchemaDataEventType 删除成员,并在 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

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 5:01pm

Request Review

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

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.

@os-zhuangClaude

Copy link
Copy Markdown
ContributorAuthor

已合入 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 queueAug 2, 2026
Merged via the queue into main with commit 36030ffAug 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 freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

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

2 participants

@os-zhuang@claude