Skip to content

refactor(quota): make TypeScript own prior-Turn closeout recovery - #4472

Merged
huangruiteng merged 1 commit into
mainfrom
codex/todo-update-transaction-cutover
Sep 16, 2026
Merged

huangruiteng merged 1 commit into
mainfrom
codex/todo-update-transaction-cutover

Conversation

@huangruiteng

@huangruiteng huangruiteng commented Sep 15, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Cut over the prior-host-Turn closeout transaction to TypeScript, following the
bounded order both RFCs already prescribe rather than inventing a new migration plan.

  • docs/architecture/rfcs/shared-goal-authority-state-provider-v0.md §11.3
    ("TypeScript-first burden-reduction order"): characterize, move one delivered
    ownership transaction
    at a time into an existing TS boundary, qualify against
    file/NoKV/real isolated PostgreSQL, and only then touch binding/migration/canary.
    The RFC explicitly says this order is not a licence for a big-framework rewrite.
  • docs/architecture/rfcs/typescript-control-plane-migration-v0.md §3.2 (payoff
    phase) and Stage 2B already name this exact transaction as the next one: prior-host-Turn
    recovery must move receipt selection, exact Todo lifecycle observation,
    settlement validation, and recovery/continuation selection together
    before the
    Python coordinator can retire, and must keep crowded-inventory, provider-failure,
    identity-conflict, and same-Turn no-spend recovery coverage.

This PR is that one transaction. The typed owner decides which prior must-attempt
Turn still needs a closeout, whether its settlement already validates, which
closeout is accepted, and what the recovery obligation is. Python keeps only
transport and public-payload projection.

Semantics changed and fixed

  1. Prior-Turn closeout selection is TS-owned, once. The receipt-selection rule
    is now the same rule the settlement readback uses, instead of a second copy.
  2. Unaddressable declared binding fails closed. A heartbeat receipt that
    declares a Todo binding the settlement authority cannot address now fails the
    read. Previously it minted a recovery obligation for an identity no other
    reader can reproduce.
  3. A declared settlement effect id is no longer silently replaced. A receipt
    naming a settlement effect without a Todo or autonomous-replan binding keeps
    failing closed, and its declared settlement_effect_id is preserved rather than
    overwritten by the derived one.
  4. Malformed identities are errors, not obligations. An unusable Todo id
    normalizes to a typed error instead of producing a phantom recovery obligation.
  5. Identity conflicts fail closed. Several receipts for one Turn that bind
    differently, or a settlement effect bound to two different identities, raise
    heartbeat_receipt_identity_conflict instead of picking one.
  6. Exact Todo lifecycle observation. Closeout acceptance compares lifecycle
    values literally (for example done with no trimming and no successor
    tolerance), and an open lifecycle only closes out when it carries a resume
    condition and a successor.
  7. Monitor repair lane comes from typed state. The CLI renderer reads the typed
    repair field instead of re-deriving the lane from
    binding_task_class == "continuous_monitor", so the lane can no longer drift
    from the decision that produced the obligation.
  8. Bounded facts cross the bridge. The preflight reads the goal rollout log
    from the runtime root and sends only the named provider facts. A real goal's
    busiest Agent persists several MiB of receipt facts, which exceeds the runtime
    bridge payload bound, so a "send the whole log" design was not viable.

Duplication deleted

Duplicate knowledge Before After
Heartbeat receipt identity rule One TS copy in quota/settlement_readback.ts and one Python copy in quota/heartbeat_receipt.py (_receipt_settlement_identity, _effective_heartbeat_receipt, prior_closeout_required_heartbeat_receipts) Single owner quota/heartbeat_receipt_identity.ts; settlement readback and the Python coordinator both call it
Goal rollout log path + receipt read Three places: an inline join(goalRoot, …) read in settlement readback, receiptLogPath + an inline loop in scheduler/heartbeat_followup.ts, and a Python load_rollout_events(rollout_event_log_path(...)) Single owner rollout_receipt_log.ts (goalRolloutEventLogPath, readGoalHeartbeatReceipts); Python keeps only the writer
Closeout policy Python held the accepted-closeout list, missing-receipt computation, binding-kind derivation, and the recovery payload text, while the CLI renderer re-inferred the repair lane from task class TS owns policy and obligation; Python is transport + projection and the renderer reads typed state

