Skip to content

Verification

The core verification engine and FastAPI router. See Tutorial: Server-side verification for usage examples.

Public API

For gateway and runtime-session binding, call the package-root export rather than the private _verify module:

from agent_manifest import RevocationStore, VerificationContext, verify_manifest

verify_manifest() is the supported high-level entry point. VerificationContext.trusted_keys maps an issuer key_id (the SHA-256 hex of the public key bytes) to its base64url-encoded Ed25519 public key, the form returned by Ed25519KeyPair.public_b64url(). A consumer that holds raw public key bytes must base64url-encode them before populating trusted_keys. Signers and verifiers share agent_manifest.signing_pre_image() for the exact RFC 8785 canonical byte sequence, including the hitl_record.approvals normalization, so a relying party never reconstructs the pre-image itself.

Core function

verify_manifest

verify_manifest(manifest: Union[dict[str, Any], bytes], context: VerificationContext, revocation_store: 'RevocationStore', *, _envelope: Optional[CoseVerification] = None) -> VerificationResult

Core verification engine - hosting-model agnostic and fail-closed.

Checks version compatibility, signature, expiry, revocation, artifact hashes, delegation chain, and HITL. Returns a VerificationResult with per-field status and mismatch details.

Accepts either envelope, selected by what it is given (ADR-0011):

  • A dict is a version 0.1 manifest carrying a detached signature block, verified over the RFC 8785 pre-image exactly as it always has been.
  • bytes are a version 0.2 COSE envelope (COSE_Sign1 or COSE_Sign). The signature is checked over the payload as received, and receipts, attestation, and approvals are read from the unprotected header after the signature is settled.

Fail-closed semantics (spec 5.3 - VALID requires a valid signature):

  • A manifest with an unsupported (or missing) version returns INCOMPATIBLE_VERSION without further processing (spec 2.4).
  • A manifest without a signature block returns SIGNATURE_MISSING.
  • A signed manifest verified without any trusted_keys in the context returns UNVERIFIABLE - never VALID.
  • A delegation chain that cannot be verified (no delegation_public_keys) is marked UNVERIFIABLE and the overall result is UNVERIFIABLE (spec 3.4.1 / 5.2).
  • A delegation chain that verifies signatures and scope narrowing but carries a non-empty scope_grant.constraints (a Cedar statement this verifier does not parse or evaluate) is also marked UNVERIFIABLE, not VALID (spec 3.4.1 / 5.2).
  • enforce_hitl=True with no hitl_record in the manifest is a failure (HitlResult.MISSING and a non-VALID overall result).
  • An approval is never APPROVED unless its own signature verifies with a trusted approver key and binds the current manifest ID and scope.

The _envelope parameter is internal: it carries an already-appraised COSE envelope into the shared pipeline and is not part of the public API.

Context

VerificationContext

Bases: BaseModel

Runtime artifact hashes and keys provided by the trusted component.

Results

VerificationResult

Bases: BaseModel

OverallResult

Bases: str, Enum

FieldsVerified

Bases: BaseModel

FieldResult

Bases: str, Enum

DelegationResult

Bases: str, Enum

HitlResult

Bases: str, Enum

MismatchDetail

Bases: BaseModel

EvidencePack is an optional reference (trace id, signer, hash, and URI) to an externally retained evidence pack that a verifier can record alongside a result.

EvidencePack

Bases: BaseModel

TRACE envelopes and evidence packs

EvidencePack above is only a reference to a pack. To appraise the pack itself, and the per-tool-call TRACE envelopes inside it, use:

from agent_manifest import verify_evidence_pack, verify_trace_envelope

A TRACE envelope (spec §6.3.2) is signed by a TEE-sealed key over the RFC 8785 canonical form of every field except signature. The envelope carries no algorithm or key id of its own, so the caller supplies both; trusted_keys uses the same key_id → base64url public key mapping as VerificationContext. An evidence pack (spec §5.2.1) instead carries a detached pack_signature object in the form of §3.6, so hybrid signatures work there but not on envelopes.

Read admissible, not just status. A TRACE reporting manifest_verification_result: MISMATCH or EXPIRED can have a perfectly valid signature — the runtime honestly recorded a bad state — but spec §6.3.2 says it "MUST NOT be accepted as evidence of a valid tool call for regulatory reporting purposes". verify_trace_envelope() returns status=VERIFIED with admissible=False in that case.

Passing manifest= additionally enforces the §6.3.2 hash-conflict rule (SCHEMA F-21): if the envelope's policy_hash differs from the manifest's artifacts.policy_bundle.hash, the envelope must declare MISMATCH. One that claims VALID over a conflicting hash is a spec violation and fails.

Verification is fail-closed: a missing key, an unknown algorithm, or a build without the [pq] extra yields UNVERIFIABLE, never VERIFIED.

verify_trace_envelope

verify_trace_envelope(envelope: dict[str, Any], *, trusted_keys: Optional[dict[str, str]] = None, key_id: Optional[str] = None, algorithm: str = 'Ed25519', manifest: Optional[dict[str, Any]] = None) -> TraceVerificationResult

