Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions docs/guides/execution-host-contract.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Execution-host contract

Verdict's execution-host boundary is defined by
`verdict.execution_hosts`. It describes what an adapter may detect, preview,
invoke, and cancel; it does not itself probe the machine or launch a process.

Before execution, an adapter produces an `ExecutionPreview` containing the
selected host, provider/model, repository/worktree, permissions, hard budget,
fan-out, lifecycle state, and a digest of the objective. Raw objectives,
credentials, and argv values are not part of the preview. The preview is
therefore suitable for consent and evidence review.

Adapters must declare operations through `HostCapabilities`. Detection is
adapter-owned and bounded; Verdict does not assume that a universal
`--version` probe is safe. Invocation and cancellation are separate declared
capabilities, and a result is accepted only when its lifecycle and termination
reason agree on a terminal outcome.

This is the initial #108 contract slice. Concrete Codex, Claude Code, and Pi
adapters remain separate implementations behind this boundary and must add
their own detection, process, cancellation, truncation, and failure tests.
127 changes: 127 additions & 0 deletions tests/test_execution_hosts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
"""Tests for the redacted execution-host boundary contract."""

from __future__ import annotations

from dataclasses import dataclass, field

import pytest

from verdict.execution_hosts import (
PROTOCOL_VERSION,
ExecutionBudget,
ExecutionHostAdapter,
ExecutionPreview,
ExecutionResult,
HostCapabilities,
HostDescriptor,
HostId,
HostLifecycle,
TerminationReason,
build_execution_preview,
canonical_preview_digest,
objective_digest,
validate_adapter_capabilities,
)


def test_preview_is_redacted_deterministic_and_bounded() -> None:
kwargs = dict(
host_id=HostId.CODEX,
provider="omniroute",
model="cx/gpt-5.6-sol",
repository="verdict-core",
worktree="feature/worktree",
permissions=("read_repo", "write_worktree"),
budget=ExecutionBudget(timeout_ms=10_000, max_fan_out=2),
)
first = build_execution_preview("do not publish this objective", **kwargs)
second = build_execution_preview("do not publish this objective", **kwargs)

assert first == second
assert first.lifecycle is HostLifecycle.PLANNED
assert "do not publish" not in str(first.to_dict())
assert first.to_dict()["schema_version"] == PROTOCOL_VERSION
assert canonical_preview_digest(first) == canonical_preview_digest(second)


def test_preview_rejects_raw_credentials_and_unhashed_objective() -> None:
with pytest.raises(ValueError, match="credential"):
build_execution_preview(
"safe objective",
host_id=HostId.PI,
provider="api_key=secret",
model="model",
repository="repo",
worktree="worktree",
)
with pytest.raises(ValueError, match="objective_digest"):
ExecutionPreview(
host_id=HostId.PI,
provider="provider",
model="model",
repository="repo",
worktree="worktree",
)


def test_capabilities_require_matching_declared_operations() -> None:
with pytest.raises(ValueError, match="invoke operation"):
HostCapabilities(supports_invocation=True)
with pytest.raises(ValueError, match="cancel operation"):
HostCapabilities(supports_cancellation=True)


def test_descriptor_and_budget_fail_closed() -> None:
with pytest.raises(ValueError, match="health"):
HostDescriptor(HostId.CLAUDE_CODE, "adapter/v1", True, health="ready")
with pytest.raises(ValueError, match="positive"):
ExecutionBudget(timeout_ms=0)


def test_result_requires_terminal_truthful_state() -> None:
with pytest.raises(ValueError, match="terminal"):
ExecutionResult(
"execution-1", HostId.CODEX, HostLifecycle.EXECUTING, TerminationReason.COMPLETED, True
)
with pytest.raises(ValueError, match="agree"):
ExecutionResult(
"execution-1", HostId.CODEX, HostLifecycle.FAILED, TerminationReason.FAILED, True
)


@dataclass
class _FixtureAdapter:
host_id: HostId = HostId.CODEX
adapter_version: str = "fixture/v1"
capabilities: HostCapabilities = field(
default_factory=lambda: HostCapabilities(
supports_invocation=True,
supports_cancellation=True,
declared_operations=("cancel", "invoke"),
)
)

def detect(self) -> HostDescriptor:
return HostDescriptor(self.host_id, self.adapter_version, True, health="healthy")

def preview(self, *args: object, **kwargs: object) -> ExecutionPreview:
raise NotImplementedError

def invoke(self, preview: ExecutionPreview) -> ExecutionResult:
raise NotImplementedError

def cancel(self, execution_id: str) -> ExecutionResult:
raise NotImplementedError


def test_adapter_protocol_and_capability_validation() -> None:
adapter = _FixtureAdapter()
assert isinstance(adapter, ExecutionHostAdapter)
assert adapter.detect().health == "healthy"
assert validate_adapter_capabilities(adapter) == ()


def test_objective_digest_is_stable_without_plaintext() -> None:
digest = objective_digest("private objective")
assert digest.startswith("sha256:")
assert "private objective" not in digest
Loading