feat(quota): migrate void commit transaction to TypeScript - #3832
Conversation
Signed-off-by: hyk <[email protected]>
Signed-off-by: hyk <[email protected]>
Signed-off-by: hyk <[email protected]>
huangruiteng
left a comment
There was a problem hiding this comment.
Findings
- P2(非阻塞):Python compatibility facade 的两项既有行为发生了变化。
loopx/control_plane/quota/void_commit.py:200在build_quota_slot_void_event的project_record路径把source原样交给 TypeScript literal decoder;迁移前会先.strip(),因此source=" heartbeat "过去会规范化为heartbeat,现在会被拒绝。另在loopx/control_plane/quota/void_commit.py:221,record_quota_slot_void_from_preview明确接收的render_markdowncallback 被直接丢弃,迁移前则使用调用方 renderer 写入 Markdown。由于这两个函数仍通过 public compatibility re-export 暴露,我建议要么保留 trim/callback 语义,要么显式 deprecate 并记录迁移说明。它们不影响本 PR 的事务正确性,因此不把 verdict 降为 request changes。
已评审 exact head:1d32a5dee2bab4b19d52dd2eb8349df20eadf838
动机
这个 PR 把 quota void 的最终事务所有权从 Python 迁移到 TypeScript,并顺势把 spend/void 重复的持久化协议抽成共享 kernel。它要解决的不只是“用 TS 重写一段代码”,而是让 effect runtime 成为 quota accounting 写入、幂等 replay、崩溃修复和冲突判断的单一权威,减少 Python/TypeScript 双实现继续漂移的风险。
改动思路
设计分为三层:Python facade 保留调用兼容、decision 投影、时间/effect identity 和迁移期的 legacy index lock;新的 quota.void.commit handler 负责请求解码、目标 spend 查找、void reduction 与结果构造;共享的 commitQuotaAccountingArtifactTransaction 负责 spend/void 两类事务的 receipt、CAS、artifact 落盘、replay 和 repair。已有 spend commit 也改走同一事务 kernel,使两种 accounting event 共用一致的失败与恢复语义。
这个方向是可复用的协议抽取,而不是只为 void 打补丁。RFC 中英文文档同步记录迁移边界,runtime handler 和 tsconfig.control-plane.json 纳入新模块,Python public surface 则改为 re-export 新 facade。
具体改动
accounting_artifact_transaction.ts新增 spend/void 共享事务内核:分配 artifact 路径、解析 index、校验 effect identity、生成 prepared/committed receipt、校验 receipt 路径、拒绝 symlink、CAS 检查、补写缺失 JSON/Markdown/index,并区分written/replayed/repaired/conflict。void_commit.ts实现quota.void.commit:严格解码请求和 decision,按goal_id + generated_at + classification查找目标 spend,构造 preview/void record/artifacts,并通过共享事务内核提交。spend_commit.ts删除原有重复事务实现并改用共享 kernel,同时保留 spend 自身的请求、record、payload 和 replay 语义。void_commit.py提供 Python bridge;slot_accounting.py删除旧的 Python void 写入实现;quota.py把 CLI/compat facade 接到新的 bridge;effect_runtime_handlers.ts注册新 effect method。test_quota_void_commit_runtime.py、两个 TypeScript quota test 文件覆盖 preview、not-found、commit、CAS、duplicate effect、replay、repair、malformed state 和 artifact boundary;fixture smoke 从字符串搜索升级为 JSON record 断言。RFC 与迁移清单同步更新。
关键代码讲解
commitQuotaAccountingArtifactTransaction是这次迁移的核心边界。新事务先在 index mutation lock 内检查已有 receipt;同一 effect/request 会 replay 或修复缺失 artifact,不同 request 复用 effect 会 fail closed。没有 receipt 时先核对 index digest,再写 prepared receipt、确保三个 artifact 一致,最后把 receipt 标记 committed。这给崩溃窗口留下了可恢复的 durable intent。evaluateQuotaVoidCommit把project_record与 preview/commit 分开:纯投影不携带 effect identity;真实 commit 则要求 effect identity 与 payload/result 对齐,避免 Python facade 接受错配结果。prepareArtifacts/findTargetSpend在锁内重新基于当前 index 找目标并生成 record、index record 与 Markdown,避免 preview 与 commit 之间的陈旧状态直接落盘;目标不存在时返回结构化not_found,不产生部分写入。commit_quota_slot_void保留了迁移期 Python writer 使用的 index lock,并把当前 digest 作为 CAS 前置条件传入 TypeScript。这样 legacy Python writer 尚未全部迁完时,仍不会绕过现有互斥约束。evaluateQuotaSpendCommit复用共享 kernel,是这个抽象没有偏向 void 的关键证明;spend 的 record/payload 仍由 spend domain 负责,事务机制则统一。
正向路径:调用方执行 void_quota_slot 后,Python 先投影当前 quota decision 并建立 effect identity,在 legacy lock 内取得 index digest;TypeScript handler 在事务锁内找到对应 spend,计算扣回后的 decision,写 prepared receipt,确保 JSON/Markdown/index 都存在且一致,再提交 receipt,最终返回 written。同一 effect 再次调用会读 receipt 并返回 replayed,不会重复扣减。
负向路径:若 index 在 preview 与 commit 间改变,CAS 返回 index_digest_conflict;若 effect identity 已绑定到不同 request,或 receipt/index/artifact 内容矛盾,则返回 conflict/抛出受控错误;若 index/path/receipt malformed 或路径经过 symlink,则 fail closed。若进程在 prepared 阶段中断,下一次同 effect 调用会根据 receipt 补齐 artifact 并返回 repaired。
对主干的风险
主风险是 accounting 热路径和 public compatibility surface 的迁移。事务层涉及多文件一致性、并发写入、崩溃恢复和路径边界,错误会导致 quota 重复记账或账本不可恢复;本 PR 的 receipt + CAS + replay/repair 测试对这些风险覆盖较完整,spend 复用也降低了两套协议分叉的长期风险。没有发现 accounting correctness、重复写入、CAS、replay、repair、authority、typed-state 或 path escape blocker。
剩余风险是上面的 P2 compatibility regression:非规范化 source 和 custom Markdown renderer 的旧调用方可能受影响。建议后续用兼容测试锁住决定后的语义。
验证
| Surface | 结果 |
|---|---|
| TypeScript focused spend/void tests | 37 passed |
| Full TypeScript control-plane suite | 407 passed, 1 PostgreSQL integration skipped |
| TypeScript strict typecheck | passed |
| Python focused void suite | 21 passed |
| Python CLI/import/void selection | 123 passed |
| Ruff on changed Python surfaces | passed |
| quota plan fixtures | passed |
| quota-plan smoke | passed |
| peer-agent-runtime-v1 smoke | passed |
| exact-head premerge (goal receipt enforcement disabled) | direct 4/4, selected 18/18, boundary scan clean, 0 failures/warnings/manual holds |
| GitHub checks | 11 checks green(3 expected release/deploy jobs skipped) |
独立兼容性 probe 复现了上述两项 P2:带首尾空白的 source 从“规范化后接受”变为拒绝;custom renderer 从“实际用于 Markdown”变为被忽略。除此以外没有未验证的适用完成门槛。
我的整体评价
APPROVE。 这个 PR 完成了 void commit 的 TypeScript authority 迁移,并把最需要统一的 durable transaction 机制抽成 spend/void 共享内核。正向提交、并发冲突、幂等 replay、prepared receipt 修复和恶意/损坏状态的失败路径都有代码与测试证据支撑;架构方向比维持两套写入实现更干净。上述 P2 应跟进,但不构成阻塞本次迁移的 correctness 问题。本 review 不授予 merge 权限。
English verdict: APPROVE at exact head 1d32a5dee2bab4b19d52dd2eb8349df20eadf838. The shared spend/void transaction kernel is well-factored and the commit, CAS, replay, repair, and fail-closed paths are strongly validated. One non-blocking P2 remains: the Python compatibility facade no longer trims source in record projection and ignores the supplied Markdown renderer; preserve or explicitly deprecate those semantics. This approval does not authorize merge.
Summary
quota void-slottransaction into the versioned TypeScript control plane: target lookup, accounting reduction, event/result construction, effect fencing, index CAS, artifact persistence, receipt replay, and repair.spend | voidaccounting-artifact transaction kernel and route the existing spend transaction through it, removing duplicate durability authority instead of adding a second writer.Why this slice
quota void-slotwas a provider-free, independently owned transaction that still left lookup, state reduction, and three-artifact persistence in Python. This cutover deletes 212 lines of that legacy semantic path and replaces 671 duplicated lines inspend_commit.tswith a production kernel already used by both accounting operations.The bounded future-facing pass is included here because spend and void share the same receipt, CAS, replay, repair, and artifact invariants. Broader quota decision and monitor-poll migration remains out of scope because those surfaces have different lifecycle owners.
Behavior disclosure
Migration receipt
quota.void.commitplus the closed accounting-artifact kernel.Validation
pytest -q -n 2 --ignore=build: 5,085 passed, 12 skipped.compatibility_facade=2, no regressions).twine check, contained all three new runtime files, installed into separate fresh Python 3.13.7 environments, and passed the packaged semantic probe.git diff --checkpassed.Performance
Integration notes
Current open-PR overlap is mechanical only: central runtime registration and TypeScript include-list additions overlap disjoint hunks in #3693, #3818, #3819, and #3820. No open PR owns the quota void/spend accounting semantics changed here.