20260626_2 优化child证书缓存并修复远端发布

This commit is contained in:
2026-06-26 18:52:27 +08:00
parent 9e339e63e7
commit f1a73bd2d1
8 changed files with 669 additions and 25 deletions

View File

@@ -1464,6 +1464,7 @@ mod tests {
child_ca_certificate_rsync_uri: "rsync://example.test/repo/child.cer".to_string(),
child_ca_certificate_sha256_hex: "00".repeat(32),
},
child_entry_projection: None,
};
runtime

View File

@@ -196,10 +196,16 @@ pub struct PublicationPointRunResult {
pub discovered_children: Vec<DiscoveredChildCaInstance>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct DiscoveredChildEntryProjection {
pub child_ski: String,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct DiscoveredChildCaInstance {
pub handle: CaInstanceHandle,
pub discovered_from: DiscoveredFrom,
pub child_entry_projection: Option<DiscoveredChildEntryProjection>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
@@ -495,6 +501,7 @@ mod tests {
.to_string(),
child_ca_certificate_sha256_hex: "00".repeat(32),
},
child_entry_projection: None,
}]
} else {
Vec::new()

View File

@@ -2444,6 +2444,7 @@ mod tests {
.to_string(),
child_ca_certificate_sha256_hex: "55".repeat(32),
},
child_entry_projection: None,
});
let output = FreshPublicationPointFinalizeOutput {
result,

View File

@@ -57,8 +57,8 @@ use crate::validation::objects::{
};
use crate::validation::publication_point::PublicationPointSnapshot;
use crate::validation::tree::{
CaCertificateRef, CaInstanceHandle, DiscoveredChildCaInstance, PublicationPointRunResult,
PublicationPointRunner,
CaCertificateRef, CaInstanceHandle, DiscoveredChildCaInstance, DiscoveredChildEntryProjection,
PublicationPointRunResult, PublicationPointRunner,
};
use sha2::Digest;
use std::collections::{HashMap, HashSet};
@@ -2689,6 +2689,7 @@ fn discover_children_from_fresh_snapshot_with_audit_cached_with_issuer_der<
match &projection.payload {
ChildCertificateCachePayload::ChildCa {
child_manifest_rsync_uri,
child_ski,
child_rsync_base_uri,
child_publication_point_rsync_uri,
child_rrdp_notification_uri,
@@ -2735,6 +2736,11 @@ fn discover_children_from_fresh_snapshot_with_audit_cached_with_issuer_der<
child_ca_certificate_sha256_hex:
child_cert_sha256_hex.clone(),
},
child_entry_projection: Some(
DiscoveredChildEntryProjection {
child_ski: child_ski.clone(),
},
),
});
ca_ok = ca_ok.saturating_add(1);
child_cert_cache_hit =
@@ -3145,6 +3151,15 @@ fn discover_children_from_fresh_snapshot_with_audit_cached_with_issuer_der<
child_ca_certificate_rsync_uri: f.rsync_uri.clone(),
child_ca_certificate_sha256_hex: child_cert_sha256_hex.clone(),
},
child_entry_projection: validated
.child_ca
.tbs
.extensions
.subject_key_identifier
.as_ref()
.map(|child_ski| DiscoveredChildEntryProjection {
child_ski: hex::encode(child_ski),
}),
});
enqueue_nanos =
enqueue_nanos.saturating_add(t3.elapsed().as_nanos().min(u128::from(u64::MAX)) as u64);
@@ -4857,6 +4872,9 @@ fn restore_children_from_vcir(
child_ca_certificate_rsync_uri: child.child_cert_rsync_uri.clone(),
child_ca_certificate_sha256_hex: child.child_cert_hash.clone(),
},
child_entry_projection: Some(DiscoveredChildEntryProjection {
child_ski: child.child_ski.clone(),
}),
});
audits.push(ObjectAuditEntry {
rsync_uri: child.child_cert_rsync_uri.clone(),
@@ -5073,6 +5091,9 @@ fn publication_point_cache_discovered_child(
child_ca_certificate_rsync_uri: child.child_cert_rsync_uri.clone(),
child_ca_certificate_sha256_hex: child.child_cert_hash.clone(),
},
child_entry_projection: Some(DiscoveredChildEntryProjection {
child_ski: child.child_ski.clone(),
}),
}
}
@@ -5713,15 +5734,21 @@ fn build_vcir_child_entries(
) -> Result<Vec<VcirChildEntry>, String> {
let mut out = Vec::with_capacity(discovered_children.len());
for child in discovered_children {
let child_der = child.handle.ca_certificate_der(store)?;
let child_cert = ResourceCertificate::decode_der(child_der.as_ref())
.map_err(|e| format!("decode child certificate for VCIR failed: {e}"))?;
let child_ski = child_cert
.tbs
.extensions
.subject_key_identifier
.as_ref()
.ok_or_else(|| "child certificate missing SubjectKeyIdentifier".to_string())?;
let child_ski = match child.child_entry_projection.as_ref() {
Some(projection) => projection.child_ski.clone(),
None => {
let child_der = child.handle.ca_certificate_der(store)?;
let child_cert = ResourceCertificate::decode_der(child_der.as_ref())
.map_err(|e| format!("decode child certificate for VCIR failed: {e}"))?;
let child_ski = child_cert
.tbs
.extensions
.subject_key_identifier
.as_ref()
.ok_or_else(|| "child certificate missing SubjectKeyIdentifier".to_string())?;
hex::encode(child_ski)
}
};
out.push(VcirChildEntry {
child_manifest_rsync_uri: child.handle.manifest_rsync_uri.clone(),
child_cert_rsync_uri: child.discovered_from.child_ca_certificate_rsync_uri.clone(),
@@ -5729,7 +5756,7 @@ fn build_vcir_child_entries(
.discovered_from
.child_ca_certificate_sha256_hex
.clone(),
child_ski: hex::encode(child_ski),
child_ski,
child_rsync_base_uri: child.handle.rsync_base_uri.clone(),
child_publication_point_rsync_uri: child.handle.publication_point_rsync_uri.clone(),
child_rrdp_notification_uri: child.handle.rrdp_notification_uri.clone(),

View File

@@ -13,7 +13,7 @@ use crate::storage::{
};
use crate::sync::rrdp::Fetcher;
use crate::validation::publication_point::PublicationPointSnapshot;
use crate::validation::tree::PublicationPointRunner;
use crate::validation::tree::{DiscoveredChildEntryProjection, PublicationPointRunner};
use std::process::Command;
use std::sync::Arc;
@@ -1210,6 +1210,45 @@ fn build_vcir_ccr_manifest_projection_from_fresh_real_snapshot_matches_manifest_
assert_eq!(projection.subordinate_skis, expected_subordinate_skis);
}
#[test]
fn build_vcir_child_entries_uses_projection_without_repo_bytes() {
let store_dir = tempfile::tempdir().expect("store dir");
let store = RocksStore::open(store_dir.path()).expect("open rocksdb");
let validation_time = time::OffsetDateTime::parse(
"2026-06-26T00:00:00Z",
&time::format_description::well_known::Rfc3339,
)
.expect("parse time");
let child = DiscoveredChildCaInstance {
handle: CaInstanceHandle {
depth: 1,
tal_id: "test-tal".to_string(),
parent_manifest_rsync_uri: Some("rsync://example.test/repo/root.mft".to_string()),
ca_certificate: CaCertificateRef::repo_bytes("aa".repeat(32)),
ca_certificate_rsync_uri: Some("rsync://example.test/repo/child.cer".to_string()),
effective_ip_resources: None,
effective_as_resources: None,
rsync_base_uri: "rsync://example.test/repo/child/".to_string(),
manifest_rsync_uri: "rsync://example.test/repo/child/child.mft".to_string(),
publication_point_rsync_uri: "rsync://example.test/repo/child/".to_string(),
rrdp_notification_uri: Some("https://example.test/notify.xml".to_string()),
},
discovered_from: crate::audit::DiscoveredFrom {
parent_manifest_rsync_uri: "rsync://example.test/repo/root.mft".to_string(),
child_ca_certificate_rsync_uri: "rsync://example.test/repo/child.cer".to_string(),
child_ca_certificate_sha256_hex: "aa".repeat(32),
},
child_entry_projection: Some(DiscoveredChildEntryProjection {
child_ski: "11".repeat(20),
}),
};
let entries = build_vcir_child_entries(&store, &[child], validation_time)
.expect("projection should avoid repo-bytes load");
assert_eq!(entries.len(), 1);
assert_eq!(entries[0].child_ski, "11".repeat(20));
}
#[test]
fn build_vcir_related_artifacts_classifies_snapshot_files_and_audit_statuses() {
let manifest_bytes = std::fs::read(
@@ -5036,3 +5075,448 @@ fn runner_dedup_paths_execute_with_timing_enabled() {
"rsync://example.test/repo/"
);
}
#[derive(Debug, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
struct RipeRootFinalizeFixtureEvent {
event_type: String,
validation_time: String,
pp_manifest_uri: Option<String>,
object_uri: Option<String>,
sha256: Option<String>,
object_type: Option<String>,
result: Option<String>,
reason: Option<String>,
}
#[derive(Debug)]
struct RipeRootFinalizeObject {
uri: String,
sha256_hex: String,
object_type: String,
result: String,
reason: Option<String>,
}
struct RipeRootFinalizeFixture {
manifest_uri: String,
publication_point_uri: String,
validation_time: time::OffsetDateTime,
manifest_sha256_hex: String,
objects: Vec<RipeRootFinalizeObject>,
}
fn load_ripe_root_finalize_fixture(
fixture_root: &std::path::Path,
run_id: &str,
) -> RipeRootFinalizeFixture {
let events_path = fixture_root
.join(format!("run_{run_id}"))
.join("ripe-root-events.jsonl");
let file = std::fs::File::open(&events_path)
.unwrap_or_else(|e| panic!("open fixture events {} failed: {e}", events_path.display()));
let reader = std::io::BufReader::new(file);
let mut manifest_uri = None;
let mut validation_time = None;
let mut manifest_sha256_hex = None;
let mut objects = Vec::new();
for line in std::io::BufRead::lines(reader) {
let line = line.expect("read fixture event line");
let event: RipeRootFinalizeFixtureEvent =
serde_json::from_str(&line).expect("decode fixture event");
if event.event_type == "publication_point" {
manifest_uri = event.pp_manifest_uri;
validation_time = Some(
time::OffsetDateTime::parse(
&event.validation_time,
&time::format_description::well_known::Rfc3339,
)
.expect("parse validation_time"),
);
continue;
}
if event.event_type != "object" {
continue;
}
let uri = event.object_uri.expect("fixture object uri");
let sha256_hex = event.sha256.expect("fixture object sha256");
let object_type = event.object_type.expect("fixture object type");
let result = event.result.expect("fixture object result");
if object_type == "manifest" {
manifest_sha256_hex = Some(sha256_hex.clone());
}
objects.push(RipeRootFinalizeObject {
uri,
sha256_hex,
object_type,
result,
reason: event.reason,
});
}
let manifest_uri = manifest_uri.expect("fixture publication_point event");
let publication_point_uri = manifest_uri
.rsplit_once('/')
.map(|(parent, _)| format!("{parent}/"))
.expect("manifest uri parent");
RipeRootFinalizeFixture {
manifest_uri,
publication_point_uri,
validation_time: validation_time.expect("fixture validation_time"),
manifest_sha256_hex: manifest_sha256_hex.expect("fixture manifest object"),
objects,
}
}
fn pack_file_from_fixture_object(
object: &RipeRootFinalizeObject,
repo_bytes: &Arc<crate::blob_store::ExternalRepoBytesDb>,
) -> PackFile {
PackFile::from_lazy_repo_bytes(
object.uri.clone(),
object.sha256_hex.clone(),
sha256_hex_to_32(&object.sha256_hex),
repo_bytes.clone(),
)
}
fn child_audit_from_fixture_object(object: &RipeRootFinalizeObject) -> ObjectAuditEntry {
ObjectAuditEntry {
rsync_uri: object.uri.clone(),
sha256_hex: object.sha256_hex.clone(),
kind: AuditObjectKind::Certificate,
result: match object.result.as_str() {
"ok" => AuditObjectResult::Ok,
"skipped" => AuditObjectResult::Skipped,
_ => AuditObjectResult::Error,
},
detail: object.reason.clone(),
}
}
fn discovered_child_from_fixture_object(
issuer: &CaInstanceHandle,
object: &RipeRootFinalizeObject,
child_entry_projection: Option<DiscoveredChildEntryProjection>,
) -> DiscoveredChildCaInstance {
let stem = object
.uri
.rsplit_once('/')
.map(|(_, file)| file.trim_end_matches(".cer"))
.unwrap_or("child");
let child_publication_point = format!("{}synthetic-child-{stem}/", issuer.rsync_base_uri);
let child_manifest = format!("{child_publication_point}child.mft");
DiscoveredChildCaInstance {
handle: CaInstanceHandle {
depth: issuer.depth + 1,
tal_id: issuer.tal_id.clone(),
parent_manifest_rsync_uri: Some(issuer.manifest_rsync_uri.clone()),
ca_certificate: CaCertificateRef::repo_bytes(object.sha256_hex.clone()),
ca_certificate_rsync_uri: Some(object.uri.clone()),
effective_ip_resources: None,
effective_as_resources: None,
rsync_base_uri: child_publication_point.clone(),
manifest_rsync_uri: child_manifest,
publication_point_rsync_uri: child_publication_point,
rrdp_notification_uri: issuer.rrdp_notification_uri.clone(),
},
discovered_from: crate::audit::DiscoveredFrom {
parent_manifest_rsync_uri: issuer.manifest_rsync_uri.clone(),
child_ca_certificate_rsync_uri: object.uri.clone(),
child_ca_certificate_sha256_hex: object.sha256_hex.clone(),
},
child_entry_projection,
}
}
fn child_entry_projection_from_fixture_object(
store: &RocksStore,
object: &RipeRootFinalizeObject,
) -> DiscoveredChildEntryProjection {
let child_der = store
.get_blob_bytes(&object.sha256_hex)
.expect("load child certificate bytes for projection")
.expect("child certificate bytes exist for projection");
let child_cert =
ResourceCertificate::decode_der(&child_der).expect("decode child certificate projection");
let child_ski = child_cert
.tbs
.extensions
.subject_key_identifier
.as_ref()
.expect("child certificate projection SKI");
DiscoveredChildEntryProjection {
child_ski: hex::encode(child_ski),
}
}
struct RipeRootChildEntryProfile {
count: usize,
load_der_nanos: u128,
decode_cert_nanos: u128,
build_entry_nanos: u128,
}
fn profile_ripe_root_child_entry_build(
store: &RocksStore,
discovered_children: &[DiscoveredChildCaInstance],
validation_time: time::OffsetDateTime,
) -> Result<RipeRootChildEntryProfile, String> {
let mut out = Vec::with_capacity(discovered_children.len());
let mut load_der_nanos = 0;
let mut decode_cert_nanos = 0;
let mut build_entry_nanos = 0;
for child in discovered_children {
let load_started = std::time::Instant::now();
let child_der = child.handle.ca_certificate_der(store)?;
load_der_nanos += load_started.elapsed().as_nanos();
let decode_started = std::time::Instant::now();
let child_cert = ResourceCertificate::decode_der(child_der.as_ref())
.map_err(|e| format!("decode child certificate for VCIR failed: {e}"))?;
decode_cert_nanos += decode_started.elapsed().as_nanos();
let build_started = std::time::Instant::now();
let child_ski = child_cert
.tbs
.extensions
.subject_key_identifier
.as_ref()
.ok_or_else(|| "child certificate missing SubjectKeyIdentifier".to_string())?;
out.push(VcirChildEntry {
child_manifest_rsync_uri: child.handle.manifest_rsync_uri.clone(),
child_cert_rsync_uri: child.discovered_from.child_ca_certificate_rsync_uri.clone(),
child_cert_hash: child
.discovered_from
.child_ca_certificate_sha256_hex
.clone(),
child_ski: hex::encode(child_ski),
child_rsync_base_uri: child.handle.rsync_base_uri.clone(),
child_publication_point_rsync_uri: child.handle.publication_point_rsync_uri.clone(),
child_rrdp_notification_uri: child.handle.rrdp_notification_uri.clone(),
child_effective_ip_resources: child.handle.effective_ip_resources.clone(),
child_effective_as_resources: child.handle.effective_as_resources.clone(),
accepted_at_validation_time: PackTime::from_utc_offset_datetime(validation_time),
});
build_entry_nanos += build_started.elapsed().as_nanos();
}
Ok(RipeRootChildEntryProfile {
count: out.len(),
load_der_nanos,
decode_cert_nanos,
build_entry_nanos,
})
}
#[test]
#[ignore = "manual performance repro: requires target/ripe-root-finalize-repro repo-bytes fixture"]
fn ripe_root_finalize_repro_from_remote_fixture() {
let fixture_root = std::env::var("RPKI_RIPE_ROOT_FINALIZE_FIXTURE")
.map(std::path::PathBuf::from)
.unwrap_or_else(|_| {
std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("target/ripe-root-finalize-repro")
});
let run_id = std::env::var("RPKI_RIPE_ROOT_FINALIZE_RUN").unwrap_or_else(|_| "0262".into());
assert!(
fixture_root.exists(),
"fixture root missing: {}; expected copied remote fixture",
fixture_root.display()
);
let fixture = load_ripe_root_finalize_fixture(&fixture_root, &run_id);
let repo_bytes_db = fixture_root.join("db/repo-bytes.db");
assert!(
repo_bytes_db.exists(),
"repo-bytes fixture missing: {}",
repo_bytes_db.display()
);
let store_dir = tempfile::tempdir().expect("store dir");
let work_db = store_dir.path().join("work-db");
let store = RocksStore::open_with_external_repo_bytes(&work_db, &repo_bytes_db)
.expect("open work-db with external repo-bytes");
let repo_bytes = Arc::new(
store
.external_repo_bytes_ref()
.expect("external repo bytes")
.clone(),
);
let manifest_bytes = store
.get_blob_bytes(&fixture.manifest_sha256_hex)
.expect("load fixture manifest bytes")
.expect("fixture manifest bytes exist");
let manifest = ManifestObject::decode_der(&manifest_bytes).expect("decode fixture manifest");
let mut files = Vec::with_capacity(fixture.objects.len().saturating_sub(1));
let mut child_audits = Vec::new();
let current_ca_hash = "007ad0c291b01ede4bd60e1204074ce3f7192186a022c9577cef5d8e91d5171a";
let current_ca_uri = "rsync://rpki.ripe.net/repository/aca/KpSo3VVK5wEHIJnHC2QHVV3d5mk.cer";
let parent_manifest_uri =
"rsync://rpki.ripe.net/repository/aca/7DNNDzoYvgAht7joQih2Qayxcxo.mft";
let ca = CaInstanceHandle {
depth: 1,
tal_id: "ripe-ncc".to_string(),
parent_manifest_rsync_uri: Some(parent_manifest_uri.to_string()),
ca_certificate: CaCertificateRef::repo_bytes(current_ca_hash.to_string()),
ca_certificate_rsync_uri: Some(current_ca_uri.to_string()),
effective_ip_resources: None,
effective_as_resources: None,
rsync_base_uri: fixture.publication_point_uri.clone(),
manifest_rsync_uri: fixture.manifest_uri.clone(),
publication_point_rsync_uri: fixture.publication_point_uri.clone(),
rrdp_notification_uri: Some("https://rrdp.ripe.net/notification.xml".to_string()),
};
let mut discovered_children = Vec::new();
let use_child_projection = std::env::var("RPKI_RIPE_ROOT_FINALIZE_USE_CHILD_PROJECTION")
.map(|value| value != "0" && value.to_ascii_lowercase() != "false")
.unwrap_or(false);
for object in &fixture.objects {
if object.uri == fixture.manifest_uri {
continue;
}
files.push(pack_file_from_fixture_object(object, &repo_bytes));
if object.object_type == "certificate" {
child_audits.push(child_audit_from_fixture_object(object));
if object.result == "ok" {
let child_entry_projection = use_child_projection
.then(|| child_entry_projection_from_fixture_object(&store, object));
discovered_children.push(discovered_child_from_fixture_object(
&ca,
object,
child_entry_projection,
));
}
}
}
if std::env::var("RPKI_RIPE_ROOT_FINALIZE_PROFILE_CHILD").is_ok() {
let profile_children = fixture
.objects
.iter()
.filter(|object| object.object_type == "certificate" && object.result == "ok")
.map(|object| discovered_child_from_fixture_object(&ca, object, None))
.collect::<Vec<_>>();
let profile_started = std::time::Instant::now();
let profile =
profile_ripe_root_child_entry_build(&store, &profile_children, fixture.validation_time)
.expect("profile child entry build");
eprintln!(
"ripe root child-entry profile: run={} count={} total_ms={} load_der_ms={:.3} decode_cert_ms={:.3} build_entry_ms={:.3}",
run_id,
profile.count,
profile_started.elapsed().as_millis(),
profile.load_der_nanos as f64 / 1_000_000.0,
profile.decode_cert_nanos as f64 / 1_000_000.0,
profile.build_entry_nanos as f64 / 1_000_000.0,
);
if std::env::var("RPKI_RIPE_ROOT_FINALIZE_ONLY_CHILD_PROFILE").is_ok() {
assert!(profile.count > 22_000);
return;
}
}
let fresh_point = FreshValidatedPublicationPoint {
manifest_rsync_uri: fixture.manifest_uri.clone(),
publication_point_rsync_uri: fixture.publication_point_uri.clone(),
manifest_number_be: manifest.manifest.manifest_number.bytes_be.clone(),
this_update: PackTime::from_utc_offset_datetime(manifest.manifest.this_update),
next_update: PackTime::from_utc_offset_datetime(manifest.manifest.next_update),
verified_at: PackTime::from_utc_offset_datetime(fixture.validation_time),
manifest_bytes,
files,
};
let policy = Policy::default();
let enable_ccr_accumulator = std::env::var("RPKI_RIPE_ROOT_FINALIZE_CCR")
.map(|value| value != "0" && value.to_ascii_lowercase() != "false")
.unwrap_or(true);
let runner = Rpkiv1PublicationPointRunner {
store: &store,
policy: &policy,
http_fetcher: &NeverHttpFetcher,
rsync_fetcher: &FailingRsyncFetcher,
validation_time: fixture.validation_time,
timing: None,
download_log: None,
replay_archive_index: None,
replay_delta_index: None,
rrdp_dedup: false,
rrdp_repo_cache: Mutex::new(HashMap::new()),
rsync_dedup: false,
rsync_repo_cache: Mutex::new(HashMap::new()),
current_repo_index: None,
repo_sync_runtime: None,
parallel_phase2_config: None,
parallel_roa_worker_pool: None,
ccr_accumulator: enable_ccr_accumulator
.then(|| Mutex::new(CcrAccumulator::new(Vec::new()))),
persist_vcir: true,
enable_roa_validation_cache: false,
enable_child_certificate_validation_cache: true,
publication_point_cache_observe_only: false,
enable_publication_point_validation_cache: true,
};
eprintln!(
"ripe root finalize repro setup: run={} ccr_accumulator={} child_projection={} objects={} files={} child_audits={} discovered_children={}",
run_id,
enable_ccr_accumulator,
use_child_projection,
fixture.objects.len(),
fresh_point.files.len(),
child_audits.len(),
discovered_children.len()
);
let started = std::time::Instant::now();
let output = runner
.finalize_fresh_publication_point_from_reducer(
&ca,
&fresh_point,
Vec::new(),
empty_objects_output(),
child_audits,
discovered_children,
Some("rrdp"),
Some("rrdp_ok"),
0,
None,
)
.expect("finalize fixture publication point");
let finalize_ms = started.elapsed().as_millis();
eprintln!(
"ripe root finalize repro timing: run={} finalize_ms={} snapshot_pack_ms={} persist_vcir_ms={} build_vcir_ms={} child_entries_ms={} related_artifacts_ms={} replace_vcir_ms={} replace_vcir_encode_ms={} replace_vcir_write_batch_ms={} ccr_projection_build_ms={} audit_build_ms={}",
run_id,
finalize_ms,
output.snapshot_pack_ms,
output.persist_vcir_ms,
output.persist_vcir_timing.build_vcir_ms,
output.persist_vcir_timing.build_vcir.child_entries_ms,
output.persist_vcir_timing.build_vcir.related_artifacts_ms,
output.persist_vcir_timing.replace_vcir_ms,
output.persist_vcir_timing.replace_vcir.vcir_encode_ms,
output.persist_vcir_timing.replace_vcir.write_batch_ms,
output.ccr_projection_build_ms,
output.audit_build_ms,
);
eprintln!(
"ripe root finalize repro result: audit_objects={} discovered_children={} ccr_manifest_count={}",
output.result.audit.objects.len(),
output.result.discovered_children.len(),
runner
.ccr_accumulator_snapshot()
.map(|snapshot| snapshot.manifest_count())
.unwrap_or(0),
);
assert!(fresh_point.files.len() > 22_000);
assert!(output.result.discovered_children.len() > 22_000);
if enable_ccr_accumulator {
assert_eq!(
runner
.ccr_accumulator_snapshot()
.expect("ccr snapshot")
.manifest_count(),
1
);
}
}