use super::enumerate_segment_dirs;
use super::segment_subdir;
use crate::graph::storage::disk::csr::{CsrEdge, DiskNodeSlot, EdgeEndpoints};
use crate::graph::storage::disk::graph_persist::{concat_segment_csrs, SegmentCsr};
use crate::graph::storage::mapped::mmap_vec::MmapOrVec;
use petgraph::graph::NodeIndex;
use tempfile::TempDir;
fn seg(
node_slots: Vec<DiskNodeSlot>,
out_offsets: Vec<u64>,
out_edges: Vec<CsrEdge>,
in_offsets: Vec<u64>,
in_edges: Vec<CsrEdge>,
edge_endpoints: Vec<EdgeEndpoints>,
) -> SegmentCsr {
SegmentCsr {
node_slots: from_vec(node_slots),
out_offsets: from_vec(out_offsets),
out_edges: from_vec(out_edges),
in_offsets: from_vec(in_offsets),
in_edges: from_vec(in_edges),
edge_endpoints: from_vec(edge_endpoints),
conn_type_index_types: MmapOrVec::new(),
conn_type_index_offsets: MmapOrVec::new(),
conn_type_index_sources: MmapOrVec::new(),
peer_count_types: MmapOrVec::new(),
peer_count_offsets: MmapOrVec::new(),
peer_count_entries: MmapOrVec::new(),
}
}
fn from_vec<T: Copy + Default + 'static>(v: Vec<T>) -> MmapOrVec<T> {
let mut m: MmapOrVec<T> = MmapOrVec::with_capacity(v.len());
for x in v {
m.push(x);
}
m
}
fn slot(node_type: u64, row_id: u32) -> DiskNodeSlot {
DiskNodeSlot {
node_type,
row_id,
flags: DiskNodeSlot::ALIVE_BIT,
}
}
#[test]
fn segment_subdir_zero_pads_three_digits() {
assert_eq!(segment_subdir(0), "seg_000");
assert_eq!(segment_subdir(1), "seg_001");
assert_eq!(segment_subdir(42), "seg_042");
assert_eq!(segment_subdir(999), "seg_999");
assert_eq!(segment_subdir(1234), "seg_1234");
}
#[test]
fn enumerate_segment_dirs_returns_sorted_ids() {
let tmp = TempDir::new().unwrap();
for id in [5u32, 0, 2, 17] {
std::fs::create_dir_all(tmp.path().join(segment_subdir(id))).unwrap();
}
let got: Vec<u32> = enumerate_segment_dirs(tmp.path())
.into_iter()
.map(|(id, _)| id)
.collect();
assert_eq!(got, vec![0, 2, 5, 17]);
}
#[test]
fn enumerate_segment_dirs_skips_non_matching_entries() {
let tmp = TempDir::new().unwrap();
std::fs::create_dir_all(tmp.path().join("seg_000")).unwrap();
std::fs::create_dir_all(tmp.path().join("seg_abc")).unwrap(); std::fs::create_dir_all(tmp.path().join("not_a_segment")).unwrap();
std::fs::write(tmp.path().join("seg_001"), b"not-a-dir").unwrap();
std::fs::write(tmp.path().join("disk_graph_meta.json"), b"{}").unwrap();
let got: Vec<u32> = enumerate_segment_dirs(tmp.path())
.into_iter()
.map(|(id, _)| id)
.collect();
assert_eq!(got, vec![0]);
}
#[test]
fn enumerate_segment_dirs_on_missing_dir_returns_empty() {
let tmp = TempDir::new().unwrap();
let missing = tmp.path().join("does-not-exist");
assert!(enumerate_segment_dirs(&missing).is_empty());
}
#[test]
fn enumerate_segment_dirs_empty_root_returns_empty() {
let tmp = TempDir::new().unwrap();
assert!(enumerate_segment_dirs(tmp.path()).is_empty());
}
#[test]
fn concat_empty_input_returns_all_empty() {
let c = concat_segment_csrs(Vec::new());
assert_eq!(c.node_slots.len(), 0);
assert_eq!(c.out_offsets.len(), 0);
assert_eq!(c.out_edges.len(), 0);
assert_eq!(c.in_offsets.len(), 0);
assert_eq!(c.in_edges.len(), 0);
assert_eq!(c.edge_endpoints.len(), 0);
}
#[test]
fn concat_single_segment_returns_it_unchanged() {
let s = seg(
vec![slot(7, 100), slot(7, 101)],
vec![0, 1, 1], vec![CsrEdge {
peer: 1,
edge_idx: 0,
}],
vec![0, 0, 1], vec![CsrEdge {
peer: 0,
edge_idx: 0,
}],
vec![EdgeEndpoints {
source: 0,
target: 1,
connection_type: 42,
}],
);
let c = concat_segment_csrs(vec![s]);
assert_eq!(c.node_slots.len(), 2);
assert_eq!(c.out_offsets.len(), 3);
assert_eq!(c.out_edges.len(), 1);
assert_eq!(c.out_edges.get(0).edge_idx, 0); assert_eq!(c.edge_endpoints.len(), 1);
assert_eq!(c.edge_endpoints.get(0).source, 0);
}
#[test]
fn concat_two_segments_stitches_offsets_and_shifts_edge_idx() {
let s0 = seg(
vec![slot(1, 10), slot(1, 11)],
vec![0, 1, 1],
vec![CsrEdge {
peer: 1,
edge_idx: 0,
}],
vec![0, 0, 1],
vec![CsrEdge {
peer: 0,
edge_idx: 0,
}],
vec![EdgeEndpoints {
source: 0,
target: 1,
connection_type: 100,
}],
);
let s1 = seg(
vec![slot(2, 20), slot(2, 21)],
vec![0, 1, 2],
vec![
CsrEdge {
peer: 3,
edge_idx: 0,
},
CsrEdge {
peer: 2,
edge_idx: 1,
},
],
vec![0, 1, 2],
vec![
CsrEdge {
peer: 3,
edge_idx: 1,
},
CsrEdge {
peer: 2,
edge_idx: 0,
},
],
vec![
EdgeEndpoints {
source: 2,
target: 3,
connection_type: 200,
},
EdgeEndpoints {
source: 3,
target: 2,
connection_type: 201,
},
],
);
let c = concat_segment_csrs(vec![s0, s1]);
assert_eq!(c.node_slots.len(), 4);
assert_eq!(c.out_offsets.len(), 5); assert_eq!(c.in_offsets.len(), 5);
assert_eq!(c.out_edges.len(), 3);
assert_eq!(c.in_edges.len(), 3);
assert_eq!(c.edge_endpoints.len(), 3);
let out_off: Vec<u64> = (0..c.out_offsets.len())
.map(|i| c.out_offsets.get(i))
.collect();
assert_eq!(out_off, vec![0, 1, 1, 2, 3]);
let in_off: Vec<u64> = (0..c.in_offsets.len())
.map(|i| c.in_offsets.get(i))
.collect();
assert_eq!(in_off, vec![0, 0, 1, 2, 3]);
assert_eq!(c.out_edges.get(0).edge_idx, 0);
assert_eq!(c.out_edges.get(0).peer, 1);
assert_eq!(c.out_edges.get(1).edge_idx, 1);
assert_eq!(c.out_edges.get(1).peer, 3);
assert_eq!(c.out_edges.get(2).edge_idx, 2);
assert_eq!(c.out_edges.get(2).peer, 2);
assert_eq!(c.in_edges.get(0).edge_idx, 0); assert_eq!(c.in_edges.get(1).edge_idx, 2); assert_eq!(c.in_edges.get(2).edge_idx, 1);
assert_eq!(c.edge_endpoints.get(0).source, 0);
assert_eq!(c.edge_endpoints.get(0).target, 1);
assert_eq!(c.edge_endpoints.get(1).source, 2);
assert_eq!(c.edge_endpoints.get(1).target, 3);
assert_eq!(c.edge_endpoints.get(2).source, 3);
assert_eq!(c.edge_endpoints.get(2).target, 2);
}
#[test]
fn concat_three_segments_keeps_offset_chain_consistent() {
let mk_one_node = |global_id: u32, conn: u64| {
seg(
vec![slot(1, 0)],
vec![0, 1],
vec![CsrEdge {
peer: global_id,
edge_idx: 0,
}],
vec![0, 1],
vec![CsrEdge {
peer: global_id,
edge_idx: 0,
}],
vec![EdgeEndpoints {
source: global_id,
target: global_id,
connection_type: conn,
}],
)
};
let c = concat_segment_csrs(vec![
mk_one_node(0, 10),
mk_one_node(1, 20),
mk_one_node(2, 30),
]);
let out_off: Vec<u64> = (0..c.out_offsets.len())
.map(|i| c.out_offsets.get(i))
.collect();
assert_eq!(out_off, vec![0, 1, 2, 3]);
assert_eq!(c.out_edges.get(0).edge_idx, 0);
assert_eq!(c.out_edges.get(1).edge_idx, 1);
assert_eq!(c.out_edges.get(2).edge_idx, 2);
for k in 0..3 {
assert_eq!(c.edge_endpoints.get(k).source, k as u32);
assert_eq!(c.edge_endpoints.get(k).target, k as u32);
}
}
#[test]
fn concat_handles_edgeless_segment() {
let s0 = seg(
vec![slot(1, 0)],
vec![0, 1],
vec![CsrEdge {
peer: 0,
edge_idx: 0,
}],
vec![0, 1],
vec![CsrEdge {
peer: 0,
edge_idx: 0,
}],
vec![EdgeEndpoints {
source: 0,
target: 0,
connection_type: 1,
}],
);
let s_empty = seg(
vec![slot(1, 1)],
vec![0, 0],
Vec::new(),
vec![0, 0],
Vec::new(),
Vec::new(),
);
let s1 = seg(
vec![slot(1, 2)],
vec![0, 1],
vec![CsrEdge {
peer: 2,
edge_idx: 0,
}],
vec![0, 1],
vec![CsrEdge {
peer: 2,
edge_idx: 0,
}],
vec![EdgeEndpoints {
source: 2,
target: 2,
connection_type: 3,
}],
);
let c = concat_segment_csrs(vec![s0, s_empty, s1]);
let out_off: Vec<u64> = (0..c.out_offsets.len())
.map(|i| c.out_offsets.get(i))
.collect();
assert_eq!(out_off, vec![0, 1, 1, 2]);
assert_eq!(c.out_edges.len(), 2);
assert_eq!(c.out_edges.get(1).edge_idx, 1);
}
use crate::datatypes::values::Value;
use crate::graph::schema::{EdgeData, NodeData, StringInterner};
fn seal_test_node(interner: &mut StringInterner, id: i64, ntype: &str) -> NodeData {
NodeData::new(
Value::Int64(id),
Value::String(format!("n{id}")),
ntype.to_string(),
std::collections::HashMap::new(),
interner,
)
}
fn seal_test_edge(interner: &mut StringInterner, ct: &str) -> EdgeData {
EdgeData::new(ct.to_string(), std::collections::HashMap::new(), interner)
}
#[test]
fn seal_rejects_when_nothing_to_seal() {
let tmp = TempDir::new().unwrap();
let mut interner = StringInterner::new();
let mut dg = super::DiskGraph::new_at_path(tmp.path()).unwrap();
dg.defer_csr = true;
let _n0 = dg.add_node(seal_test_node(&mut interner, 0, "A"));
dg.build_csr_from_pending();
dg.save_to_dir(tmp.path(), &interner).unwrap();
let err = dg.seal_to_new_segment(tmp.path()).unwrap_err();
assert!(err.to_string().contains("nothing to seal"));
}
#[test]
fn seal_accepts_cross_segment_edges_via_full_range() {
let tmp = TempDir::new().unwrap();
let mut interner = StringInterner::new();
let mut dg = super::DiskGraph::new_at_path(tmp.path()).unwrap();
dg.defer_csr = true;
let n0 = dg.add_node(seal_test_node(&mut interner, 0, "A"));
let n1 = dg.add_node(seal_test_node(&mut interner, 1, "A"));
dg.add_edge(n0, n1, seal_test_edge(&mut interner, "T"));
dg.build_csr_from_pending();
dg.save_to_dir(tmp.path(), &interner).unwrap();
let n2 = dg.add_node(seal_test_node(&mut interner, 2, "A"));
dg.add_edge(n0, n2, seal_test_edge(&mut interner, "T"));
let seg_id = dg.seal_to_new_segment(tmp.path()).unwrap();
assert_eq!(seg_id, 1);
let out_offsets_size = std::fs::metadata(tmp.path().join("seg_001/out_offsets.bin"))
.unwrap()
.len();
assert_eq!(
out_offsets_size,
(3 + 1) * 8,
"seg_001 must be full-range (4 u64 offsets covering 3 global nodes)"
);
drop(dg);
let mut interner2 = StringInterner::new();
let (reloaded, _tmp_zst) = super::DiskGraph::load_from_dir(tmp.path(), &mut interner2).unwrap();
let start = reloaded.out_offsets.get(0) as usize;
let end = reloaded.out_offsets.get(1) as usize;
let peers: Vec<u32> = (start..end)
.map(|i| reloaded.out_edges.get(i).peer)
.collect();
assert!(peers.contains(&1), "missing seg_0 edge n0→n1");
assert!(peers.contains(&2), "missing seg_1 cross-segment edge n0→n2");
}
#[test]
fn seal_round_trip_basic_reads() {
let tmp = TempDir::new().unwrap();
let mut interner = StringInterner::new();
let mut dg = super::DiskGraph::new_at_path(tmp.path()).unwrap();
dg.defer_csr = true;
let n0 = dg.add_node(seal_test_node(&mut interner, 0, "A"));
let n1 = dg.add_node(seal_test_node(&mut interner, 1, "A"));
let _n2 = dg.add_node(seal_test_node(&mut interner, 2, "A"));
dg.add_edge(n0, n1, seal_test_edge(&mut interner, "T"));
dg.build_csr_from_pending();
dg.save_to_dir(tmp.path(), &interner).unwrap();
assert_eq!(dg.node_count, 3);
assert_eq!(dg.sealed_nodes_bound, 3);
let n3 = dg.add_node(seal_test_node(&mut interner, 3, "B"));
let n4 = dg.add_node(seal_test_node(&mut interner, 4, "B"));
dg.add_edge(n3, n4, seal_test_edge(&mut interner, "U"));
let pre_edge_count = dg.edge_count;
let pre_node_count = dg.node_count;
assert_eq!(pre_node_count, 5);
assert_eq!(pre_edge_count, 2);
let seg_id = dg.seal_to_new_segment(tmp.path()).unwrap();
assert_eq!(seg_id, 1);
assert_eq!(dg.sealed_nodes_bound, 5);
assert!(dg.overflow_out.is_empty());
assert!(dg.overflow_in.is_empty());
let seg1 = tmp.path().join("seg_001");
for name in [
"node_slots.bin",
"out_offsets.bin",
"out_edges.bin",
"in_offsets.bin",
"in_edges.bin",
"edge_endpoints.bin",
] {
assert!(seg1.join(name).exists(), "missing {name}");
}
let manifest = super::super::segment_summary::SegmentManifest::load_from(tmp.path()).unwrap();
assert_eq!(manifest.len(), 2);
assert_eq!(manifest.segments[1].segment_id, 1);
assert_eq!(manifest.segments[1].node_id_lo, 3);
assert_eq!(manifest.segments[1].node_id_hi, 5);
assert_eq!(manifest.segments[1].edge_count, 1);
drop(dg);
let mut interner2 = StringInterner::new();
let (reloaded, _tmp_zst) = super::DiskGraph::load_from_dir(tmp.path(), &mut interner2).unwrap();
assert_eq!(reloaded.node_count, pre_node_count);
assert_eq!(reloaded.edge_count, pre_edge_count);
assert_eq!(reloaded.sealed_nodes_bound, 5);
let n3_idx = 3usize;
let start = reloaded.out_offsets.get(n3_idx) as usize;
let end = reloaded.out_offsets.get(n3_idx + 1) as usize;
assert_eq!(end - start, 1, "expected 1 outgoing edge for seg_1 node 3");
let e = reloaded.out_edges.get(start);
assert_eq!(e.peer, 4);
assert_eq!(e.edge_idx, 1);
let ep = reloaded.edge_endpoints.get(1);
assert_eq!(ep.source, 3);
assert_eq!(ep.target, 4);
let ep0 = reloaded.edge_endpoints.get(0);
assert_eq!(ep0.source, 0);
assert_eq!(ep0.target, 1);
let _ = (n1, n4); }
#[test]
fn seal_round_trip_auxiliary_indexes() {
let tmp = TempDir::new().unwrap();
let mut interner = StringInterner::new();
let mut dg = super::DiskGraph::new_at_path(tmp.path()).unwrap();
dg.defer_csr = true;
let n0 = dg.add_node(seal_test_node(&mut interner, 0, "A"));
let n1 = dg.add_node(seal_test_node(&mut interner, 1, "A"));
let _n2 = dg.add_node(seal_test_node(&mut interner, 2, "A"));
dg.add_edge(n0, n1, seal_test_edge(&mut interner, "T"));
dg.build_csr_from_pending();
dg.save_to_dir(tmp.path(), &interner).unwrap();
let n3 = dg.add_node(seal_test_node(&mut interner, 3, "B"));
let n4 = dg.add_node(seal_test_node(&mut interner, 4, "B"));
dg.add_edge(n3, n4, seal_test_edge(&mut interner, "U"));
let self_loop = {
let weight_key = interner.get_or_intern("weight");
let ed = crate::graph::schema::EdgeData {
connection_type: interner.get_or_intern("U"),
properties: vec![(weight_key, Value::Float64(2.5))],
};
dg.add_edge(n3, n3, ed)
};
dg.seal_to_new_segment(tmp.path()).unwrap();
let seg1 = tmp.path().join("seg_001");
for name in [
"conn_type_index_types.bin",
"conn_type_index_offsets.bin",
"conn_type_index_sources.bin",
"peer_count_types.bin",
"peer_count_offsets.bin",
"peer_count_entries.bin",
] {
assert!(seg1.join(name).exists(), "phase-5 missing {name}");
}
drop(dg);
let mut interner2 = StringInterner::new();
let (reloaded, _tmp_zst) = super::DiskGraph::load_from_dir(tmp.path(), &mut interner2).unwrap();
let t_key = interner2.get_or_intern("T").as_u64();
let u_key = interner2.get_or_intern("U").as_u64();
let cti_types: Vec<u64> = (0..reloaded.conn_type_index_types.len())
.map(|i| reloaded.conn_type_index_types.get(i))
.collect();
assert!(
cti_types.contains(&t_key),
"T missing from merged conn_type_index"
);
assert!(
cti_types.contains(&u_key),
"U missing from merged conn_type_index"
);
let u_counts = reloaded
.lookup_peer_counts(u_key)
.expect("U histogram present");
assert_eq!(u_counts.get(&4), Some(&1));
assert_eq!(u_counts.get(&3), Some(&1));
let t_counts = reloaded
.lookup_peer_counts(t_key)
.expect("T histogram present");
assert_eq!(t_counts.get(&1), Some(&1));
let weight_key = interner2.get_or_intern("weight");
let weight = reloaded
.edge_properties
.get(self_loop.index() as u32)
.expect("self_loop has props");
let (k, v) = &weight.as_ref()[0];
assert_eq!(*k, weight_key);
assert_eq!(*v, Value::Float64(2.5));
}
#[test]
fn save_to_dir_auto_wires_seal_when_tail_is_clean() {
let tmp = TempDir::new().unwrap();
let mut interner = StringInterner::new();
let mut dg = super::DiskGraph::new_at_path(tmp.path()).unwrap();
dg.defer_csr = true;
let n0 = dg.add_node(seal_test_node(&mut interner, 0, "A"));
let n1 = dg.add_node(seal_test_node(&mut interner, 1, "A"));
dg.add_edge(n0, n1, seal_test_edge(&mut interner, "T"));
dg.build_csr_from_pending();
dg.save_to_dir(tmp.path(), &interner).unwrap();
assert!(tmp.path().join("seg_000").exists());
assert!(!tmp.path().join("seg_001").exists());
assert_eq!(dg.sealed_nodes_bound, 2);
let n2 = dg.add_node(seal_test_node(&mut interner, 2, "B"));
let n3 = dg.add_node(seal_test_node(&mut interner, 3, "B"));
dg.add_edge(n2, n3, seal_test_edge(&mut interner, "U"));
dg.save_to_dir(tmp.path(), &interner).unwrap();
assert!(
tmp.path().join("seg_001").exists(),
"phase-6 auto-wire should have produced seg_001/"
);
assert_eq!(dg.sealed_nodes_bound, 4);
let manifest = super::super::segment_summary::SegmentManifest::load_from(tmp.path()).unwrap();
assert_eq!(manifest.len(), 2, "manifest should have 2 segments");
drop(dg);
let mut interner2 = StringInterner::new();
let (reloaded, _tmp_zst) = super::DiskGraph::load_from_dir(tmp.path(), &mut interner2).unwrap();
assert_eq!(reloaded.node_count, 4);
assert_eq!(reloaded.edge_count, 2);
}
#[test]
fn save_to_dir_seals_cross_segment_overflow_as_full_range() {
let tmp = TempDir::new().unwrap();
let mut interner = StringInterner::new();
let mut dg = super::DiskGraph::new_at_path(tmp.path()).unwrap();
dg.defer_csr = true;
let n0 = dg.add_node(seal_test_node(&mut interner, 0, "A"));
let n1 = dg.add_node(seal_test_node(&mut interner, 1, "A"));
dg.add_edge(n0, n1, seal_test_edge(&mut interner, "T"));
dg.build_csr_from_pending();
dg.save_to_dir(tmp.path(), &interner).unwrap();
let n2 = dg.add_node(seal_test_node(&mut interner, 2, "B"));
let n3 = dg.add_node(seal_test_node(&mut interner, 3, "B"));
dg.add_edge(n0, n2, seal_test_edge(&mut interner, "T"));
dg.add_edge(n2, n3, seal_test_edge(&mut interner, "U"));
dg.save_to_dir(tmp.path(), &interner).unwrap();
assert!(
tmp.path().join("seg_001").exists(),
"phase-7 save_to_dir should seal cross-segment overflow"
);
drop(dg);
let mut interner2 = StringInterner::new();
let (reloaded, _tmp_zst) = super::DiskGraph::load_from_dir(tmp.path(), &mut interner2).unwrap();
assert_eq!(reloaded.node_count, 4);
assert_eq!(reloaded.edge_count, 3);
}
#[test]
fn conn_type_index_sources_are_global_after_segment_local_seal() {
let tmp = TempDir::new().unwrap();
let mut interner = StringInterner::new();
let mut dg = super::DiskGraph::new_at_path(tmp.path()).unwrap();
for i in 0..5 {
dg.add_node(seal_test_node(&mut interner, i, "A"));
}
dg.save_to_dir(tmp.path(), &interner).unwrap();
let n5 = dg.add_node(seal_test_node(&mut interner, 5, "B"));
let n6 = dg.add_node(seal_test_node(&mut interner, 6, "B"));
let n7 = dg.add_node(seal_test_node(&mut interner, 7, "B"));
dg.add_edge(n5, n6, seal_test_edge(&mut interner, "T"));
dg.add_edge(n6, n7, seal_test_edge(&mut interner, "T"));
dg.add_edge(n7, n5, seal_test_edge(&mut interner, "T"));
dg.save_to_dir(tmp.path(), &interner).unwrap();
drop(dg);
let mut interner2 = StringInterner::new();
let (reloaded, _tmp_zst) = super::DiskGraph::load_from_dir(tmp.path(), &mut interner2).unwrap();
let t_key = interner2.get_or_intern("T").as_u64();
let sources = reloaded
.sources_for_conn_type(t_key)
.expect("T index should exist");
let mut sources = sources;
sources.sort_unstable();
assert_eq!(
sources,
vec![5u32, 6, 7],
"segment-local seal's conn_type_index_sources must be shifted \
by node_lo on merge (regression from 0.8.11 pre-fix)"
);
}
#[test]
fn compact_rewrite_after_seal_cleans_stale_segs_and_persists_heap_arrays() {
let tmp = TempDir::new().unwrap();
let mut interner = StringInterner::new();
let mut dg = super::DiskGraph::new_at_path(tmp.path()).unwrap();
for i in 0..5 {
dg.add_node(seal_test_node(&mut interner, i, "A"));
}
dg.save_to_dir(tmp.path(), &interner).unwrap();
let n5 = dg.add_node(seal_test_node(&mut interner, 5, "B"));
let n6 = dg.add_node(seal_test_node(&mut interner, 6, "B"));
let n7 = dg.add_node(seal_test_node(&mut interner, 7, "B"));
dg.add_edge(n5, n6, seal_test_edge(&mut interner, "T"));
dg.add_edge(n6, n7, seal_test_edge(&mut interner, "T"));
dg.save_to_dir(tmp.path(), &interner).unwrap();
assert!(
tmp.path().join("seg_001").exists(),
"phase-6 seal should have produced seg_001"
);
let n0 = NodeIndex::new(0);
dg.add_edge(n0, n5, seal_test_edge(&mut interner, "T"));
dg.save_to_dir(tmp.path(), &interner).unwrap();
assert!(
!tmp.path().join("seg_001").exists(),
"compact-rewrite must clean up stale seg_NNN dirs"
);
drop(dg);
let mut interner2 = StringInterner::new();
let (reloaded, _tmp_zst) = super::DiskGraph::load_from_dir(tmp.path(), &mut interner2).unwrap();
assert_eq!(reloaded.node_count, 8, "all 8 nodes must survive");
assert_eq!(reloaded.edge_count, 3, "3 T-edges must survive");
}