Skip to content
Open
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
89 changes: 89 additions & 0 deletions mesh/agents/sar402_verification_agent.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
"""SAR-402 execution/service-delivery verification agent.

This agent and its sibling module `sar402_verify_tool.py` import only from
`sar402_verify_tmp` (a small, self-contained, vendored package included in
this repository -- see `sar402_verify_tmp/README.md`) and the framework's
own `mesh.mesh_agent.MeshAgent`. Neither module has any dependency on an
external project or repository at runtime.

`sar402_verify_tmp` is a verbatim-logic copy of the SAR-402 reference
implementation's evidence-normalization, receipt-construction, and
schema/authority-validation code, with only its schema file path and
internal import prefix adjusted for standalone operation -- no predicate,
constant, or verdict-vocabulary change.
"""

from __future__ import annotations

from typing import Any, Dict, List, Optional

from mesh.mesh_agent import MeshAgent # real framework import; only resolves
# when this file is placed inside a pinned-framework checkout.

from .sar402_verify_tool import TOOL_SCHEMA, VERIFY_TOOL_NAME, verify_settlement_evidence


class Sar402VerificationAgent(MeshAgent):
"""Deterministic SAR-402 execution/service-delivery verification agent.

Evaluates supplied structured evidence (optionally alongside x402
settlement context, treated as unverified supporting context) and
returns a deterministic PASS / FAIL / INDETERMINATE receipt with a
recomputable, digest-based integrity value. Never holds funds, custody,
or execution/release authority, and never claims payment settlement
finality, a cryptographic signature, or legal finality.
"""

def __init__(self) -> None:
super().__init__()
self.metadata.update(
{
"name": "SAR-402 Execution Verification",
"version": "0.1.0",
"author": "SAR-402 contributors",
"author_address": "0x0000000000000000000000000000000000000000",
"description": (
"Evaluates supplied execution/service-delivery evidence "
"(optionally with x402 settlement context as unverified "
"supporting input) and returns a deterministic "
"PASS/FAIL/INDETERMINATE receipt with reason codes and a "
"recomputable digest-based integrity value. Does not "
"verify payment settlement finality and does not produce "
"a cryptographic signature."
),
"external_apis": [],
"tags": ["verification", "x402", "sar-402"],
"hidden": True,
"verified": False,
"recommended": False,
"examples": [
"Verify a record-mode settlement + delivery evidence bundle",
"Verify a gate-mode payment-verified-pre-delivery bundle",
],
}
)

def get_system_prompt(self) -> str:
return (
"You evaluate structured execution/service-delivery evidence, "
"optionally alongside x402 settlement context supplied as "
"unverified context, and return a deterministic PASS, FAIL, or "
"INDETERMINATE receipt with reason codes. You never hold funds, "
"custody, or execution/release authority. You never claim to "
"verify payment settlement finality, and you never claim the "
"receipt's integrity value is a cryptographic signature -- it is "
"a recomputable digest only."
)

def get_tool_schemas(self) -> List[Dict[str, Any]]:
return [TOOL_SCHEMA]

async def _handle_tool_logic(
self,
tool_name: str,
function_args: Dict[str, Any],
session_context: Optional[Dict[str, Any]] = None,
) -> Dict[str, Any]:
if tool_name != VERIFY_TOOL_NAME:
return {"error": f"Unsupported tool: {tool_name}"}
return await verify_settlement_evidence(function_args, session_context)
180 changes: 180 additions & 0 deletions mesh/agents/sar402_verify_tool.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
"""The single tool this agent exposes: `verify_settlement_evidence`.

This module contains no verification logic of its own. It:

1. accepts a Heurist Mesh-shaped `tool_arguments` dict (structured
execution/service-delivery evidence, with an optional `x402_context`
passthrough block),
2. maps it onto the `sar402_verify_tmp.sar402_agent` manual evidence shape
(reusing the vendored normalizer, builder, and validator verbatim),
3. fails closed (returns a Mesh-style `{"error": ...}` payload, never a
guessed verdict) on malformed, incomplete, or authority-violating input,
4. returns a Mesh-style `{"status": "success", "data": {...}}` payload
wrapping the full SAR-402 receipt (PASS | FAIL | INDETERMINATE) plus a
`reproduction` block that lets an independent party recompute the
receipt's integrity digest without trusting this adapter's own verdict.

x402 context (`x402_context` in the input) is carried through into the
receipt's `notes`/`links` only as supporting/contextual information. This
adapter never claims that a receipt independently proves x402 payment
settlement or finality -- it only evaluates the five committed Continuity
predicates over the evidence it was given.
"""

