Signed policy bundles & evidence proofs
Two separate guarantees: a policy bundle lets you verify the RULES an agent is bound to without trusting whoever handed you a copy of them, and an evidence proof lets you verify that a DECISION was actually made without trusting MetaMynd's own record of it.
Signed policy bundles
- GET /policy/bundle/:did — the agent's evaluable policy (its Standards and SOPs, compiled), signed by the issuer
- GET /magp/policy/pubkey — the verification key
- A guard MUST verify the signature before evaluating, and MUST refuse an unsigned or invalid bundle for a value-bearing action — POLICY_BUNDLE_UNSIGNED, POLICY_BUNDLE_SIGNATURE_INVALID
- Bundles carry a maxStaleness; a value-bearing action fails closed on a stale one too — POLICY_BUNDLE_STALE
- policyPublicKey pins the key at the CALLER, not just the bundle fetch — a scaffolded gateway that sets it can no longer be handed a valid-looking bundle signed by the wrong key at all, closing a man-in-the-middle swap rather than merely detecting one after the fact
Evidence and anchoring
Every decision produces an evidence record. Records are batched, hashed into a Merkle tree, and a single root is anchored — so anchoring cost doesn't scale with decision volume. GET /magp/evidence/:eventId/proof returns the leaf, the path, and the anchored root: enough to check inclusion completely offline, without asking MetaMynd anything. That's the actual property being claimed: a regulator or an auditor recomputes the proof themselves rather than trusting a copy of the record. The guard's own proof(eventId) does exactly that recomputation client-side, and reports failed and pending as different outcomes — a decision made seconds ago is normally pending (anchoring is asynchronous), never silently read as failed.
What's in the leaf, and what isn't
The leaf composition is part of the protocol — adding a field changes every digest and invalidates every previously anchored proof, so it's treated as a breaking change. What's deliberately excluded: context field VALUES. A decision record keeps the names of the context fields a rule evaluated (so an auditor can tell "the spend-cap clause never saw an amount" from "it saw one and passed it"), never their values — those stay the tenant's.
See it in code
// decision.eventId comes back on every verdict (allow, block or escalate alike).
const result = await guard.proof(decision.eventId);
// The SDK fetched the leaf + sibling chain + anchored root, then recomputed the
// root ITSELF and compared — `verified` is this code's own conclusion, not a
// claim it took on trust.
switch (result.status) {
case "verified": /* provably in the anchored batch */ break;
case "pending": /* anchoring is async — check again shortly, not an error */ break;
case "failed": /* the proof does NOT reconstruct the root — take this seriously */ break;
case "unreachable": /* could not ask the gate — nothing implied either way */ break;
}