From 28498ae9d6c99f9d3734886bb5386e91de579f6e Mon Sep 17 00:00:00 2001 From: huangruiteng <14976749+huangruiteng@users.noreply.github.com> Date: Sun, 13 Sep 2026 16:55:20 +0800 Subject: [PATCH] fix(quota): isolate recovery from current replan Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com> --- loopx/control_plane/quota/settlement_cli.py | 8 +++++ .../test_quota_settlement_cli.py | 30 ++++++++++++++++++- 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/loopx/control_plane/quota/settlement_cli.py b/loopx/control_plane/quota/settlement_cli.py index d9e19375d0..9bc0acc544 100644 --- a/loopx/control_plane/quota/settlement_cli.py +++ b/loopx/control_plane/quota/settlement_cli.py @@ -193,6 +193,14 @@ def quota_rollout_settlement_binding( packet is only a diagnostic fallback when no concrete Todo is selected. """ + if payload.get("effective_action") == "unsettled_host_turn_recovery": + # This Turn only repairs the preceding Turn's closeout. A concurrently + # projected Todo or autonomous replan belongs to the post-recovery + # decision and must not become this receipt's settlement identity. + # Keeping the recovery receipt identity-less lets a verified same-Turn + # reentry bind the next independent action without rewriting history. + return None, None + explicit_todo_id = normalize_todo_id(getattr(args, "todo_id", None)) explicit_replan_obligation_id = normalize_todo_replan_obligation_id( getattr(args, "replan_obligation_id", None) diff --git a/tests/control_plane/test_quota_settlement_cli.py b/tests/control_plane/test_quota_settlement_cli.py index 2ddd1828cd..457f1fe94f 100644 --- a/tests/control_plane/test_quota_settlement_cli.py +++ b/tests/control_plane/test_quota_settlement_cli.py @@ -1033,7 +1033,7 @@ def test_in_flight_progress_preserves_todo_across_heartbeat_settlements( } -def test_next_heartbeat_forces_recovery_for_unsettled_must_attempt_turn( +def test_recovery_does_not_bind_current_replan_and_reenters_same_turn( tmp_path: Path, ) -> None: project, runtime, registry_path = _write_fixture(tmp_path) @@ -1061,6 +1061,21 @@ def test_next_heartbeat_forces_recovery_for_unsettled_must_attempt_turn( assert prior_rc == 0, prior assert prior["heartbeat_receipt"]["closeout_required"] is True + state_path = project / f".codex/goals/{GOAL_ID}/ACTIVE_GOAL_STATE.md" + state_before_replan = state_path.read_text(encoding="utf-8") + replan_backlog = "\n".join( + f"- [ ] [P1] Review recovery breadth {index}.\n" + " " + for index in range(15) + ) + state_path.write_text( + state_before_replan.rstrip() + "\n\n" + replan_backlog + "\n", + encoding="utf-8", + ) + recovery_rc, recovery = _run_cli( registry_path, runtime, @@ -1077,6 +1092,7 @@ def test_next_heartbeat_forces_recovery_for_unsettled_must_attempt_turn( ) assert recovery_rc == 0, recovery assert recovery["effective_action"] == "unsettled_host_turn_recovery" + replan_obligation_id = recovery["replan_action_packet"]["obligation_id"] packet = recovery["unsettled_host_turn_recovery"] assert packet["prior_turn_instance_id"] == prior_turn_id assert packet["binding_id"] == TODO_ID @@ -1101,6 +1117,14 @@ def test_next_heartbeat_forces_recovery_for_unsettled_must_attempt_turn( "next_cli_actions" ][1] assert "settlement_identity" not in recovery["heartbeat_receipt"] + assert recovery["heartbeat_receipt"]["semantic_replan_obligation_id"] == ( + replan_obligation_id + ) + + # Model the separately verified replan settlement without touching the + # append-only heartbeat receipt. The same recovery Turn must then be able + # to bind an independent successor instead of preserving an obsolete replan. + state_path.write_text(state_before_replan, encoding="utf-8") wait_rc, wait = _run_cli( registry_path, @@ -1145,8 +1169,12 @@ def test_next_heartbeat_forces_recovery_for_unsettled_must_attempt_turn( ) assert resumed_rc == 0, resumed assert resumed["effective_action"] != "unsettled_host_turn_recovery" + assert resumed.get("autonomous_replan_obligation") is None assert resumed["selected_todo"]["todo_id"] == ALTERNATIVE_TODO_ID assert resumed["heartbeat_receipt"]["status"] == "upgraded" + assert resumed["heartbeat_receipt"]["settlement_identity"]["todo_id"] == ( + ALTERNATIVE_TODO_ID + ) def test_standard_codex_app_settlement_is_receipted_and_idempotent(