Skip to content

fix(metadata-protocol): deleteMany/updateMany 的 atomic 要么为真、要么拒绝 (#4620) - #4798

Merged
xuyushun441-sys merged 2 commits into
mainfrom
claude/issue-4620-many-data-atomic
Aug 3, 2026
Merged

fix(metadata-protocol): deleteMany/updateMany 的 atomic 要么为真、要么拒绝 (#4620)#4798
xuyushun441-sys merged 2 commits into
mainfrom
claude/issue-4620-many-data-atomic

Conversation

@xuyushun441-sys

Copy link
Copy Markdown
Contributor

Fixes #4620

ADR-0119 D4 把 batchDataatomic 修成了真承诺。同一文件里的两个同胞当时不在那次 PR 的确认范围内,缺陷原样留着 —— 本 PR 只做 issue 的第 1、2 节。

修了什么

1. deleteManyData 的假原子(protocol.ts)

if (options?.atomic) break; 只是跳出循环:没有事务、没有回滚。失败前已删掉的行保持已删除,而响应以原子自居并把它们报成 success: true。这比 batchData 那次更糟 —— 部分删除没有自然的撤销手段,客户端无法从自己的请求里把行重建回来。

2. updateManyData 完全不读 atomic

选项被接受、在 BatchOptionsSchema 里被声明成「全有或全无」,却从未被读取。调用方要原子性,拿到的是 best-effort,且没有任何信号 —— 典型的 declared ≠ enforced,而且是写路径上的保证。

怎么修的:复用,不重新推导

D4 的三块料(原子臂、共享逐记录循环、fail-closed 能力闸门、ROLLED_BACK: / NOT_ATTEMPTED: 行标记)都已在 main 上,本 PR 一律复用。为避免出现第三、第四份事务处理代码(抄写正是下一个同胞漂移回谎言的路径),把 batchData 原子臂里「逐记录循环 + 两个响应构造器」以外的部分抽成一个共享 runner runAtomicBatch,三个入口共用:

  • atomic: true → 整批跑在一个 engine.transaction() 里,首个失败回滚此前全部写入;
  • 回滚批次报告零成功:成功过的行标 ROLLED_BACK: record < i > failed — < cause >,没轮到的行标 NOT_ATTEMPTED: atomic batch aborted by record < i >,致因行保留自己的原始错误 —— 客户端能区分「已尝试但被撤销」与「从未执行」;
  • atomic 优先于 continueOnError(后者的契约文本本来就把自己限定在 atomic=false);
  • 无法回滚的运行时(无 engine.transaction(),或默认 driver 无 beginTransaction)→ 501 NOT_IMPLEMENTED,而不是静默降级成 best-effort。静默降级正是本 issue 的缺陷类别,所以这里 fail closed。

batchData 的行为一点没动,其既有测试文件未作任何修改仍全部通过。

行为变更(需要知会使用者)

atomic: truedeleteMany / updateMany 在不能回滚的运行时上从「静默 best-effort」变成 501 拒绝。想要 best-effort 就明确要 —— atomic: false 或不传。changeset 里写了这条。

非原子路径(含 continueOnError 交互与两个端点的响应形状)保持不变,并加了回归钉。

测试

新增 packages/metadata-protocol/src/protocol.many-data-atomic.test.ts(14 例,均引用 #4620)。假引擎是一个带快照/恢复事务语义的内存 store,所以回滚是真回滚:

  • deleteMany 原子 + 中途失败:回滚后 ac 两行仍可读(这是本 issue 的关键 —— 部分删除没有自然撤销),零成功,行标 ROLLED_BACK: / NOT_ATTEMPTED:;
  • updateMany 原子 + 中途失败:先前的更新被恢复成旧值,同样零成功、同样标记;
  • 两个端点在无事务能力的 engine / driver 上均 501,且一次写都没发生;
  • 非原子路径逐条钉住:不开事务、保留此前成功、continueOnError 交互不变。

protocol.delete-many.test.ts 里原先钉住假原子的那一例(atomic aborts the remaining ids on the first failure,断言 succeeded: 1)改成钉拒绝语义 —— 它钉的正是本次要修掉的行为。

pnpm --filter @objectstack/metadata-protocol test
 Test Files  31 passed (31)
      Tests  260 passed (260)

npx vitest run src/protocol.many-data-atomic.test.ts
 Test Files  1 passed (1)
      Tests  14 passed (14)

pnpm turbo run build --filter=@objectstack/metadata-protocol   # tsup dts = tsc 类型检查,通过
packages/rest: rest-batch-size-cap + rest-delete-many-ingress   15 passed

刻意没做

issue 第 3 节(逐行结果形状 { id, success, error, record }BatchOperationResultSchema{ errors, data } 分歧)未触碰。那是公开 wire 契约的方向决定,两个方向对已发布客户端的影响相反;ADR-0119 D4 当时刻意没让它搭在 bug fix 上,让它搭在这次修复上会是同一种静默兼容性破坏。已由单独的决策 issue 承接。

🤖 Generated with Claude Code

https://claude.ai/code/session_018iARDqtrhQgz6fVHDeDkbQ


Generated by Claude Code

ADR-0119 D4 把 `batchData` 的 `atomic` 修成了真承诺,同一文件里的两个同胞
当时不在那次 PR 的确认范围内,缺陷原样留着:

- `deleteManyData` 是假原子:`if (options?.atomic) break;` 没开任何事务,
  失败前已删掉的行保持已删除,响应却以原子自居并把它们报成 success。比
  `batchData` 那次更糟 —— 部分删除没有自然的撤销手段。
- `updateManyData` 根本不读 `atomic`:选项被接受、被声明成全有或全无,却从
  未被读取,调用方拿到的是无任何信号的 best-effort。

两者现在走与 `batchData` 完全相同的原子臂 —— 抽成一个共享 runner
(`runAtomicBatch`),而不是把事务处理再抄两份(抄写正是下一个同胞漂移回
谎言的路径):

- `atomic: true` 时整批跑在一个 `engine.transaction()` 里,首个失败回滚此前
  全部写入;
- 回滚批次报告零成功,行分别标记 `ROLLED_BACK:` / `NOT_ATTEMPTED:`,致因行
  保留原始错误,客户端可区分「已尝试但被撤销」与「从未执行」;
- `atomic` 优先于 `continueOnError`;
- 无法回滚的运行时(无 `engine.transaction()`,或默认 driver 无
  `beginTransaction`)对 `atomic: true` 返回 501 NOT_IMPLEMENTED,而不是静默
  降级成 best-effort —— 静默降级正是本 issue 的缺陷类别。

非原子路径(含 `continueOnError` 交互与响应形状)保持不变;`batchData` 的
既有测试未作任何改动仍全部通过。

逐行结果形状与 `BatchOperationResultSchema` 的分歧(issue 第 3 节)刻意未动:
那是公开 wire 契约决定,单独跟踪。

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

vercel Bot commented Aug 3, 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 3, 2026 7:09am

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling labels Aug 3, 2026
@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/metadata-protocol.

3 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/concepts/metadata-lifecycle.mdx (via @objectstack/metadata-protocol)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/metadata-protocol)
  • content/docs/releases/v9.mdx (via @objectstack/metadata-protocol)

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.

@github-actions github-actions Bot added the size/l label Aug 3, 2026
)

同一个包、同一类变更、同一种调用方可见后果:`atomic: true` 在不能回滚的运行时上
从「200 部分成功」变成 501。D4 给 `batchData` 定的就是 minor
(`.changeset/adr-0119-plugin-reachable-transactions.md`),这次给
`deleteManyData` / `updateManyData` 做同一件事不能是 patch —— 包当前是
17.0.0-rc.1,patch 会发成 17.0.1,等于告诉消费者什么都没变。

只改 bump 一行,正文不动。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018iARDqtrhQgz6fVHDeDkbQ
@xuyushun441-sys
xuyushun441-sys marked this pull request as ready for review August 3, 2026 07:34
@xuyushun441-sys
xuyushun441-sys added this pull request to the merge queue Aug 3, 2026
Merged via the queue into main with commit 4c80fd6 Aug 3, 2026
31 of 33 checks passed
@xuyushun441-sys
xuyushun441-sys deleted the claude/issue-4620-many-data-atomic branch August 3, 2026 07:46
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/l tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(metadata-protocol): deleteManyData has the same fake-atomic as batchData, updateManyData ignores atomic entirely

2 participants