Repository navigation
fix(objectql): engine.transaction hardening — silent degrade, default-driver-only, and no owned-vs-joined signal (ADR-0118 D1 caveats) #4619
Description
Activity
- addedbugSomething isn't workingSomething isn't workingpriority:p2Medium: important, M3Medium: important, M3
on Aug 3, 2026 - added a commit that references this issue
on Aug 3, 2026 退回队列(PM 循环第 1 轮,会话
session_01Pbu27iNUfQCHeuS551Rqo7):本单曾短暂认领,因维护者加急 #5038(批量写按行语义,同在packages/objectql/src/engine.ts)而让位 —— 同文件严格串行,本单待 #5038 落地后下轮再派。给下一次派发的已知事实(本轮核查所得,避免重查):
transaction的契约声明在packages/spec/src/contracts/objectql-engine.ts:229,其 TSDoc 明写两条 caveat(default-driver-only、无 beginTransaction 时静默降级)是声明语义的一部分(ADR-0119 D1)。因此本单三点中,凡要收紧行为(opts.require抛错、跨驱动拒绝、owned 信号进签名)都动 spec 契约,归 spec 车道;engine 车道可做的是不改契约的可发现性修复(降级 warn-once、事务内写路由到非默认驱动时的响亮诊断)。派发时按此拆定范围,或与 spec 车道协同。
Generated by Claude Code
认领:PM 循环第 2 轮追加批(engine-core 车道,维护者指令并发提至 5)
会话:session_01V7WetGmnfoXNn8cLieKKmx
分支:claude/issue-4619-transaction-observability
Worktree:objectstack-issue-4619
域:domain:engine-core
范围:按 2026-08-04 退回评论的既知事实拆定 —— 仅 engine 侧不改契约的可发现性半边:①无beginTransaction降级时 warn-once;②事务内写被路由到非默认驱动时响亮诊断。⛔ 收紧行为的半边(opts.require抛错、跨驱动拒绝、owned 信号进签名)动packages/spec/src/contracts/objectql-engine.ts:229的声明语义(ADR-0119 D1),归 spec 车道,本单不做 —— PR 用 "Part of #4619",落地后 PM 走跨座位转移把契约半边立到 spec 队列。
文件面:packages/objectql/src/engine.ts(仅transaction()方法区)+ 测试、.changeset/*.md。⚠️ 同文件 #5504 在飞(insert/update 返回区),区域不相交,后落地者合 main 重验。
Generated by Claude Code
复核通过,ACCEPT(engine-core 车道 PM,第 2 轮追加批):engine 侧可发现性半边交付于 PR #5724("Part of",本 issue 待 spec 半边 #5696 完成后一并收口)。CI 绿后转 ready 入队。
交付:行为一字未改,两条诊断按 AGENTS.md 降级日志判据逐条论证级别(降级 warn:什么都还没丢;跨数据源 error:声称原子的写已独立落盘);引擎
transaction()与沙箱ctx.api.transaction共用同一对 helper,共享「只说一次」额度;TransactionScope纯记录不参与路由。反向验证 10/14 红 + 似是而非实现探针证明 4 条反向对照不是幽灵检查 —— 这个双层验证形状值得写进范本。与 #5504 同文件但区域相距 250-400 行(方法开头 vs 返回路径),实测无需串行。重要连带发现(已按查重纪律评论至既有 #5351,未立重单):跨数据源写入实测拿到的是另一连接的事务句柄(
buildDriverOptions无条件穿 ambient trx),比契约 TSDoc 写的「在事务外写入」更糟;已用独立 in-memory 双数据源复现并在测试中如实钉住(标注「观测到,非认可」)。两条排序结论:(a) #5351 的方案 A 前置(txStore 记录属主 driver)已随本 PR 落地;(b) #5351 派发时应把 PR #5724 列为前序,其裁决落地后本 PR 的 error 语义需随之调整。
Generated by Claude Code
- added a commit that references this issue
on Aug 6, 2026 收口(执行席 PM):三点全部落地,本单关闭(completed)。
- 静默降级:可发现性 = warn-once(PR fix(objectql): make transaction() 的两条既声明 caveat 可被观测 (#4619) #5724);收紧 =
opts.requirefail-closed(PR feat(spec,objectql): transaction 契约收紧第一批 ——opts.requirefail-closed 与owned信号 (#5696) #6165,TransactionUnsupportedError); - default-driver-only:可发现性 = 诊断(PR fix(objectql): make transaction() 的两条既声明 caveat 可被观测 (#4619) #5724);收紧 = PR fix(objectql)!: 事务句柄不再跨数据源穿透 —— 业务写响亮拒绝、系统账本移出事务落盘 (#5351) #6171 —— 事务句柄不再跨驱动穿透(结构半边
transactionCoversDriverFor)、业务写跨驱动响亮拒绝(CrossDatasourceTransactionWriteError)、audit/telemetry/event 系统账本 carve-out 移出事务落盘(孤儿行语义为已裁决接受的代价); - owned-vs-joined:
EngineTransactionInfo.owned信号(PR feat(spec,objectql): transaction 契约收紧第一批 ——opts.requirefail-closed 与owned信号 (#5696) #6165,spec 席已在 [转席确认·spec 座位] PR #6165(#5696)transaction 契约收紧:EngineTransactionOptions / EngineTransactionInfo + TSDoc 事实性改写 —— 请核可或接管 #6184 五点核可)。
契约与语义修订常驻 ADR-0119 / ADR-0067 的 2026-08-06 Amendment 段。两件独立跟进不属于本单三点、不拖住关闭:#6167(不可归属句柄的同源校验收口,需动 IDataDriver 契约)、#6168(ScopedContext.transaction 不 join ambient)。跨驱动原子性/两阶段提交是本单原文明确排除、被裁掉的方向 —— 不是被推迟,不欠。
执行席 PM
session_019Q7oc7ASjh8yxyS3Yz78We。
Generated by Claude Code
- 静默降级:可发现性 = warn-once(PR fix(objectql): make transaction() 的两条既声明 caveat 可被观测 (#4619) #5724);收紧 =
- added a commit that references this issue
on Aug 17, 2026
ADR-0118 D1 declares
transactiononIObjectQLEngineand writes its caveats into the contract TSDoc as declared meaning rather than hidden behaviour. Declaring them is not fixing them. These are the three, recorded while scoping #4612.All three are in
ObjectQL.transaction(),packages/objectql/src/engine.ts:4934-4973.1. Silent degrade when the driver has no
beginTransaction(:4952-4954)The callback runs with no transaction and no rollback, and the caller cannot tell. Every in-tree driver implements
beginTransaction(it is required onIDataDriver), so this path exists for test doubles and foreign engines — but a caller asking for a transaction and silently not getting one is the failure mode that madebatchData'satomicflag a lie for as long as it was (ADR-0118 D4). Proposal: warn-once, or an explicitopts.require: truethat throws instead of degrading. ADR-0118 D4'sbatchDatagate is the fail-closed pattern to generalize.2. Default-driver-only (
:4950)Objects mapped to another datasource via
setDatasourceMappingare written outside the transaction with no diagnostic, so a multi-datasource "atomic" write partially commits. Refusing loudly when the operations inside a transaction span drivers would be a real improvement over today's silent partial commit; genuine cross-driver atomicity is a much larger design question (two-phase commit) and is explicitly not what this asks for.3. No owned-vs-joined signal (
:4946-4948)Per ADR-0067 D2 a nested
transaction()joins the ambient transaction and returns without owning commit/rollback. That is correct, but the caller cannot distinguish "I own this transaction" from "I joined someone else's". It matters for any caller whose response claims durability:batchData's rolled-back response (ADR-0118 D4) asserts prior ops were undone, which is only locally guaranteed when it owns the transaction. Today's callers are all top-level so the claim holds; nothing enforces that it keeps holding. Proposal:transaction(cb, base, opts?)surfacing anownedsignal to the callback.Refs: ADR-0118 D1, ADR-0034, ADR-0067 D2, #4612.