from __future__ import annotations

from typing import Any, Dict, Optional

from sar402_verify_tmp.sar402 import compute_integrity, validate_receipt
from sar402_verify_tmp.sar402_agent import EvidenceError, normalize_manual
from sar402_verify_tmp.sar402_agent.runner import build_receipt

VERIFY_TOOL_NAME = "verify_settlement_evidence"

TOOL_SCHEMA: Dict[str, Any] = {
"type": "function",
"function": {
"name": VERIFY_TOOL_NAME,
"description": (
"Verify structured execution/service-delivery evidence (optionally "
"with x402 settlement context) and return a deterministic "
"PASS | FAIL | INDETERMINATE SAR-402 receipt, reproducible by an "
"independent verifier. Never releases funds, resources, or holds "
"execution authority."
),
"parameters": {
"type": "object",
"properties": {
"mode": {
"type": "string",
"enum": ["record", "gate"],
"description": (
"record = post-delivery (requires delivery evidence); "
"gate = payment-verified pre-delivery (no delivery yet)."
),
},
"payment": {
"type": "object",
"description": "Quote/settlement constraints (resource, quote_id, price, asset, chain, recipient, payer, payment_ref, ...).",
},
"identity": {
"type": "object",
"description": "Optional agent/wallet/authorized_payers context.",
},
"timestamps": {
"type": "object",
"description": "quoted_at, verified_at, issued_at (required); paid_at, delivered_at, quote_expires_at (optional).",
},
"delivery": {
"type": "object",
"description": "Delivery evidence (record mode only): delivered_resource, evidence_type, evidence_digest, status_code, delivered_at, failed.",
},
"authority": {
"type": "object",
"description": "acting_party (record mode) or gate_controller/release_policy (gate mode). The verifier itself can never be named as gate_controller.",
},
"x402_context": {
"type": "object",
"description": (
"Optional supporting x402 facilitator/settlement reference "
"(e.g. facilitator id, tx hash). Carried through as context "
"only -- this tool does not independently re-verify x402 "
"payment finality."
),
},
},
"required": ["mode", "payment", "timestamps"],
},
},
}


def _evidence_doc(function_args: Dict[str, Any]) -> Dict[str, Any]:
"""Strip the Mesh-only `x402_context` passthrough key before handing the
rest to the committed `normalize_manual` shape (which does not know about
it). x402_context, when present, is folded into `notes` for traceability."""
doc = {k: v for k, v in function_args.items() if k != "x402_context"}
return doc


def _x402_note(function_args: Dict[str, Any]) -> Optional[str]:
ctx = function_args.get("x402_context")
if not ctx:
return None
parts = [f"{k}={v}" for k, v in sorted(ctx.items())]
return "x402_context(supporting, not independently reverified): " + ", ".join(parts)


def independent_reproduction(receipt: Dict[str, Any]) -> Dict[str, Any]:
"""Recompute the receipt's integrity digest independently of the adapter
that produced it, and re-run the committed schema/authority validator.

This is the "independent verifier" step: it does not trust the adapter's
own `sar_verdict` claim -- it recomputes the digest from the receipt body
(minus the `integrity` block) using the same committed canonicalization the
builder used, and compares it against the receipt's stated digest. A
tampered or fabricated receipt will fail this check.
"""
stated_integrity = receipt.get("integrity")
body = {k: v for k, v in receipt.items() if k != "integrity"}
recomputed = compute_integrity(body)
digest_match = stated_integrity == recomputed

schema_errors = []
try:
validate_receipt(receipt)
schema_valid = True
except Exception as exc: # committed validator raises on any failure
schema_valid = False
schema_errors.append(str(exc))

return {
"digest_match": digest_match,
"stated_integrity": stated_integrity,
"recomputed_integrity": recomputed,
"schema_valid": schema_valid,
"schema_errors": schema_errors,
"reproducible": digest_match and schema_valid,
}


