proof-aware-crypto-tooling-.../tests/test_walletmcp.py

194 lines
7.9 KiB
Python
Raw Permalink Normal View History

"""The agent-native MCP surface: JSON-RPC plumbing + structured errors.
Protocol plumbing and read-only tools are tested with a hand-sealed
wallet (no live quorum needed). The crypto tools (verify_inbound,
request_signature, posture_challenge) are covered by the live end-to-end
test when the real quorum + dogfood signer are present.
"""
import base64
import hashlib
import json
import stat
from pathlib import Path
import pytest
from pacta.quorum import binary_path
from pacta.signing import generate_ed25519_keypair
from pacta.wallet import Wallet
from pacta.walletmcp import TOOLS, WalletMCP
def _sha256(data: bytes) -> str:
return hashlib.sha256(data).hexdigest()
def _fake_member(path: Path, verdict: str) -> None:
code = {"accept": 0, "reject": 1}[verdict]
out = {"accept": "OK", "reject": "INVALID"}[verdict]
path.write_text(f"#!/bin/sh\necho {out}\nexit {code}\n")
path.chmod(path.stat().st_mode | stat.S_IEXEC)
def _seal_wallet(tmp_path: Path, verdicts: dict, state_dir: Path) -> Wallet:
state_dir.mkdir(parents=True, exist_ok=True)
members = []
for name, verdict in verdicts.items():
binary = binary_path(name, state_dir)
_fake_member(binary, verdict)
members.append({
"backend": name, "component": f"{name}-ed25519-verified", "semantics": "test",
"entry_point": "test", "source_commit": "de" * 20, "repo_commit": "ca" * 20,
"binary_sha256": _sha256(binary.read_bytes()), "backend_cfg": "test", "risk_tier": "R4",
"evidence": {"leaf_hash": "00", "leaf_index": 0, "tree_size": 1, "inclusion_proof": [],
"sth": {"timestamp": "2099-01-01T00:00:00Z"}},
})
wallet = Wallet(tmp_path / "w")
for sub in (wallet.keys_dir, wallet.incidents_dir, wallet.receipts_dir,
wallet.quarantine_dir, wallet.airgap_dir / "outbox", wallet.airgap_dir / "inbox"):
sub.mkdir(parents=True, exist_ok=True)
capsule = {"type": "pacta.wallet.custody_capsule.v1", "created_at": "2026-07-06T00:00:00Z",
"members": members,
"policy": {"require_unanimity": True, "min_members": 2, "require_tier": "R4",
"freshness_max_age_days": 0},
"signing": {"backend": "test"}, "problems_at_init": []}
wallet.capsule_path.write_text(json.dumps(capsule, indent=2, sort_keys=True) + "\n")
wallet._append_ledger("genesis", {"capsule_sha256": "x"})
generate_ed25519_keypair(wallet.keys_dir / "warden.key.pem", wallet.keys_dir / "warden.pub.pem")
wallet._test_state_dir = state_dir # type: ignore[attr-defined]
return wallet
def _mcp(tmp_path):
wallet = _seal_wallet(tmp_path, {"a": "accept", "b": "accept"}, tmp_path / "state")
srv = WalletMCP(wallet.dir)
return srv, wallet
def _call(srv, method, params=None, msg_id=1):
return srv.handle({"jsonrpc": "2.0", "id": msg_id, "method": method, "params": params or {}})
def test_initialize_advertises_server(tmp_path):
srv, _ = _mcp(tmp_path)
res = _call(srv, "initialize")["result"]
assert res["serverInfo"]["name"] == "warden"
assert "protocolVersion" in res
def test_initialized_notification_has_no_response(tmp_path):
srv, _ = _mcp(tmp_path)
assert srv.handle({"jsonrpc": "2.0", "method": "notifications/initialized"}) is None
def test_tools_list_matches_declared(tmp_path):
srv, _ = _mcp(tmp_path)
names = {t["name"] for t in _call(srv, "tools/list")["result"]["tools"]}
assert names == {t["name"] for t in TOOLS}
assert "request_signature" in names
def test_every_tool_has_a_strict_schema():
for tool in TOOLS:
schema = tool["inputSchema"]
assert schema["type"] == "object"
assert schema.get("additionalProperties") is False, tool["name"]
def test_unknown_tool_is_jsonrpc_error(tmp_path):
srv, _ = _mcp(tmp_path)
res = _call(srv, "tools/call", {"name": "nope", "arguments": {}})
assert res["error"]["code"] == -32602
def test_wallet_status_tool(tmp_path):
srv, _ = _mcp(tmp_path)
res = _call(srv, "tools/call", {"name": "wallet_status"})["result"]
assert not res["isError"]
assert res["structuredContent"]["type"] == "pacta.wallet.posture.v1"
def test_custody_card_is_self_proving_shape(tmp_path):
srv, _ = _mcp(tmp_path)
card = _call(srv, "tools/call", {"name": "custody_card"})["result"]["structuredContent"]
assert card["type"] == "pacta.wallet.custody_card.v1"
for member in card["quorum"]["members"]:
assert "transparency_evidence" in member
def test_structured_error_on_bad_base64(tmp_path):
srv, _ = _mcp(tmp_path)
res = _call(srv, "tools/call", {
"name": "verify_inbound",
"arguments": {"payload_b64": "x", "signature_b64": "!!!", "public_key_b64": "!!!"},
})["result"]
assert res["isError"]
body = res["structuredContent"]
assert set(body) >= {"code", "reason", "missing", "remediation"}
assert body["code"] == "MALFORMED_INTENT"
def test_explain_refusal_when_none(tmp_path):
srv, _ = _mcp(tmp_path)
res = _call(srv, "tools/call", {"name": "explain_refusal"})["result"]
assert res["isError"]
assert res["structuredContent"]["code"] == "EVIDENCE_REQUIRED"
def test_server_never_crashes_on_garbage(tmp_path):
srv, _ = _mcp(tmp_path)
# missing method
res = _call(srv, "does/not/exist")
assert res["error"]["code"] == -32601
def test_request_signature_and_verify_roundtrip_live(tmp_path):
if not all(binary_path(b).exists() for b in ("dalek", "anza")):
pytest.skip("real quorum not built")
from pacta.dogfood import locate_verifier
if locate_verifier() is None:
pytest.skip("dogfood signer not built")
# seal against the REAL default-location binaries
real_hashes = {b: _sha256(binary_path(b).read_bytes()) for b in ("dalek", "anza", "risc0", "betrusted")
if binary_path(b).exists()}
wallet = Wallet(tmp_path / "w")
for sub in (wallet.keys_dir, wallet.incidents_dir, wallet.receipts_dir,
wallet.quarantine_dir, wallet.airgap_dir / "outbox", wallet.airgap_dir / "inbox"):
sub.mkdir(parents=True, exist_ok=True)
members = [{
"backend": b, "component": f"{b}-ed25519-verified", "semantics": "t", "entry_point": "t",
"source_commit": "x", "repo_commit": "y", "binary_sha256": h, "backend_cfg": "t",
"risk_tier": "R4",
"evidence": {"leaf_hash": "00", "leaf_index": 0, "tree_size": 1, "inclusion_proof": [],
"sth": {"timestamp": "2099-01-01T00:00:00Z"}},
} for b, h in real_hashes.items()]
capsule = {"type": "pacta.wallet.custody_capsule.v1", "created_at": "2026-07-06T00:00:00Z",
"members": members,
"policy": {"require_unanimity": True, "min_members": 2, "require_tier": "R4",
"freshness_max_age_days": 0},
"signing": {"backend": "test"}, "problems_at_init": []}
wallet.capsule_path.write_text(json.dumps(capsule, indent=2, sort_keys=True) + "\n")
wallet._append_ledger("genesis", {"capsule_sha256": "x"})
generate_ed25519_keypair(wallet.keys_dir / "warden.key.pem", wallet.keys_dir / "warden.pub.pem")
srv = WalletMCP(wallet.dir)
payload = b"live mcp roundtrip"
res = _call(srv, "tools/call", {
"name": "request_signature",
"arguments": {"payload_b64": base64.b64encode(payload).decode(), "purpose": "test"},
})["result"]
assert not res["isError"], res["structuredContent"]
sig = bytes.fromhex(res["structuredContent"]["signature_hex"])
pub = bytes.fromhex(res["structuredContent"]["public_key_hex"])
vi = _call(srv, "tools/call", {
"name": "verify_inbound",
"arguments": {
"payload_b64": base64.b64encode(payload).decode(),
"signature_b64": base64.b64encode(sig).decode(),
"public_key_b64": base64.b64encode(pub).decode(),
},
})["result"]
assert vi["structuredContent"]["classification"] == "unanimous-accept"