Verify one TRACE envelope's signature and manifest binding (spec 6.3.2).

Parameters:

Name Type Description Default
envelope dict[str, Any]

The TRACE envelope dict.

required
trusted_keys Optional[dict[str, str]]

key_id (sha256 hex of raw public key bytes) -> base64url public key. The envelope carries no key id of its own.

None
key_id Optional[str]

Which trusted key signed this envelope. Optional when exactly one key is trusted.

None
algorithm str

"Ed25519" or "ML-DSA-65". Spec 6.3.2 types the envelope signature as a bare string, so hybrid is not expressible.

'Ed25519'
manifest Optional[dict[str, Any]]

When supplied, enforces the spec 6.3.2 hash-conflict rule (SCHEMA F-21) against artifacts.policy_bundle.hash.

None

Returns:

Name Type Description
A TraceVerificationResult

class:TraceVerificationResult. Check admissible before using

TraceVerificationResult

the envelope as evidence -- a verified signature alone is not enough.

verify_evidence_pack

verify_evidence_pack(pack: dict[str, Any], *, trusted_keys: Optional[dict[str, str]] = None, expected_pack_hash: Optional[str] = None, trace_key_id: Optional[str] = None, trace_algorithm: str = 'Ed25519', verify_envelopes: bool = True) -> EvidencePackVerificationResult

Verify an evidence pack's pack_signature and its TRACE envelopes.

Parameters:

Name Type Description Default
pack dict[str, Any]

The evidence pack document (spec 5.2.1).

required
trusted_keys Optional[dict[str, str]]

key_id -> base64url public key, covering both the pack's TEE-sealed key and the envelope signing key.

None
expected_pack_hash Optional[str]

When supplied (e.g. the pack_hash carried in a verification result's evidence_pack reference), it is compared against the recomputed hash.

None
trace_key_id Optional[str]

Key id for the envelopes inside the pack.

None
trace_algorithm str

Envelope signature algorithm.

'Ed25519'
verify_envelopes bool

Set False to appraise only the pack signature.

True

Returns:

Name Type Description
An EvidencePackVerificationResult

class:EvidencePackVerificationResult. status is VERIFIED

EvidencePackVerificationResult

only when the pack signature verifies, any supplied pack_hash

EvidencePackVerificationResult

matches, and (when checked) every envelope is admissible.

TraceVerificationResult dataclass

Appraisal of one TRACE envelope.

admissible is the question a relying party actually asks: may this record be used as evidence of a valid tool call? That needs a verified signature and a manifest_verification_result outside :data:INADMISSIBLE_RESULTS. A perfectly-signed envelope reporting MISMATCH is authentic and inadmissible at the same time -- the signature proves the runtime honestly recorded that the policy hash did not match, which is exactly the case spec 6.3.2 excludes from reporting.

EvidencePackVerificationResult dataclass

Appraisal of an evidence pack and, optionally, the TRACEs inside it.

TraceStatus

Bases: str, Enum

Outcome of appraising a TRACE envelope or evidence pack.

compute_pack_hash() returns the spec §5.2.1 pack_hash — the SHA-256 of the pack's canonical bytes excluding pack_signature. trace_signing_pre_image() and evidence_pack_pre_image() are the shared pre-image functions; producers and verifiers MUST both use them so the byte sequences match.

compute_pack_hash

compute_pack_hash(pack: dict[str, Any]) -> str

Return "sha256:<64-hex>" over the pack's canonical bytes (spec 5.2.1).

trace_signing_pre_image

trace_signing_pre_image(envelope: dict[str, Any]) -> bytes

Return the RFC 8785 canonical bytes a TRACE signature covers.

Every field except signature itself. Both producers and verifiers MUST call this so the byte sequences are identical.

evidence_pack_pre_image

evidence_pack_pre_image(pack: dict[str, Any]) -> bytes

Return the RFC 8785 canonical bytes pack_signature covers.

Every field except pack_signature, which spec 5.2.1 appends to the document after hashing and signing.

Revocation

RevocationStore is the revocation lookup a verifier consults during verify_manifest(); the default is in-memory, and production deployments back it with a persistent store. RevocationRecord is a single revocation entry: which manifest was revoked, when, why, and by whom.

RevocationStore

In-memory revocation store. Production should use a persistent backend.

RevocationRecord

Bases: BaseModel

FastAPI router

create_router

create_router(manifest_store: dict[str, dict[str, Any]], revocation_store: RevocationStore, cose_context: Optional[VerificationContext] = None) -> Any

Return a FastAPI APIRouter with /verify and /revocation-status endpoints.

Parameters:

Name Type Description Default
manifest_store dict[str, dict[str, Any]]

Dict mapping manifest_id -> manifest dict.

required
revocation_store RevocationStore

Revocation store instance.

required
cose_context Optional[VerificationContext]

Trust configuration for POST /verify/cose, held by the server rather than accepted from the caller. Omit it and that endpoint is fail-closed: every result is UNVERIFIABLE, never VALID, exactly as GET /verify behaves without keys.

None