async def verify_settlement_evidence(
function_args: Dict[str, Any],
session_context: Optional[Dict[str, Any]] = None,
) -> Dict[str, Any]:
"""Mesh-shaped tool handler: normalize -> build (committed) -> validate ->
attach independent-reproduction evidence. Fails closed on any evidence or
authority error (never guesses a verdict)."""
try:
doc = _evidence_doc(function_args)
normalized = normalize_manual(doc)
receipt = build_receipt(normalized)
validate_receipt(receipt) # defense in depth, mirrors sar402_agent.runner
except EvidenceError as exc:
return {
"status": "rejected",
"error": f"evidence rejected ({type(exc).__name__}): {exc}",
}
except Exception as exc: # schema/build errors: fail closed, do not guess
return {
"status": "rejected",
"error": f"verification failed closed ({type(exc).__name__}): {exc}",
}

note = _x402_note(function_args)
if note:
receipt = dict(receipt)
receipt["notes"] = (receipt.get("notes") + " | " + note) if receipt.get("notes") else note
# Notes were appended after the committed builder computed integrity;
# recompute so the returned receipt's own digest stays self-consistent.
body = {k: v for k, v in receipt.items() if k != "integrity"}
receipt["integrity"] = compute_integrity(body)
validate_receipt(receipt)

