Probity A2A-Mesh-Scorecard,v2
predicateType: https://getprobity.dev/predicate/v2/a2a-mesh-scorecard
An A2A-mesh-scorecard is a signed, offline-verifiable, composition-safety verdict about a mesh of agent-to-agent (A2A) microVMs. Where an adversarial-execution-evidence attestation records one SUT's bounded adversarial campaign, and a security-verdict reports one artifact's outcome against an attack corpus, an A2A-mesh-scorecard binds an aggregate subject - the whole mesh - and carries the verdict of an offline mesh evaluator over the delegation graph the mesh produced: did authority attenuate correctly as it flowed hop-to-hop, or did some hop broaden scope, break lineage, or escape its token?
The aggregate subject is a MeshManifest: the N member microVMs, each bound to its frozen-VM byte-identity substrate root and its execution-grounded model bill-of-materials, plus the transport hop-observation log and the closed-world egress-policy digest. The verdict surface is the MeshVerdict produced by an offline evaluator that replays the hop log - never re-run at verification time, and never parsed by the transport layer that recorded it (the layering rule below).
This document specifies the predicate body. The signing envelope, subject binding, and offline-verify mechanics are in signing-envelope.md. The member substrate roots are byteident proofs and the per-member model BOM is the ml-bom shape; the freeze-atomicity soundness of the underlying substrate is the mesh-quiescence attestation.
The key words MUST, MUST NOT, REQUIRED, SHOULD, MAY, and OPTIONAL are to be interpreted as in RFC 2119.
Subject
The subject is the MeshManifest, by aggregate digest - a single SHA-256 that commits to WHO ran (member ids), on WHAT frozen substrate (per-member substrate roots), with WHAT model weights (per-member ModelBOM digests), over WHAT delegation graph (hop log), under WHAT closed-world boundary (egress-policy digest).
"subject": [
{ "name": "<mesh-topology-label>", "digest": { "sha256": "<64-hex-mesh-manifest-digest>" } }
]
The MeshManifest digest is computed over a member-id-sorted canonical projection of the manifest,
so the digest is stable regardless of the order in which members were discovered. A producer MUST NOT
synthesize a missing member substrate root, ModelBOM, or egress-policy digest; an empty or
incomplete mesh is refused, never bound. The manifest schema is the reference implementation's
MeshManifest (cited below); this predicate binds it by digest and projects the per-member evidence,
rather than re-embedding the full BOMs.
Predicate
{
"member_count": 2,
"bound_members": [
{
"member_id": "mesh-member-a",
"substrate_root": "<64-hex>",
"substrate_proof_type": "fold-correctness-v1",
"model_bom_digest": "<64-hex>",
"model_bom_entries": 1,
"model_bom_unresolved": 0
}
],
"hop_count": 2,
"hop_log_sha256": "<64-hex>",
"egress_policy_digest": "<64-hex>",
"verdict": {
"scope": "ATTENUATED",
"lineage": "CONTINUOUS",
"reason": "all downstream hops attenuate under the root grant",
"causes": []
},
"composition_safe": true,
"verdict_rendered": "SAFE",
"quiescence_sound": true,
"quiescence_evidence_digest": null,
"honest_scope": {
"substrate_roots_real": false,
"hop_graph_live": false,
"note": "ILLUSTRATIVE SUBSTRATE: ...",
"quiescence_tier": "..."
},
"engine_commit": "<engine/schema-commit>"
}
Field semantics
-
member_count(REQUIRED, integer) - the number of mesh members. Equalslen(bound_members). -
bound_members(REQUIRED, array) - the per-member evidence projection, sorted bymember_id. Each entry binds one frozen microVM's substrate and model surface so a reader can join the routing graph (the hop log'starget_agent) to the member that ran it, and fetch the full BOM by id:member_id(REQUIRED, string) - the member microVM's mesh identity. It matches thetarget_agenta hop records when delegating to this member.substrate_root(REQUIRED, 64-hex) - the per-page Merkle root of this member's frozen-VM byte-identity capture (the byteident substrate-integrity root). Binding by root, not the whole proof, keeps the scorecard a pure subject-binding artifact; the proof is served alongside, keyed by the root.substrate_proof_type(REQUIRED, string) - the byteident proof-family discriminator the root came from (for examplefold-correctness-v1,clone-uniqueness-v1,entropy-uniqueness-v1), so a reader knows which proof family to fetch and verify for this member.model_bom_digest(REQUIRED, 64-hex) - the SHA-256 of this member's canonical ml-bomModelBOM, binding WHAT model surface the member ran without re-embedding it.model_bom_entries(REQUIRED, integer) - the count of resolved BOM entries.model_bom_unresolved(REQUIRED, integer) - the count of unresolved (honest-gap) BOM regions.
-
hop_count(REQUIRED, integer) - the number of delegation hops in the hop log. -
hop_log_sha256(REQUIRED, 64-hex) - the SHA-256 of the deterministic JSONL hop-observation log. The hop log is the closed-world who-called-whom record over which the offline evaluator's verdict was computed; this digest binds the exact graph the verdict was derived from. The HopObservation record schema is specified below. -
egress_policy_digest(REQUIRED, 64-hex) - the SHA-256 of the canonical egress policy in force, the closed-world boundary that makes the proxy the sole inter-agent channel. Equal to the manifest's egress digest and to the mesh-quiescence attestation'segress_policy_digestfor the same mesh. -
verdict(REQUIRED) - the offline mesh evaluator'sMeshVerdict. The producer carries it verbatim; it never re-runs the evaluator at verification time. Fields:scope(REQUIRED, string) - theAttenuationClass: the worst downstream authority scope observed across the graph. The benign value isATTENUATED(no provable escalation). Other values name a broadening relationship.lineage(REQUIRED, string) - theContinuityClass: whether the delegation lineage (context-id continuity across hops) is intact. The benign value isCONTINUOUS.reason(REQUIRED, string) - a human-readable summary of the verdict.causes(REQUIRED, array of strings) - the trace-to-auth-downgrade invariant cause set: the specific composition-safety violations the evaluator found. Each value MUST be from the closedauth_downgrade_causevocabulary (see below); an unknown cause is rejected, not silently bound. An empty array means no composition-safety violation was found.
-
composition_safe(REQUIRED, boolean) - the derived safety flag:trueiffverdict.scopeequalsATTENUATEDandverdict.causesis empty. This is the single machine-readable admission bit a consumer reads. -
verdict_rendered(REQUIRED, string) -"SAFE"or"UNSAFE", derived fromcomposition_safe. A display convenience; automated consumers readcomposition_safeand the structuredverdict. -
quiescence_sound(REQUIRED, boolean) - whether the freeze-atomicity tier for this mesh's substrate was demonstrated sound (the mesh-quiescence leg). For a synthetic fixture this is a TEMPLATE value, not a measured result; thehonest_scopeblock says which. -
quiescence_evidence_digest(OPTIONAL) - the SHA-256 of the companion mesh-quiescence bundle this scorecard cross-references, ornullwhen none is bound. When present, a verifier can fetch and verify that the freeze-atomicity evidence behindquiescence_soundexists and is itself signed. -
honest_scope(REQUIRED, object) - the maturity-honesty block. Two orthogonal booleans replace the retired coarseis_synthetic_fixtureflag, because "real jailed mesh" is two independent claims:substrate_roots_real(REQUIRED, boolean) -trueiff each member's substrate root was computed over a real frozen.memfrom a jailed microVM (vs an illustrative placeholder).hop_graph_live(REQUIRED, boolean) -trueiff the A→B→C delegation was observed live on the wire by the host mesh-relay (vs replayed from the canonical adversarial chain).hop_graph_live=truewithsubstrate_roots_real=falseis incoherent (a live hop implies the members ran sealed). A conformant PRODUCER MUST NOT emit this pair - it has no legal state and the producer fails closed at construction; a VERIFIER, by the verifier-tolerant rule, still treats a received(false, true)as illustrative rather than rejecting the whole predicate.note(REQUIRED, string) - the human-readable scope statement. For a synthetic fixture it states plainly that the per-member roots, BOM digests, and hop-graph verdict are illustrative and that this is NOT a live certification.quiescence_tier(REQUIRED, string) - a note on what thequiescence_soundflag reflects and that a synthetic fixture's flag is a template value.
-
engine_commit(REQUIRED, string) - the auditor-pinnable engine/manifest-schema commit, in the spirit of an SLSAbuilder.id. Honesty: an identity to compare, not a means to re-derive the producer. -
resident_members(OPTIONAL, array of strings) - the subset of member ids whose VMs were frozen WITH in-flight delegation state at the freeze instant - the memory-co-resident set the host relay reports. This is orthogonal tohonest_scope.hop_graph_live:hop_graph_livesays the delegation topology was observed on a real wire, whereasresident_memberssays which members were still memory-resident when the cohort was frozen. The two can diverge for async/streaming delegations, where an upstream member can tear down before the terminal freeze even though its hop was observed live; in a synchronous-nested run every member is resident, in an async run only a subset is. Every id MUST be a member id present inbound_members; a producer MUST NOT bind an id that is not in the mesh. The field is absent when the producer reports no resident set (an old verifier ignores it), and an empty array means no member was resident. It does NOT change the twohonest_scopebooleans. Values are sorted and deduplicated.
Forward-compatibility: a verifier MUST ignore unknown fields in the predicate and unknown keys in
honest_scopeor in abound_membersentry. A producer MAY add fields additively within v2.
auth_downgrade_cause vocabulary (closed)
verdict.causes draws from the closed trace-to-auth-downgrade invariant cause set. As with the
vocabulary registry, this enumeration is fail-closed at a version: a
verifier MUST reject any cause not in this list. The registered causes are:
IDENTITY_MISMATCH
TEMPORAL
DOWNGRADE
SCOPE_BROADENING
TOKENLESS_BREAK
IDENTITY_MISMATCH- a hop's verified identity does not match the identity the delegation graph requires (the callee is not who the lineage says it should be).TEMPORAL- a hop violates a temporal ordering invariant (a delegation references a task or context that, by timestamp, could not yet exist).DOWNGRADE- an authentication or attenuation downgrade across a hop (the invariant's namesake: authority that should only narrow instead loses a binding).SCOPE_BROADENING- a downstream hop requests or is granted a broader scope than its caller held - the core composition-safety failure.TOKENLESS_BREAK- a hop proceeds without the attenuation token the lineage required (an authority break with no token to bound it).
The full vocabulary registration is in vocabulary-registry.md.
HopObservation record schema
The hop log committed by hop_log_sha256 is deterministic JSONL: one HopObservation per line, sorted
keys. The record is the transport-level subset of one observed delegation hop. The recording layer
(the host mesh-router in the reference implementation) observes only what it can see at the wire
chokepoint without parsing the forwarded card-JWS or attenuation token - it records WHO forwarded
to WHOM and WHETHER a token was present by header presence only. The scope / identity / token-grant
fields are emitted at their empty encodings; the offline evaluator derives those facts (Tier-1 from
the callee card, Tier-2 from the token) when it replays the log. Parsing a JWS at the transport layer
would be a layering violation.
Per-hop record (the mesh-router's hopWire contract):
| JSON key | Type | Semantics |
|---|---|---|
context_id | string or null | The hop's A2A lineage anchor; null if none (the evaluator's lineage classifier reads a null root context as INDETERMINATE). |
reference_task_ids | array of strings | Prior task ids this hop references; emitted sorted for byte-stable records. Empty array (never null) when none. |
requested_scope | array of capability objects | The scope the hop requested. Empty at the transport layer (the router does not derive scope). Each capability: { "verb", "resource", "ttl_seconds" }. |
target_agent | string or null | The callee this hop delegated to (the callee member's mesh id; matches bound_members[].member_id). Caller is positional - the chain order is the call order. |
task_id | string or null | The id of the task this hop produced/advanced, or null. |
timestamp_ns | integer or null | Wall-clock observation time in nanoseconds; null (unstamped) no-ops the temporal invariant. The live router stamps every hop. |
token_bound_identity | string or null | The identity an attenuation token bound, derived offline; null at the transport layer. |
token_granted | array of capability objects or null | The capabilities a token granted, derived offline; null at the transport layer. |
token_present | boolean | Whether an attenuation token was present on the forwarded request - by header PRESENCE only; the token bytes are forwarded untouched and never parsed by the router. |
verified_identities | array of strings | The identities verified for the hop, derived offline; empty array at the transport layer. |
The router emits compact-separator JSON; the offline evaluator's record_hops produces the same key
set with spaced separators. The two are SEMANTICALLY identical JSON and replay to the same verdict -
the conformance property that matters is verdict-equality on replay, not byte-equality across the
language boundary (cited from the mesh-router's documentation).
Verification (third-party, offline)
After the envelope checks in signing-envelope.md succeed:
- Confirm the
subjectdigest equals the MeshManifest aggregate digest the verifier intends to bind to (recomputing it from the served manifest if available). - Validate every
verdict.causesvalue against the closedauth_downgrade_causevocabulary; reject any unknown cause. - Confirm
composition_safe == (verdict.scope == "ATTENUATED" and verdict.causes == [])and thatverdict_renderedagrees. - For each
bound_membersentry, optionally resolvesubstrate_rootto its byteident proof (thesubstrate_proof_typenames the family) andmodel_bom_digestto the served ml-bom, confirming the served BOM canonical-hashes to the bound digest. - If
quiescence_evidence_digestis present, resolve and verify the companion mesh-quiescence bundle and confirm it backs thequiescence_soundclaim. - Read
honest_scope.substrate_roots_real+hop_graph_live: whensubstrate_roots_realisfalsethe per-member roots are illustrative (not a jailed run); whenhop_graph_liveisfalsethe delegation is the canonical chain, not observed live. Only(true, true)is a fully live jailed-mesh certification;(true, false)is real sealed images with replayed delegation.(false, true)is incoherent — treat as illustrative. - If
resident_membersis present, confirm every id is one of thebound_members[].member_id, and read it as the frozen-mid-delegation subset - the members memory-co-resident at the freeze. Do NOT conflate it withhop_graph_live: a member can be observed on the wire (counted towardhop_graph_live) yet not be inresident_membersif it tore down before the terminal freeze. When the field is absent, the producer reported no resident set; an empty array means none were resident.
No live execution environment is required, and the offline evaluator is NOT re-run; the verdict it produced is carried under the signature.
What a verifier can and cannot independently check (honesty)
- A verifier CAN confirm the scorecard is authentic, bound to its MeshManifest subject, signed by a
trusted key, that the causes are all registered, and that
composition_safeis consistent with the recorded verdict; and it can fetch and verify the per-member byteident proofs, the model BOMs, and the companion quiescence evidence to confirm the bound evidence exists. - A verifier CANNOT, from the scorecard alone, confirm that the offline evaluator ran the way the producer reports, nor that the hop log is the real delegation graph the mesh produced (the verdict and the hop log are producer self-assertions carried under the signature). The scorecard's guarantee is integrity, provenance, subject binding, and internal consistency - plus the ability to chase every bound digest to its served evidence - not external re-execution of the mesh evaluation.
Why no perimeter sandbox can emit it (rationale)
The aggregate subject is the standards-capture point. A perimeter sandbox can record an application's
A2A calls, but it cannot bind each mesh member to a frozen-VM byte-identity substrate root
(byteident is a hypervisor-boundary capture) nor to an execution-grounded ml-bom (the
BOM's identity sources read the host's view of guest memory, not the application's API). The
hop log's value is that it is recorded at the sole inter-agent proxy chokepoint in a closed-world
mesh - a topology a perimeter sandbox does not impose. A competitor can copy the verdict shape; it
cannot honestly fill substrate_root, model_bom_digest, or a closed-world hop log without the
hypervisor-boundary substrate.
Status and maturity
- Format and verification: stable, real. The envelope, the aggregate subject binding, the
auth_downgrade_causevocabulary check, and the offline-verify procedure are implemented; the shipped bundle verifies under the pinned key (see the conformance vectors inconformance.json). - Shipped bundle: real escalation verdict over the A->B->C fixture (illustrative substrate). The
shipped signed bundle binds the non-trivial escalating A->B->C delegation mesh: the orchestrator A
holds a
readgrant, delegates to B (which attenuates faithfully), and B delegates to C which broadens towrite. The hop graph (hop_count == 3) and the offlineevaluate_meshverdict are REAL -- the evaluator's verbatim output:scope == "ESCALATED",lineage == "CONTINUOUS",causes == ["SCOPE_BROADENING"](theRUNTIME-CS-DELEGATIONgate catch), socomposition_safe == falseandverdict_rendered == "UNSAFE".honest_scopeis(substrate_roots_real=true, hop_graph_live=false): the three members were booted sealed in their own microVMs and frozen, so the per-membersubstrate_rootvalues are REAL page-merkle byte-identity roots (substrate_proof_type == "page-merkle-root-v1", three byte-distinct values, each cross-checked against the member's signed freeze-binding). The A→B→C delegation is still the canonical adversarial chain, not observed live. The ModelBOM is honestly empty for every member. The detection + substrate legs are real and re-verifiable; only the on-the-wire-observation leg is not yet. - Shipped: Phase 1a (real sealed substrate roots).
cmd/mesh-assembleover three frozen.memsnapshots → real roots →(substrate_roots_real=true, hop_graph_live=false). - Open deliverable: Phase 2 (live on-the-wire delegation). A host-side L7 mesh-relay observes a live
A→B→C delegation and freezes the cohort in-flight at the escalating hop →
(true, true). The builder and signer consume the real manifest unchanged (the--manifestpath;--hops-liveflips the second boolean). Seeresearch/62-mesh-cli-substrate-roots/PLAN.md. quiescence_soundhere is a template value carried alongside the fixture-derived manifest, not a measured freeze result tied to this specific mesh.
Related standards
- adversarial-execution-evidence - the single-agent adversarial-execution attestation; this is its multi-member composition-safety analog.
- mesh-quiescence - the freeze-atomicity attestation behind
quiescence_sound. - ml-bom - the per-member execution-grounded model bill-of-materials bound by digest.
- in-toto / DSSE
- the envelope; see signing-envelope.md.
- SLSA VSA -
engine_commitfollows the VSAbuilder.ididea.
Versioning
Why v2 exists
v2 renames the engine-identity field to engine_commit. v1 spelled that field with a vendor-specific name, which a vendor-neutral standard other implementers are expected to emit must not require. Renaming a REQUIRED key is breaking under the rule stated below, so it takes a new predicateType URI rather than a silent in-place edit: one type URI never means two shapes. There is no alias and no dual-read compatibility path, and v1 is withdrawn - a verifier reads engine_commit or it rejects the statement.
Rule
This is the a2a-mesh-scorecard v2 predicate. Adding a new optional field is additive and stays within
v2; a verifier ignores fields it does not recognize. The OPTIONAL resident_members field was added
additively (it does not change predicateType, the subject digest, or any required field).
A rename or removal of a field, a change to the
verdict shape, a change to the composition_safe derivation, or a change to the HopObservation
record key set is breaking and requires a new major version. The auth_downgrade_cause vocabulary follows the
vocabulary registry's own versioning (an added cause is a registry revision;
a renamed or removed cause is breaking).