Repository navigation
fix(metadata-protocol): deleteManyData has the same fake-atomic as batchData, updateManyData ignores atomic entirely #4620
Description
Activity
- added a commit that references this issue
on Aug 2, 2026 xuyushun441-sys commented
on Aug 3, 2026 CollaboratorMore actions认领:PM 循环第 3 轮
会话:session_018iARDqtrhQgz6fVHDeDkbQ
分支:claude/issue-4620-many-data-atomic
Worktree:objectstack-issue-4620分诊:落地
objectstack(后端packages/metadata-protocol),与 objectui / cloud 无关。批次独立性:与本轮在飞的 #4784(
packages/objectql+skills/)文件面完全不相交,可并行,不串行等待。前提复核(issue 写于 08-02,行号已漂,逐条对着最新
main重查)issue 引用的行号已经不准(
deleteManyData现在在protocol.ts:5754,不是 5543;updateManyData在 5661,不是 5405)。但两个缺陷都仍然成立,而且 issue 要求复用的两块料确实已经在main上:deleteManyData仍是假原子(protocol.ts:5799):if (options?.atomic) break;—— 只是跳出循环,没有任何回滚,失败前已删掉的行保持已删除,而响应仍以原子自居。issue 说这比batchData那次更糟,我同意:部分删除没有自然的撤销手段。updateManyData仍然完全不读atomic(5661 起的整个方法体里atomic一次都没出现)。选项被接受、被忽略、无任何信号 —— 典型的 declared ≠ enforced。- 可复用的料在位:ADR-0119 D4 的原子臂(5338)、共享的逐记录循环(5408)、fail-closed 能力闸门(5382 抛
NOT_IMPLEMENTED)、以及ROLLED_BACK:/NOT_ATTEMPTED:的行标记(5563-5566)都已合入。派发时要求复用它们,不许再推导一套。
派发范围:只含第 1、2 节,不含第 3 节
第 1、2 节(两个
atomic缺陷)直接派发,不问。理由:仓里已经把不变量写死了 —— ADR-0119 D4 刚给batchData定了原子语义的正确形状,ADR-0049 的 enforce-or-remove 也摆在那里。「同一个文件里的两个同胞有同一类缺陷」这个发现本身就带着它的结论,不需要再确认一次。第 3 节(逐行结果形状与
BatchOperationResultSchema不一致)不派发:实现推的是{ id, success, error: string, record },schema 声明的是{ errors: ApiError[], data }。issue 自己写着「Decide the direction on purpose」—— 这是公开 wire 契约的形状决定,两个方向(放宽 schema 迁就现实 vs. 迁实现并给响应加版本)对已发布客户端的影响相反。ADR-0119 D4 当时刻意没动它,正是因为让它搭在一个 bug fix 上是静默的 wire 兼容性破坏。同样的理由,它也不该搭在这次修复上。已另开决策 issue 承接,见下条评论。
Generated by Claude Code
xuyushun441-sys commented
on Aug 3, 2026 CollaboratorMore actions第 3 节(逐行结果形状与
BatchOperationResultSchema不一致)已拆到 #4793,标needs-user-decision等维护者拍板。逐字核对后的分歧比 issue 描述的更具体:两个键名字对不上(
error: stringvserrors: ApiError[]、recordvsdata)、一个键实现从不发(index),而droppedFields反倒是对齐的。三个方案沿两轴分析完毕,建议 B(迁实现到 schema 形状)—— A(放宽 schema)我建议直接排除,它会让 schema 里同时躺着error/errors与record/data,AI 照着声明写result.errors[0].message会静默拿到 undefined。真正需要维护者回答的是一个事实问题:仓外有没有已发布的消费方在读旧键 —— 有则 C(双发过渡),无则 B 直接切。本 issue 只跟第 1、2 节,dev agent 已派发。
Generated by Claude Code
- added a commit that references this issue
on Aug 3, 2026 - added a commit that references this issue
on Aug 3, 2026 xuyushun441-sys commented
on Aug 3, 2026 CollaboratorMore actionsPM 复核:返工已完成,通过(ACCEPT),等 CI 全绿后进合并队列
PR:#4798(草稿,base
main,body 首行Fixes objectstack-ai/objectstack#4620,未触碰content/docs/releases/)返工的那一处
首轮 changeset 写的是
@objectstack/metadata-protocol: patch,与它自己援引的先例矛盾 ——.changeset/adr-0119-plugin-reachable-transactions.md(D4 给batchData做同一件事)是 minor。同一个包、同一类变更、同一种调用方可见后果(atomic: true从「200 + 部分成功响应体」变成「抛 501」),不可能是 patch;而且包版本是17.0.0-rc.1,patch 会发成17.0.1,一个告诉消费方「什么都没变」的版本号。已修正并核对:changeset 现为
minor,返工提交1f049c982是一行 diff(1 file changed, 1 insertion(+), 1 deletion(-)),分支相对main的总 diffstat 仍是 4 文件 / 556 增 / 15 删,与首轮复核时逐字一致 —— 没有夹带任何其它改动。dev 也如实说明「changeset-only 编辑未重跑测试套件」,而不是把上一轮的输出当新结果报,这个诚实是对的。实现部分(首轮已核,记录在案)
- 抽成一份而不是抄两遍:D4 的原子臂提成共享的
runAtomicBatch,batchData/updateManyData/deleteManyData三个入口共用 —— 能力闸门、单个engine.transaction()、abort 哨兵、零成功回滚响应都只存在一份。理由给得对:抄写正是下一个同胞漂移回谎言的路径。 - 回滚是真回滚,钉的是状态不是响应体:假引擎是带快照/恢复语义的内存 store,测试在调用后读回存储断言
a、c两行仍在。响应里的succeeded: 0是廉价的,行还在不在才是atomic一直在承诺的东西 —— 这一点本 issue 说得最重(部分删除没有自然撤销),测试钉在了正确的地方。 - fail closed 而非静默降级:无
engine.transaction()或默认 driver 无beginTransaction→ 501NOT_IMPLEMENTED,且一次写都没发生。静默降级正是本 issue 的缺陷类别。 - 克制得当:
buildUpdateManyResponse特意不复用buildBatchDataResponse,避免顺手改掉非原子路径的returnRecords载荷;batchData行为与其测试文件均未改动。 protocol.delete-many.test.ts里那条atomic aborts the remaining ids(断言succeeded: 1)改成拒绝语义 —— 它钉的正是本次要修掉的缺陷,改它是对的,且注释写清了原委。
第 3 节
未触碰,已由 #4793 承接(等维护者回答一个事实问题:仓外有没有已发布消费方在读
result.error/result.record)。
Generated by Claude Code
- 抽成一份而不是抄两遍:D4 的原子臂提成共享的
xuyushun441-sys commented
on Aug 3, 2026 CollaboratorMore actions结案:PR #4798 已合入
main(07:46Z),issue 由Fixes自动关闭落地的是第 1、2 节:
deleteManyData的假原子与updateManyData完全不读atomic,现在都走真事务。atomic: true→ 整批跑在一个engine.transaction()里,首个失败回滚此前全部写入;- 回滚批次报告零成功,行标
ROLLED_BACK:/NOT_ATTEMPTED:,致因行保留原始错误; - 无法回滚的运行时 → 501
NOT_IMPLEMENTED,而不是静默降级成 best-effort —— 静默降级正是本 issue 的缺陷类别; - 三个入口(
batchData/updateManyData/deleteManyData)共用抽出的runAtomicBatch,而不是把 D4 的原子臂再抄两遍。抄写正是下一个同胞漂移回谎言的路径。
关键验证是状态而非响应体:回滚测试在调用后读回存储,断言
a、c两行仍可读 ——succeeded: 0是廉价的,行还在不在才是atomic一直在承诺的东西。一轮返工:首版 changeset 标
patch,与它自己援引的先例(adr-0119-plugin-reachable-transactions.md,D4 给batchData做同一件事标的是minor)矛盾。已改minor—— 包版本17.0.0-rc.1,patch 会发成17.0.1,一个告诉消费方「什么都没变」的版本号。CI 插曲:
Test Core (1/2)曾红一次,死在@objectstack/spec的 tsup DTS build 且无任何输出。本 PR 不碰packages/spec,判定为瞬态并重跑 —— 重跑后 20 项全绿,判定成立。(相关:#4796 记录了 spec 侧一条会随机把不相干 PR 踢出合并队列的 flaky,归 spec 车道。)第 3 节未触碰,已由 #4793 承接(方案 B 已由维护者拍板:迁实现到 schema 形状、硬切、changelog 诚实写明升级路径)。
⚠️ 按维护者的 PM 车道裁定,#4793 属协议线,不由本会话派发;本 PR 合入后它的前置已清空。
Generated by Claude Code
- added 4 commits that reference this issue
on Aug 3, 2026 - added a commit that references this issue
on Aug 17, 2026
ADR-0118 D4 fixes
batchData'satomicflag (it opened no transaction — it only broke the loop, stranding every prior write as committed). Its two siblings in the same file have the same class of defect and were out of that PR's confirmed scope.1.
deleteManyData— fake-atomic (packages/metadata-protocol/src/protocol.ts:5543)Same break-without-rollback shape as pre-fix
batchData; the comment there even says "Same stop semantics as batchData". Deletes performed before the failure stay deleted while the response is titled atomic — worse than thebatchDatacase, because a partial delete has no natural undo.2.
updateManyData— ignoresatomicentirely (protocol.ts:5405-5444)The option is accepted and never read. A caller passing
atomic: truegets best-effort with no signal.Work
Reuse the two pieces ADR-0118 D4 introduces rather than re-deriving them: the extracted per-record loop helper, and the fail-closed capability gate (engine exposes
transactionand, where the driver registry is inspectable, the default driver hasbeginTransaction; otherwise501 NOT_IMPLEMENTEDrather than a silent best-effort run). Apply the same response contract: a rolled-back batch reports zero successes, with rows markedROLLED_BACK:/NOT_ATTEMPTED:so a client can tell which is which.3. Second finding — per-row result shape diverges from its own schema
The implementation pushes
{ id, success, error: string, record }whileBatchOperationResultSchemadeclares{ errors: ApiError[], data }. ADR-0118 D4 deliberately kept the implementation shape (changing it there would have been a silent wire-compat break riding on a bug fix). Decide the direction on purpose: widen the schema to match reality, or migrate the implementation and version the response. Whichever way, the two should stop disagreeing.Refs: ADR-0118 D4, #4612, ADR-0049.