# 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](../v1/adversarial-execution-evidence.md) attestation records one SUT's
bounded adversarial campaign, and a [security-verdict](../v1/security-verdict.md) 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](../v1/signing-envelope.md). The member substrate roots are byteident
proofs and the per-member model BOM is the [ml-bom](../v1/ml-bom.md) shape; the freeze-atomicity soundness
of the underlying substrate is the [mesh-quiescence](mesh-quiescence.md) attestation.

The key words MUST, MUST NOT, REQUIRED, SHOULD, MAY, and OPTIONAL are to be interpreted as in
[RFC 2119](https://www.rfc-editor.org/rfc/rfc2119).

## 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).

```json
"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

```json
{
  "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. Equals `len(bound_members)`.

- **`bound_members`** (REQUIRED, array) - the per-member evidence projection, sorted by `member_id`.
  Each entry binds one frozen microVM's substrate and model surface so a reader can join the routing
  graph (the hop log's `target_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 the
    `target_agent` a 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 example `fold-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-bom](../v1/ml-bom.md) `ModelBOM`, 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](mesh-quiescence.md) attestation's `egress_policy_digest`
  for the same mesh.

- **`verdict`** (REQUIRED) - the offline mesh evaluator's `MeshVerdict`. The producer carries it
  verbatim; it never re-runs the evaluator at verification time. Fields:
  - **`scope`** (REQUIRED, string) - the `AttenuationClass`: the worst downstream authority scope
    observed across the graph. The benign value is `ATTENUATED` (no provable escalation). Other
    values name a broadening relationship.
  - **`lineage`** (REQUIRED, string) - the `ContinuityClass`: whether the delegation lineage
    (context-id continuity across hops) is intact. The benign value is `CONTINUOUS`.
  - **`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
    closed `auth_downgrade_cause` vocabulary (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: `true` iff `verdict.scope`
  equals `ATTENUATED` and `verdict.causes` is empty. This is the single machine-readable admission
  bit a consumer reads.

- **`verdict_rendered`** (REQUIRED, string) - `"SAFE"` or `"UNSAFE"`, derived from `composition_safe`.
  A display convenience; automated consumers read `composition_safe` and the structured `verdict`.

- **`quiescence_sound`** (REQUIRED, boolean) - whether the freeze-atomicity tier for this mesh's
  substrate was demonstrated sound (the [mesh-quiescence](mesh-quiescence.md) leg). For a synthetic
  fixture this is a TEMPLATE value, not a measured result; the `honest_scope` block says which.

- **`quiescence_evidence_digest`** (OPTIONAL) - the SHA-256 of the companion
  [mesh-quiescence](mesh-quiescence.md) bundle this scorecard cross-references, or `null` when none is
  bound. When present, a verifier can fetch and verify that the freeze-atomicity evidence behind
  `quiescence_sound` exists and is itself signed.

- **`honest_scope`** (REQUIRED, object) - the maturity-honesty block. Two orthogonal booleans replace
  the retired coarse `is_synthetic_fixture` flag, because "real jailed mesh" is two independent claims:
  - **`substrate_roots_real`** (REQUIRED, boolean) - `true` iff each member's substrate root was computed
    over a real frozen `.mem` from a jailed microVM (vs an illustrative placeholder).
  - **`hop_graph_live`** (REQUIRED, boolean) - `true` iff 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=true`
    with `substrate_roots_real=false` is 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 the `quiescence_sound` flag 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 SLSA `builder.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 to `honest_scope.hop_graph_live`**: `hop_graph_live` says the delegation
  topology was observed on a real wire, whereas `resident_members` says 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 in `bound_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 two `honest_scope` booleans. Values are sorted and
  deduplicated.

> Forward-compatibility: a verifier MUST ignore unknown fields in the predicate and unknown keys in
> `honest_scope` or in a `bound_members` entry. 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](../v1/vocabulary-registry.md), 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](../v1/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](../v1/signing-envelope.md) succeed:

1. Confirm the `subject` digest equals the MeshManifest aggregate digest the verifier intends to bind
   to (recomputing it from the served manifest if available).
2. Validate every `verdict.causes` value against the closed `auth_downgrade_cause` vocabulary; reject any unknown cause.
3. Confirm `composition_safe == (verdict.scope == "ATTENUATED" and verdict.causes == [])` and that
   `verdict_rendered` agrees.
4. For each `bound_members` entry, optionally resolve `substrate_root` to its byteident proof (the
   `substrate_proof_type` names the family) and `model_bom_digest` to the served [ml-bom](../v1/ml-bom.md),
   confirming the served BOM canonical-hashes to the bound digest.
5. If `quiescence_evidence_digest` is present, resolve and verify the companion
   [mesh-quiescence](mesh-quiescence.md) bundle and confirm it backs the `quiescence_sound` claim.
6. Read `honest_scope.substrate_roots_real` + `hop_graph_live`: when `substrate_roots_real` is `false`
   the per-member roots are illustrative (not a jailed run); when `hop_graph_live` is `false` the
   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.
7. If `resident_members` is present, confirm every id is one of the `bound_members[].member_id`, and read
   it as the frozen-mid-delegation subset - the members memory-co-resident at the freeze. Do NOT conflate
   it with `hop_graph_live`: a member can be observed on the wire (counted toward `hop_graph_live`) yet
   not be in `resident_members` if 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_safe` is 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](../v1/ml-bom.md) (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_cause` vocabulary check, and the offline-verify procedure are implemented; the shipped bundle verifies
  under the pinned key (see the conformance vectors in `conformance.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 `read` grant, delegates to B (which attenuates faithfully), and B delegates to C which
  broadens to `write`. The hop graph (`hop_count == 3`) and the offline `evaluate_mesh` verdict are
  REAL -- the evaluator's verbatim output: `scope == "ESCALATED"`, `lineage == "CONTINUOUS"`,
  `causes == ["SCOPE_BROADENING"]` (the `RUNTIME-CS-DELEGATION` gate catch), so `composition_safe ==
  false` and `verdict_rendered == "UNSAFE"`. `honest_scope` is `(substrate_roots_real=true,
  hop_graph_live=false)`: the three members were booted sealed in their own microVMs and frozen, so the
  per-member `substrate_root` values 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-assemble` over three frozen `.mem`
  snapshots → 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 `--manifest` path; `--hops-live` flips the second
  boolean). See `research/62-mesh-cli-substrate-roots/PLAN.md`.
- **`quiescence_sound` here 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](../v1/adversarial-execution-evidence.md)** - the single-agent
  adversarial-execution attestation; this is its multi-member composition-safety analog.
- **[mesh-quiescence](mesh-quiescence.md)** - the freeze-atomicity attestation behind `quiescence_sound`.
- **[ml-bom](../v1/ml-bom.md)** - the per-member execution-grounded model bill-of-materials bound by digest.
- **[in-toto](https://github.com/in-toto/attestation) / [DSSE](https://github.com/secure-systems-lab/dsse)**
  - the envelope; see [signing-envelope.md](../v1/signing-envelope.md).
- **[SLSA VSA](https://slsa.dev/spec/v1.0/verification_summary)** - `engine_commit` follows the VSA
  `builder.id` idea.

## 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](../v1/vocabulary-registry.md)'s own versioning (an added cause is a registry revision;
a renamed or removed cause is breaking).
