use crate::destination::gcs::GcsStore;
use crate::manifest::{MANIFEST_FILENAME, ManifestStatus, RunManifest};
use anyhow::{Context, Result, bail};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LoadIntegrity {
pub source_rows: Option<u64>,
pub file_rows: u64,
pub manifests: usize,
}
impl LoadIntegrity {
pub fn chain_prefix(&self) -> String {
let src = self
.source_rows
.map_or_else(|| "?".to_string(), |n| n.to_string());
format!("source {src} → files {}", self.file_rows)
}
}
#[allow(private_interfaces)]
pub fn fetch_manifests_keyed(
store: &GcsStore,
gcs_prefix: &str,
) -> Result<Vec<(String, RunManifest)>> {
let (_, base) = crate::load::split_gs_uri(gcs_prefix)?;
let keys = list_manifest_keys(store, base)?;
keys.into_iter()
.map(|key| {
let bytes = store.read(&key)?;
let m = serde_json::from_slice::<RunManifest>(&bytes)
.with_context(|| format!("parsing manifest {key}"))?;
Ok((key, m))
})
.collect()
}
#[allow(private_interfaces)]
pub fn select_load_uris(
store: &GcsStore,
gcs_prefix: &str,
new: &[(String, RunManifest)],
) -> Result<Vec<String>> {
let (bucket, base) = crate::load::split_gs_uri(gcs_prefix)?;
let all_parquet: Vec<String> = store
.list_files(base)?
.into_iter()
.filter(|k| k.ends_with(".parquet"))
.collect();
Ok(select_load_keys(new, &all_parquet)
.into_iter()
.map(|k| format!("gs://{bucket}/{k}"))
.collect())
}
fn resolve_parts<'a>(
manifest_key: &'a str,
m: &'a RunManifest,
) -> impl Iterator<Item = String> + 'a {
let dir = manifest_key.rsplit_once('/').map(|(d, _)| d).unwrap_or("");
m.parts.iter().map(move |p| {
if dir.is_empty() {
p.path.clone()
} else {
format!("{dir}/{}", p.path)
}
})
}
pub fn select_load_keys(new: &[(String, RunManifest)], all_parquet: &[String]) -> Vec<String> {
use std::collections::BTreeSet;
let present: std::collections::HashSet<&str> = all_parquet.iter().map(String::as_str).collect();
let mut selected: BTreeSet<String> = BTreeSet::new();
for (key, m) in new {
let mut resolved_any = false;
for full in resolve_parts(key, m) {
if present.contains(full.as_str()) {
selected.insert(full);
resolved_any = true;
}
}
if !resolved_any {
return all_parquet.to_vec();
}
}
selected.into_iter().collect()
}
#[allow(private_interfaces)]
pub fn gc_orphans(
store: &GcsStore,
gcs_prefix: &str,
keyed: &[(String, RunManifest)],
) -> Result<(usize, u64)> {
let (_bucket, base) = crate::load::split_gs_uri(gcs_prefix)?;
let keep: std::collections::HashSet<String> = keyed
.iter()
.filter(|(_, m)| m.status == ManifestStatus::Success)
.flat_map(|(key, m)| resolve_parts(key, m))
.collect();
let mut removed = 0usize;
let mut removed_bytes = 0u64;
for key in store.list_files(base)? {
if key.ends_with(".parquet") && !keep.contains(&key) {
removed_bytes += store.stat_size(&key).unwrap_or(0);
store.remove(&key)?;
removed += 1;
}
}
Ok((removed, removed_bytes))
}
pub fn latest_full(keyed: Vec<(String, RunManifest)>) -> Vec<(String, RunManifest)> {
keyed
.into_iter()
.max_by(|a, b| {
match (
chrono::DateTime::parse_from_rfc3339(&a.1.finished_at).ok(),
chrono::DateTime::parse_from_rfc3339(&b.1.finished_at).ok(),
) {
(Some(x), Some(y)) => x.cmp(&y),
_ => a.1.finished_at.cmp(&b.1.finished_at),
}
})
.into_iter()
.collect()
}
pub fn select_runs(
keyed: Vec<(String, RunManifest)>,
loaded: &std::collections::HashSet<String>,
mode: crate::load::plan::LoadMode,
) -> Vec<(String, RunManifest)> {
match mode {
crate::load::plan::LoadMode::Full => latest_full(keyed),
_ => keyed
.into_iter()
.filter(|(_, m)| !loaded.contains(&m.run_id))
.collect(),
}
}
fn list_manifest_keys(store: &GcsStore, base: &str) -> Result<Vec<String>> {
let all: Vec<String> = store
.list_files(base)?
.into_iter()
.filter(|k| is_manifest_key(k))
.collect();
let run_unique: Vec<String> = all
.iter()
.filter(|k| is_run_unique_manifest(k.rsplit('/').next().unwrap_or("")))
.cloned()
.collect();
Ok(if run_unique.is_empty() {
all
} else {
run_unique
})
}
fn is_manifest_key(key: &str) -> bool {
let base = key.rsplit('/').next().unwrap_or("");
base == MANIFEST_FILENAME || is_run_unique_manifest(base)
}
fn is_run_unique_manifest(base: &str) -> bool {
base.starts_with("manifest-") && base.ends_with(".json")
}
pub fn reconcile(manifests: &[RunManifest], allow_source_drift: bool) -> Result<LoadIntegrity> {
if manifests.is_empty() {
bail!(
"no `{MANIFEST_FILENAME}` found under the export prefix — refusing to load \
unverified files. A rivet export writes a manifest on success; its absence \
means the run never completed (or points at the wrong prefix)."
);
}
let mut file_rows: u64 = 0;
let mut source_rows: u64 = 0;
let mut any_source = false;
for m in manifests {
if m.status != ManifestStatus::Success {
bail!(
"manifest for run `{}` (export `{}`) is {:?}, not Success — refusing to load a \
partial export",
m.run_id,
m.export_name,
m.status
);
}
m.validate_self_consistency().map_err(|e| {
anyhow::anyhow!(
"manifest for run `{}` (export `{}`) is internally inconsistent: {e} — refusing \
to load",
m.run_id,
m.export_name
)
})?;
let rows = u64::try_from(m.row_count).with_context(|| {
format!(
"manifest for run `{}` has a negative row_count ({})",
m.run_id, m.row_count
)
})?;
file_rows += rows;
if let Some(src) = m
.source
.extraction
.as_ref()
.and_then(|x| x.source_row_count)
{
let src = u64::try_from(src).with_context(|| {
format!(
"manifest for run `{}` has a negative source_row_count ({src})",
m.run_id
)
})?;
any_source = true;
source_rows += src;
if src != rows {
if allow_source_drift {
eprintln!(
"warning: source→file drift for run `{}` (export `{}`): source had {src} \
rows, extracted {rows} (--allow-source-drift)",
m.run_id, m.export_name
);
} else {
bail!(
"source→file mismatch for run `{}` (export `{}`): source had {src} rows \
but {rows} were extracted — the extract dropped {} row(s). Investigate \
before loading, or pass --allow-source-drift to override.",
m.run_id,
m.export_name,
src.abs_diff(rows)
);
}
}
}
}
Ok(LoadIntegrity {
source_rows: any_source.then_some(source_rows),
file_rows,
manifests: manifests.len(),
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::manifest::{
ExtractionMetadata, ManifestDestination, ManifestPart, ManifestSource, PartStatus,
};
fn manifest(run: &str, rows: i64, source: Option<i64>) -> RunManifest {
RunManifest {
manifest_version: crate::manifest::MANIFEST_VERSION,
run_id: run.into(),
export_name: "orders".into(),
mode: "batch".into(),
started_at: "t".into(),
finished_at: "t".into(),
status: ManifestStatus::Success,
source: ManifestSource {
engine: "pg".into(),
schema: None,
table: None,
extraction: source.map(|n| ExtractionMetadata {
strategy: "full".into(),
cursor_column: None,
cursor_type: None,
cursor_low: None,
cursor_high: None,
source_row_count: Some(n),
}),
},
destination: ManifestDestination {
kind: "gcs".into(),
uri: "gs://b/p".into(),
},
format: "parquet".into(),
compression: "zstd".into(),
schema_fingerprint: "xxh3:0".into(),
row_count: rows,
part_count: 1,
parts: vec![ManifestPart {
part_id: 0,
path: "part-000000.parquet".into(),
rows,
size_bytes: 1,
content_fingerprint: "xxh3:0".into(),
content_md5: String::new(),
status: PartStatus::Committed,
}],
column_checksums: None,
checksum_key_column: None,
}
}
#[test]
fn sums_file_and_source_rows_across_manifests() {
let ms = vec![manifest("r1", 100, Some(100)), manifest("r2", 40, Some(40))];
let got = reconcile(&ms, false).unwrap();
assert_eq!(got.file_rows, 140);
assert_eq!(got.source_rows, Some(140));
assert_eq!(got.manifests, 2);
}
#[test]
fn source_rows_is_none_when_no_manifest_probed_the_source() {
let ms = vec![manifest("r1", 100, None), manifest("r2", 40, None)];
let got = reconcile(&ms, false).unwrap();
assert_eq!(got.file_rows, 140);
assert_eq!(
got.source_rows, None,
"unprobed source is unknown, not zero"
);
}
#[test]
fn source_rows_present_even_if_only_some_manifests_probed() {
let ms = vec![manifest("r1", 100, Some(100)), manifest("r2", 40, None)];
let got = reconcile(&ms, false).unwrap();
assert_eq!(got.source_rows, Some(100));
}
#[test]
fn empty_manifests_refuses_to_load() {
let err = reconcile(&[], false).unwrap_err().to_string();
assert!(err.contains("refusing to load"), "{err}");
}
#[test]
fn non_success_manifest_refuses_to_load() {
let mut m = manifest("r1", 100, Some(100));
m.status = ManifestStatus::Interrupted;
let err = reconcile(&[m], false).unwrap_err().to_string();
assert!(err.contains("not Success"), "{err}");
}
#[test]
fn self_inconsistent_manifest_refuses_to_load() {
let mut m = manifest("r1", 100, Some(100));
m.row_count = 999; let err = reconcile(&[m], false).unwrap_err().to_string();
assert!(err.contains("inconsistent"), "{err}");
}
#[test]
fn source_file_mismatch_hard_fails_by_default() {
let m = manifest("r1", 100, Some(120));
let err = reconcile(&[m], false).unwrap_err().to_string();
assert!(err.contains("source→file mismatch"), "{err}");
assert!(err.contains("dropped 20"), "{err}");
}
#[test]
fn source_file_mismatch_is_allowed_under_the_override() {
let m = manifest("r1", 100, Some(120));
let got = reconcile(&[m], true).expect("--allow-source-drift proceeds");
assert_eq!(got.file_rows, 100);
assert_eq!(
got.source_rows,
Some(120),
"the probed source count is still surfaced"
);
}
fn keyed(key: &str, run: &str, part: &str) -> (String, RunManifest) {
let mut m = manifest(run, 10, Some(10));
m.parts[0].path = part.into();
(key.to_string(), m)
}
#[test]
fn select_load_keys_picks_only_the_new_runs_parts() {
let all = vec![
"base/r1-000.parquet".to_string(),
"base/r2-000.parquet".to_string(),
];
let new = vec![keyed("base/manifest-r2.json", "r2", "r2-000.parquet")];
assert_eq!(
select_load_keys(&new, &all),
vec!["base/r2-000.parquet".to_string()],
"loads r2's part only — not r1's already-loaded file"
);
}
#[test]
fn select_load_uris_lists_and_re_prefixes_selected_keys_with_the_source_bucket() {
let (store, _g) = fs_store(&[
("base/r1-000.parquet", b"a".to_vec()),
("base/manifest-r1.json", b"{}".to_vec()), ]);
let new = vec![keyed("base/manifest-r1.json", "r1", "r1-000.parquet")];
assert_eq!(
select_load_uris(&store, "gs://my-bucket/base", &new).unwrap(),
vec!["gs://my-bucket/base/r1-000.parquet".to_string()],
"the selected key, re-prefixed with the source bucket — never the manifest"
);
}
#[test]
fn select_load_keys_resolves_a_snapshot_subprefix_manifest() {
let all = vec!["base/snapshot/snap-000.parquet".to_string()];
let new = vec![keyed(
"base/snapshot/manifest-r1.json",
"r1",
"snap-000.parquet",
)];
assert_eq!(
select_load_keys(&new, &all),
vec!["base/snapshot/snap-000.parquet".to_string()]
);
}
#[test]
fn select_load_keys_falls_back_to_full_listing_when_a_manifest_has_no_present_part() {
let all = vec!["base/a.parquet".to_string(), "base/b.parquet".to_string()];
let new = vec![keyed("base/manifest-r1.json", "r1", "missing.parquet")];
assert_eq!(
select_load_keys(&new, &all),
all,
"unresolvable part → blanket fallback"
);
}
#[test]
fn select_load_keys_empty_new_set_selects_nothing_never_the_full_listing() {
let all = vec![
"base/r1-000.parquet".to_string(),
"base/r2-000.parquet".to_string(),
];
assert!(
select_load_keys(&[], &all).is_empty(),
"no new runs ⇒ load nothing, not everything"
);
}
#[test]
fn gc_orphans_removes_unmanifested_parquet_only() {
let (store, _g) = fs_store(&[
("base/r1-000.parquet", b"aa".to_vec()), ("base/orphan.parquet", b"junk".to_vec()), ("base/manifest.json", b"{}".to_vec()), ("base/_SUCCESS", b"".to_vec()), ]);
let keyed = vec![keyed("base/manifest-r1.json", "r1", "r1-000.parquet")];
let (removed, bytes) = gc_orphans(&store, "gs://b/base", &keyed).unwrap();
assert_eq!(removed, 1, "only the unmanifested part is removed");
assert_eq!(bytes, 4, "'junk' is 4 bytes");
let mut left = store.list_files("base").unwrap();
left.sort();
assert_eq!(
left,
vec![
"base/_SUCCESS".to_string(),
"base/manifest.json".to_string(),
"base/r1-000.parquet".to_string(),
],
"the manifested part, the manifest, and _SUCCESS all survive"
);
}
#[test]
fn gc_orphans_of_an_all_manifested_prefix_removes_nothing() {
let (store, _g) = fs_store(&[("base/r1-000.parquet", b"a".to_vec())]);
let keyed = vec![keyed("base/manifest-r1.json", "r1", "r1-000.parquet")];
assert_eq!(gc_orphans(&store, "gs://b/base", &keyed).unwrap().0, 0);
}
#[test]
fn gc_orphans_keeps_a_snapshot_subprefix_manifested_part() {
let (store, _g) = fs_store(&[
("base/snapshot/s-000.parquet", b"a".to_vec()), ("base/orphan.parquet", b"x".to_vec()), ]);
let keyed = vec![keyed("base/snapshot/manifest-s.json", "s", "s-000.parquet")];
let (removed, _) = gc_orphans(&store, "gs://b/base", &keyed).unwrap();
assert_eq!(removed, 1, "the top-level orphan goes");
assert_eq!(
store.list_files("base/snapshot").unwrap(),
vec!["base/snapshot/s-000.parquet".to_string()],
"the snapshot-subprefix manifested part is kept"
);
}
#[test]
fn gc_orphans_does_not_protect_a_failed_runs_parts() {
let (store, _g) = fs_store(&[("base/f-000.parquet", b"x".to_vec())]);
let mut kv = keyed("base/manifest-f.json", "f", "f-000.parquet");
kv.1.status = ManifestStatus::Failed;
assert_eq!(gc_orphans(&store, "gs://b/base", &[kv]).unwrap().0, 1);
}
fn keyed_at(run: &str, finished_at: &str) -> (String, RunManifest) {
let mut m = manifest(run, 100, None);
m.finished_at = finished_at.into();
(format!("base/manifest-{run}.json"), m)
}
#[test]
fn latest_full_picks_the_newest_snapshot_not_all() {
let keyed = vec![
keyed_at("r1", "2026-01-01T00:00:00Z"),
keyed_at("r3", "2026-01-03T00:00:00Z"),
keyed_at("r2", "2026-01-02T00:00:00Z"),
];
let sel = latest_full(keyed);
assert_eq!(sel.len(), 1, "exactly one snapshot, never all");
assert_eq!(sel[0].1.run_id, "r3", "the newest by finished_at");
}
#[test]
fn latest_full_re_materializes_even_when_the_latest_is_already_loaded() {
let keyed = vec![
keyed_at("r1", "2026-01-01T00:00:00Z"),
keyed_at("r2", "2026-01-02T00:00:00Z"),
];
let sel = latest_full(keyed);
assert_eq!(sel.len(), 1);
assert_eq!(sel[0].1.run_id, "r2", "always the latest, loaded or not");
}
#[test]
fn latest_full_of_no_staged_runs_is_empty_so_the_caller_no_ops_without_truncating() {
assert!(latest_full(Vec::new()).is_empty());
}
#[test]
fn latest_full_orders_by_parsed_instant_not_lexical_bytes() {
let keyed = vec![
keyed_at("older", "2026-01-01T00:00:00Z"),
keyed_at("newer", "2026-01-01T00:00:00.500Z"),
];
let sel = latest_full(keyed);
assert_eq!(sel.len(), 1);
assert_eq!(
sel[0].1.run_id, "newer",
"the fractional-second run is the newer instant"
);
}
#[test]
fn select_runs_full_picks_the_latest_even_when_loaded_and_even_when_stateless() {
use crate::load::plan::LoadMode;
use std::collections::HashSet;
let keyed = vec![
keyed_at("r1", "2026-01-01T00:00:00Z"),
keyed_at("r2", "2026-01-02T00:00:00Z"),
];
let loaded = HashSet::from(["r2".to_string()]);
let sel = select_runs(keyed.clone(), &loaded, LoadMode::Full);
assert_eq!(sel.len(), 1);
assert_eq!(sel[0].1.run_id, "r2", "Full picks latest, loaded or not");
let sel = select_runs(keyed, &HashSet::new(), LoadMode::Full);
assert_eq!(sel.len(), 1, "stateless Full is not a blanket load");
assert_eq!(sel[0].1.run_id, "r2");
}
#[test]
fn select_runs_append_modes_filter_loaded_and_load_all_when_stateless() {
use crate::load::plan::LoadMode;
use std::collections::HashSet;
let keyed = vec![
keyed_at("r1", "2026-01-01T00:00:00Z"),
keyed_at("r2", "2026-01-02T00:00:00Z"),
];
let loaded = HashSet::from(["r1".to_string()]);
for mode in [LoadMode::Incremental, LoadMode::Cdc] {
let sel = select_runs(keyed.clone(), &loaded, mode);
assert_eq!(sel.len(), 1, "{mode:?}: only the unloaded run");
assert_eq!(sel[0].1.run_id, "r2");
assert_eq!(
select_runs(keyed.clone(), &HashSet::new(), mode).len(),
2,
"{mode:?}: stateless loads every run"
);
}
}
#[test]
fn is_run_unique_manifest_needs_both_prefix_and_json() {
assert!(is_run_unique_manifest("manifest-20260101T000000.json"));
assert!(!is_run_unique_manifest("manifest.json")); assert!(!is_run_unique_manifest("manifest-abc.txt")); assert!(!is_run_unique_manifest("data.json")); }
#[test]
fn chain_prefix_renders_source_and_files() {
let known = LoadIntegrity {
source_rows: Some(100),
file_rows: 100,
manifests: 1,
};
assert_eq!(known.chain_prefix(), "source 100 → files 100");
let unknown = LoadIntegrity {
source_rows: None,
file_rows: 40,
manifests: 1,
};
assert_eq!(unknown.chain_prefix(), "source ? → files 40");
}
#[test]
fn is_manifest_key_matches_only_the_final_segment() {
assert!(is_manifest_key("gs://b/p/manifest.json"));
assert!(is_manifest_key("manifest.json"));
assert!(!is_manifest_key("gs://b/p/part-0.parquet"));
assert!(!is_manifest_key("gs://b/p/x_manifest.json"));
}
fn fs_store(files: &[(&str, Vec<u8>)]) -> (GcsStore, tempfile::TempDir) {
let dir = tempfile::tempdir().unwrap();
for (rel, bytes) in files {
let p = dir.path().join(rel);
std::fs::create_dir_all(p.parent().unwrap()).unwrap();
std::fs::write(p, bytes).unwrap();
}
let store = GcsStore::open_fs(dir.path().to_str().unwrap()).unwrap();
(store, dir)
}
fn manifest_bytes(run: &str, rows: i64, source: Option<i64>) -> Vec<u8> {
serde_json::to_vec(&manifest(run, rows, source)).unwrap()
}
#[test]
fn list_manifest_keys_prefers_run_unique_copies_over_the_canonical_pointer() {
let (store, _g) = fs_store(&[
("base/manifest.json", b"{}".to_vec()),
("base/manifest-r1.json", b"{}".to_vec()),
("base/manifest-r2.json", b"{}".to_vec()),
("base/part-0.parquet", b"x".to_vec()), ]);
let mut keys = list_manifest_keys(&store, "base").unwrap();
keys.sort();
assert_eq!(
keys,
vec![
"base/manifest-r1.json".to_string(),
"base/manifest-r2.json".to_string(),
]
);
}
#[test]
fn list_manifest_keys_falls_back_to_the_canonical_name_for_a_single_run() {
let (store, _g) = fs_store(&[("base/manifest.json", b"{}".to_vec())]);
assert_eq!(
list_manifest_keys(&store, "base").unwrap(),
vec!["base/manifest.json".to_string()]
);
}
#[test]
fn fetch_manifests_keyed_reads_and_parses_every_run_copy_under_the_prefix() {
let (store, _g) = fs_store(&[
("base/manifest.json", manifest_bytes("r2", 40, Some(40))),
(
"base/manifest-r1.json",
manifest_bytes("r1", 100, Some(100)),
),
("base/manifest-r2.json", manifest_bytes("r2", 40, Some(40))),
]);
let manifests: Vec<_> = fetch_manifests_keyed(&store, "gs://my-bucket/base")
.unwrap()
.into_iter()
.map(|(_, m)| m)
.collect();
assert_eq!(manifests.len(), 2);
let integrity = reconcile(&manifests, false).unwrap();
assert_eq!(integrity.file_rows, 140);
assert_eq!(integrity.manifests, 2);
}
#[test]
fn fetch_manifests_keyed_names_the_key_when_a_manifest_is_unparseable() {
let (store, _g) = fs_store(&[("base/manifest.json", b"{ not json".to_vec())]);
let err = fetch_manifests_keyed(&store, "gs://my-bucket/base")
.unwrap_err()
.to_string();
assert!(
err.contains("parsing manifest") && err.contains("base/manifest.json"),
"error should name the offending key: {err}"
);
}
const FAKE_GCS_ENDPOINT: &str = "http://127.0.0.1:4443";
const FAKE_GCS_BUCKET: &str = "rivet-load-emulator";
fn fake_gcs_store() -> GcsStore {
let created = std::process::Command::new("curl")
.args([
"-s",
"-X",
"POST",
&format!("{FAKE_GCS_ENDPOINT}/storage/v1/b?project=rivet-test"),
"-H",
"Content-Type: application/json",
"-d",
&format!("{{\"name\":\"{FAKE_GCS_BUCKET}\"}}"),
])
.output();
assert!(
created.is_ok_and(|o| o.status.success()),
"could not reach fake-gcs to create the bucket — is `docker compose up -d fake-gcs` running on :4443?"
);
let cfg = crate::config::DestinationConfig {
destination_type: crate::config::DestinationType::Gcs,
bucket: Some(FAKE_GCS_BUCKET.into()),
endpoint: Some(FAKE_GCS_ENDPOINT.into()),
allow_anonymous: true,
..Default::default()
};
GcsStore::new(&cfg).expect("build GcsStore against fake-gcs")
}
fn drain(store: &GcsStore, prefix: &str) {
for key in store.list_files(prefix).unwrap() {
store.remove(&key).unwrap();
}
}
#[test]
#[ignore = "emulator: needs `docker compose up -d fake-gcs` (fsouza/fake-gcs-server :4443)"]
fn storage_contract_over_fake_gcs() {
let store = fake_gcs_store();
let prefix = "load-contract/orders";
drain(&store, prefix);
let gs = format!("gs://{FAKE_GCS_BUCKET}/{prefix}");
store
.put(
&format!("{prefix}/manifest-r1.json"),
&manifest_bytes("r1", 100, Some(100)),
)
.unwrap();
store
.put(&format!("{prefix}/part-000000.parquet"), b"rows-of-r1")
.unwrap();
store
.put(&format!("{prefix}/orphan.parquet"), b"crash-leftover")
.unwrap();
let keyed = fetch_manifests_keyed(&store, &gs).unwrap();
assert_eq!(keyed.len(), 1, "the run's manifest, read back over GCS");
let manifests: Vec<_> = keyed.iter().map(|(_, m)| m.clone()).collect();
assert_eq!(
reconcile(&manifests, false).unwrap().file_rows,
100,
"file_rows drives the count-gate; a bad GCS read would corrupt it"
);
assert_eq!(
select_load_uris(&store, &gs, &keyed).unwrap(),
vec![format!(
"gs://{FAKE_GCS_BUCKET}/{prefix}/part-000000.parquet"
)],
"load pulls the manifested part, not the unmanifested crash orphan"
);
let (removed, _bytes) = gc_orphans(&store, &gs, &keyed).unwrap();
assert_eq!(
removed, 1,
"exactly the orphan parquet is GC'd over real GCS"
);
let mut left = store.list_files(prefix).unwrap();
left.sort();
assert_eq!(
left,
vec![
format!("{prefix}/manifest-r1.json"),
format!("{prefix}/part-000000.parquet"),
],
"the manifested part + its manifest survive the orphan GC"
);
drain(&store, prefix);
assert!(
store.list_files(prefix).unwrap().is_empty(),
"teardown left the prefix clean over real GCS"
);
}
}