Skip to content

feat(quota): migrate void commit transaction to TypeScript - #3832

Merged
huangruiteng merged 3 commits into
huangruiteng:mainfrom
hhyykk:codex/ts-control-plane-next-payoff-20260902
Sep 2, 2026
Merged

feat(quota): migrate void commit transaction to TypeScript#3832
huangruiteng merged 3 commits into
huangruiteng:mainfrom
hhyykk:codex/ts-control-plane-next-payoff-20260902

Conversation

@hhyykk

@hhyykk hhyykk commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Move the complete quota void-slot transaction into the versioned TypeScript control plane: target lookup, accounting reduction, event/result construction, effect fencing, index CAS, artifact persistence, receipt replay, and repair.
  • Extract one closed spend | void accounting-artifact transaction kernel and route the existing spend transaction through it, removing duplicate durability authority instead of adding a second writer.
  • Preserve the public Python compatibility imports and payload contract. The shipping Python CLI now sends one coarse request and retains only compact input facts, clock/effect identity, result validation, and the legacy cross-writer index lock.

Why this slice

quota void-slot was 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 in spend_commit.ts with 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

  • Malformed run-index rows now fail closed instead of being silently skipped.
  • Void artifact names include an effect digest and JSONL rows use compact JSON.
  • Spend recovery now validates the persisted receipt, path, and exact index-row identity through the shared kernel.
  • Public void payload semantics and legacy import paths remain stable.

Migration receipt

  • Canonical owner: TypeScript operation quota.void.commit plus the closed accounting-artifact kernel.
  • Runtime crossings: execute, dry-run, replay, and repair each use one coarse request/response. The legacy two-step preview/record API uses one call per retained entry point.
  • Product code: +2,210/-898 LOC, net +1,312; tests/examples: +1,416/-3; build configuration: +3; docs excluded.
  • Bounded Python bridge: 263 diff LOC, including the 243-line transport/compatibility facade.
  • Facade exit: remove it when quota decision and the top-level CLI execute in-process TypeScript, all run-index writers use the native lock, and the legacy Python void API window closes.

Validation

  • pytest -q -n 2 --ignore=build: 5,085 passed, 12 skipped.
  • TypeScript control-plane suite: 407 passed, 1 skipped; strict TypeScript typecheck passed.
  • Ruff passed across the CI-owned paths; configured mypy passed all 15 kernel sources.
  • Quota-plan smoke and control-plane maintainability ratchet passed (compatibility_facade=2, no regressions).
  • Exact-head risk-based LoopX premerge: 4/4 direct checks and 18/18 selected canaries passed, with 0 failures, warnings, advisories, or manual holds.
  • Exact-head wheel and sdist passed twine check, contained all three new runtime files, installed into separate fresh Python 3.13.7 environments, and passed the packaged semantic probe.
  • Public/private boundary scan and git diff --check passed.

Performance

  • Cold start p50/p95: 230.88/260.92 ms.
  • Warm typed ping p50/p95: 1.07/1.29 ms; warm void preview: 1.93/2.34 ms.
  • Durable facade commit p50/p95: 30.64/37.49 ms; exact-effect replay: 8.05/9.86 ms.
  • Full CLI, 64 interleaved pairs: p95 828.68 -> 856.49 ms, +27.81 ms (+3.36%), below the RFC's 5% material-regression gate.
  • Daemon RSS: 108.38 MiB idle, 109.80 MiB after 256 requests.

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.

@huangruiteng huangruiteng left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Findings

  • P2(非阻塞):Python compatibility facade 的两项既有行为发生了变化。 loopx/control_plane/quota/void_commit.py:200build_quota_slot_void_eventproject_record 路径把 source 原样交给 TypeScript literal decoder;迁移前会先 .strip(),因此 source=" heartbeat " 过去会规范化为 heartbeat,现在会被拒绝。另在 loopx/control_plane/quota/void_commit.py:221record_quota_slot_void_from_preview 明确接收的 render_markdown callback 被直接丢弃,迁移前则使用调用方 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 与迁移清单同步更新。

关键代码讲解

  1. commitQuotaAccountingArtifactTransaction 是这次迁移的核心边界。新事务先在 index mutation lock 内检查已有 receipt;同一 effect/request 会 replay 或修复缺失 artifact,不同 request 复用 effect 会 fail closed。没有 receipt 时先核对 index digest,再写 prepared receipt、确保三个 artifact 一致,最后把 receipt 标记 committed。这给崩溃窗口留下了可恢复的 durable intent。
  2. evaluateQuotaVoidCommitproject_record 与 preview/commit 分开:纯投影不携带 effect identity;真实 commit 则要求 effect identity 与 payload/result 对齐,避免 Python facade 接受错配结果。
  3. prepareArtifacts / findTargetSpend 在锁内重新基于当前 index 找目标并生成 record、index record 与 Markdown,避免 preview 与 commit 之间的陈旧状态直接落盘;目标不存在时返回结构化 not_found,不产生部分写入。
  4. commit_quota_slot_void 保留了迁移期 Python writer 使用的 index lock,并把当前 digest 作为 CAS 前置条件传入 TypeScript。这样 legacy Python writer 尚未全部迁完时,仍不会绕过现有互斥约束。
  5. 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.

@huangruiteng
huangruiteng merged commit cabf7ec into huangruiteng:main Sep 2, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants