diff --git a/src/ccr/accumulator.rs b/src/ccr/accumulator.rs index 3f12be1..ce4587c 100644 --- a/src/ccr/accumulator.rs +++ b/src/ccr/accumulator.rs @@ -406,6 +406,7 @@ mod tests { sha256: manifest_hash, object_type: Some("mft".to_string()), validation_status: VcirArtifactValidationStatus::Accepted, + reject_reason: None, }], summary: VcirSummary { local_vrp_count: 0, diff --git a/src/ccr/build.rs b/src/ccr/build.rs index 96282c2..cb82450 100644 --- a/src/ccr/build.rs +++ b/src/ccr/build.rs @@ -729,6 +729,7 @@ mod tests { sha256: hash, object_type: Some("mft".to_string()), validation_status: crate::storage::VcirArtifactValidationStatus::Accepted, + reject_reason: None, }], summary: crate::storage::VcirSummary { local_vrp_count: 0, diff --git a/src/ccr/export.rs b/src/ccr/export.rs index 44bee7a..3b76664 100644 --- a/src/ccr/export.rs +++ b/src/ccr/export.rs @@ -267,6 +267,7 @@ mod tests { sha256: hash, object_type: Some("mft".to_string()), validation_status: VcirArtifactValidationStatus::Accepted, + reject_reason: None, }], summary: VcirSummary { local_vrp_count: 0, diff --git a/src/ccr/verify.rs b/src/ccr/verify.rs index 294c1e9..4b883ce 100644 --- a/src/ccr/verify.rs +++ b/src/ccr/verify.rs @@ -645,6 +645,7 @@ mod tests { sha256: "ff".repeat(32), object_type: Some("mft".to_string()), validation_status: VcirArtifactValidationStatus::Accepted, + reject_reason: None, }], summary: VcirSummary { local_vrp_count: 0, diff --git a/src/storage.rs b/src/storage.rs index cd42805..c88f9d8 100644 --- a/src/storage.rs +++ b/src/storage.rs @@ -1660,6 +1660,9 @@ pub struct PublicationPointCacheObject { pub object_type: Option, #[serde(rename = "s")] pub validation_status: VcirArtifactValidationStatus, + /// See `VcirRelatedArtifact::reject_reason`. + #[serde(rename = "e", default, skip_serializing_if = "Option::is_none")] + pub reject_reason: Option, } impl PublicationPointCacheObject { @@ -1671,6 +1674,7 @@ impl PublicationPointCacheObject { sha256: artifact.sha256.clone(), object_type: artifact.object_type.clone(), validation_status: artifact.validation_status, + reject_reason: artifact.reject_reason.clone(), } } @@ -1682,6 +1686,7 @@ impl PublicationPointCacheObject { sha256: self.sha256.clone(), object_type: self.object_type.clone(), validation_status: self.validation_status, + reject_reason: self.reject_reason.clone(), } } @@ -2157,6 +2162,11 @@ pub struct VcirRelatedArtifact { pub object_type: Option, #[serde(rename = "s")] pub validation_status: VcirArtifactValidationStatus, + /// Reject reason captured at fresh validation time for `Rejected` artifacts. + /// `None` for accepted/warning artifacts and for cache entries written + /// before this field existed. + #[serde(rename = "e", default, skip_serializing_if = "Option::is_none")] + pub reject_reason: Option, } impl VcirRelatedArtifact { diff --git a/src/storage/tests.rs b/src/storage/tests.rs index b207cc8..45ff050 100644 --- a/src/storage/tests.rs +++ b/src/storage/tests.rs @@ -257,6 +257,7 @@ fn sample_vcir(manifest_rsync_uri: &str) -> ValidatedCaInstanceResult { sha256: sha256_hex(b"manifest-object"), object_type: Some("mft".to_string()), validation_status: VcirArtifactValidationStatus::Accepted, + reject_reason: None, }, VcirRelatedArtifact { artifact_role: VcirArtifactRole::CurrentCrl, @@ -265,6 +266,7 @@ fn sample_vcir(manifest_rsync_uri: &str) -> ValidatedCaInstanceResult { sha256: sha256_hex(b"current-crl"), object_type: Some("crl".to_string()), validation_status: VcirArtifactValidationStatus::Accepted, + reject_reason: None, }, ], summary: VcirSummary { @@ -2394,3 +2396,35 @@ fn repository_blob_verification_detects_missing_external_blob() { .expect_err("missing blob must fail"); assert!(error.to_string().contains("missing.roa"), "{error}"); } + +#[test] +fn vcir_related_artifact_reject_reason_serde_backward_compatible() { + // Legacy cache JSON written before the reject_reason ("e") field existed. + let legacy = r#"{"r":"manifest","k":"mft","u":"rsync://example.test/a.mft","h":"0000000000000000000000000000000000000000000000000000000000000000","t":"mft","s":"accepted"}"#; + let artifact: VcirRelatedArtifact = serde_json::from_str(legacy).expect("legacy artifact"); + assert_eq!(artifact.reject_reason, None); + + // None reason is skipped on serialization, keeping cache entries compact. + let json_none = serde_json::to_string(&artifact).expect("serialize none"); + assert!(!json_none.contains("\"e\"")); + + // A recorded reason round-trips through the "e" field. + let mut with_reason = artifact.clone(); + with_reason.validation_status = VcirArtifactValidationStatus::Rejected; + with_reason.reject_reason = Some("bad object".to_string()); + let json = serde_json::to_string(&with_reason).expect("serialize"); + assert!(json.contains("\"e\":\"bad object\"")); + let decoded: VcirRelatedArtifact = serde_json::from_str(&json).expect("deserialize"); + assert_eq!(decoded.reject_reason.as_deref(), Some("bad object")); + + // PublicationPointCacheObject conversion carries the reason both ways. + let cache_object = PublicationPointCacheObject::from_related_artifact(&with_reason); + assert_eq!(cache_object.reject_reason.as_deref(), Some("bad object")); + let back = cache_object.to_related_artifact(); + assert_eq!(back.reject_reason.as_deref(), Some("bad object")); + + // Legacy PublicationPointCacheObject JSON without "e" still deserializes. + let legacy_object: PublicationPointCacheObject = + serde_json::from_str(legacy).expect("legacy cache object"); + assert_eq!(legacy_object.reject_reason, None); +} diff --git a/src/validation/manifest.rs b/src/validation/manifest.rs index 1fc0950..00979ed 100644 --- a/src/validation/manifest.rs +++ b/src/validation/manifest.rs @@ -1199,6 +1199,7 @@ mod tests { sha256: manifest_sha256.to_string(), object_type: Some("mft".to_string()), validation_status: VcirArtifactValidationStatus::Accepted, + reject_reason: None, }, VcirRelatedArtifact { artifact_role: VcirArtifactRole::SignedObject, @@ -1207,6 +1208,7 @@ mod tests { sha256: locked_object_sha256.to_string(), object_type: Some("roa".to_string()), validation_status: VcirArtifactValidationStatus::Accepted, + reject_reason: None, }, ], summary: VcirSummary { diff --git a/src/validation/objects.rs b/src/validation/objects.rs index 37bfb12..5575436 100644 --- a/src/validation/objects.rs +++ b/src/validation/objects.rs @@ -4023,6 +4023,7 @@ mod tests { sha256: sha256_hex(issuer_der), object_type: Some("cer".to_string()), validation_status: VcirArtifactValidationStatus::Accepted, + reject_reason: None, }, VcirRelatedArtifact { artifact_role: VcirArtifactRole::CurrentCrl, @@ -4031,6 +4032,7 @@ mod tests { sha256: sha256_hex_from_32(&crl_hash), object_type: Some("crl".to_string()), validation_status: VcirArtifactValidationStatus::Accepted, + reject_reason: None, }, ], summary: VcirSummary { @@ -4543,6 +4545,7 @@ mod tests { sha256: "00".repeat(32), object_type: Some("cer".to_string()), validation_status: VcirArtifactValidationStatus::Rejected, + reject_reason: None, }, ); vcir.local_outputs.push(VcirLocalOutput { diff --git a/src/validation/tree_runner.rs b/src/validation/tree_runner.rs index b13ee09..653bf47 100644 --- a/src/validation/tree_runner.rs +++ b/src/validation/tree_runner.rs @@ -3840,6 +3840,28 @@ fn audit_result_from_vcir_status(status: VcirArtifactValidationStatus) -> AuditO } } +/// Fallback detail for cached artifacts rejected by an earlier run whose cache +/// entry predates reject-reason recording (field added 2026-07-27). +const CACHED_REJECT_REASON_NOT_RECORDED: &str = + "rejected in an earlier validation run (reject reason not recorded in cache)"; + +/// Detail for an audit entry rebuilt from a cached artifact: the real reject +/// reason when the cache recorded one, an explicit fallback for legacy cache +/// entries, and `None` for non-rejected artifacts. +fn audit_detail_from_vcir_status( + status: VcirArtifactValidationStatus, + reject_reason: Option<&str>, +) -> Option { + match status { + VcirArtifactValidationStatus::Rejected => Some( + reject_reason + .map(str::to_string) + .unwrap_or_else(|| CACHED_REJECT_REASON_NOT_RECORDED.to_string()), + ), + VcirArtifactValidationStatus::Accepted | VcirArtifactValidationStatus::WarningOnly => None, + } +} + fn build_publication_point_audit_from_snapshot( ca: &CaInstanceHandle, source: PublicationPointSource, @@ -4059,7 +4081,10 @@ fn build_publication_point_audit_from_vcir( sha256_hex: artifact.sha256.clone(), kind: kind_from_vcir_artifact_kind(artifact.artifact_kind), result: audit_result_from_vcir_status(artifact.validation_status), - detail: None, + detail: audit_detail_from_vcir_status( + artifact.validation_status, + artifact.reject_reason.as_deref(), + ), }, ); } @@ -4170,7 +4195,10 @@ fn build_publication_point_audit_from_publication_point_cache_projection( sha256_hex: artifact.sha256.clone(), kind: kind_from_vcir_artifact_kind(artifact.artifact_kind), result: audit_result_from_vcir_status(artifact.validation_status), - detail: None, + detail: audit_detail_from_vcir_status( + artifact.validation_status, + artifact.reject_reason.as_deref(), + ), }, ); } @@ -6003,9 +6031,9 @@ fn build_vcir_related_artifacts( objects: &crate::validation::objects::ObjectsOutput, child_audits: &[ObjectAuditEntry], ) -> Vec { - let mut audit_by_uri: HashMap<&str, AuditObjectResult> = HashMap::new(); + let mut audit_by_uri: HashMap<&str, &ObjectAuditEntry> = HashMap::new(); for entry in child_audits.iter().chain(objects.audit.iter()) { - audit_by_uri.insert(entry.rsync_uri.as_str(), entry.result.clone()); + audit_by_uri.insert(entry.rsync_uri.as_str(), entry); } let mut artifacts = Vec::with_capacity(pack.files.len() + 2); @@ -6016,6 +6044,7 @@ fn build_vcir_related_artifacts( sha256: sha256_hex(&pack.manifest_bytes), object_type: Some("mft".to_string()), validation_status: VcirArtifactValidationStatus::Accepted, + reject_reason: None, }); artifacts.push(VcirRelatedArtifact { artifact_role: if ca.parent_manifest_rsync_uri.is_none() { @@ -6035,13 +6064,20 @@ fn build_vcir_related_artifacts( }), object_type: Some("cer".to_string()), validation_status: VcirArtifactValidationStatus::Accepted, + reject_reason: None, }); for file in &pack.files { - let result = audit_by_uri - .get(file.rsync_uri.as_str()) - .cloned() + let audit_entry = audit_by_uri.get(file.rsync_uri.as_str()).copied(); + let result = audit_entry + .map(|entry| entry.result.clone()) .unwrap_or(AuditObjectResult::Ok); + let validation_status = audit_result_to_vcir_status(&result); + let reject_reason = if validation_status == VcirArtifactValidationStatus::Rejected { + audit_entry.and_then(|entry| entry.detail.clone()) + } else { + None + }; let (artifact_role, artifact_kind) = artifact_role_and_kind(file, current_crl_rsync_uri); artifacts.push(VcirRelatedArtifact { artifact_role, @@ -6049,7 +6085,8 @@ fn build_vcir_related_artifacts( uri: Some(file.rsync_uri.clone()), sha256: sha256_hex_from_32(&file.sha256), object_type: object_type_from_uri(file.rsync_uri.as_str()), - validation_status: audit_result_to_vcir_status(&result), + validation_status, + reject_reason, }); } diff --git a/src/validation/tree_runner/tests.rs b/src/validation/tree_runner/tests.rs index 479c5f7..9aeb767 100644 --- a/src/validation/tree_runner/tests.rs +++ b/src/validation/tree_runner/tests.rs @@ -708,6 +708,7 @@ fn sample_vcir_for_projection( sha256: manifest_hash, object_type: Some("mft".to_string()), validation_status: VcirArtifactValidationStatus::Accepted, + reject_reason: None, }, VcirRelatedArtifact { artifact_role: VcirArtifactRole::CurrentCrl, @@ -716,6 +717,7 @@ fn sample_vcir_for_projection( sha256: current_crl_hash, object_type: Some("crl".to_string()), validation_status: VcirArtifactValidationStatus::Accepted, + reject_reason: None, }, VcirRelatedArtifact { artifact_role: VcirArtifactRole::ChildCaCert, @@ -724,6 +726,7 @@ fn sample_vcir_for_projection( sha256: child_cert_hash.to_string(), object_type: Some("cer".to_string()), validation_status: VcirArtifactValidationStatus::Accepted, + reject_reason: None, }, VcirRelatedArtifact { artifact_role: VcirArtifactRole::SignedObject, @@ -732,6 +735,7 @@ fn sample_vcir_for_projection( sha256: roa_hash, object_type: Some("roa".to_string()), validation_status: VcirArtifactValidationStatus::Accepted, + reject_reason: None, }, VcirRelatedArtifact { artifact_role: VcirArtifactRole::SignedObject, @@ -740,6 +744,7 @@ fn sample_vcir_for_projection( sha256: aspa_hash, object_type: Some("aspa".to_string()), validation_status: VcirArtifactValidationStatus::Accepted, + reject_reason: None, }, ], summary: VcirSummary { @@ -1455,10 +1460,12 @@ fn build_vcir_related_artifacts_classifies_snapshot_files_and_audit_statuses() { && artifact.artifact_role == VcirArtifactRole::ChildCaCert)); assert!(artifacts.iter().any(|artifact| artifact.uri.as_deref() == Some("rsync://example.test/repo/issuer/a.roa") - && artifact.validation_status == VcirArtifactValidationStatus::Rejected)); + && artifact.validation_status == VcirArtifactValidationStatus::Rejected + && artifact.reject_reason.as_deref() == Some("bad roa"))); assert!(artifacts.iter().any(|artifact| artifact.uri.as_deref() == Some("rsync://example.test/repo/issuer/a.asa") - && artifact.validation_status == VcirArtifactValidationStatus::WarningOnly)); + && artifact.validation_status == VcirArtifactValidationStatus::WarningOnly + && artifact.reject_reason.is_none())); assert!(artifacts.iter().any(|artifact| artifact.uri.as_deref() == Some("rsync://example.test/repo/issuer/a.gbr") && artifact.artifact_kind == VcirArtifactKind::Gbr)); @@ -4510,6 +4517,7 @@ fn build_objects_output_from_vcir_tracks_expired_and_invalid_cached_outputs() { .to_string(), ), validation_status: VcirArtifactValidationStatus::Accepted, + reject_reason: None, }); } @@ -4732,6 +4740,173 @@ fn build_publication_point_audit_from_vcir_uses_vcir_metadata_and_overlays_child })); } +#[test] +fn build_publication_point_audit_from_vcir_restores_reject_reason_with_legacy_fallback() { + let now = time::OffsetDateTime::now_utc(); + let child_cert_hash = sha256_hex(b"child-cert"); + let mut vcir = sample_vcir_for_projection(now, &child_cert_hash); + vcir.related_artifacts.push(VcirRelatedArtifact { + artifact_role: VcirArtifactRole::SignedObject, + artifact_kind: VcirArtifactKind::Roa, + uri: Some("rsync://example.test/repo/issuer/rejected-with-reason.roa".to_string()), + sha256: sha256_hex(b"rejected-with-reason"), + object_type: Some("roa".to_string()), + validation_status: VcirArtifactValidationStatus::Rejected, + reject_reason: Some("EE certificate path validation failed: test".to_string()), + }); + vcir.related_artifacts.push(VcirRelatedArtifact { + artifact_role: VcirArtifactRole::SignedObject, + artifact_kind: VcirArtifactKind::Roa, + uri: Some("rsync://example.test/repo/issuer/rejected-legacy.roa".to_string()), + sha256: sha256_hex(b"rejected-legacy"), + object_type: Some("roa".to_string()), + validation_status: VcirArtifactValidationStatus::Rejected, + reject_reason: None, + }); + + let ca = CaInstanceHandle { + depth: 0, + tal_id: "test-tal".to_string(), + parent_manifest_rsync_uri: None, + ca_certificate: CaCertificateRef::inline_der(Vec::new()), + ca_certificate_rsync_uri: None, + effective_ip_resources: None, + effective_as_resources: None, + rsync_base_uri: "rsync://example.test/repo/issuer/".to_string(), + manifest_rsync_uri: vcir.manifest_rsync_uri.clone(), + publication_point_rsync_uri: "rsync://example.test/repo/issuer/".to_string(), + rrdp_notification_uri: None, + }; + let objects = crate::validation::objects::ObjectsOutput { + vrps: Vec::new(), + aspas: Vec::new(), + router_keys: Vec::new(), + local_outputs_cache: Vec::new(), + warnings: Vec::new(), + stats: crate::validation::objects::ObjectsStats::default(), + audit: Vec::new(), + roa_cache_stats: crate::validation::objects::RoaValidationCacheStats::default(), + roa_cache_object_meta: Vec::new(), + }; + + let audit = build_publication_point_audit_from_vcir( + &ca, + PublicationPointSource::VcirCurrentInstance, + None, + None, + None, + None, + Some(&vcir), + None, + &[], + &objects, + &[], + &[], + ); + + assert!(audit.objects.iter().any(|entry| { + entry.rsync_uri == "rsync://example.test/repo/issuer/rejected-with-reason.roa" + && matches!(entry.result, AuditObjectResult::Error) + && entry.detail.as_deref() + == Some("EE certificate path validation failed: test") + })); + assert!(audit.objects.iter().any(|entry| { + entry.rsync_uri == "rsync://example.test/repo/issuer/rejected-legacy.roa" + && matches!(entry.result, AuditObjectResult::Error) + && entry.detail.as_deref() == Some(CACHED_REJECT_REASON_NOT_RECORDED) + })); + assert!(audit.objects.iter().all(|entry| { + !matches!(entry.result, AuditObjectResult::Ok) || entry.detail.is_none() + })); +} + +#[test] +fn build_publication_point_audit_from_pp_cache_projection_restores_reject_reason() { + let now = time::OffsetDateTime::now_utc(); + let child_cert_hash = sha256_hex(b"child-cert"); + let mut vcir = sample_vcir_for_projection(now, &child_cert_hash); + vcir.related_artifacts.push(VcirRelatedArtifact { + artifact_role: VcirArtifactRole::SignedObject, + artifact_kind: VcirArtifactKind::Roa, + uri: Some("rsync://example.test/repo/issuer/rejected-with-reason.roa".to_string()), + sha256: sha256_hex(b"rejected-with-reason"), + object_type: Some("roa".to_string()), + validation_status: VcirArtifactValidationStatus::Rejected, + reject_reason: Some("EE certificate path validation failed: test".to_string()), + }); + vcir.related_artifacts.push(VcirRelatedArtifact { + artifact_role: VcirArtifactRole::SignedObject, + artifact_kind: VcirArtifactKind::Roa, + uri: Some("rsync://example.test/repo/issuer/rejected-legacy.roa".to_string()), + sha256: sha256_hex(b"rejected-legacy"), + object_type: Some("roa".to_string()), + validation_status: VcirArtifactValidationStatus::Rejected, + reject_reason: None, + }); + let projection = PublicationPointCacheProjection::from_vcir_with_context( + &vcir, + "rsync://example.test/repo/issuer/".to_string(), + None, + [0x11; 32], + [0x22; 32], + [0x33; 32], + [0x44; 32], + [0x55; 32], + ) + .expect("build publication point projection"); + + let ca = CaInstanceHandle { + depth: 0, + tal_id: "test-tal".to_string(), + parent_manifest_rsync_uri: None, + ca_certificate: CaCertificateRef::inline_der(Vec::new()), + ca_certificate_rsync_uri: None, + effective_ip_resources: None, + effective_as_resources: None, + rsync_base_uri: "rsync://example.test/repo/issuer/".to_string(), + manifest_rsync_uri: vcir.manifest_rsync_uri.clone(), + publication_point_rsync_uri: "rsync://example.test/repo/issuer/".to_string(), + rrdp_notification_uri: None, + }; + let objects = crate::validation::objects::ObjectsOutput { + vrps: Vec::new(), + aspas: Vec::new(), + router_keys: Vec::new(), + local_outputs_cache: Vec::new(), + warnings: Vec::new(), + stats: crate::validation::objects::ObjectsStats::default(), + audit: Vec::new(), + roa_cache_stats: crate::validation::objects::RoaValidationCacheStats::default(), + roa_cache_object_meta: Vec::new(), + }; + + let audit = build_publication_point_audit_from_publication_point_cache_projection( + &ca, + PublicationPointSource::PublicationPointCache, + None, + None, + None, + None, + &projection, + now, + &[], + &objects, + &[], + ); + + assert!(audit.objects.iter().any(|entry| { + entry.rsync_uri == "rsync://example.test/repo/issuer/rejected-with-reason.roa" + && matches!(entry.result, AuditObjectResult::Error) + && entry.detail.as_deref() + == Some("EE certificate path validation failed: test") + })); + assert!(audit.objects.iter().any(|entry| { + entry.rsync_uri == "rsync://example.test/repo/issuer/rejected-legacy.roa" + && matches!(entry.result, AuditObjectResult::Error) + && entry.detail.as_deref() == Some(CACHED_REJECT_REASON_NOT_RECORDED) + })); +} + #[test] fn build_publication_point_audit_from_vcir_failed_no_cache_keeps_current_reject_only() { let now = time::OffsetDateTime::now_utc(); @@ -4948,6 +5123,7 @@ fn reconstruct_snapshot_from_vcir_reports_missing_manifest_and_related_raw_bytes sha256: sha256_hex(b"dup-roa-1"), object_type: Some("roa".to_string()), validation_status: VcirArtifactValidationStatus::Accepted, + reject_reason: None, }); vcir.related_artifacts.push(VcirRelatedArtifact { artifact_role: VcirArtifactRole::SignedObject, @@ -4956,6 +5132,7 @@ fn reconstruct_snapshot_from_vcir_reports_missing_manifest_and_related_raw_bytes sha256: sha256_hex(b"dup-roa-2"), object_type: Some("roa".to_string()), validation_status: VcirArtifactValidationStatus::Accepted, + reject_reason: None, }); vcir.related_artifacts.push(VcirRelatedArtifact { artifact_role: VcirArtifactRole::IssuerCert, @@ -4964,6 +5141,7 @@ fn reconstruct_snapshot_from_vcir_reports_missing_manifest_and_related_raw_bytes sha256: sha256_hex(b"issuer-cert"), object_type: Some("cer".to_string()), validation_status: VcirArtifactValidationStatus::Accepted, + reject_reason: None, }); let ca = CaInstanceHandle { diff --git a/tests/test_ccr_m7.rs b/tests/test_ccr_m7.rs index f5a3e07..a44093b 100644 --- a/tests/test_ccr_m7.rs +++ b/tests/test_ccr_m7.rs @@ -244,6 +244,7 @@ fn verify_against_vcir_store_matches_manifest_hashes() { sha256: manifest_hash.clone(), object_type: Some("mft".to_string()), validation_status: VcirArtifactValidationStatus::Accepted, + reject_reason: None, }], summary: VcirSummary { local_vrp_count: 0, diff --git a/tests/test_manifest_processor_m4.rs b/tests/test_manifest_processor_m4.rs index 4f9c945..9029575 100644 --- a/tests/test_manifest_processor_m4.rs +++ b/tests/test_manifest_processor_m4.rs @@ -109,6 +109,7 @@ fn store_validated_manifest_baseline( sha256: manifest_sha256, object_type: Some("mft".to_string()), validation_status: VcirArtifactValidationStatus::Accepted, + reject_reason: None, }], summary: VcirSummary { local_vrp_count: 0, diff --git a/tests/test_manifest_processor_repo_sync_and_cached_snapshot_cov.rs b/tests/test_manifest_processor_repo_sync_and_cached_snapshot_cov.rs index 6d46403..1452c4c 100644 --- a/tests/test_manifest_processor_repo_sync_and_cached_snapshot_cov.rs +++ b/tests/test_manifest_processor_repo_sync_and_cached_snapshot_cov.rs @@ -112,6 +112,7 @@ fn store_validated_manifest_baseline( sha256: manifest_sha256, object_type: Some("mft".to_string()), validation_status: VcirArtifactValidationStatus::Accepted, + reject_reason: None, }], summary: VcirSummary { local_vrp_count: 0,