diff --git a/tests/control_plane/codex_checkpoint_fixture.py b/tests/control_plane/codex_checkpoint_fixture.py index 9ae3c197c0..a83a984b36 100644 --- a/tests/control_plane/codex_checkpoint_fixture.py +++ b/tests/control_plane/codex_checkpoint_fixture.py @@ -128,6 +128,8 @@ def native_process_continuation(request, identity, *, code_mode=False): def reference_metadata_oracle(view): """Inspect complete metadata as an oracle, never as a model read receipt.""" + if view.get("schema_version") == "context_capture_selection_page_v1": + view = view["navigation"] if view.get("schema_version") == "context_capture_navigation_v1": from loopx.control_plane.turn_driver.decision_capture import read_context_read_reference @@ -147,6 +149,8 @@ def read_cli_policy_payload(stdout, *, returncode): ) payload = json.loads(stdout) + if returncode == 0 and payload.get("schema_version") == "context_capture_selection_page_v1": + payload = payload["navigation"] if returncode == 0 and payload.get("mode") in {"full", "delta"} and isinstance(payload.get("recovery"), dict): recovery = payload["recovery"] if "reference_file" in recovery: @@ -187,8 +191,13 @@ def follow(self, request): if continuation := native_process_continuation(request, f"{self.identity}_{self.calls + 1}"): self.calls += 1 return continuation - page = last_context_page(request, allow_reference=True) + page = last_context_page(request, allow_reference=True, allow_selection=True) assert page and page["ok"], "Native reader output must reach the model intact" + if page.get("schema_version") == "context_capture_selection_page_v1": + # The initial read carries a selected view and a separate navigation + # reference. Only complete delivery of the latter earns byte coverage. + page = page["navigation"] + assert page["ok"] and page["schema_version"] == "context_capture_navigation_v1" if page.get("schema_version") != "context_capture_page_v1": assert self.pending["descriptor"] is None and "reference" not in self.pending # This fixture voluntarily reads the complete saved input for its diff --git a/tests/control_plane/test_quota_acceptance_references.py b/tests/control_plane/test_quota_acceptance_references.py index 593de862d6..2a7592d8e1 100644 --- a/tests/control_plane/test_quota_acceptance_references.py +++ b/tests/control_plane/test_quota_acceptance_references.py @@ -273,3 +273,67 @@ def first_read(*args): assert outcome == expected["outcome_requirements"]["outcomes"][0] assert original["revision"] < outcome["revision"] assert run_reader(command)["outcome"] == original + + +@pytest.mark.parametrize("provider", ["file", "sqlite"]) +def test_inline_cli_delivers_complete_acceptance_for_selection_and_replan(tmp_path, monkeypatch, provider): + from test_replan_successor_guard_reentry import _fixture, AGENT, GOAL + monkeypatch.setenv("LOOPX_USAGE_PING", "0") + call, runtime, index = _fixture(tmp_path, monkeypatch, provider, future_monitor=False) + registry_path = tmp_path / "registry.json" + registry = json.loads(registry_path.read_text()) + registry["goals"][0]["execution_profile"] = {"planning_horizon_goal": True} + registry_path.write_text(json.dumps(registry)) + document = tmp_path / "requirements.json" + document.write_text(json.dumps({"outcome_id": "export", "owner_agent_id": AGENT, + "objective": "Deliver the complete export", "requirements": [ + {"id": "content", "description": "Preserve every original section."}, + {"id": "recovery", "description": "Keep the tail requirement and the publication stop."}], + "source": {"ref": "message:owner", "summary": "The owner requested a complete export."}})) + before = call("goal-acceptance", "inspect", "--goal-id", GOAL) + call("goal-acceptance", "refine", "--goal-id", GOAL, "--agent-id", AGENT, + "--document", str(document), "--expected-provider-revision", before["provider_revision"], + "--operation-id", "source-requirements", "--execute") + expected = call("goal-acceptance", "inspect", "--goal-id", GOAL)["goal_acceptance_contract"] + guard = call("quota", "should-run", "--codex-app", "--context-delivery", "off", "--goal-id", GOAL, "--agent-id", AGENT, + "--turn-instance-id", "turn-acceptance-sharing") + assert guard["effective_action"] == "autonomous_replan_required" + ref = "$.interaction_contract.agent_channel.work_context.sources[0].content.goal_acceptance_contract" + assert guard["autonomous_replan_obligation"]["replan_context"]["core_goal"]["acceptance_contract_ref"] == ref + assert guard["agent_todo_summary"]["goal_acceptance_contract_ref"] == ref + assert guard["interaction_contract"]["agent_channel"]["work_context"]["sources"][0]["content"]["goal_acceptance_contract"] == expected + assert build_turn_envelope(guard)["action_signature"]["matches"] is True + + # Normal selected delivery must use the exact inline acceptance read and + # relocate its reference when a host consumes the CLI as a TurnEnvelope. + index.write_text("") + added = call("todo", "add", "--goal-id", GOAL, "--role", "agent", "--claimed-by", AGENT, + "--text", "Validate the complete export", "--task-class", "advancement_task", + "--action-kind", "validate") + call("todo", "add", "--goal-id", GOAL, "--role", "agent", "--claimed-by", AGENT, + "--text", "Validate independent recovery", "--task-class", "advancement_task", + "--action-kind", "validate") + linked = json.loads(document.read_text()) + linked["todo_ids"] = [added["todo_id"]] + document.write_text(json.dumps(linked)) + revision = call("goal-acceptance", "inspect", "--goal-id", GOAL)["provider_revision"] + call("goal-acceptance", "refine", "--goal-id", GOAL, "--agent-id", AGENT, + "--document", str(document), "--expected-provider-revision", revision, + "--operation-id", "link-export", "--execute") + expected = call("goal-acceptance", "inspect", "--goal-id", GOAL)["goal_acceptance_contract"] + planning = call("quota", "should-run", "--codex-app", "--context-delivery", "off", "--goal-id", GOAL, "--agent-id", AGENT, + "--turn-instance-id", "turn-export-planning") + assert planning["planning_horizon"]["core_goal"]["acceptance_contract"] == expected + assert planning["agent_todo_summary"]["goal_acceptance_contract_ref"] == "$.planning_horizon.core_goal.acceptance_contract" + selected = call("quota", "should-run", "--codex-app", "--context-delivery", "off", "--goal-id", GOAL, "--agent-id", AGENT, + "--todo-id", added["todo_id"], "--turn-instance-id", "turn-export-delivery") + assert selected["normal_delivery_allowed"] is True + sources = selected["interaction_contract"]["agent_channel"]["work_context"]["sources"] + source_index = next(i for i, row in enumerate(sources) if row["source"] == "goal_acceptance") + retained = sources[source_index]["content"]["goal_acceptance_contract"] + assert retained == expected + assert selected["agent_todo_summary"]["goal_acceptance_contract_ref"] == ( + f"$.interaction_contract.agent_channel.work_context.sources[{source_index}].content.goal_acceptance_contract") + envelope = build_turn_envelope(selected) + assert envelope["work_context"]["sources"][source_index]["content"]["goal_acceptance_contract"] == expected + assert envelope["action_signature"]["matches"] is True diff --git a/tests/control_plane/test_replan_staged_read.py b/tests/control_plane/test_replan_staged_read.py index f8a2dd7eb1..05df95313d 100644 --- a/tests/control_plane/test_replan_staged_read.py +++ b/tests/control_plane/test_replan_staged_read.py @@ -262,7 +262,7 @@ def observe(command, **kwargs): projected = run(shlex.split(command), cwd=fixture["project"], env=env, capture_output=True, text=True, timeout=60) assert projected.returncode == 0, projected.stdout - presentation = json.loads(projected.stdout) + presentation = reference_metadata_oracle(json.loads(projected.stdout)) assert presentation["ok"] and presentation["mode"] == "full" assert reference_metadata_oracle(presentation)["recovery"]["path"] == str(view_path) assert read_cli_policy_payload(projected.stdout, returncode=projected.returncode) == view diff --git a/tests/control_plane/test_saved_context_project.py b/tests/control_plane/test_saved_context_project.py index c544fe6836..20b1ea99ad 100644 --- a/tests/control_plane/test_saved_context_project.py +++ b/tests/control_plane/test_saved_context_project.py @@ -530,6 +530,7 @@ def test_native_saved_preflight_uses_lifecycle_without_guard_replay(tmp_path, mo from loopx.control_plane.quota.turn_envelope import prepare_context_delivery, restore_context_delivery from tests.control_plane.codex_checkpoint_fixture import ( last_context_page, local_inference_provider, native_process_continuation, + selected_context_values, ) from tests.control_plane.test_codex_context_boundary import _app_server, _configure_provider, _native_fixture, _input_text from tests.control_plane.test_codex_context_capture import _delivered @@ -639,7 +640,6 @@ def response(request, number, message): initial_content[name] += page["content"].encode() assert len(initial_content[name]) == (page["total_bytes"] if page["eof"] else page["next_offset"]) if page["eof"]: - from tests.control_plane.codex_checkpoint_fixture import selected_context_values assert selected_context_values(initial_content[name].decode())["/goal_id"] == GOAL_ID reading_initial = False else: @@ -675,7 +675,10 @@ def response(request, number, message): assert "--initial-read" in argv if progressive: command = shlex.join([word for word in argv if word != "--initial-read"] + [ - "--select", "/goal_id", "--select", "/heartbeat_receipt"]) + "--select", "/goal_id", "--select", "/heartbeat_receipt", + "--select", "/interaction_contract/cli_channel/selection_command", + "--select", "/action_portfolio/suggested_actions/0/todo_id", + "--select", "/action_portfolio/suggested_actions/1/todo_id"]) command += " --max-page-output-bytes 32000" else: source, raw = sources[0 if phase == "cold" else 1] @@ -699,7 +702,7 @@ def response(request, number, message): navigation = page["navigation"] if progressive: assert page["eof"] - values = {row["pointer"]: row["value"] for row in json.loads(page["content"])["selections"]} + values = selected_context_values(page["content"]) assert values["/goal_id"] == GOAL_ID and values["/heartbeat_receipt"]["turn_instance_id"] == actual_turn descriptor = machine_reference_oracle(navigation) # A selectively read large capture cannot become a retained @@ -766,7 +769,7 @@ def response(request, number, message): page = last_context_page(request, allow_selection=progressive) assert page and page["ok"] if progressive: - values = {item["pointer"]: item["value"] for item in json.loads(page["content"])["selections"]} + values = selected_context_values(page["content"]) assert values["/goal_id"] == GOAL_ID assert values["/heartbeat_receipt"]["turn_instance_id"] == ( actual_turn if benchmark else "saved-native-turn")