"""Durable admission representation: persist before dispatch (T05).""" from __future__ import annotations from datetime import UTC, datetime from pathlib import Path import pytest from wf_api.run_lifecycle import ( load_stored_run, materialize_admitted_view, persist_admission, recover_admission_view, ) from wf_artifacts import ( PinnedRunEnvironment, StoredRunStatus, WorkflowArtifact, WorkflowDeployment, ) from wf_artifacts.runs.store import FileRunStore def _env() -> PinnedRunEnvironment: deployment = WorkflowDeployment( id="dep-1", artifact_id="wf-1", artifact_version=1, bindings=[{"logical_source": "demo", "concrete_source": "demo.personal"}], ) artifact = WorkflowArtifact( id="wf-1", version=1, title="Wf-1", input_schema={"type": "object", "properties": {}}, output_schema={"type": "object", "properties": {}}, outcomes=("ok",), plan={"name": "wf-1", "nodes": [], "edges": []}, ) return PinnedRunEnvironment( deployment=deployment, root_artifact=artifact, child_artifacts=[] ) def test_admission_persists_authority_before_view(tmp_path: Path) -> None: store = FileRunStore(tmp_path) run_id = store.allocate_run_id() at = datetime(2026, 9, 8, 13, 0, tzinfo=UTC) admission = persist_admission( store=store, run_id=run_id, environment=_env(), resolved_input={"team": "eng"}, max_steps=100, scheduled_at=at, schedule_id="sched-1", schedule_revision=2, ) assert admission.id == run_id assert admission.scheduled_at == at # Admission record exists before any run view or checkpoint. assert store.get_admission(run_id).id == run_id with pytest.raises(KeyError): store.get_run(run_id) record = materialize_admitted_view(store=store, admission=admission) assert record.status is StoredRunStatus.ADMITTED assert record.latest_checkpoint_id is None # Inspection distinguishes admitted (no checkpoint) from stopped. with pytest.raises(KeyError, match="no checkpoints"): store.get_latest_checkpoint(run_id) def test_admission_view_round_trips_real_serialization(tmp_path: Path) -> None: store = FileRunStore(tmp_path) run_id = store.allocate_run_id() admission = persist_admission( store=store, run_id=run_id, environment=_env(), resolved_input={"a": [1, None, "x"]}, max_steps=None, scheduled_at=None, schedule_id=None, schedule_revision=None, ) record = materialize_admitted_view(store=store, admission=admission) reloaded = store.get_run(run_id) assert reloaded.id == record.id assert reloaded.status is StoredRunStatus.ADMITTED reloaded_admission = store.get_admission(run_id) assert reloaded_admission.resolved_input == {"a": [1, None, "x"]} assert reloaded_admission.environment.deployment.id == "dep-1" def test_run_ids_are_store_backed_across_restart(tmp_path: Path) -> None: first = FileRunStore(tmp_path).allocate_run_id() second = FileRunStore(tmp_path).allocate_run_id() assert first != second # Reopening the store never reuses identities. third = FileRunStore(tmp_path).allocate_run_id() assert third not in {first, second} def test_fault_before_admission_persists_nothing(tmp_path: Path, monkeypatch) -> None: store = FileRunStore(tmp_path) run_id = store.allocate_run_id() def _boom(path: Path, payload: object) -> None: raise OSError("injected write failure") monkeypatch.setattr(store, "_write_json", _boom) with pytest.raises(OSError, match="injected"): persist_admission( store=store, run_id=run_id, environment=_env(), resolved_input={}, max_steps=None, scheduled_at=None, schedule_id=None, schedule_revision=None, ) assert FileRunStore(tmp_path).list_admissions() == [] with pytest.raises(KeyError): store.get_admission(run_id) def test_fault_between_admission_and_view_recovers_without_dispatch( tmp_path: Path, ) -> None: store = FileRunStore(tmp_path) run_id = store.allocate_run_id() admission = persist_admission( store=store, run_id=run_id, environment=_env(), resolved_input={"team": "eng"}, max_steps=None, scheduled_at=None, schedule_id=None, schedule_revision=None, ) assert admission.id == run_id # Crash before the view is materialized: admission exists, view missing. with pytest.raises(KeyError): store.get_run(run_id) # Recovery materializes the missing view but never executes work. record = recover_admission_view(store=store, run_id=run_id) assert record.status is StoredRunStatus.ADMITTED assert store.get_run(run_id).id == run_id # Idempotent: second recovery returns the same view. again = recover_admission_view(store=store, run_id=run_id) assert again.id == run_id def test_admitted_run_has_no_fabricated_checkpoint_or_output( tmp_path: Path, ) -> None: store = FileRunStore(tmp_path) run_id = store.allocate_run_id() admission = persist_admission( store=store, run_id=run_id, environment=_env(), resolved_input={}, max_steps=None, scheduled_at=None, schedule_id=None, schedule_revision=None, ) record = materialize_admitted_view(store=store, admission=admission) assert record.latest_checkpoint_id is None assert store.list_checkpoints(run_id) == [] # Loading a stopped run from an admitted view must fail closed, not # fabricate trace/output/step counts. with pytest.raises(ValueError, match="admitted"): load_stored_run(store, run_id) def test_corrupt_run_id_sequence_fails_closed(tmp_path: Path) -> None: FileRunStore(tmp_path) seq_path = tmp_path / "runs" / "_run_id_seq.json" seq_path.write_text('{"seq": "not-an-int"}', encoding="utf-8") with pytest.raises(ValueError, match="corrupt run-id sequence"): FileRunStore(tmp_path).allocate_run_id() def test_view_without_admission_fails_closed(tmp_path: Path) -> None: store = FileRunStore(tmp_path) run_id = store.allocate_run_id() admission = persist_admission( store=store, run_id=run_id, environment=_env(), resolved_input={}, max_steps=None, scheduled_at=None, schedule_id=None, schedule_revision=None, ) materialize_admitted_view(store=store, admission=admission) (tmp_path / "runs" / run_id / "admission.json").unlink() with pytest.raises(KeyError, match="unknown run admission"): recover_admission_view(store=store, run_id=run_id) def test_non_json_resolved_input_rejected_before_dispatch(tmp_path: Path) -> None: store = FileRunStore(tmp_path) run_id = store.allocate_run_id() with pytest.raises(ValueError, match="must be finite"): persist_admission( store=store, run_id=run_id, environment=_env(), resolved_input={"x": float("inf")}, # type: ignore[dict-item] max_steps=None, scheduled_at=None, schedule_id=None, schedule_revision=None, )