Skip to content

Commit 8ff0be2

Browse files
committed
Verify agent harness terminal receipts
1 parent 29c4b12 commit 8ff0be2

1 file changed

Lines changed: 78 additions & 0 deletions

File tree

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
#!/usr/bin/env python3
2+
"""Verify TurtleTerm Agent Harness terminal receipt fixture."""
3+
4+
from __future__ import annotations
5+
6+
import json
7+
import sys
8+
from pathlib import Path
9+
from typing import Any
10+
11+
ROOT = Path(__file__).resolve().parents[1]
12+
SCHEMA = ROOT / "schemas" / "agent-harness-terminal-receipts.schema.json"
13+
EXAMPLE = ROOT / "examples" / "agent-harness-terminal-receipts.example.json"
14+
15+
16+
class ValidationError(Exception):
17+
pass
18+
19+
20+
def load_json(path: Path) -> Any:
21+
with path.open("r", encoding="utf-8") as handle:
22+
return json.load(handle)
23+
24+
25+
def require(condition: bool, message: str) -> None:
26+
if not condition:
27+
raise ValidationError(message)
28+
29+
30+
def validate(data: dict[str, Any]) -> None:
31+
require(SCHEMA.exists(), f"missing schema: {SCHEMA}")
32+
require(data.get("schemaVersion") == "v0.1", "schemaVersion must be v0.1")
33+
require(data.get("kind") == "AgentHarnessTerminalReceipts", "kind mismatch")
34+
35+
session = data.get("terminalSessionReceipt")
36+
require(isinstance(session, dict), "terminalSessionReceipt must be object")
37+
for field in ["sessionId", "actorRef", "workspaceRef", "shellProfile", "gatewayProfile", "policyAdmissionRef", "agentplaneRunRef", "environmentProfileHash"]:
38+
require(field in session, f"terminalSessionReceipt missing {field}")
39+
require(session["environmentProfileHash"].startswith("sha256:"), "environmentProfileHash must be a sha256 ref")
40+
41+
command = data.get("commandReceipt")
42+
require(isinstance(command, dict), "commandReceipt must be object")
43+
for field in ["commandId", "terminalSessionRef", "commandHash", "workingDirectory", "environmentProfileHash", "exitCode", "policyDecisionRef", "sideEffectClass", "replayEligible"]:
44+
require(field in command, f"commandReceipt missing {field}")
45+
require(command["commandHash"].startswith("sha256:"), "commandHash must be a sha256 ref")
46+
require(command["policyDecisionRef"], "commandReceipt requires policyDecisionRef")
47+
if command["exitCode"] != 0:
48+
require(command["replayEligible"] is False, "failed command should not be replayEligible in baseline fixture")
49+
50+
mutation = data.get("mutationReceipt")
51+
require(isinstance(mutation, dict), "mutationReceipt must be object")
52+
for field in ["mutationId", "commandRef", "mutationClass", "targetScope", "mode", "policyDecisionRef", "mutatedHost"]:
53+
require(field in mutation, f"mutationReceipt missing {field}")
54+
require(mutation["mode"] in {"dry-run", "live"}, "invalid mutation mode")
55+
if mutation["mutatedHost"]:
56+
require(mutation["mode"] == "live", "mutatedHost=true requires mode=live")
57+
require(mutation.get("humanControlEventRef"), "live host mutation requires human control event ref")
58+
59+
approval = data.get("operatorApprovalReceipt")
60+
require(isinstance(approval, dict), "operatorApprovalReceipt must be object")
61+
for field in ["approvalId", "actorRef", "subjectRef", "decision", "policyGateRef", "agentplaneRunRef", "deliveryExcellenceEventRef"]:
62+
require(field in approval, f"operatorApprovalReceipt missing {field}")
63+
require(approval["decision"] in {"approved", "rejected", "deferred", "accepted-risk", "revoked"}, "invalid operator decision")
64+
65+
66+
def main() -> int:
67+
try:
68+
data = load_json(EXAMPLE)
69+
validate(data)
70+
except (json.JSONDecodeError, ValidationError) as exc:
71+
print(f"TurtleTerm Agent Harness receipt validation failed: {exc}", file=sys.stderr)
72+
return 1
73+
print("OK: TurtleTerm Agent Harness receipt fixture validates")
74+
return 0
75+
76+
77+
if __name__ == "__main__":
78+
raise SystemExit(main())

0 commit comments

Comments
 (0)