From a079e3e4923d5a17e31cb2f8cafced8407cab24d Mon Sep 17 00:00:00 2001 From: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> Date: Thu, 12 Nov 2026 09:23:38 +0800 Subject: [PATCH] fix(replan): recover an open Vision review on its admitted receipt Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> --- .../goals/goal_frontier/__init__.py | 6 ++- .../work_items/replan_semantics.ts | 21 ++++++++- .../test_refresh_state_replan_gate.py | 43 +++++++++++++++++++ .../control_plane_ts/replan_semantics.test.ts | 22 ++++++++++ 4 files changed, 90 insertions(+), 2 deletions(-) diff --git a/loopx/control_plane/goals/goal_frontier/__init__.py b/loopx/control_plane/goals/goal_frontier/__init__.py index 1bbdaa3d76..8bf8ffd925 100644 --- a/loopx/control_plane/goals/goal_frontier/__init__.py +++ b/loopx/control_plane/goals/goal_frontier/__init__.py @@ -1699,7 +1699,11 @@ def build_goal_frontier_projection_context_from_status( agent_todo_items=agent_todo_source_items, stride_strategy=stride_strategy, ) - frontier_transition_candidate = {"obligation": source, "ack": source_ack} + frontier_transition_candidate = { + "obligation": source, + "ack": source_ack, + "reconstructed_vision_review": True, + } lifecycle_candidate = None if receipt_bound_replan_obligation_id and receipt_bound_guard_recorded_at: diff --git a/loopx/control_plane/work_items/replan_semantics.ts b/loopx/control_plane/work_items/replan_semantics.ts index bb92141fe3..742726875d 100644 --- a/loopx/control_plane/work_items/replan_semantics.ts +++ b/loopx/control_plane/work_items/replan_semantics.ts @@ -264,6 +264,7 @@ function preserveReceiptBoundObligation(request: JsonObject): JsonObject { throw new EffectRuntimeRequestError("receipt-bound replan facts are malformed"); } let original: JsonObject | null = null; + let openVisionReview: JsonObject | null = null; let delta: JsonObject | null = null; let historicalAckRecorded = false; for (const value of request.transition_candidates) { @@ -280,7 +281,19 @@ function preserveReceiptBoundObligation(request: JsonObject): JsonObject { historicalAckRecorded = true; break; } - if (obligation.obligation_id !== selected || !requiredSemanticOutcomes(obligation).includes("new_runnable_successor")) continue; + if (obligation.obligation_id !== selected) continue; + if (request.guard_scoped && candidate.reconstructed_vision_review === true && + obligation.schema_version === "autonomous_replan_obligation_v0" && obligation.required === true && + typeof request.agent_id === "string" && request.agent_id.trim() && + obligation.agent_id === request.agent_id && obligation.resolution_mode == null && + obligation.capability_guard == null) { + const kinds = triggerKinds(obligation); + if (kinds.some(kind => VISION_TRIGGERS.has(kind)) && + kinds.every(kind => VISION_TRIGGERS.has(kind) || kind === "goal_outcome_uncovered")) { + openVisionReview = obligation; + } + } + if (!requiredSemanticOutcomes(obligation).includes("new_runnable_successor")) continue; const result = projectTurnTransition({guard_scoped: request.guard_scoped, selected_obligation_id: selected, transition_candidates: [candidate], stride_strategy: request.stride_strategy, goal_id: request.goal_id, @@ -331,6 +344,12 @@ function preserveReceiptBoundObligation(request: JsonObject): JsonObject { triggerKinds(current).some(kind => ACCEPTANCE_HOLD_TRIGGERS.has(kind))) { return {obligation: {...current, obligation_id: selected, selection_binding: "heartbeat_receipt"}}; } + // Another review may own the live frontier while the canonical Vision + // gaps still reconstruct the admitted review. Keep its full checks and + // require a fresh qualifying outcome before settlement. + if (openVisionReview !== null) { + return {obligation: {...openVisionReview, selection_binding: "heartbeat_receipt"}}; + } // Work-link maintenance can remove this frontier-only review before its // writeback. The exact receipt retains its typed kind, never raw task prose // or an old decision context. Other duties keep their canonical owners. diff --git a/tests/control_plane/test_refresh_state_replan_gate.py b/tests/control_plane/test_refresh_state_replan_gate.py index 7401e55d1c..23d7c772cc 100644 --- a/tests/control_plane/test_refresh_state_replan_gate.py +++ b/tests/control_plane/test_refresh_state_replan_gate.py @@ -679,6 +679,49 @@ def test_rotated_vision_obligation_contains_all_three_acceptance_gaps() -> None: assert obligation["triggers"][2]["completed_todo_threshold"] == 5 +def test_admitted_vision_checkpoint_survives_a_new_periodic_review() -> None: + admission_runs = [_open_vision_after_prior_ack()] + arguments = {"state_text": STATE_TEXT, "agent_id": AGENT_ID, "goal_id": GOAL_ID} + admitted, _ = qualify_replan_writeback(newest_first_runs=admission_runs, **arguments) + assert admitted is not None + assert [trigger["kind"] for trigger in admitted["triggers"]] == [ + "vision_acceptance_gap", + "vision_outcome_checkpoint_required", + ] + + later_runs = [ + {**run, "generated_at": f"2026-08-13T12:{index:02d}:00+08:00"} + for index, run in enumerate(_durable_runs(AUTONOMOUS_REPLAN_PERIODIC_RUN_THRESHOLD)) + ][::-1] + current, _ = qualify_replan_writeback( + newest_first_runs=[*later_runs, *admission_runs], **arguments, + ) + assert current is not None + assert [trigger["kind"] for trigger in current["triggers"]] == ["periodic_review_due"] + assert current["obligation_id"] != admitted["obligation_id"] + + bound = { + **arguments, + "newest_first_runs": [*later_runs, *admission_runs], + "guard_scoped": True, + "guard_semantic_replan_obligation_id": admitted["obligation_id"], + "receipt_bound_guard_recorded_at": "2026-08-13T11:28:00+08:00", + "receipt_bound_guard_turn_instance_id": "vision-before-periodic", + } + with pytest.raises(ReplanWritebackRejected, match="typed semantic delta"): + enforce_open_replan_writeback(**bound) + delta = enforce_open_replan_writeback( + **bound, + agent_vision={ + "vision_patch": {"acceptance_summary": "An independent check supports continuing."}, + "path_delta": {"outcome": "continue", "evidence_refs": ["evidence:checkpoint-review"]}, + }, + ) + assert delta is not None + assert delta["obligation_id"] == admitted["obligation_id"] + assert delta["satisfying_outcomes"] == ["fresh_vision_path_outcome"] + + def test_refresh_state_run_rejects_maintenance_after_vision_obligation_rotation( tmp_path: Path, ) -> None: diff --git a/tests/control_plane_ts/replan_semantics.test.ts b/tests/control_plane_ts/replan_semantics.test.ts index aed96e547b..3707423a3d 100644 --- a/tests/control_plane_ts/replan_semantics.test.ts +++ b/tests/control_plane_ts/replan_semantics.test.ts @@ -153,6 +153,28 @@ test("verified original writeback projects only its outstanding debit even after current_obligation: null, transition_candidates: []}), /malformed/); }); +test("an open Vision reconstruction must come from the exact scoped receipt source", () => { + const id = "replan-1111111111111111"; + const original = {schema_version: "autonomous_replan_obligation_v0", required: true, + obligation_id: id, agent_id: "reviewer", triggers: [{kind: "vision_outcome_checkpoint_required"}]}; + const candidate = {obligation: original, ack: null, reconstructed_vision_review: true}; + const request = {operation: "receipt_bound_obligation", guard_scoped: true, + replay_phase: "open", selected_obligation_id: id, agent_id: "reviewer", + current_obligation: null, transition_candidates: [candidate]}; + assert.deepEqual(projectReplanSemantics(request).obligation, + {...original, selection_binding: "heartbeat_receipt"}); + for (const changes of [ + {guard_scoped: false}, {agent_id: "peer"}, {selected_obligation_id: "replan-2222222222222222"}, + {transition_candidates: [{...candidate, reconstructed_vision_review: false}]}, + {transition_candidates: [{...candidate, obligation: {...original, + triggers: [...original.triggers, {kind: "periodic_review_due"}]}}]}, + {transition_candidates: [{...candidate, obligation: {...original, + capability_guard: {schema_version: "semantic_replan_capability_guard_v0"}}}]}, + ]) { + assert.throws(() => projectReplanSemantics({...request, ...changes}), /identity conflicts/); + } +}); + test("only plain vision and outcome reviews have a scalar receipt source", () => { const source = {schema_version: "autonomous_replan_obligation_v0", required: true, obligation_id: "replan-1111111111111111", agent_id: "reviewer", trigger_count: 1,