"""Synthetic attended admission/dispatch regressions; no owner/native actions.""" from dataclasses import replace import pytest from tests.test_case_admission_modes import admit, mode_step from tests.test_case_run import fixture_catalog, lane_entry, make_rig from warden.case_receipt_state import Outcome, Refused from warden.case_type import check_follow_through from warden.routing.catalog import CatalogError, _parse_entry, load_catalog def attended(rig, entries): return admit(rig, step=mode_step(False, True), entries=entries, desc=dict(effect="change", revocation_scopes=("parent-session",))) @pytest.mark.parametrize("operation", ["run", "rollback", "reconcile"]) @pytest.mark.parametrize("marker", ["agent-1", "", None]) def test_identity_change_refuses_before_any_write_or_callback(tmp_path, monkeypatch, operation, marker): monkeypatch.delenv("WARDEN_AGENT_ID", raising=False) rig = make_rig(tmp_path) executor = rig[0] admitted = attended(rig, [dict(lane_entry(), lane="login")]) executor.evidence_verifier = lambda *_: True if operation == "rollback": executor.run(*admitted) elif operation == "reconcile": old = executor.adapters[admitted[0].adapter] def lost(_): raise RuntimeError("synthetic lost reply") executor.adapters[old.reference] = replace(old, invoke=lost) executor.run(*admitted) calls = [] old = executor.adapters[admitted[0].adapter] callback = getattr(old, "invoke" if operation == "run" else operation) def observed(*args): calls.append(args) return callback(*args) executor.adapters[old.reference] = replace(old, **{ "invoke" if operation == "run" else operation: observed, }) def rows(): return tuple(executor.journal.db.execute(f"SELECT COUNT(*) FROM {table}").fetchone()[0] for table in ("intents", "receipts")) before = rows() if marker is None: result = getattr(executor, operation)(*admitted) assert result.last.outcome is {"run": Outcome.SUCCESS, "rollback": Outcome.ROLLED_BACK, "reconcile": Outcome.NO_CHANGE}[operation] assert len(calls) == 1 assert rows() != before return monkeypatch.setenv("WARDEN_AGENT_ID", marker) with pytest.raises(Refused, match="attended step requires a human parent session"): getattr(executor, operation)(*admitted) assert rows() == before assert not calls def entries_for(case): change = dict(lane_entry(), lane="attended-change", session_lane="login-lane") login = dict(lane_entry("login-lane"), lane="login") if case == "missing": return [change] if case == "draft-login": login["status"] = "draft" elif case == "unreviewed-login": login["reviewed"] = "" elif case == "wrong-kind-login": login["lane"] = "secret" elif case == "draft-change": change["status"] = "draft" elif case == "unreviewed-change": change["reviewed"] = "" elif case == "missing-session-field": del change["session_lane"] elif case in ("secret", "ceremony"): change["lane"] = case del change["session_lane"] return [change, login] @pytest.mark.parametrize("case, code", [ ("valid", None), ("missing", "LANE-KIND"), ("draft-login", "LANE-KIND"), ("unreviewed-login", "LANE-KIND"), ("wrong-kind-login", "LANE-KIND"), ("draft-change", "LANE-UNREVIEWED"), ("unreviewed-change", "LANE-UNREVIEWED"), ("secret", "LANE-KIND"), ("ceremony", "LANE-KIND"), ]) def test_attended_change_planner_and_executor_agree(tmp_path, monkeypatch, case, code): monkeypatch.delenv("WARDEN_AGENT_ID", raising=False) entries = entries_for(case) path = tmp_path / "catalog.yaml" path.write_bytes(fixture_catalog(*entries)) catalog = load_catalog(path) step = dict(mode_step(False, True)[0], attended_justification="synthetic test", entry_point="owner/test", auth="synthetic session", value_handling="none") doc = {"spec": {"follow_through": [step], "rollback": [{"reverses": step["id"]}]}} report = check_follow_through(doc, catalog) assert [f.code for f in report.findings] == ([] if code is None else [code]) rig = make_rig(tmp_path) if code: with pytest.raises(Refused): attended(rig, entries) assert rig[0].journal.db.execute("SELECT COUNT(*) FROM receipts").fetchone()[0] == 0 else: assert attended(rig, entries)[1].lane == "issuer-lane" def test_attended_change_missing_session_is_parser_rejection(): with pytest.raises(CatalogError, match="must name session_lane"): _parse_entry(entries_for("missing-session-field")[0], 0)