What are attestations?
An attestation is a cryptographically signed proof that a verification occurred. It:- Uses ES256 (ECDSA P-256) signatures
- Is formatted as a JWT
- Can be verified independently
- Can be stored on-chain
Requesting attestations
Fail-closed contract
AttestationResult
AttestationStatus
required
Lifecycle state of the attestation. One of
ISSUED, BLOCKED, or UNVERIFIABLE.string | None
Signed JWT string. Present only when
status == ISSUED; None otherwise.string | None
Machine-readable failure code:
"SIGNING_FAILURE" when signing failed, "CRYPTO_UNAVAILABLE" when the cryptography / PyJWT package is not installed, "VERIFIED_WITHOUT_PROOF" when the caller asked to sign a VERIFIED result without a proof_data artifact, None on success.string | None
Human-readable failure detail.
None on success.bool
Property.
True only when status is AttestationStatus.ISSUED. You must check this flag before you use token.AttestationStatus values
Caller pattern
Issuance-side proof enforcement
create_verification_attestation() will not sign a VERIFIED verdict that has no proof artifact. If you pass verified=True (or status="VERIFIED") with proof_data empty or omitted, the crypto layer short-circuits and returns:
UNVERIFIABLE and BLOCKED verdicts are unaffected — only VERIFIED requires a proof_data artifact. Pass the same JSON string (typically json.dumps(evidence, sort_keys=True)) that your engine used to derive the diagnostic’s proof_ref, so the token’s qwed.proof_hash claim matches the result on the consuming side.
Key lifecycle auditability
EveryIssuerKeyPair records generated_at (epoch seconds) and key_continuity_policy. QWED emits a structured log entry (attestation.key_generated) on every new key generation, so you can audit continuity events.
string
default:"ephemeral"
Policy for the issuer key pair. Must be one of
"ephemeral" (in-memory, non-persistent — default) or "persistent" (durably stored, e.g. external KMS). Any other value raises ValueError.AttestationService.get_issuer_info() now includes key_generated_at and key_continuity_policy alongside the existing issuer registry fields.
Attestation structure
Header
Payload
Verifying attestations
Using the API
Using the SDK
Uniform rejection error
All rejection paths return the same"Invalid token" message — the response never discloses why a token was rejected. Expired, revoked, malformed, oversized, untrusted-issuer, and unsupported-external-issuer tokens all look identical from the outside:
"Attestation has expired", "Untrusted issuer: did:...:X", and "Attestation has been revoked" would let an attacker enumerate the trusted-issuer registry, probe token expiry, and confirm revocation state through response text alone. The detailed reason is preserved in the server-side audit log (attestation.rejected with a structured reason field) so operators can still triage failures.
Enforce the trust boundary
Release gates and any code path that admits aVERIFIED result MUST route through enforce_trust_decision() before acting on it. It is the single consumption-side entry point that binds a verification result to its attestation token and fails closed on any mismatch — you should not consume DiagnosticResult.status == VERIFIED directly.
Parameters
DiagnosticResult
required
The verification result returned by the engine.
enforce_trust_decision will pass through any already-fail-closed status (BLOCKED, UNVERIFIABLE) unchanged, and only apply attestation checks to VERIFIED.string | None
The JWT emitted by
create_verification_attestation(). May be None — but if require_attestation=True and the result is VERIFIED, a missing token blocks the decision.bool
default:"True"
When
True (default, mandatory policy), a VERIFIED result without a valid token is downgraded to BLOCKED. When False (advisory policy), attestation is best-effort — a missing token still passes, but a present-and-invalid token still blocks.list[str] | None
Optional list of trusted issuer DIDs. When set, only tokens signed by an issuer in this list are accepted. Defaults to the
AttestationService’s trust anchors.string | None
Optional original query string. When provided, the token’s
qwed.query_hash claim must equal sha256(query), otherwise the decision is blocked. Without it, query binding is not checked.Detached result snapshot
enforce_trust_decision() always returns a new DiagnosticResult — never the reference you passed in. Before running any validation, it takes an isolated snapshot of developer_fields (recursive rebuild admitting only immutable scalars, AdvisoryCheck, and JSON-safe containers), so:
- The value you validate is the value you return. Concurrent code holding the original
resultreference cannot mutatedeveloper_fieldsbetween the enforcement check and your admission decision, closing a TOCTOU window thatfrozen=Truealone did not cover. - The returned result does not alias caller data. Downstream code can safely add breadcrumbs to
decision.developer_fieldswithout leaking into the original object.
developer_fields contains an unsupported value type or an object whose __deepcopy__ refuses to copy — enforce_trust_decision() fails closed with a BLOCKED result carrying constraint_id="trust_gate.diagnostic_snapshot_failed". Only the exception type is recorded in the audit log; caller-supplied error text is never surfaced.
Claims binding
When a token is present,enforce_trust_decision verifies three bindings against the result before admitting it:
Every block decision is logged at
WARNING with a structured trust_gate.blocked event (constraint_id, reason, policy) so auditors can replay the enforcement trail.
Fail-closed matrix
Trust anchors
QWED maintains a registry of trusted attestation issuers:Attestation chains
Link multiple attestations together:Use cases
- Audit Trails - Prove AI outputs were verified
- Compliance - Regulatory verification records
- Blockchain - Anchor proofs on-chain
- Badges - Show verification status in UIs