From a96aaa291d74f6d11ec0b0a202973c1e96c1e56a Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Thu, 30 Apr 2026 16:33:11 -0400 Subject: [PATCH 1/4] Add cloudshell-fog adapter scaffold --- src/agent_term/cloudshell_fog.py | 228 +++++++++++++++++++++++++++++++ 1 file changed, 228 insertions(+) create mode 100644 src/agent_term/cloudshell_fog.py diff --git a/src/agent_term/cloudshell_fog.py b/src/agent_term/cloudshell_fog.py new file mode 100644 index 0000000..0629b79 --- /dev/null +++ b/src/agent_term/cloudshell_fog.py @@ -0,0 +1,228 @@ +"""cloudshell-fog adapter primitives. + +AgentTerm requests governed shell sessions; cloudshell-fog remains the authority for +OIDC, placement, TTL, PTY attach, and audit semantics. This module is a fakeable +adapter boundary and does not open local shells. +""" + +from __future__ import annotations + +from dataclasses import dataclass, field +from datetime import UTC, datetime +from typing import Protocol + +from agent_term.adapters import AdapterResult +from agent_term.events import AgentTermEvent + + +@dataclass(frozen=True) +class CloudShellSessionRequest: + """Governed shell-session request handed to cloudshell-fog.""" + + profile: str + ttl_seconds: int + placement_hint: str + operator_ref: str + channel: str + thread_id: str | None = None + agent_id: str | None = None + policy_decision_ref: str | None = None + metadata: dict[str, object] = field(default_factory=dict) + + def to_metadata(self) -> dict[str, object]: + return { + "profile": self.profile, + "ttl_seconds": self.ttl_seconds, + "placement_hint": self.placement_hint, + "operator_ref": self.operator_ref, + "channel": self.channel, + "thread_id": self.thread_id, + "agent_id": self.agent_id, + "policy_decision_ref": self.policy_decision_ref, + **self.metadata, + } + + +@dataclass(frozen=True) +class CloudShellSession: + """cloudshell-fog session metadata visible to AgentTerm.""" + + session_id: str + status: str + placement: str + attach_ref: str + audit_correlation_id: str + expires_at: str + metadata: dict[str, object] = field(default_factory=dict) + + def to_metadata(self) -> dict[str, object]: + return { + "cloudshell_session_id": self.session_id, + "cloudshell_status": self.status, + "cloudshell_placement": self.placement, + "cloudshell_attach_ref": self.attach_ref, + "cloudshell_audit_correlation_id": self.audit_correlation_id, + "cloudshell_expires_at": self.expires_at, + **self.metadata, + } + + +class CloudShellFogBackend(Protocol): + """Backend contract for cloudshell-fog session lifecycle.""" + + def request_session(self, request: CloudShellSessionRequest) -> CloudShellSession: + """Create or reserve a governed shell session.""" + + def attach_session(self, session_id: str) -> CloudShellSession | None: + """Return attach metadata for an existing session, if available.""" + + +class InMemoryCloudShellFogBackend: + """Test/development backend for cloudshell-fog session lifecycle.""" + + def __init__(self) -> None: + self._sessions: dict[str, CloudShellSession] = {} + + def request_session(self, request: CloudShellSessionRequest) -> CloudShellSession: + session_id = f"shell-{len(self._sessions) + 1}" + expires_at = datetime.now(UTC).isoformat() + session = CloudShellSession( + session_id=session_id, + status="running", + placement=request.placement_hint, + attach_ref=f"cloudshell-fog://sessions/{session_id}/pty", + audit_correlation_id=f"audit-{session_id}", + expires_at=expires_at, + metadata={ + "profile": request.profile, + "ttl_seconds": request.ttl_seconds, + }, + ) + self._sessions[session_id] = session + return session + + def attach_session(self, session_id: str) -> CloudShellSession | None: + return self._sessions.get(session_id) + + +class CloudShellFogAdapter: + """Adapter that prepares governed shell-session operations.""" + + key = "cloudshell-fog" + + def __init__(self, backend: CloudShellFogBackend) -> None: + self.backend = backend + + def supports(self, event: AgentTermEvent) -> bool: + return event.source == self.key or event.kind in {"shell_session", "shell_attach"} + + def handle(self, event: AgentTermEvent) -> AdapterResult: + if event.kind == "shell_session": + return self._request_session(event) + if event.kind == "shell_attach": + return self._attach_session(event) + return AdapterResult( + ok=False, + source=self.key, + body=f"Unsupported cloudshell-fog event kind: {event.kind}", + metadata={"cloudshell_status": "unsupported_kind", "fail_closed": True}, + ) + + def _request_session(self, event: AgentTermEvent) -> AdapterResult: + policy_ref = _policy_decision_ref(event) + if not policy_ref: + return _deny(event, "missing_policy_decision") + + ttl_seconds = int(event.metadata.get("ttl_seconds") or 3600) + if ttl_seconds <= 0: + return _deny(event, "invalid_ttl_seconds") + + request = CloudShellSessionRequest( + profile=str(event.metadata.get("profile") or "default"), + ttl_seconds=ttl_seconds, + placement_hint=str(event.metadata.get("placement_hint") or "fog-first"), + operator_ref=event.sender, + channel=event.channel, + thread_id=event.thread_id, + agent_id=_optional_str(event.metadata.get("agent_id")), + policy_decision_ref=policy_ref, + metadata={ + "matrix_room_id": event.metadata.get("matrix_room_id"), + "workroom": event.metadata.get("workroom"), + "topic_scope": event.metadata.get("topic_scope"), + }, + ) + session = self.backend.request_session(request) + return AdapterResult( + ok=True, + source=self.key, + body=f"cloudshell-fog session requested: {session.session_id}", + kind="shell_session", + metadata={ + "request_event_id": event.event_id, + "cloudshell_status": "session_requested", + **request.to_metadata(), + **session.to_metadata(), + }, + ) + + def _attach_session(self, event: AgentTermEvent) -> AdapterResult: + policy_ref = _policy_decision_ref(event) + if not policy_ref: + return _deny(event, "missing_policy_decision") + + session_id = _optional_str(event.metadata.get("cloudshell_session_id")) + if not session_id: + return _deny(event, "missing_session_id") + + session = self.backend.attach_session(session_id) + if session is None: + return _deny(event, "unknown_session", session_id=session_id) + + return AdapterResult( + ok=True, + source=self.key, + body=f"cloudshell-fog attach prepared: {session.session_id}", + kind="shell_attach", + metadata={ + "request_event_id": event.event_id, + "cloudshell_status": "attach_prepared", + "policy_decision_ref": policy_ref, + **session.to_metadata(), + }, + ) + + +def _policy_decision_ref(event: AgentTermEvent) -> str | None: + value = ( + event.metadata.get("policy_decision_ref") + or event.metadata.get("policy_decision_id") + or event.metadata.get("policyDecisionRef") + ) + return _optional_str(value) + + +def _deny( + event: AgentTermEvent, + reason: str, + *, + session_id: str | None = None, +) -> AdapterResult: + metadata: dict[str, object] = { + "request_event_id": event.event_id, + "cloudshell_status": "denied", + "deny_reason": reason, + "fail_closed": True, + } + if session_id: + metadata["cloudshell_session_id"] = session_id + return AdapterResult( + ok=False, + source="cloudshell-fog", + body=f"cloudshell-fog denied request: {reason}", + metadata=metadata, + ) + + +def _optional_str(value: object) -> str | None: + return str(value) if value is not None else None From 8a09082e84e038b5791c6fec3a75b904b04898b4 Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Thu, 30 Apr 2026 23:03:58 -0400 Subject: [PATCH 2/4] Add AgentPlane adapter scaffold --- src/agent_term/agentplane.py | 273 +++++++++++++++++++++++++++++++++++ 1 file changed, 273 insertions(+) create mode 100644 src/agent_term/agentplane.py diff --git a/src/agent_term/agentplane.py b/src/agent_term/agentplane.py new file mode 100644 index 0000000..6e38918 --- /dev/null +++ b/src/agent_term/agentplane.py @@ -0,0 +1,273 @@ +"""AgentPlane adapter primitives. + +AgentTerm does not execute bundles. AgentPlane remains the authority for validation, +placement, run, replay, and evidence artifacts. This module provides a dependency-free +adapter boundary for recording governed execution operations and artifact references. +""" + +from __future__ import annotations + +from dataclasses import dataclass, field +from datetime import UTC, datetime +from typing import Protocol + +from agent_term.adapters import AdapterResult +from agent_term.events import AgentTermEvent + + +@dataclass(frozen=True) +class AgentPlaneArtifact: + """AgentPlane evidence artifact reference.""" + + kind: str + ref: str + digest: str | None = None + metadata: dict[str, object] = field(default_factory=dict) + + def to_metadata(self) -> dict[str, object]: + return { + "artifact_kind": self.kind, + "artifact_ref": self.ref, + "artifact_digest": self.digest, + **self.metadata, + } + + +@dataclass(frozen=True) +class AgentPlaneResult: + """Normalized result from an AgentPlane operation.""" + + operation: str + status: str + bundle_ref: str + run_id: str | None = None + executor_ref: str | None = None + artifacts: tuple[AgentPlaneArtifact, ...] = () + metadata: dict[str, object] = field(default_factory=dict) + + def to_metadata(self) -> dict[str, object]: + return { + "agentplane_operation": self.operation, + "agentplane_status": self.status, + "bundle_ref": self.bundle_ref, + "run_id": self.run_id, + "executor_ref": self.executor_ref, + "artifacts": [artifact.to_metadata() for artifact in self.artifacts], + **self.metadata, + } + + +class AgentPlaneBackend(Protocol): + """Backend contract for AgentPlane operations.""" + + def validate(self, bundle_ref: str) -> AgentPlaneResult: + """Validate a bundle.""" + + def place(self, bundle_ref: str) -> AgentPlaneResult: + """Select an executor for a bundle.""" + + def run(self, bundle_ref: str, executor_ref: str | None = None) -> AgentPlaneResult: + """Run a bundle.""" + + def replay(self, run_id: str) -> AgentPlaneResult | None: + """Replay an existing run.""" + + +class InMemoryAgentPlaneBackend: + """Test/development backend for AgentPlane operations.""" + + def __init__(self) -> None: + self._runs: dict[str, AgentPlaneResult] = {} + + def validate(self, bundle_ref: str) -> AgentPlaneResult: + return AgentPlaneResult( + operation="validate", + status="valid", + bundle_ref=bundle_ref, + artifacts=( + AgentPlaneArtifact( + kind="ValidationArtifact", + ref=f"agentplane://artifacts/{bundle_ref}/validation-artifact.json", + ), + ), + ) + + def place(self, bundle_ref: str) -> AgentPlaneResult: + return AgentPlaneResult( + operation="place", + status="placed", + bundle_ref=bundle_ref, + executor_ref="executor.local", + artifacts=( + AgentPlaneArtifact( + kind="PlacementDecision", + ref=f"agentplane://artifacts/{bundle_ref}/placement-decision.json", + ), + ), + ) + + def run(self, bundle_ref: str, executor_ref: str | None = None) -> AgentPlaneResult: + run_id = f"run-{len(self._runs) + 1}" + result = AgentPlaneResult( + operation="run", + status="completed", + bundle_ref=bundle_ref, + run_id=run_id, + executor_ref=executor_ref or "executor.local", + artifacts=( + AgentPlaneArtifact( + kind="RunArtifact", + ref=f"agentplane://runs/{run_id}/run-artifact.json", + ), + AgentPlaneArtifact( + kind="ReplayArtifact", + ref=f"agentplane://runs/{run_id}/replay-artifact.json", + ), + ), + metadata={"completed_at": datetime.now(UTC).isoformat()}, + ) + self._runs[run_id] = result + return result + + def replay(self, run_id: str) -> AgentPlaneResult | None: + prior = self._runs.get(run_id) + if prior is None: + return None + return AgentPlaneResult( + operation="replay", + status="prepared", + bundle_ref=prior.bundle_ref, + run_id=run_id, + executor_ref=prior.executor_ref, + artifacts=prior.artifacts, + ) + + +class AgentPlaneAdapter: + """Adapter that prepares governed AgentPlane operations.""" + + key = "agentplane" + + def __init__(self, backend: AgentPlaneBackend) -> None: + self.backend = backend + + def supports(self, event: AgentTermEvent) -> bool: + return event.source == self.key or event.kind in { + "validation", + "placement", + "run", + "replay", + } + + def handle(self, event: AgentTermEvent) -> AdapterResult: + if event.kind == "validation": + return self._validate(event) + if event.kind == "placement": + return self._place(event) + if event.kind == "run": + return self._run(event) + if event.kind == "replay": + return self._replay(event) + return AdapterResult( + ok=False, + source=self.key, + body=f"Unsupported AgentPlane event kind: {event.kind}", + metadata={"agentplane_status": "unsupported_kind", "fail_closed": True}, + ) + + def _validate(self, event: AgentTermEvent) -> AdapterResult: + bundle_ref = _bundle_ref(event) + if not bundle_ref: + return _deny(event, "missing_bundle_ref") + return _result(event, self.backend.validate(bundle_ref)) + + def _place(self, event: AgentTermEvent) -> AdapterResult: + bundle_ref = _bundle_ref(event) + if not bundle_ref: + return _deny(event, "missing_bundle_ref") + return _result(event, self.backend.place(bundle_ref)) + + def _run(self, event: AgentTermEvent) -> AdapterResult: + policy_ref = _policy_decision_ref(event) + if not policy_ref: + return _deny(event, "missing_policy_decision") + bundle_ref = _bundle_ref(event) + if not bundle_ref: + return _deny(event, "missing_bundle_ref") + result = self.backend.run(bundle_ref, _optional_str(event.metadata.get("executor_ref"))) + return _result(event, result, policy_decision_ref=policy_ref) + + def _replay(self, event: AgentTermEvent) -> AdapterResult: + policy_ref = _policy_decision_ref(event) + if not policy_ref: + return _deny(event, "missing_policy_decision") + run_id = _optional_str(event.metadata.get("run_id")) + if not run_id: + return _deny(event, "missing_run_id") + result = self.backend.replay(run_id) + if result is None: + return _deny(event, "unknown_run", run_id=run_id) + return _result(event, result, policy_decision_ref=policy_ref) + + +def _result( + event: AgentTermEvent, + result: AgentPlaneResult, + *, + policy_decision_ref: str | None = None, +) -> AdapterResult: + metadata = { + "request_event_id": event.event_id, + "agentplane_status": result.status, + "policy_decision_ref": policy_decision_ref, + "agent_id": event.metadata.get("agent_id"), + "workroom": event.metadata.get("workroom"), + "topic_scope": event.metadata.get("topic_scope"), + "matrix_room_id": event.metadata.get("matrix_room_id"), + **result.to_metadata(), + } + return AdapterResult( + ok=True, + source="agentplane", + body=f"AgentPlane {result.operation} {result.status}: {result.bundle_ref}", + kind=result.operation, + metadata=metadata, + ) + + +def _deny( + event: AgentTermEvent, + reason: str, + *, + run_id: str | None = None, +) -> AdapterResult: + metadata: dict[str, object] = { + "request_event_id": event.event_id, + "agentplane_status": "denied", + "deny_reason": reason, + "fail_closed": True, + } + if run_id: + metadata["run_id"] = run_id + return AdapterResult( + ok=False, + source="agentplane", + body=f"AgentPlane denied request: {reason}", + metadata=metadata, + ) + + +def _bundle_ref(event: AgentTermEvent) -> str | None: + return _optional_str(event.metadata.get("bundle_ref") or event.metadata.get("bundle")) + + +def _policy_decision_ref(event: AgentTermEvent) -> str | None: + return _optional_str( + event.metadata.get("policy_decision_ref") + or event.metadata.get("policy_decision_id") + or event.metadata.get("policyDecisionRef") + ) + + +def _optional_str(value: object) -> str | None: + return str(value) if value is not None else None From 75d2a8404ae47826dfe4f6cc273b04ca312ac90b Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Thu, 30 Apr 2026 23:04:46 -0400 Subject: [PATCH 3/4] Add cloudshell-fog adapter tests --- tests/test_cloudshell_fog.py | 106 +++++++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 tests/test_cloudshell_fog.py diff --git a/tests/test_cloudshell_fog.py b/tests/test_cloudshell_fog.py new file mode 100644 index 0000000..3bcb859 --- /dev/null +++ b/tests/test_cloudshell_fog.py @@ -0,0 +1,106 @@ +from agent_term.cloudshell_fog import CloudShellFogAdapter, InMemoryCloudShellFogBackend +from agent_term.events import AgentTermEvent + + +def make_event(kind: str, metadata: dict[str, object] | None = None) -> AgentTermEvent: + return AgentTermEvent( + channel="!cloudshell-fog", + sender="@operator", + kind=kind, + source="cloudshell-fog", + body="cloudshell test event", + thread_id="thread-1", + metadata=metadata or {}, + ) + + +def test_shell_session_requires_policy_decision(): + adapter = CloudShellFogAdapter(InMemoryCloudShellFogBackend()) + + result = adapter.handle(make_event("shell_session", {"profile": "default"})) + + assert result.ok is False + assert result.metadata["fail_closed"] is True + assert result.metadata["deny_reason"] == "missing_policy_decision" + + +def test_shell_session_request_preserves_governed_metadata(): + adapter = CloudShellFogAdapter(InMemoryCloudShellFogBackend()) + event = make_event( + "shell_session", + { + "profile": "default", + "ttl_seconds": 1800, + "placement_hint": "fog-first", + "policy_decision_id": "decision-shell-1", + "agent_id": "agent.codex", + "workroom": "pi-demo", + "topic_scope": "professional-intelligence", + "matrix_room_id": "!room:example.org", + }, + ) + + result = adapter.handle(event) + + assert result.ok is True + assert result.kind == "shell_session" + assert result.metadata["cloudshell_status"] == "running" + assert result.metadata["cloudshell_session_id"] == "shell-1" + assert result.metadata["cloudshell_attach_ref"] == "cloudshell-fog://sessions/shell-1/pty" + assert result.metadata["policy_decision_ref"] == "decision-shell-1" + assert result.metadata["agent_id"] == "agent.codex" + assert result.metadata["workroom"] == "pi-demo" + + +def test_attach_requires_policy_decision(): + adapter = CloudShellFogAdapter(InMemoryCloudShellFogBackend()) + + result = adapter.handle(make_event("shell_attach", {"cloudshell_session_id": "shell-1"})) + + assert result.ok is False + assert result.metadata["deny_reason"] == "missing_policy_decision" + + +def test_attach_prepares_known_session(): + backend = InMemoryCloudShellFogBackend() + adapter = CloudShellFogAdapter(backend) + created = adapter.handle( + make_event( + "shell_session", + {"profile": "default", "policy_decision_ref": "decision-shell-create"}, + ) + ) + + result = adapter.handle( + make_event( + "shell_attach", + { + "cloudshell_session_id": created.metadata["cloudshell_session_id"], + "policy_decision_ref": "decision-shell-attach", + }, + ) + ) + + assert result.ok is True + assert result.kind == "shell_attach" + assert result.metadata["cloudshell_status"] == "running" + assert result.metadata["policy_decision_ref"] == "decision-shell-attach" + + +def test_attach_unknown_session_fails_closed(): + adapter = CloudShellFogAdapter(InMemoryCloudShellFogBackend()) + + result = adapter.handle( + make_event( + "shell_attach", + { + "cloudshell_session_id": "shell-missing", + "policy_decision_ref": "decision-shell-attach", + }, + ) + ) + + assert result.ok is False + assert result.metadata["fail_closed"] is True + assert result.metadata["deny_reason"] == "unknown_session" + assert result.metadata["cloudshell_session_id"] == "shell-missing" From 1a63209747d8d924e38f0dba282bd2bf980675c5 Mon Sep 17 00:00:00 2001 From: mdheller <21163552+mdheller@users.noreply.github.com> Date: Fri, 1 May 2026 15:27:46 -0400 Subject: [PATCH 4/4] Add AgentPlane adapter tests --- tests/test_agentplane.py | 136 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 136 insertions(+) create mode 100644 tests/test_agentplane.py diff --git a/tests/test_agentplane.py b/tests/test_agentplane.py new file mode 100644 index 0000000..3403367 --- /dev/null +++ b/tests/test_agentplane.py @@ -0,0 +1,136 @@ +from agent_term.agentplane import AgentPlaneAdapter, InMemoryAgentPlaneBackend +from agent_term.events import AgentTermEvent + + +def make_event(kind: str, metadata: dict[str, object] | None = None) -> AgentTermEvent: + return AgentTermEvent( + channel="!agentplane", + sender="@operator", + kind=kind, + source="agentplane", + body="agentplane test event", + thread_id="thread-1", + metadata=metadata or {}, + ) + + +def test_validation_requires_bundle_ref(): + adapter = AgentPlaneAdapter(InMemoryAgentPlaneBackend()) + + result = adapter.handle(make_event("validation")) + + assert result.ok is False + assert result.metadata["fail_closed"] is True + assert result.metadata["deny_reason"] == "missing_bundle_ref" + + +def test_validation_emits_validation_artifact_reference(): + adapter = AgentPlaneAdapter(InMemoryAgentPlaneBackend()) + + result = adapter.handle(make_event("validation", {"bundle_ref": "bundles/example-agent"})) + + assert result.ok is True + assert result.kind == "validate" + assert result.metadata["agentplane_status"] == "valid" + assert result.metadata["bundle_ref"] == "bundles/example-agent" + assert result.metadata["artifacts"][0]["artifact_kind"] == "ValidationArtifact" + + +def test_placement_emits_placement_decision_reference(): + adapter = AgentPlaneAdapter(InMemoryAgentPlaneBackend()) + + result = adapter.handle(make_event("placement", {"bundle_ref": "bundles/example-agent"})) + + assert result.ok is True + assert result.kind == "place" + assert result.metadata["agentplane_status"] == "placed" + assert result.metadata["executor_ref"] == "executor.local" + assert result.metadata["artifacts"][0]["artifact_kind"] == "PlacementDecision" + + +def test_run_requires_policy_decision(): + adapter = AgentPlaneAdapter(InMemoryAgentPlaneBackend()) + + result = adapter.handle(make_event("run", {"bundle_ref": "bundles/example-agent"})) + + assert result.ok is False + assert result.metadata["fail_closed"] is True + assert result.metadata["deny_reason"] == "missing_policy_decision" + + +def test_run_preserves_policy_agent_workroom_topic_and_artifacts(): + adapter = AgentPlaneAdapter(InMemoryAgentPlaneBackend()) + event = make_event( + "run", + { + "bundle_ref": "bundles/example-agent", + "policy_decision_ref": "decision-run-1", + "executor_ref": "executor.fog-1", + "agent_id": "agent.codex", + "workroom": "pi-demo", + "topic_scope": "professional-intelligence", + "matrix_room_id": "!room:example.org", + }, + ) + + result = adapter.handle(event) + + assert result.ok is True + assert result.kind == "run" + assert result.metadata["policy_decision_ref"] == "decision-run-1" + assert result.metadata["agent_id"] == "agent.codex" + assert result.metadata["workroom"] == "pi-demo" + assert result.metadata["topic_scope"] == "professional-intelligence" + assert result.metadata["executor_ref"] == "executor.fog-1" + assert {artifact["artifact_kind"] for artifact in result.metadata["artifacts"]} == { + "RunArtifact", + "ReplayArtifact", + } + + +def test_replay_requires_policy_decision(): + adapter = AgentPlaneAdapter(InMemoryAgentPlaneBackend()) + + result = adapter.handle(make_event("replay", {"run_id": "run-1"})) + + assert result.ok is False + assert result.metadata["deny_reason"] == "missing_policy_decision" + + +def test_replay_unknown_run_fails_closed(): + adapter = AgentPlaneAdapter(InMemoryAgentPlaneBackend()) + + result = adapter.handle( + make_event("replay", {"run_id": "run-missing", "policy_decision_ref": "decision-replay"}) + ) + + assert result.ok is False + assert result.metadata["fail_closed"] is True + assert result.metadata["deny_reason"] == "unknown_run" + assert result.metadata["run_id"] == "run-missing" + + +def test_replay_known_run_preserves_artifacts(): + backend = InMemoryAgentPlaneBackend() + adapter = AgentPlaneAdapter(backend) + run_result = adapter.handle( + make_event( + "run", + {"bundle_ref": "bundles/example-agent", "policy_decision_ref": "decision-run"}, + ) + ) + + replay_result = adapter.handle( + make_event( + "replay", + { + "run_id": run_result.metadata["run_id"], + "policy_decision_ref": "decision-replay", + }, + ) + ) + + assert replay_result.ok is True + assert replay_result.kind == "replay" + assert replay_result.metadata["policy_decision_ref"] == "decision-replay" + assert replay_result.metadata["artifacts"] == run_result.metadata["artifacts"]