return {
"status": "success",
"data": {
"receipt": receipt,
"reproduction": independent_reproduction(receipt),
},
}
40 changes: 40 additions & 0 deletions mesh/tests/sar402_fixtures/fail_constraint_drift.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
{
"mode": "record",
"payment": {
"resource": "https://mesh.example/agents/weather-forecast?region=eu-west",
"quote_id": "q_heu_002",
"price": { "amount": "5000", "asset": "USDC", "decimals": 6 },
"amount_paid": { "amount": "4000", "asset": "USDC", "decimals": 6 },
"asset": "USDC",
"chain": "eip155:8453",
"recipient": "0x9aE2b7c1F0d4E5a6B8c3D2e1F09A8b7C6d5E4F30",
"payer": "0x1f4C8d2A9b3E6c7D0a5B4e3F2c1D0a9B8e7C6d5F",
"payment_ref": "0xbf42d1e0c3e958f7b2d6f4013c8e5f9d2a7b1c0e3f4d5e6f708192a3b4c5d6e",
"facilitator": "facilitator.heurist.xyz"
},
"identity": {
"agent": "mesh.heurist.ai/weather-forecast-agent",
"wallet": "0x1f4C8d2A9b3E6c7D0a5B4e3F2c1D0a9B8e7C6d5F",
"authorized_payers": ["0x1f4C8d2A9b3E6c7D0a5B4e3F2c1D0a9B8e7C6d5F"]
},
"timestamps": {
"quoted_at": "2026-07-31T14:10:00Z",
"paid_at": "2026-07-31T14:10:20Z",
"verified_at": "2026-07-31T14:10:25Z",
"delivered_at": "2026-07-31T14:10:30Z",
"issued_at": "2026-07-31T14:10:31Z",
"quote_expires_at": "2026-07-31T14:15:00Z"
},
"delivery": {
"delivered_resource": "https://mesh.example/agents/weather-forecast?region=eu-west",
"evidence_type": "http_response",
"evidence_digest": "sha256:2b3c4d5e6f708192a3b4c5d6e7f80911223344556677889900aabbccddeeff",
"status_code": 200,
"delivered_at": "2026-07-31T14:10:30Z"
},
"authority": {
"acting_party": "resource_server",
"verifier_has_execution_authority": false
},
"_fixture_note": "amount_paid (4000) drifted below the quoted price (5000) -> constraint_continuity FAIL -> sar_verdict FAIL."
}
40 changes: 40 additions & 0 deletions mesh/tests/sar402_fixtures/fail_evidence_mismatch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
{
"mode": "record",
"payment": {
"resource": "https://mesh.example/agents/weather-forecast?region=eu-west",
"quote_id": "q_heu_003",
"price": { "amount": "5000", "asset": "USDC", "decimals": 6 },
"amount_paid": { "amount": "5000", "asset": "USDC", "decimals": 6 },
"asset": "USDC",
"chain": "eip155:8453",
"recipient": "0x9aE2b7c1F0d4E5a6B8c3D2e1F09A8b7C6d5E4F30",
"payer": "0x1f4C8d2A9b3E6c7D0a5B4e3F2c1D0a9B8e7C6d5F",
"payment_ref": "0xc053e2f1d4fa69f8c3e7f5124d9f6e0a3b8c2d1f4e5f6f708192a3b4c5d6e7f",
"facilitator": "facilitator.heurist.xyz"
},
"identity": {
"agent": "mesh.heurist.ai/weather-forecast-agent",
"wallet": "0x1f4C8d2A9b3E6c7D0a5B4e3F2c1D0a9B8e7C6d5F",
"authorized_payers": ["0x1f4C8d2A9b3E6c7D0a5B4e3F2c1D0a9B8e7C6d5F"]
},
"timestamps": {
"quoted_at": "2026-07-31T14:20:00Z",
"paid_at": "2026-07-31T14:20:20Z",
"verified_at": "2026-07-31T14:20:25Z",
"delivered_at": "2026-07-31T14:20:30Z",
"issued_at": "2026-07-31T14:20:31Z",
"quote_expires_at": "2026-07-31T14:25:00Z"
},
"delivery": {
"delivered_resource": "https://mesh.example/agents/wrong-resource?region=us-east",
"evidence_type": "http_response",
"evidence_digest": "sha256:3c4d5e6f708192a3b4c5d6e7f80911223344556677889900aabbccddeeff01",
"status_code": 200,
"delivered_at": "2026-07-31T14:20:30Z"
},
"authority": {
"acting_party": "resource_server",
"verifier_has_execution_authority": false
},
"_fixture_note": "delivered_resource does not match the requested/paid-for resource -> object_continuity FAIL, executor_continuity FAIL -> sar_verdict FAIL."
}
39 changes: 39 additions & 0 deletions mesh/tests/sar402_fixtures/indeterminate_unknown_authority.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
{
"mode": "record",
"payment": {
"resource": "https://mesh.example/agents/weather-forecast?region=eu-west",
"quote_id": "q_heu_004",
"price": { "amount": "5000", "asset": "USDC", "decimals": 6 },
"amount_paid": { "amount": "5000", "asset": "USDC", "decimals": 6 },
"asset": "USDC",
"chain": "eip155:8453",
"recipient": "0x9aE2b7c1F0d4E5a6B8c3D2e1F09A8b7C6d5E4F30",
"payer": "0x1f4C8d2A9b3E6c7D0a5B4e3F2c1D0a9B8e7C6d5F",
"payment_ref": "0xd164f3021f5b70a9d4f8062350a7f1b4c9d3e2f5f6f708192a3b4c5d6e7f809",
"facilitator": "facilitator.heurist.xyz"
},
"identity": {
"agent": "mesh.heurist.ai/weather-forecast-agent",
"wallet": "0x1f4C8d2A9b3E6c7D0a5B4e3F2c1D0a9B8e7C6d5F"
},
"timestamps": {
"quoted_at": "2026-07-31T14:30:00Z",
"paid_at": "2026-07-31T14:30:20Z",
"verified_at": "2026-07-31T14:30:25Z",
"delivered_at": "2026-07-31T14:30:30Z",
"issued_at": "2026-07-31T14:30:31Z",
"quote_expires_at": "2026-07-31T14:35:00Z"
},
"delivery": {
"delivered_resource": "https://mesh.example/agents/weather-forecast?region=eu-west",
"evidence_type": "http_response",
"evidence_digest": "sha256:4d5e6f708192a3b4c5d6e7f80911223344556677889900aabbccddeeff0112",
"status_code": 200,
"delivered_at": "2026-07-31T14:30:30Z"
},
"authority": {
"acting_party": "resource_server",
"verifier_has_execution_authority": false
},
"_fixture_note": "identity.authorized_payers is omitted (unknown, not asserted) -> authority_continuity INDETERMINATE (explicit reason code in the receipt's continuity block) -> sar_verdict INDETERMINATE, even though delivery/constraint checks pass."
}
Loading