acdp_client/verified.rs
1//! VerifiedContext: retrieve + verify in one call.
2
3use super::data_ref::{fetch_and_verify_data_ref, DataRefFetcher};
4use super::registry::RegistryClient;
5use acdp_did::WebResolver;
6use acdp_primitives::error::AcdpError;
7use acdp_types::{body::FullContext, primitives::CtxId};
8use acdp_verify::Verifier;
9
10/// Consumer-tunable strictness for [`VerifiedContext::fetch_with_policy`].
11///
12/// For ACDP v0.1.0 the verification profile is **always strict**:
13///
14/// - `did:web` is required for every producer identity — enforced
15/// unconditionally by `verify_signature_envelope`
16/// (RFC-ACDP-0001 §5.4), regardless of any policy field.
17/// - Embedded `DataRef` hashes are verified by
18/// [`acdp_validation::validate_body`] whenever `validate_body_schema`
19/// is set.
20///
21/// Only the fields below have real effect in this version; there are no
22/// relaxed-mode `did:web` or embedded-hash knobs.
23#[derive(Debug, Clone, PartialEq, Eq)]
24pub struct VerificationPolicy {
25 /// If true, run [`acdp_validation::validate_body`] (structural
26 /// schema checks plus embedded-`DataRef` hash verification) before
27 /// any cryptographic check. Default `true`. Set `false` only in
28 /// diagnostic paths that want to attempt signature verification
29 /// despite a body known to fail structural checks.
30 pub validate_body_schema: bool,
31
32 /// If true, accept `Status::Other` values (degrade to active per
33 /// RFC-ACDP-0004 §4.1). When false, reject unknown statuses.
34 /// Default `true`.
35 pub allow_unknown_status: bool,
36
37 /// Registry-receipt handling (ACDP 0.2, RFC-ACDP-0010).
38 /// Default [`ReceiptPolicy::VerifyIfPresent`].
39 pub receipts: ReceiptPolicy,
40
41 /// Historical-key handling (ACDP 0.2, WS-B). Default
42 /// [`HistoricalKeyPolicy::AcceptWithReceipt`].
43 pub historical_keys: HistoricalKeyPolicy,
44}
45
46impl Default for VerificationPolicy {
47 fn default() -> Self {
48 Self {
49 validate_body_schema: true,
50 allow_unknown_status: true,
51 receipts: ReceiptPolicy::VerifyIfPresent,
52 historical_keys: HistoricalKeyPolicy::AcceptWithReceipt,
53 }
54 }
55}
56
57/// How to treat the optional `registry_receipt` on retrieval
58/// (RFC-ACDP-0010).
59#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
60pub enum ReceiptPolicy {
61 /// Skip receipt verification entirely (0.1.0 behavior). The
62 /// receipt value is still preserved verbatim on the context.
63 Ignore,
64 /// Verify the receipt when one is present; absence is not an
65 /// error (the registry may simply be a 0.1.0 registry). Default.
66 #[default]
67 VerifyIfPresent,
68 /// Fail closed unless a receipt is present AND verifies. Use when
69 /// the deployment requires audit-grade provenance — registry
70 /// claims (`ctx_id`, `created_at`, `origin_registry`) are
71 /// assertions, not proofs, without a receipt.
72 Require,
73}
74
75/// How to treat a producer key that is present in the DID document's
76/// `verificationMethod` but no longer in `assertionMethod` — i.e. a
77/// key the producer rotated out but retained per the RFC-ACDP-0010
78/// key-retention rule.
79#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
80pub enum HistoricalKeyPolicy {
81 /// Strict 0.1.0 behavior: only `assertionMethod` keys verify.
82 /// Every context signed by a rotated-out key fails.
83 Reject,
84 /// Accept a retained key **only** when a verified registry receipt
85 /// attests (via `key_fingerprint`) that this exact key was the
86 /// authorized one at publish time. Without a verified receipt the
87 /// historical path never activates — fail closed. Default.
88 #[default]
89 AcceptWithReceipt,
90}
91
92/// How the producer key that verified the body relates to the
93/// producer's *current* DID document.
94#[derive(Debug, Clone, Copy, PartialEq, Eq)]
95pub enum KeyAuthorization {
96 /// The signing key is currently listed in `assertionMethod`.
97 CurrentlyAuthorized,
98 /// The signing key was rotated out of `assertionMethod` but is
99 /// retained in `verificationMethod`, and a verified registry
100 /// receipt attests it was the authorized key at publish time
101 /// (RFC-ACDP-0010). Weigh accordingly: valid history, not a
102 /// current endorsement.
103 HistoricallyAuthorized,
104}
105
106impl VerificationPolicy {
107 /// The v0.1.0 strict verification profile (RFC-ACDP-0001 §5.11, §9.2).
108 ///
109 /// Runs the full §5.11 pipeline: body schema validation, `content_hash`
110 /// recomputation, `did:web` key resolution, signature verification, and
111 /// embedded `data_ref.content_hash` checks. Returns on the first failure.
112 ///
113 /// This is the **only** mode covered by the `acdp-consumer` conformance
114 /// profile. Relaxed modes (`Diagnostic`, `UnsafeForTests`) are NOT
115 /// available in this crate in v0.1.0 — they would be separately-named
116 /// opt-ins per §9.2, and are not currently implemented.
117 ///
118 /// NOT identical to [`Default::default()`] as of 0.2: the default
119 /// policy is receipt-aware (`VerifyIfPresent` + `AcceptWithReceipt`),
120 /// while this named profile preserves the exact v0.1.0 semantics —
121 /// receipts inert ([`ReceiptPolicy::Ignore`]) and only
122 /// `assertionMethod` keys accepted
123 /// ([`HistoricalKeyPolicy::Reject`]). Callers pinned to this
124 /// constructor keep v0.1.0 behavior across the 0.2 upgrade.
125 pub fn strict_v0_1_0() -> Self {
126 Self {
127 validate_body_schema: true,
128 allow_unknown_status: true,
129 receipts: ReceiptPolicy::Ignore,
130 historical_keys: HistoricalKeyPolicy::Reject,
131 }
132 }
133}
134
135/// A retrieved context that has been cryptographically verified.
136#[derive(Debug)]
137pub struct VerifiedContext {
138 pub inner: FullContext,
139 /// Whether the body verified against a currently authorized key or
140 /// a receipt-attested historical one (ACDP 0.2, WS-B).
141 pub key_status: KeyAuthorization,
142 /// The verified registry receipt, when one was present and the
143 /// policy verified it (RFC-ACDP-0010). `None` under
144 /// [`ReceiptPolicy::Ignore`] or when the registry minted none.
145 pub verified_receipt: Option<acdp_types::receipt::RegistryReceipt>,
146}
147
148impl VerifiedContext {
149 /// Retrieve a context and verify its signature using the strict
150 /// default [`VerificationPolicy`].
151 pub async fn fetch(
152 client: &RegistryClient,
153 resolver: &WebResolver,
154 ctx_id: &CtxId,
155 ) -> Result<Self, AcdpError> {
156 Self::fetch_with_policy(client, resolver, ctx_id, &VerificationPolicy::default()).await
157 }
158
159 /// Retrieve a context and verify its signature with caller-controlled
160 /// strictness.
161 ///
162 /// 1. Fetches `body + registry_state` from the registry.
163 /// 2. Optionally runs `validate_body` — structural schema checks
164 /// plus embedded-`DataRef` hash verification (policy-controlled).
165 /// 3. Recomputes `content_hash` over ProducerContent.
166 /// 4. Resolves the producer's DID document. `did:web` is required
167 /// unconditionally for v0.1.0 (RFC-ACDP-0001 §5.4).
168 /// 5. Verifies the Ed25519 signature (or other supported algorithm).
169 /// 6. Optionally verifies the `registry_receipt` placeholder.
170 /// 7. Optionally rejects unknown statuses.
171 pub async fn fetch_with_policy(
172 client: &RegistryClient,
173 resolver: &WebResolver,
174 ctx_id: &CtxId,
175 policy: &VerificationPolicy,
176 ) -> Result<Self, AcdpError> {
177 let ctx = client.retrieve(ctx_id).await?;
178
179 if policy.validate_body_schema {
180 acdp_validation::validate_body(&ctx.body)?;
181 }
182
183 // Hash recomputation first: from here on `ctx.body.content_hash`
184 // IS the independently recomputed value, which the receipt
185 // cross-check below relies on.
186 let verifier = Verifier::new(resolver);
187 verifier.verify_body_hash(&ctx.body)?;
188
189 // ── Receipt phase (RFC-ACDP-0010) ───────────────────────────
190 // Verified BEFORE the signature phase because the historical-
191 // key path is gated on a verified receipt.
192 let serving_authority = client
193 .authority()
194 .unwrap_or_else(|| ctx_id.authority().to_string());
195 let verified_receipt = match (policy.receipts, &ctx.registry_receipt) {
196 (ReceiptPolicy::Ignore, _) | (ReceiptPolicy::VerifyIfPresent, None) => None,
197 (ReceiptPolicy::Require, None) => {
198 return Err(AcdpError::InvalidReceipt(
199 "policy requires a registry receipt but the response carries none \
200 (registry without the acdp-registry-receipts profile, or a \
201 pre-receipts context)"
202 .into(),
203 ));
204 }
205 (_, Some(value)) => {
206 let fingerprint = acdp_crypto::fingerprint::fingerprint_for_key_id(
207 &ctx.body.signature.key_id,
208 &ctx.body.signature.algorithm,
209 resolver,
210 )
211 .await?;
212 Some(
213 super::receipt::verify_receipt_value(
214 value,
215 ctx_id,
216 &ctx.body,
217 &ctx.body.content_hash,
218 &fingerprint,
219 &serving_authority,
220 resolver,
221 )
222 .await?,
223 )
224 }
225 };
226
227 // ── Signature phase ──────────────────────────────────────────
228 // Standard path enforces assertionMethod membership. A
229 // KeyNotAuthorized failure falls back to the historical path
230 // only under AcceptWithReceipt AND a verified receipt — the
231 // receipt's key_fingerprint (already cross-checked against this
232 // exact key above) is what attests publish-time authorization.
233 let key_status = match verifier.verify_body_signature(&ctx.body).await {
234 Ok(()) => KeyAuthorization::CurrentlyAuthorized,
235 Err(AcdpError::KeyNotAuthorized(_))
236 if policy.historical_keys == HistoricalKeyPolicy::AcceptWithReceipt
237 && verified_receipt.is_some() =>
238 {
239 acdp_verify::verify_body_signature_historical(&ctx.body, resolver).await?;
240 KeyAuthorization::HistoricallyAuthorized
241 }
242 Err(e) => return Err(e),
243 };
244
245 if !policy.allow_unknown_status {
246 if let Some(other) = ctx.registry_state.status.as_other() {
247 return Err(AcdpError::SchemaViolation(format!(
248 "policy.allow_unknown_status=false; registry returned '{other}'"
249 )));
250 }
251 }
252
253 Ok(Self {
254 inner: ctx,
255 key_status,
256 verified_receipt,
257 })
258 }
259
260 /// Retrieve + verify, returning a structured [`VerificationReport`]
261 /// alongside the verified context. Does NOT attempt external
262 /// `DataRef` fetches — use [`Self::fetch_report_with_fetcher`] for
263 /// that. Each `data_ref_external` slot in the returned report is
264 /// `None`.
265 ///
266 /// Unlike [`Self::fetch_with_policy`], per-`DataRef` embedded-hash
267 /// failures are recorded in the report instead of aborting the
268 /// verification. The top-level checks (schema, body hash,
269 /// signature) remain hard-fail: if any of them fails, the method
270 /// returns an `AcdpError` and produces no report.
271 ///
272 /// For diagnostic callers that want a populated report even when
273 /// a top-level check fails (e.g. an audit walker that needs to
274 /// distinguish "wrong hash" from "wrong signature"), use
275 /// [`Self::fetch_report_diagnose`] instead.
276 pub async fn fetch_report(
277 client: &RegistryClient,
278 resolver: &WebResolver,
279 ctx_id: &CtxId,
280 policy: &VerificationPolicy,
281 ) -> Result<(Self, VerificationReport), AcdpError> {
282 Self::fetch_report_inner::<NoFetcher>(client, resolver, ctx_id, policy, None).await
283 }
284
285 /// Diagnostic variant of [`Self::fetch_report`] that never
286 /// short-circuits on a top-level failure — schema, body-hash, and
287 /// signature outcomes are each recorded individually in the
288 /// returned [`VerificationReport`]. Returns `Ok((None, report))`
289 /// when any top-level stage failed (the report shows which one);
290 /// `Ok((Some(verified), report))` only when every check passed
291 /// (FEAT-05).
292 ///
293 /// Use cases:
294 /// - Audit walkers that need to classify failures by stage.
295 /// - Admin tooling that wants to distinguish "hash mismatch"
296 /// (probable tampering / encoding drift) from "signature
297 /// verification failed" (key compromise / DID resolution
298 /// problem).
299 ///
300 /// Network errors (retrieve, DID resolution) still propagate as
301 /// `Err` — there's no body to inspect when the registry is
302 /// unreachable.
303 pub async fn fetch_report_diagnose(
304 client: &RegistryClient,
305 resolver: &WebResolver,
306 ctx_id: &CtxId,
307 policy: &VerificationPolicy,
308 ) -> Result<(Option<Self>, VerificationReport), AcdpError> {
309 let ctx = client.retrieve(ctx_id).await?;
310 let mut report = VerificationReport {
311 body_hash_ok: false,
312 signature_ok: false,
313 schema_ok: false,
314 data_ref_embedded: Vec::with_capacity(ctx.body.data_refs.len()),
315 data_ref_external: Vec::with_capacity(ctx.body.data_refs.len()),
316 };
317
318 // Schema (structural) — record pass/fail.
319 if policy.validate_body_schema {
320 match acdp_validation::validate_body_structural(&ctx.body) {
321 Ok(()) => report.schema_ok = true,
322 Err(_) => { /* keep schema_ok=false; continue collecting */ }
323 }
324 } else {
325 report.schema_ok = true;
326 }
327
328 // Per-DataRef embedded hashes — same as fetch_report_inner.
329 for dr in &ctx.body.data_refs {
330 if let (Some(emb), Some(_)) = (&dr.embedded, &dr.content_hash) {
331 let outcome = acdp_validation::verify_embedded_hash(dr)
332 .and_then(|()| acdp_validation::embedded_decoded_bytes(emb).map(|b| b.len()));
333 report.data_ref_embedded.push(outcome);
334 } else {
335 report.data_ref_embedded.push(Ok(0));
336 }
337 }
338
339 // Hash + signature recorded independently (FEAT-05).
340 let verifier = Verifier::new(resolver);
341 report.body_hash_ok = verifier.verify_body_hash(&ctx.body).is_ok();
342 report.signature_ok = verifier.verify_body_signature(&ctx.body).await.is_ok();
343
344 // External fetches were not attempted (this method has no
345 // fetcher param — diagnostic callers can wire their own).
346 for _ in &ctx.body.data_refs {
347 report.data_ref_external.push(None);
348 }
349
350 // Decide whether to surface the verified handle. Report paths
351 // run the strict assertionMethod check only (no receipt /
352 // historical handling — use `fetch_with_policy` for those).
353 let all_top_level_pass = report.schema_ok && report.body_hash_ok && report.signature_ok;
354 let verified = if all_top_level_pass {
355 Some(Self {
356 inner: ctx,
357 key_status: KeyAuthorization::CurrentlyAuthorized,
358 verified_receipt: None,
359 })
360 } else {
361 None
362 };
363 Ok((verified, report))
364 }
365
366 /// Retrieve + verify like [`Self::fetch_report`], and additionally
367 /// fetch every `DataRef` whose `location` resolves through `fetcher`.
368 /// Each external fetch outcome is recorded in `report.data_ref_external`.
369 pub async fn fetch_report_with_fetcher<F: DataRefFetcher>(
370 client: &RegistryClient,
371 resolver: &WebResolver,
372 ctx_id: &CtxId,
373 policy: &VerificationPolicy,
374 fetcher: &F,
375 ) -> Result<(Self, VerificationReport), AcdpError> {
376 Self::fetch_report_inner(client, resolver, ctx_id, policy, Some(fetcher)).await
377 }
378
379 async fn fetch_report_inner<F: DataRefFetcher>(
380 client: &RegistryClient,
381 resolver: &WebResolver,
382 ctx_id: &CtxId,
383 policy: &VerificationPolicy,
384 fetcher: Option<&F>,
385 ) -> Result<(Self, VerificationReport), AcdpError> {
386 let ctx = client.retrieve(ctx_id).await?;
387 let mut report = VerificationReport {
388 body_hash_ok: false,
389 signature_ok: false,
390 schema_ok: false,
391 data_ref_embedded: Vec::with_capacity(ctx.body.data_refs.len()),
392 data_ref_external: Vec::with_capacity(ctx.body.data_refs.len()),
393 };
394
395 // Structural-only schema validation — embedded-hash checks are
396 // intentionally skipped here so per-DataRef hash failures land
397 // in the report (below) instead of short-circuiting the whole
398 // verification. That's the diagnostic shape `fetch_report`
399 // promises in its docstring.
400 if policy.validate_body_schema {
401 acdp_validation::validate_body_structural(&ctx.body)?;
402 }
403 report.schema_ok = true;
404
405 // Per-DataRef embedded-hash outcomes — recorded individually.
406 for dr in &ctx.body.data_refs {
407 if let (Some(emb), Some(_)) = (&dr.embedded, &dr.content_hash) {
408 let outcome = acdp_validation::verify_embedded_hash(dr)
409 .and_then(|()| acdp_validation::embedded_decoded_bytes(emb).map(|b| b.len()));
410 report.data_ref_embedded.push(outcome);
411 } else {
412 report.data_ref_embedded.push(Ok(0));
413 }
414 }
415
416 // `verify_body_signed` recomputes content_hash + verifies the
417 // signature WITHOUT re-running the schema validator (we already
418 // ran the structural part above, and embedded-hash failures are
419 // recorded per-DataRef rather than aborting). It still enforces
420 // `did:web` for the producer key (RFC-ACDP-0001 §5.4).
421 Verifier::new(resolver)
422 .verify_body_signed(&ctx.body)
423 .await?;
424 report.body_hash_ok = true;
425 report.signature_ok = true;
426
427 if !policy.allow_unknown_status {
428 if let Some(other) = ctx.registry_state.status.as_other() {
429 return Err(AcdpError::SchemaViolation(format!(
430 "policy.allow_unknown_status=false; registry returned '{other}'"
431 )));
432 }
433 }
434
435 // External fetches — record per-ref outcomes when a fetcher is
436 // supplied; otherwise leave each slot as `None` so callers can
437 // distinguish "skipped" from "failed".
438 for dr in &ctx.body.data_refs {
439 let slot: Option<Result<usize, AcdpError>> = match (fetcher, &dr.location) {
440 (Some(f), Some(_)) => Some(fetch_and_verify_data_ref(dr, f).await.map(|b| b.len())),
441 _ => None,
442 };
443 report.data_ref_external.push(slot);
444 }
445
446 Ok((
447 Self {
448 inner: ctx,
449 key_status: KeyAuthorization::CurrentlyAuthorized,
450 verified_receipt: None,
451 },
452 report,
453 ))
454 }
455
456 pub fn body(&self) -> &acdp_types::body::Body {
457 &self.inner.body
458 }
459
460 pub fn registry_state(&self) -> &acdp_types::body::RegistryState {
461 &self.inner.registry_state
462 }
463
464 /// Raw registry receipt value as served on the wire
465 /// (RFC-ACDP-0010), preserved verbatim. For the verified, typed
466 /// form see [`Self::verified_receipt`].
467 pub fn receipt(&self) -> Option<&serde_json::Value> {
468 self.inner.registry_receipt.as_ref()
469 }
470
471 /// Verify the registry receipt, when one is present
472 /// (RFC-ACDP-0010).
473 ///
474 /// Standalone variant for contexts obtained via the report paths or
475 /// constructed externally; `fetch_with_policy` already does this
476 /// under [`ReceiptPolicy::VerifyIfPresent`]/`Require`. The serving
477 /// authority is taken from the context's own `ctx_id` — correct
478 /// when the context was fetched from its home registry, which is
479 /// the only retrieval shape v0.2 defines.
480 ///
481 /// Returns `Ok(None)` when no receipt is present, `Ok(Some(_))`
482 /// with the verified receipt otherwise.
483 pub async fn verify_receipt(
484 &self,
485 resolver: &WebResolver,
486 ) -> Result<Option<acdp_types::receipt::RegistryReceipt>, AcdpError> {
487 let Some(value) = &self.inner.registry_receipt else {
488 return Ok(None);
489 };
490 // Recompute the body hash rather than trusting the echoed
491 // `body.content_hash`: all fields of this type are public, so a
492 // caller may have constructed it around an unverified
493 // FullContext, and the receipt cross-check is only meaningful
494 // against an independently recomputed hash (RFC-ACDP-0010 §8
495 // step 4).
496 let body_val = serde_json::to_value(&self.inner.body)?;
497 let recomputed = acdp_crypto::compute_content_hash(&body_val)?;
498 if recomputed != self.inner.body.content_hash {
499 return Err(AcdpError::HashMismatch {
500 stored: self.inner.body.content_hash.clone(),
501 recomputed,
502 });
503 }
504 let fingerprint = acdp_crypto::fingerprint::fingerprint_for_key_id(
505 &self.inner.body.signature.key_id,
506 &self.inner.body.signature.algorithm,
507 resolver,
508 )
509 .await?;
510 let receipt = super::receipt::verify_receipt_value(
511 value,
512 &self.inner.body.ctx_id,
513 &self.inner.body,
514 &self.inner.body.content_hash,
515 &fingerprint,
516 self.inner.body.ctx_id.authority(),
517 resolver,
518 )
519 .await?;
520 Ok(Some(receipt))
521 }
522}
523
524/// Structured diagnostic outcome from [`VerifiedContext::fetch_report`].
525///
526/// Top-level booleans report the per-stage outcome of the verification
527/// pipeline. Per-`DataRef` slots track outcomes for each entry in
528/// `body.data_refs`, in declaration order:
529///
530/// - `data_ref_embedded[i]` — `Ok(decoded_size_bytes)` when the embedded
531/// payload's `content_hash` matched; `Err` when it didn't (or the
532/// embedded was malformed). Refs without an embedded payload or
533/// without a declared `content_hash` produce `Ok(0)`.
534/// - `data_ref_external[i]` — `None` when no external fetch was
535/// attempted (either no `location` or no `fetcher` was provided);
536/// `Some(Ok(bytes_len))` when the fetch + hash succeeded;
537/// `Some(Err(_))` on any failure (SSRF rejection, hash mismatch,
538/// timeout, …).
539///
540/// `AcdpError` doesn't implement `Clone`, so the report is move-only.
541#[derive(Debug)]
542pub struct VerificationReport {
543 /// `content_hash` recomputed from the body matches the declared one.
544 pub body_hash_ok: bool,
545 /// The producer signature verified against the resolved DID key.
546 pub signature_ok: bool,
547 /// `validate_body` passed (or was disabled by policy).
548 pub schema_ok: bool,
549 /// Per-`DataRef` embedded-hash outcome, in `body.data_refs` order.
550 pub data_ref_embedded: Vec<Result<usize, AcdpError>>,
551 /// Per-`DataRef` external-fetch outcome, in `body.data_refs` order.
552 /// `None` indicates "not attempted" (no fetcher provided or no
553 /// `location` to fetch from).
554 pub data_ref_external: Vec<Option<Result<usize, AcdpError>>>,
555}
556
557/// Sentinel `DataRefFetcher` used as the type parameter for
558/// `fetch_report_inner` when no fetcher is supplied. `fetch` is never
559/// actually called — the option is matched out before that — but
560/// providing a real impl lets the generic monomorphize cleanly without
561/// requiring `fetch_report`'s callers to name a type.
562struct NoFetcher;
563
564impl DataRefFetcher for NoFetcher {
565 async fn fetch(
566 &self,
567 _location: &acdp_types::data_ref::Location,
568 ) -> Result<Vec<u8>, AcdpError> {
569 Err(AcdpError::NotImplemented(
570 "NoFetcher should never be called — this is a fetch_report sentinel".into(),
571 ))
572 }
573}
574
575#[cfg(test)]
576mod tests {
577 use super::{HistoricalKeyPolicy, ReceiptPolicy, VerificationPolicy};
578
579 /// The RFC-ACDP-0001 §9.2 named constructor preserves exact v0.1.0
580 /// semantics: receipts inert, assertionMethod-only keys. It is
581 /// deliberately NOT the 0.2 default (which is receipt-aware).
582 #[test]
583 fn strict_v0_1_0_preserves_v0_1_0_semantics() {
584 let strict = VerificationPolicy::strict_v0_1_0();
585 assert!(strict.validate_body_schema);
586 assert!(strict.allow_unknown_status);
587 assert_eq!(strict.receipts, ReceiptPolicy::Ignore);
588 assert_eq!(strict.historical_keys, HistoricalKeyPolicy::Reject);
589 assert_ne!(
590 strict,
591 VerificationPolicy::default(),
592 "the 0.2 default is receipt-aware; the v0.1.0 profile is not"
593 );
594 }
595}