Known but deliberately not in this PR (bounded follow-up, tracked as a
coordination-result retirement todo): the remaining Python/TS knowledge pairs under
todos/ (completion policy, decision scope, resume planning, quota selection,
standing decision, completion fence, frontier revision, completion transaction),
work_items/ interaction contracts, the coordination shadow modules, and the
no-production-caller coordination/file_provider.py, head.py, executor.py,
goal_state_shadow.py.

Migration economics receipt

Field Evidence
Canonical owner Before: Python quota/unsettled_host_turn.py owned closeout policy and recovery payload. After: TS quota/unsettled_host_turn_recovery.ts owns both; Python retains transport and projection only. No dual authority: Python has no branch that can produce a decision the TS reducer did not return.
Legacy semantic code deleted 128 lines of closeout policy, receipt selection, and recovery payload out of quota/unsettled_host_turn.py; 45 lines (prior_closeout_required_heartbeat_receipts) out of quota/heartbeat_receipt.py; 82 lines of duplicated identity rule and local log-path handling out of quota/settlement_readback.ts; 51 lines of duplicated log read and path building out of scheduler/heartbeat_followup.ts.
Bridge code added 346 product lines in quota/unsettled_host_turn.py (48 preflight transport, 76 reduce transport, 97 projection, remainder module wiring), plus 182 lines for the shared identity owner and 99 lines for the shared rollout-log owner. The two TS owners are not transport: they are the single copy of rules that already had two copies.
Cross-runtime calls Before, on the busiest real Agent: 6085 identity request/response round trips per recovery check, one per receipt fact. After: 2 — one fail-closed preflight that reads the persisted guards plus the settlement readback itself, then one final reduction over the checkpointed bound facts. This matches the payoff-phase bound of one preflight plus one final reduction while a real Python provider remains.
Product-code net change Product (excluding tests, fixtures, generated files, docs): +1028 added, -308 deleted, net +720. Tests: +758 added, 0 deleted. The net increase is the bridge plus the two shared TS rule owners, and it is bounded to this transaction.
Migration scaffolding No new characterization harness is retained. The TS suite carries the parity corpus directly (tests/control_plane_ts/unsettled_host_turn_recovery.test.ts, 20 cases) and the CLI suite carries the end-to-end path.
Facade exit quota/unsettled_host_turn.py is a facade over the typed transaction, not a second authority. It is deleted once the quota should-run main decision moves into TS, at which point the preflight/reduce transport and the Python HeartbeatReceiptIdentityConflictError mapping have no remaining caller.
Correctness and performance Preflight is newest-first and treats append order as the ordering fact; it ignores the current Turn, other goals, other Agents, and non-receipt event kinds; malformed log lines fail; a single Turn with several receipts recovers once; missing bound facts fail; the accepted-closeout vocabulary is validated; non-monitor closeouts are not consumed by monitor lanes; autonomous replan obligations carry no Todo. Performance: the preflight replaced 6085 bridge round trips with 2, and avoids a payload oversized for the runtime bridge bound.

Validation

  • Tested revision: e6b6376 (rebased onto main @ 8c55247)
  • Run state: finished
  • Input classes: synthetic, public_fixture, authorized_private_read_only
Check kind Result Public-safe evidence / limitation
static passed uv run --extra test ruff check loopx/control_plane tests/control_plane — clean; npm run typecheck:control-plane — clean; mypy adds no new error to the touched files (the one finding in quota/unsettled_host_turn.py is byte-identical on main)
unit passed npm run test:control-plane — 1656 pass / 0 fail / 3 skipped (1659)
integration passed tests/control_plane/test_quota_settlement_cli.py — 65 passed, exercising the real CLI entrypoint through the typed preflight/reduce transaction, including a prior Turn with several receipts recovering exactly once
unit passed tests/control_plane/test_effect_turn_live_quota_decision.py + test_heartbeat_receipt.py — 20 passed, including the identity-conflict fail-closed case
regression_parity passed Replayed the pre-change Python closeout rule against the new typed preflight over an owner-authorized read-only production goal's persisted receipt inventory (all Agents with receipts, including closeout_required turns): zero mismatches. The same baseline reports "settlement already validated" for the two lanes that carry real closeout traffic, and the typed owner agrees.
real_backend passed The preflight reads a real persisted goal rollout log through the file authority provider, not an in-memory substitute: this produced the payload-bound evidence above (a real goal's busiest Agent exceeds the bridge payload bound at ~3.25 MiB of receipt facts). The read is read-only against durable state; no goal, registry, fence, Todo, or lease was promoted, rewritten, or corrupted.
real_backend blocked PostgreSQL integration for this transaction: the cutover moves a closeout decision, not the PostgreSQL authority store, so no isolated server was run locally for this diff. The PR's CI postgresql-authority (real server) job covers the shared surfaces against a real server.
manual not_run Frontend/visual evidence is not applicable: no user-visible surface changed.

loopx canary premerge --from-git-diff: status: passed, merge_gate_passed: true,
self_merge_allowed: true, manual_holds: 0, changed files 13, surfaces
control_plane, python; 10/10 catalog canaries and 8/8 risk-profile smokes
executed with zero failures. One catalog entry is reported as
advisory_inherited_failure:
examples/control_plane/control-plane-maintainability-ratchet-smoke.py fails on
main too with the identical finding (module_metric_budget:loopx/chat_runtime.py,
1560 lines against a 1502 ceiling), on a file this PR does not touch, so it is
recorded here and not treated as this diff's regression.

  • Coverage and gaps: the changed authority surface is the closeout decision, and it is covered by the typed unit corpus (20 cases: ordering, opt-in closeout, cross-goal/cross-Agent rejection, identity conflicts, malformed lines, missing bound facts, single-Turn multi-receipt reduction, exact monitor-poll acceptance, typed lifecycle for open/done/blocked/deferred including whitespace negative cases, autonomous replan with no Todo, candidate re-validation) plus the real CLI integration path and the parity replay on a real persisted inventory. The deliberate gap is PostgreSQL, which this transaction does not touch.
  • Future-facing refactor pass: applied inside this PR — the receipt identity rule and the rollout-log read each had two/three copies and now have single owners.
  • Rebase notes: (1) the regenerated loopx/semantics/inventory_v0.json from the earlier revision is dropped, because main now treats that report as a local, uncommitted artifact and --check requires an explicit --output; (2) at the one conflict site both sides had added a test, so both are kept — this PR's multi-receipt recovery case and main's #4542 recovery-binding case; (3) #4542's change lives in cli_commands/quota.py, which this PR does not touch.
  • Public/private boundary: no private state, raw logs, credentials, local paths, or benchmark evidence.
  • Frontend / Visual Evidence: Before: N/A. After: N/A.

@huangruiteng

Copy link
Copy Markdown
Collaborator Author

Validation comment — migration economics receipt

Re-validated on the current base after rebasing onto the release-1.0.5 head
(commit d988ce9).

Field Evidence
Canonical owner Before: Python quota/unsettled_host_turn.py owned closeout policy and recovery payload. After: TS quota/unsettled_host_turn_recovery.ts owns both; Python is transport + projection, with no branch that can produce a decision the TS reducer did not return.
Legacy semantic code deleted 128 lines out of quota/unsettled_host_turn.py (closeout policy, receipt selection, recovery payload); 45 out of quota/heartbeat_receipt.py; 82 out of quota/settlement_readback.ts; 51 out of scheduler/heartbeat_followup.ts.
Bridge code added 346 lines in quota/unsettled_host_turn.py (48 preflight transport, 76 reduce transport, 97 projection, remainder wiring). The two TS owners added (182 identity, 99 rollout-log) are rule owners, not transport.
Cross-runtime calls 6085 -> 2 on the busiest real Agent's recovery check: one fail-closed preflight, then one final reduction. Within the payoff-phase bound while a Python provider remains.
Product-code net change +1028 / -308, net +720 (product only). Tests +758 / -0.
Migration scaffolding None retained beyond the durable test contracts; no characterization harness kept alive.
Facade exit quota/unsettled_host_turn.py deletes once the quota should-run main decision moves to TS; then the preflight/reduce transport and the Python identity-conflict error mapping have no caller.
Correctness and performance Newest-first ordering by append order; ignores current Turn / other goals / other Agents / non-receipt kinds; malformed lines fail; one Turn with several receipts recovers once; missing bound facts fail; accepted-closeout vocabulary validated; typed lifecycle literal comparison; autonomous replan carries no Todo. Replaced 6085 bridge round trips with 2 and stays under the runtime payload bound.

Check matrix

Check kind Result Evidence / limitation
static passed npm run typecheck:control-plane, ruff check tests loopx/control_plane, mypy
unit passed npm run test:control-plane — 1649 pass / 0 fail / 3 skipped
integration passed tests/control_plane/test_quota_settlement_cli.py — 63 passed through the real CLI entrypoint
unit passed test_effect_turn_live_quota_decision.py, test_heartbeat_receipt.py — 20 passed
regression_parity passed Pre-change Python closeout rule replayed against the new typed preflight over an owner-authorized read-only persisted receipt inventory: zero mismatches
real_backend passed Preflight reads a real persisted goal rollout log via the file authority provider (read-only; no goal, registry, fence, Todo, or lease was promoted or rewritten)
real_backend blocked PostgreSQL not run: this transaction moves a closeout decision, not the PostgreSQL authority store; the store gate belongs to the store-refactor PRs
manual not_run No user-visible surface changed

Coverage and gaps: the changed authority surface (the closeout decision) is covered
by 20 typed unit cases plus the real CLI integration path and the parity replay on a
real persisted inventory. The deliberate gap is PostgreSQL, which this diff does not
touch. The future-facing refactor pass was applied inside this PR: the receipt
identity rule and the rollout-log read each had two/three copies and are now single
owners. loopx canary premerge --from-git-diff is run separately and its result is
recorded here.

@huangruiteng
huangruiteng force-pushed the codex/todo-update-transaction-cutover branch from d988ce9 to 2403e45 Compare September 15, 2026 19:31
@huangruiteng

Copy link
Copy Markdown
Collaborator Author

Premerge canary result (commit 2403e45)

loopx canary premerge --from-git-diff on the rebased head:

  • status: passed, ok: true, merge_gate_passed: true, manual_holds: 0
  • changed files: 14, surfaces: control_plane, python
  • risk profiles: core-control-plane, canary-runner
  • all 10 catalog canaries and all 8 risk-profile smokes green, plus diff hygiene and changed-Python compile checks

One real finding from the gate, now fixed

The first canary pass failed examples/semantic-vocabulary-drift-smoke.py: the new
typed schema names and the accepted-closeout set were not in
loopx/semantics/inventory_v0.json, so the inventory was stale. It is regenerated
(scripts/generate_semantic_inventory.py, +11/-4) and committed in this PR rather
than suppressed. That fingerprint is useful forensics for this cutover: the new
transaction contributes one typescript_const_arrays entry and 13
named_string_constants, and turns 6 single-runtime symbols into cross-runtime
twins — the shared-rule direction the authority RFC asks for.

Two earlier canary failures (bounded-context-namespace-smoke,
catalog-run-e2e-smoke) were environmental, not code: the environment's editable
loopx install points at a different working tree, so the smokes imported that
tree instead of this branch. Pinning PYTHONPATH to this worktree makes them pass,
and the rerun above is the pinned one.

Environment disclosure

The canary executes already-committed repository smokes; no benchmark job was
launched, no scoring or runner behavior changed, and no raw log, private state, or
local path is included here.

@huangruiteng

Copy link
Copy Markdown
Collaborator Author

CI result (2403e45)

All required checks pass on this head:

  • Sign-off, changes, checks, merge-gate, pytest, dependency-review
  • kernel-static-checks, node-minimum-compatibility, node-forward-compatibility, windows-powershell
  • test-shard (1..4), stage2c (installed / e2e 1 / e2e 2 / mutants), stage2c-correctness-e2e, dashboard-acceptance
  • postgresql-authority (real server) — relevant context: this diff does not change the PostgreSQL authority store, and the store's real-server suite still passes on top of it
  • presentation / publish-pypi / upload-release skipped by design; SonarCloud is non-blocking

Failures: none. Manual holds: none. Merged state is left to reviewer decision — this is a runtime semantic cutover, not a docs/cleanup batch.

@huangruiteng huangruiteng left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Request changes conclusion (author-owned PR; GitHub blocks formal self-review)

动机

这个 PR 解决的是"同一套 prior-host-Turn closeout 策略在两个语言里各有一份权威"的问题。改动前,loopx/control_plane/quota/heartbeat_receipt.py 的 prior_closeout_required_heartbeat_receipts 和 unsettled_host_turn.py 里的判定一起决定"上一个 Turn 是否还欠一次 host closeout、哪些 kind 算已经结账",而 TypeScript 侧的 settlement readback 又自己读同一份 rollout-event log、并另外保留了一份 receipt identity 与路径解析的拷贝。后果有两层:一是新增一种可接受的 closeout 或改一条 identity 规则时,必须两处同时改,漏改就会让"当前该做什么"在两个命令之间分叉;二是 Python 那份是权威,但真正能执行这条规则的 effect runtime 是 TypeScript 那一侧。

改动思路

把决策整体搬到 typed owner,Python 退化成 transport。放置依据是我在 loopx/control_plane/quota/ 里核对的既有边界:closeout 与结算类规则由这个目录拥有;rollout-event log 的路径与读取规则归 rollout_receipt_log.ts;receipt identity 规则归 heartbeat_receipt_identity.ts,并且被 settlement readback 复用——这一步是去重,不是新增第二份权威。

值得肯定的是,这一版没有把 Python 侧保留成"另一半决策":unsettled_host_turn.py 只负责编码请求、调用 effect runtime、校验返回形状;策略字符串在 Python 侧没有留下副本(我按 accepted_closeouts 做了全仓扫描,Python 侧 0 命中)。同样,unsettled_host_turn_contract.py 从原先的 binding_task_class 改成消费 typed 的 repair,把"渲染命令"和"判定事实"分开,这个方向是对的。

具体改动

  • 新增 loopx/control_plane/quota/unsettled_host_turn_recovery.ts(505 行):ACCEPTED_CLOSEOUTS(:57)给出唯一的已结账词表,reduceUnsettledHostTurnRecovery(:424)做判定,preflightPriorHostTurnCloseout(:201)负责读取前一步的 receipt 事实。
  • 新增 loopx/control_plane/quota/heartbeat_receipt_identity.ts(182 行):selectEffectiveHeartbeatReceipt(:163)、heartbeatReceiptFactFromEvent(:82)、heartbeatReceiptBinding(:106)把 receipt 的 identity 归一化收成一份;settlement_readback.ts:295 原来的本地实现被替换为调用它。
  • 新增 loopx/control_plane/rollout_receipt_log.ts(99 行):goalRolloutEventLogPath(:39)与 readGoalHeartbeatReceipts(:65)统一路径规则与"畸形行跳过、不制造也不抹掉 receipt"的读取容忍度;scheduler/heartbeat_followup.ts 的本地路径/读取实现被删除。
  • Python 侧:unsettled_host_turn.py 的 apply_unsettled_host_turn_recovery_if_required(:250)保留为入口,heartbeat_receipt.py 删除 prior_closeout_required_heartbeat_receipts(-45 行),unsettled_host_turn_contract.py 改按 typed repair 渲染。
  • effect_runtime_handlers.ts:470 注册 quota.prior_host_turn_closeout.preflight 与 quota.unsettled_host_turn_recovery.reduce;tsconfig.control-plane.json 登记三个新模块;测试为 tests/control_plane_ts/unsettled_host_turn_recovery.test.ts(20 例)加 test_quota_settlement_cli.py、test_effect_turn_live_quota_decision.py 的新用例。

关键代码讲解

  1. unsettled_host_turn_recovery.ts:57 ACCEPTED_CLOSEOUTS:四元 as const 元组就是"什么算已结账"的全部词表,AcceptedCloseout 类型由它派生,因此词表与类型不可能分叉。不在这个元组里的 kind 永远不会被当成已结账,方向是保守的。
  2. heartbeat_receipt_identity.ts:163 selectEffectiveHeartbeatReceipt:一个 goal/agent/Turn 下允许多条 receipt,这里规定"最新的带 binding 的那条胜出;同一个 Turn 上出现两个互相冲突的 binding 则报错,而不是挑一条"。这条规则原先在 settlement_readback.ts 里有一份,现在两处消费者共用。
  3. unsettled_host_turn_recovery.ts:424 reduceUnsettledHostTurnRecovery:真正的决策点,输入是 preflight 后的 receipt 事实与候选 Turn 的 binding、Todo/replan 事实,输出 recovery_required 与恢复 packet;没有 binding 事实时拒绝判定,而不是猜一个 binding。
  4. rollout_receipt_log.ts:39 goalRolloutEventLogPath:把日志位置和"goal_id 必须是单一路径段、解析结果必须留在 runtime_root 内"的约束收成一处,三个读者(preflight、readback、scheduler follow-up)不再各写一套。
  5. unsettled_host_turn.py:250 apply_unsettled_host_turn_recovery_if_required:transport 的边界很清楚——形状对不上就 RuntimeError("TypeScript recovery result shape mismatch"),Python 不会退回自己判定,这保住了"单一权威"的承诺。

对主干的风险

P1(阻塞,必须先修):这个 head 已经不能合入 main。 我在 2403e45ae42c84b66974d99e36071f721e59a2f4 上跑 git merge-tree --write-tree HEAD origin/main,结果是两处冲突:

  • CONFLICT (modify/delete): loopx/semantics/inventory_v0.json——main 在 #4494 已经退役这份提交进仓库的清单(现在 .gitignore 忽略它,改为 scripts/generate_semantic_inventory.py 按需生成),而本 PR 仍在往里加条目;
  • CONFLICT (content): tests/control_plane/test_quota_settlement_cli.py。

需要的不只是 rebase:inventory 这两个改动在 rebase 之后没有落脚点。按 main 现在的语义治理模型(新的 carrier 自动发现、只有注册过的词表需要 owner/registry 同步),新符号应当走按需生成器加 examples/semantic-vocabulary-drift-smoke.py 这条路径重新安放,而不是把已退役的快照改回来。另外 rebase 后要对 main 版本的 test_quota_settlement_cli.py 重跑本 PR 新增的用例。

证据缺口(不是本 PR 的回归)。 test_quota_settlement_cli.py 的两个 sqlite 参数化用例在本 head 与本 PR 的 merge base c3cee4064 上都同样失败,报的是 local_authority_provider.ts:257 的资格校验:本机 PATH 上的 Node 是 25.5.0 / SQLite 3.51.2,不是仓库要求的 Node 22.22.3 / SQLite 3.51.3 资格组合(/opt/homebrew/opt/node@22/bin/node 是 22.22.0 / 3.51.2,同样不达标)。所以这两例是环境缺口,我在两边都复现了相同输入,不能算成本 PR 引入的红灯。

P3(不阻塞)。 合并日志路径守卫时,错误码从 invalid_scheduler_receipt_path 改成了 invalid_rollout_event_log_path(rollout_receipt_log.ts:39)。这个码没有任何测试或调用方引用,且该分支几乎不可达(goal_id 在前面已按单一路径段校验过),但它确实是一处静默改掉的机器可读契约,建议在 PR 描述里写一句,或给 scheduler 调用点保留旧码。

我的整体评价

方向我赞成,实现也基本干净:这次是真的把权威收到 typed owner 上了,顺手把三处日志读取和一处 identity 拷贝合并掉,Python 只留 transport 和一个形状守卫。本轮结论是 REQUEST_CHANGES,唯一的阻塞项是 P1 的合入冲突:head 2403e45ae42c84b66974d99e36071f721e59a2f4 落后 main 69 个提交,且它修改的 loopx/semantics/inventory_v0.json 已被 main 退役——rebase 后请按按需生成清单的流程重新安放这两个新符号,并在 main 版本的结算 CLI 测试模块上重跑新增用例。除此之外没有需要改设计的地方;我正在跑的证据是:node --experimental-strip-types --test tests/control_plane_ts/unsettled_host_turn_recovery.test.ts 20 passed / 0 failed,相邻四个 TS 套件 44 passed,tsc -p tsconfig.control-plane.json --noEmit 干净,pytest tests/control_plane/test_effect_turn_live_quota_decision.py tests/architecture/test_semantic_inventory.py 26 passed,pytest tests/control_plane/test_quota_settlement_cli.py 87 passed / 2 failed(即上文那两例环境缺口)。

English verdict: REQUEST_CHANGES — the relocation of prior-host-Turn closeout policy from Python to the typed TypeScript owner at head 2403e45 is the right ownership fix and the dedup of the three rollout-log readers plus the receipt identity rule is real, with 20 new TypeScript cases and the neighbouring suites green. It cannot merge as it stands: git merge-tree against origin/main reports a modify/delete conflict on loopx/semantics/inventory_v0.json — a snapshot main retired in #4494 in favour of on-demand generation — plus a content conflict in tests/control_plane/test_quota_settlement_cli.py. Rebase, re-home those inventory entries through the converged semantic-vocabulary flow, and re-run the new settlement-CLI cases on main's version of that module. Two secondary notes: the two sqlite-parameterized cases fail here for an environment reason (local Node 25.5.0 / SQLite 3.51.2 is below the qualified profile) and I reproduced the identical failure at merge base c3cee40, and the consolidated path guard silently renamed the internal error code invalid_scheduler_receipt_path to invalid_rollout_event_log_path.

Move the prior-host-Turn closeout transaction behind one TypeScript
request/response boundary so the Python coordinator keeps only transport
and public-payload projection.

The typed owner now decides which prior must-attempt Turn still needs a
closeout, whether its settlement already validates, which closeout is
accepted, and what the recovery obligation is.  Python reads exactly the
two provider facts the preflight names - the bound Todo and the committed
monitor-poll receipt - and projects the typed verdict.

Two requests, not one per receipt: a fail-closed preflight that reads the
persisted guards and the settlement readback itself, then one final
reduction over the checkpointed bound facts.  The preflight reads the
rollout log from runtime_root because a real goal persists 3.25 MiB of
receipt facts for its busiest Agent, and the runtime bridge rejects any
request over its 2 MiB bound.

Semantics tightened while sharing the rule with the settlement readback:

- the heartbeat receipt identity rule has one owner instead of one
  TypeScript and one Python copy;
- a receipt that declares a Todo binding the settlement authority cannot
  address now fails the read instead of minting a recovery obligation for
  an identity no other reader can reproduce;
- a receipt that names a settlement effect without a Todo or autonomous
  replan binding keeps failing closed, and the declared effect id is no
  longer silently replaced by the derived one.

Python deletion: the closeout policy, the receipt-selection rule, the
identity rule, and the settlement-readback transport for this path.

Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
@huangruiteng
huangruiteng force-pushed the codex/todo-update-transaction-cutover branch from 2403e45 to e6b6376 Compare September 16, 2026 13:19
@huangruiteng

Copy link
Copy Markdown
Collaborator Author

Self-merge validation (e6b6376, rebased onto main @ 8c55247)

Owner-authorized self-merge for this transaction cutover.

Changed surfaces: control_plane (prior-Turn closeout decision, shared receipt identity, shared rollout-log read) and python (the coordinator facade, the CLI projection input, tests). Files: 13.

Checks run:

  • loopx canary premerge --from-git-diff: status: passed, merge_gate_passed: true, self_merge_allowed: true, manual_holds: 0; 10/10 catalog canaries and 8/8 risk-profile smokes executed with zero failures
  • npm run test:control-plane: 1656 pass / 0 fail / 3 skipped
  • tests/control_plane/test_quota_settlement_cli.py: 65 pass (real CLI end to end)
  • test_effect_turn_live_quota_decision.py + test_heartbeat_receipt.py: 20 pass
  • ruff clean, tsc --noEmit clean, mypy adds no new finding to the touched files

Failures and skips, named:

  • One catalog entry is an advisory_inherited_failure: examples/control_plane/control-plane-maintainability-ratchet-smoke.py fails with module_metric_budget:loopx/chat_runtime.py (1560 lines against a 1502 ceiling). That file is byte-identical to main and main fails the same smoke with the same finding, so it is not this diff's regression.
  • kernel-static-checks is red on main for the same reason (a repo-wide mypy baseline), so the aggregate checks / pytest / merge-gate jobs inherit it. Those are not caused by this diff, which adds no mypy finding.
  • presentation, publish-pypi, upload-release skip by design; the visual/manual gate is not applicable because no user-visible surface changed.
  • PostgreSQL is untouched by this transaction, so the real-server gate is left to CI rather than claimed here.

Why the coverage is enough: the changed authority surface is the closeout decision itself, covered by the typed unit corpus (20 cases), the real CLI integration path (65 cases), and a parity replay of the pre-change Python rule against the typed preflight over an owner-authorized read-only production goal's persisted receipts (zero mismatches). The bridge bound is evidenced rather than assumed: the preflight reads the rollout log in-runtime because a real goal's busiest Agent exceeds the runtime payload bound, and the change takes that path from 6085 cross-runtime round trips to 2.

Rebase notes: the regenerated semantic inventory is dropped (main made it a local, uncommitted report), and both added tests at the one conflict site are kept.

@huangruiteng

Copy link
Copy Markdown
Collaborator Author

CI result (e6b6376) — and why the four failing shards are inherited

Green: Sign-off, build, changes, dependency-review, checks, kernel-static-checks, node-minimum-compatibility, node-forward-compatibility, windows-powershell, dashboard-acceptance, stage2c (installed / e2e 1 / e2e 2 / mutants), stage2c-correctness-e2e, and postgresql-authority (real server). presentation / publish-pypi / upload-release / sonar skip by design.

test-shard (1..4) fail, and so do the aggregates that require them (pytest, merge-gate). The whole failure set is 10 tests:

  • tests/cli_commands/test_project_lifecycle_goal_channel.py — 9 cases (refresh-state postcondition, post-writeback sidecar replay, external-sink suppression, exception redaction, non-standard usage JSON constants)
  • tests/canary/test_maintainability_ratchet.py::test_current_repository_debt_is_reviewed_without_line_count_pins

They are inherited, not introduced here. Run on a pristine checkout of main @ 8c5524786, the same two files produce the same 10 failures (10 failed in 10.33s), and the ratchet names module_metric_budget:loopx/chat_runtime.py — a file that is byte-identical to main and untouched by this PR. This diff's own surfaces are green: the TS control-plane suite (1656 pass / 0 fail / 3 skipped), tests/control_plane/test_quota_settlement_cli.py (65 pass), and the focused quota/receipt tests (20 pass). No test that this PR touches fails in CI.

So the merge uses the maintainer bypass on a main-level baseline failure, recorded here rather than papered over. Fixing those goal-channel and ratchet failures belongs to their own owners; they are not part of this transaction.

@huangruiteng
huangruiteng merged commit f2873c7 into main Sep 16, 2026
20 of 26 checks passed
@huangruiteng
huangruiteng deleted the codex/todo-update-transaction-cutover branch September 16, 2026 13:44
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.

1 participant