A2A Verification Context¶
Status: v1.4.0-alpha.3 — implemented in Rust, Python, JavaScript, and Go. Mirrors the AgentPin v0.3
AllowedDomainsconvention so the two trust stacks compose.
When agents collaborate over A2A (Agent-to-Agent), a tool schema verified by one agent crosses a trust boundary into another. The standard offline verification answers "is this schema authentically signed by its provider?" — but in an A2A flow you also need to answer "is this provider's domain one the calling agent is allowed to trust?"
verify_schema_for_a2a runs the standard verification and adds two A2A-aware checks:
- Delegation-depth cap — reject when
delegation_depthexceedsA2A_MAX_DELEGATION_DEPTH(3), matching AgentPin'smax_delegation_depth. - Scope check — reject when the tool provider's domain is not allowed by the caller's trusted-domains allow-list.
The cryptographic outcome is unchanged — A2A context only adds a policy gate. A failure surfaces as the A2A_SCOPE_VIOLATION error code.
AllowedDomains convention¶
The trusted_domains allow-list follows AgentPin v0.3's AllowedDomains semantics exactly:
- An empty list means unrestricted (all domains trusted) — not "deny-all". This matches v1.3 behaviour where an omitted allow-list permitted all domains.
- A non-empty list allows a domain when it matches an entry literally, or via a leading
*.wildcard (*.client.commatchesapi.client.combut notclient.comitself). - Intersection follows AgentPin spec §4.11.4:
unrestricted ∩ X = X.
SchemaPin re-implements these helpers (is_unrestricted / allows / intersect) locally rather than depending on the AgentPin package, keeping the tool-integrity library self-contained. The wire and in-memory shapes are identical, so callers who do link AgentPin can pass agentpin.AllowedDomains.intersect(...) results straight into trusted_domains.
See Technical specification §20 for the normative definition.
A2aVerificationContext¶
| Field | Meaning |
|---|---|
caller_agent_id |
Caller's agent identity (URN-style, matching AgentPin). Informational. |
delegation_depth |
Depth in the A2A delegation chain; 0 = direct caller. Rejected above 3. |
originating_domain |
Originating domain of the A2A request. Informational. |
trusted_domains |
Caller-trusted domains. Empty = unrestricted. |
(Field names are camel/Pascal-cased per language — e.g. delegationDepth in JS, DelegationDepth in Go.)
Usage¶
Rust¶
use schemapin::A2aVerificationContext;
use schemapin::verification::verify_schema_for_a2a;
use schemapin::pinning::KeyPinStore;
let context = A2aVerificationContext {
caller_agent_id: "urn:agent:coordinator".to_string(),
delegation_depth: 1,
originating_domain: "coordinator.example".to_string(),
trusted_domains: vec!["*.thirdkey.ai".to_string()],
};
let result = verify_schema_for_a2a(
&schema,
&signature_b64,
"api.thirdkey.ai", // tool provider domain
"calculate_sum", // tool_id
&discovery,
None, // revocation
&mut KeyPinStore::new(),
&context,
None, // canonicalization (default schemapin-v1)
);
assert!(result.valid);
Use A2aVerificationContext::unrestricted("urn:agent:...") to verify with no domain restriction.
Python¶
from schemapin.a2a import A2aVerificationContext
from schemapin.verification import verify_schema_for_a2a, KeyPinStore
context = A2aVerificationContext(
caller_agent_id="urn:agent:coordinator",
delegation_depth=1,
originating_domain="coordinator.example",
trusted_domains=["*.thirdkey.ai"],
)
result = verify_schema_for_a2a(
schema, signature_b64, "api.thirdkey.ai", "calculate_sum",
discovery, None, KeyPinStore(), context,
)
assert result.valid
JavaScript¶
import { A2aVerificationContext } from "schemapin";
import { verifySchemaForA2a, KeyPinStore } from "schemapin";
const context = new A2aVerificationContext({
callerAgentId: "urn:agent:coordinator",
delegationDepth: 1,
originatingDomain: "coordinator.example",
trustedDomains: ["*.thirdkey.ai"],
});
const result = verifySchemaForA2a(
schema, signatureB64, "api.thirdkey.ai", "calculate_sum",
discovery, null, new KeyPinStore(), context,
);
Go¶
ctx := &verification.A2AVerificationContext{
CallerAgentID: "urn:agent:coordinator",
DelegationDepth: 1,
OriginatingDomain: "coordinator.example",
TrustedDomains: []string{"*.thirdkey.ai"},
}
result := verification.VerifySchemaForA2A(
schema, signatureB64, "api.thirdkey.ai", "calculate_sum",
discovery, nil, pinStore, ctx,
)
Failure modes¶
| Condition | Result |
|---|---|
delegation_depth > 3 |
A2A_SCOPE_VIOLATION (checked before any crypto) |
Provider domain not in a non-empty trusted_domains |
A2A_SCOPE_VIOLATION |
| Standard verification fails (bad signature, revoked key, pin mismatch, …) | the underlying error, unchanged |
A2A context never makes a cryptographically invalid schema pass — it can only add a restriction.
Related¶
- Trust Bundle Distribution — sign and exchange trust bundles between agents over A2A.