From 2929e87f2fb90185e2e76cf598d6634718345ef0 Mon Sep 17 00:00:00 2001 From: huangruiteng <14976749+huangruiteng@users.noreply.github.com> Date: Mon, 21 Sep 2026 11:24:08 +0800 Subject: [PATCH] fix(refresh): admit checkpoint-only validator recovery Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com> --- loopx/state_refresh.py | 18 ++- .../test_refresh_checkpoint_recovery.py | 135 ++++++++++++++++++ 2 files changed, 149 insertions(+), 4 deletions(-) diff --git a/loopx/state_refresh.py b/loopx/state_refresh.py index 2ae3347349..19328183cc 100644 --- a/loopx/state_refresh.py +++ b/loopx/state_refresh.py @@ -883,6 +883,7 @@ def refresh_state_run( settlement_readback = None refresh_recovery = None prior_writeback_run = None + checkpoint_supplement = False if todo_id or normalized_replan_obligation_id or turn_instance_id: if not turn_scoped_settlement_qualified: raise ValueError( @@ -929,6 +930,9 @@ def refresh_state_run( if not refresh_recovery: raise RuntimeError("settlement readback omitted refresh recovery admission") prior_writeback_run = settlement_readback.writeback_run + checkpoint_supplement = bool( + refresh_recovery["decision"] == "supplement_checkpoint" + ) recovery_payload = refresh_recovery_payload( settlement_readback, registry_path=registry_path, runtime_root=runtime_root, goal_id=safe_goal_id, dry_run=dry_run, @@ -1122,7 +1126,16 @@ def refresh_state_run( effective_autonomous_replan_recorded = ( replan_qualification.autonomous_replan_recorded ) - if normalized_delivery_outcome in ACCOUNTABLE_DELIVERY_OUTCOMES: + # read_heartbeat_settlement admits checkpoint_supplement only for a + # checkpoint-only retry of an already committed writeback with the + # exact Goal/Agent/Todo/Turn and delivery identity. It rejects replayed + # mutations and conflicting vision decisions before this fence. The + # supplement repairs only the missing vision decision, so terminal + # validation remains attached to the original Todo completion. + if ( + normalized_delivery_outcome in ACCOUNTABLE_DELIVERY_OUTCOMES + and not checkpoint_supplement + ): require_accountable_completion_validation( state_text, todo_fields=todo_fields, @@ -1146,9 +1159,6 @@ def refresh_state_run( completion_todo_id=completion_todo_id, autonomous_replan_recorded=effective_autonomous_replan_recorded, ) - checkpoint_supplement = bool( - refresh_recovery and refresh_recovery["decision"] == "supplement_checkpoint" - ) if checkpoint_supplement and not vision_checkpoint.get("satisfied"): raise ValueError( "checkpoint supplement did not satisfy the missing decision; " diff --git a/tests/control_plane/test_refresh_checkpoint_recovery.py b/tests/control_plane/test_refresh_checkpoint_recovery.py index 39a4a44f88..094f02e355 100644 --- a/tests/control_plane/test_refresh_checkpoint_recovery.py +++ b/tests/control_plane/test_refresh_checkpoint_recovery.py @@ -15,8 +15,11 @@ AGENT_ID, REPO_ROOT, GOAL_ID, + SELECTED_REPLAN_TODO_ID, TODO_ID, TURN_ID, + _configure_selected_todo_replan_fixture, + _initialize_git_checkout, _run_cli, _write_fixture, _spend_run_count, @@ -292,6 +295,138 @@ def test_same_turn_checkpoint_supplement_is_idempotent(tmp_path: Path, decision: assert _spend_run_count(runtime) == 1 +def test_checkpoint_only_recovery_bypasses_open_todo_completion_validation( + tmp_path: Path, +) -> None: + project, runtime, registry = _write_fixture(tmp_path) + _configure_selected_todo_replan_fixture(project, registry) + _initialize_git_checkout(project) + state_path = project / f".codex/goals/{GOAL_ID}/ACTIVE_GOAL_STATE.md" + state_text = state_path.read_text(encoding="utf-8") + selected_marker = ( + f"todo_id={SELECTED_REPLAN_TODO_ID} status=open " + "task_class=advancement_task action_kind=validate " + f"claimed_by={AGENT_ID} -->" + ) + assert selected_marker in state_text + state_path.write_text( + state_text.replace( + selected_marker, + selected_marker.replace(" -->", " validation_command=pytest -->"), + 1, + ), + encoding="utf-8", + ) + turn_id = "turn-checkpoint-open-validator" + binding = ( + "--agent-id", + AGENT_ID, + "--todo-id", + SELECTED_REPLAN_TODO_ID, + "--turn-instance-id", + turn_id, + ) + rc, guard = _run_cli( + registry, + runtime, + "quota", + "should-run", + "--codex-app", + "--goal-id", + GOAL_ID, + "--agent-id", + AGENT_ID, + "--turn-instance-id", + turn_id, + "--scan-path", + str(project), + cwd=project, + ) + assert rc == 0, guard + assert guard["decision"] == "autonomous_replan_required" + assert guard["selected_todo"]["todo_id"] == SELECTED_REPLAN_TODO_ID + + delivery = ( + "refresh-state", + "--goal-id", + GOAL_ID, + *binding, + "--classification", + "validated_progress", + "--delivery-batch-scale", + "implementation", + "--delivery-outcome", + "outcome_progress", + "--delivery-workspace-path", + str(project), + "--progress-result-class", + "advanced", + "--progress-surface-id", + "surface:checkpoint-recovery", + "--progress-probe-kind", + "probe:checkpoint-recovery", + "--progress-evidence-id", + "evidence:checkpoint-recovery", + "--no-global-sync", + "--suppress-external-sinks", + ) + mutations = ( + "--autonomous-replan-recorded", + "--repair-delta-kind", + "successor_or_supersede", + ) + rc, first = _run_cli(registry, runtime, *delivery, *mutations, cwd=project) + assert rc == 0, first + assert first["appended"] is True + assert first["vision_checkpoint"]["decision"] == "missing_required" + + vision = ( + "--vision-summary", + "Continue the accepted checkpoint recovery scope.", + "--vision-acceptance", + "The exact recovery preserves validation and identity fences.", + ) + rc, rejected = _run_cli( + registry, runtime, *delivery, *mutations, *vision, cwd=project + ) + assert rc == 1, rejected + assert ( + rejected["refresh_recovery"]["reason"] + == "checkpoint_supplement_must_not_repeat_mutations" + ) + + wrong_binding = list(delivery) + wrong_todo_index = wrong_binding.index(SELECTED_REPLAN_TODO_ID) + wrong_binding[wrong_todo_index] = "todo_chain_000000000001" + rc, wrong_identity = _run_cli( + registry, runtime, *wrong_binding, *vision, cwd=project + ) + assert rc == 1, wrong_identity + assert "settlement binding does not match" in wrong_identity["error"] + + rc, repaired = _run_cli(registry, runtime, *delivery, *vision, cwd=project) + assert rc == 0, repaired + assert repaired["appended"] is True + assert repaired["refresh_recovery"]["decision"] == "supplement_checkpoint" + assert repaired["vision_checkpoint"]["satisfied"] is True + assert repaired["settlement_identity"] == first["settlement_identity"] + + rc, conflict = _run_cli( + registry, + runtime, + *delivery, + "--vision-summary", + "Choose a conflicting recovery path.", + cwd=project, + ) + assert rc == 1, conflict + assert ( + conflict["refresh_recovery"]["reason"] + == "committed_vision_decision_conflict" + ) + assert _spend_run_count(runtime) == 0 + + def test_invalid_supplement_leaves_original_writeback_intact(tmp_path: Path): project, runtime, registry = _write_fixture(tmp_path) binding = (