|
| 1 | +"""History retains control evidence without probing every discarded artifact.""" |
| 2 | +from __future__ import annotations |
| 3 | + |
| 4 | +import hashlib |
| 5 | +import json |
| 6 | +import subprocess |
| 7 | +import sys |
| 8 | +from datetime import datetime, timedelta, timezone |
| 9 | + |
| 10 | +import pytest |
| 11 | + |
| 12 | +import loopx.history as history_module |
| 13 | +from loopx.history import collect_history, load_index_snapshot |
| 14 | + |
| 15 | + |
| 16 | +@pytest.fixture |
| 17 | +def history_case(tmp_path, monkeypatch): |
| 18 | + registry = tmp_path / "registry.json" |
| 19 | + registry.write_text("{}\n") |
| 20 | + runtime = tmp_path / "runtime" |
| 21 | + index = runtime / "goals" / "example" / "runs" / "index.jsonl" |
| 22 | + index.parent.mkdir(parents=True) |
| 23 | + artifacts = tmp_path / "artifacts" |
| 24 | + artifacts.mkdir() |
| 25 | + start = datetime(2026, 9, 1, tzinfo=timezone.utc) |
| 26 | + now = start + timedelta(seconds=1001) |
| 27 | + monkeypatch.setattr( |
| 28 | + "loopx.control_plane.runtime.run_context_retention.now_utc_iso", |
| 29 | + lambda: now.isoformat(), |
| 30 | + ) |
| 31 | + rows = [] |
| 32 | + for n in range(1000): |
| 33 | + rows.append({ |
| 34 | + "generated_at": (start + timedelta(seconds=n)).isoformat(), |
| 35 | + "run_id": f"run-{n}", "classification": "progress", |
| 36 | + "agent_id": "worker", "json_path": f"artifacts/{n}.json", |
| 37 | + "markdown_path": f"artifacts/{n}.md", |
| 38 | + # Persisted booleans are deliberately wrong: each read observes files. |
| 39 | + "json_exists": True, "markdown_exists": True, |
| 40 | + }) |
| 41 | + rows[0]["agent_vision"] = {"agent_id": "worker", "revision": "retained"} |
| 42 | + rows[1]["human_reward"] = {"decision": "revise", "reason_summary": "Keep evidence"} |
| 43 | + rows[990].update({ |
| 44 | + "todo_id": "todo_blocked_work", "turn_instance_id": "turn-1", |
| 45 | + "delivery_outcome": "outcome_gap", |
| 46 | + "progress_observation": {"result_class": "blocked"}, |
| 47 | + "blocked_retry": { |
| 48 | + "schema_version": "quota_blocked_retry_v0", "source": "turn_settlement", |
| 49 | + "todo_id": "todo_blocked_work", "observed_at": rows[990]["generated_at"], |
| 50 | + "due_at": (start + timedelta(seconds=1290)).isoformat(), |
| 51 | + "resume_when": f"resume_at:{(start + timedelta(seconds=1290)).isoformat()}", |
| 52 | + }, |
| 53 | + }) |
| 54 | + rows[995]["agent_id"] = "quiet-worker" |
| 55 | + # Recent neutral records must not hide the latest status-relevant run. |
| 56 | + rows[999]["classification"] = "quota_slot_spent" |
| 57 | + rows[998]["classification"] = "quota_slot_spent" |
| 58 | + # Last duplicate replaces only supplied fields, without another unique Run. |
| 59 | + duplicate = {**rows[0], "annotation": "merged duplicate"} |
| 60 | + index.write_text("\n".join(json.dumps(row) for row in [*rows, duplicate]) + "\nnot-json\n[]\n\n") |
| 61 | + for n in (0, 1, 990, 995, 997, 999): |
| 62 | + (artifacts / f"{n}.json").write_text("{}\n") |
| 63 | + return registry, runtime, index, artifacts |
| 64 | + |
| 65 | + |
| 66 | +def _collect(case, *, limit=1, agent_lane_id=None): |
| 67 | + registry, runtime, _, _ = case |
| 68 | + return collect_history(registry_path=registry, runtime_root=runtime, |
| 69 | + goal_id="example", limit=limit, agent_lane_id=agent_lane_id) |
| 70 | + |
| 71 | + |
| 72 | +@pytest.mark.parametrize("limit", [0, 1, 3]) |
| 73 | +def test_history_observes_retained_evidence_after_full_reduction(history_case, monkeypatch, limit): |
| 74 | + _, _, index, _ = history_case |
| 75 | + calls = [] |
| 76 | + original = history_module._indexed_artifact_exists |
| 77 | + |
| 78 | + def observe(value, *, artifact_root): |
| 79 | + calls.append(value) |
| 80 | + return original(value, artifact_root=artifact_root) |
| 81 | + |
| 82 | + monkeypatch.setattr(history_module, "_indexed_artifact_exists", observe) |
| 83 | + result = _collect(history_case, limit=limit) |
| 84 | + goal = result["goals"][0] |
| 85 | + assert result["run_count"] == goal["unique_runs"] == 1000 |
| 86 | + assert goal["raw_index_records"] == 1003 |
| 87 | + assert goal["index_digest"] == f"sha256:{hashlib.sha256(index.read_bytes()).hexdigest()}" |
| 88 | + assert len(result["runs"]) == len(goal["latest_runs"]) == limit |
| 89 | + semantic = goal["semantic_history"] |
| 90 | + vision = semantic["agents"][0]["latest_agent_vision_run"] |
| 91 | + correction = semantic["latest_owner_correction_run"] |
| 92 | + retry = semantic["active_blocked_retry_runs"][0] |
| 93 | + assert vision["run_id"] == "run-0" |
| 94 | + assert vision["annotation"] == "merged duplicate" |
| 95 | + assert correction["run_id"] == "run-1" |
| 96 | + assert retry["run_id"] == "run-990" |
| 97 | + assert goal["latest_status_run"]["run_id"] == "run-997" |
| 98 | + for row in [vision, correction, retry, goal["latest_status_run"]]: |
| 99 | + assert row["json_exists"] is True |
| 100 | + assert row["markdown_exists"] is False |
| 101 | + # Counts depend on retained output, never the 1,000-row historical population. |
| 102 | + expected = {0, 1, 990, 997, *range(1000 - limit, 1000)} |
| 103 | + assert set(calls) == {f"artifacts/{n}.{ext}" for n in expected for ext in ("json", "md")} |
| 104 | + assert len(calls) == len(set(calls)) |
| 105 | + |
| 106 | + |
| 107 | +def test_lane_window_artifacts_remain_observed(history_case): |
| 108 | + result = _collect(history_case, agent_lane_id="quiet-worker") |
| 109 | + runs = result["goals"][0]["latest_runs"] |
| 110 | + assert [run["run_id"] for run in runs] == ["run-999", "run-995"] |
| 111 | + assert all(run["json_exists"] and not run["markdown_exists"] for run in runs) |
| 112 | + |
| 113 | + |
| 114 | +def test_unchanged_index_does_not_cache_artifact_existence(history_case): |
| 115 | + _, _, index, artifacts = history_case |
| 116 | + before = index.read_bytes() |
| 117 | + initial = _collect(history_case) |
| 118 | + assert initial["runs"][0]["json_exists"] is True |
| 119 | + assert initial["runs"][0]["markdown_exists"] is False |
| 120 | + (artifacts / "999.json").unlink() |
| 121 | + (artifacts / "999.md").write_text("# Delivered\n") |
| 122 | + (artifacts / "0.json").unlink() |
| 123 | + reread = _collect(history_case) |
| 124 | + assert reread["runs"][0]["json_exists"] is False |
| 125 | + assert reread["runs"][0]["markdown_exists"] is True |
| 126 | + assert reread["goals"][0]["semantic_history"]["agents"][0]["latest_agent_vision_run"]["json_exists"] is False |
| 127 | + assert index.read_bytes() == before |
| 128 | + assert reread["goals"][0]["index_digest"] == initial["goals"][0]["index_digest"] |
| 129 | + |
| 130 | + |
| 131 | +def test_full_index_reader_still_observes_every_row(history_case): |
| 132 | + registry, _, index, artifacts = history_case |
| 133 | + (artifacts / "500.md").write_text("# Older artifact\n") |
| 134 | + (artifacts / "501.json").symlink_to(artifacts / "absent.json") |
| 135 | + snapshot = load_index_snapshot(index, artifact_root=registry.parent) |
| 136 | + assert len(snapshot.records) == 1000 |
| 137 | + assert snapshot.records[500]["markdown_exists"] is True |
| 138 | + assert snapshot.records[501]["json_exists"] is False |
| 139 | + assert snapshot.records[502]["json_exists"] is False |
| 140 | + |
| 141 | + |
| 142 | +@pytest.mark.parametrize("provider", ["file", "sqlite"]) |
| 143 | +def test_public_history_and_status_keep_artifacts_with_canonical_todos(history_case, monkeypatch, tmp_path, provider): |
| 144 | + from tests.control_plane.canonical_authority_fixture import ( |
| 145 | + initialize_canonical_authority, isolate_sqlite_runtime, |
| 146 | + ) |
| 147 | + from loopx.control_plane.coordination.runtime_shadow import build_todo_runtime_shadow_projection |
| 148 | + from loopx.control_plane.effect_runtime import restart_effect_runtime |
| 149 | + from loopx.control_plane.testing.canary_harness import write_fixture_registry |
| 150 | + |
| 151 | + isolate_sqlite_runtime(tmp_path, monkeypatch) |
| 152 | + registry, runtime, _, _ = history_case |
| 153 | + state = tmp_path / "state.md" |
| 154 | + state.write_text("---\nstatus: active\n---\n# Example\n\n## Agent Todo\n") |
| 155 | + write_fixture_registry(project=tmp_path, registry_path=registry, runtime_root=runtime, |
| 156 | + goal_id="example", domain="engineering", state_file=str(state), |
| 157 | + adapter_kind="generic_project_goal_v0") |
| 158 | + initialize_canonical_authority( |
| 159 | + runtime, "example", build_todo_runtime_shadow_projection(goal_id="example", todos=[]), |
| 160 | + state_path=state, provider=provider, |
| 161 | + ) |
| 162 | + try: |
| 163 | + for command in ("history", "status"): |
| 164 | + result = subprocess.run( |
| 165 | + [sys.executable, "-m", "loopx.entrypoint", "--registry", str(registry), |
| 166 | + "--runtime-root", str(runtime), "--format", "json", command, |
| 167 | + "--goal-id", "example", "--limit", "1"], |
| 168 | + capture_output=True, text=True, timeout=60, |
| 169 | + ) |
| 170 | + assert result.returncode == 0, result.stdout + result.stderr |
| 171 | + payload = json.loads(result.stdout) |
| 172 | + history = payload if command == "history" else payload["run_history"] |
| 173 | + goal = history["goals"][0] |
| 174 | + assert goal["unique_runs"] == 1000 |
| 175 | + assert goal["latest_status_run"]["run_id"] == "run-997" |
| 176 | + assert goal["latest_status_run"]["json_exists"] is True |
| 177 | + assert goal["latest_status_run"]["markdown_exists"] is False |
| 178 | + finally: |
| 179 | + restart_effect_runtime() |
0 commit comments