use std::collections::HashMap;
use crate::graph::unified::bind::scope::provenance::{
FileStableId, ScopeProvenance, ScopeProvenanceStore, compute_scope_stable_id,
};
use crate::graph::unified::concurrent::CodeGraph;
use crate::graph::unified::edge::kind::EdgeKind;
use crate::graph::unified::mutation_target::GraphMutationTarget;
use crate::graph::unified::node::id::NodeId;
use super::super::bind::alias::derive_aliases;
use super::super::bind::scope::derive::derive_scopes;
use super::super::bind::shadow::derive_shadows;
pub struct BindingEdgeIndex {
pub contains_parents: HashMap<NodeId, NodeId>,
pub imports_by_target: HashMap<NodeId, Vec<(NodeId, EdgeKind)>>,
pub defines_contains_by_source: HashMap<NodeId, Vec<(NodeId, EdgeKind)>>,
}
fn build_binding_edge_index<G: GraphMutationTarget>(graph: &G) -> BindingEdgeIndex {
let mut contains_parents: HashMap<NodeId, NodeId> = HashMap::new();
let mut imports_by_target: HashMap<NodeId, Vec<(NodeId, EdgeKind)>> = HashMap::new();
let mut defines_contains_by_source: HashMap<NodeId, Vec<(NodeId, EdgeKind)>> = HashMap::new();
let forward = graph.edges().forward();
for edge in forward.delta().iter() {
if !edge.is_add() {
continue;
}
match &edge.kind {
EdgeKind::Contains => {
contains_parents.insert(edge.target, edge.source);
defines_contains_by_source
.entry(edge.source)
.or_default()
.push((edge.target, EdgeKind::Contains));
}
EdgeKind::Defines => {
defines_contains_by_source
.entry(edge.source)
.or_default()
.push((edge.target, EdgeKind::Defines));
}
EdgeKind::Imports { alias, is_wildcard } => {
imports_by_target.entry(edge.target).or_default().push((
edge.source,
EdgeKind::Imports {
alias: *alias,
is_wildcard: *is_wildcard,
},
));
}
_ => {}
}
}
drop(forward);
BindingEdgeIndex {
contains_parents,
imports_by_target,
defines_contains_by_source,
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct BindingDerivationStats {
pub scopes: u64,
pub aliases: u64,
pub aliases_with_invalid_scope: u64,
pub shadows: u64,
}
pub fn derive_binding_plane(graph: &mut CodeGraph) -> BindingDerivationStats {
derive_binding_plane_generic(graph)
}
pub(crate) fn derive_binding_plane_generic<G: GraphMutationTarget>(
graph: &mut G,
) -> BindingDerivationStats {
let mut stats = BindingDerivationStats::default();
let edge_index = build_binding_edge_index(graph);
let scope_arena = derive_scopes(graph, &edge_index);
stats.scopes = u64::try_from(scope_arena.len()).unwrap_or(u64::MAX);
graph.set_scope_arena(scope_arena);
let scope_arena_ref = GraphMutationTarget::scope_arena(graph);
let (alias_table, invalid_scope_count) = derive_aliases(graph, scope_arena_ref, &edge_index);
stats.aliases = u64::try_from(alias_table.len()).unwrap_or(u64::MAX);
stats.aliases_with_invalid_scope = invalid_scope_count;
if invalid_scope_count > 0 {
log::warn!(
"Phase 4e: {} Import nodes had no enclosing scope; \
alias entries emitted with ScopeId::INVALID — \
check Phase 1 plugins for missing Contains edges",
invalid_scope_count
);
}
graph.set_alias_table(alias_table);
let scope_arena_ref = GraphMutationTarget::scope_arena(graph);
let shadow_table = derive_shadows(graph, scope_arena_ref, &edge_index);
stats.shadows = u64::try_from(shadow_table.len()).unwrap_or(u64::MAX);
graph.set_shadow_table(shadow_table);
let fact_epoch = GraphMutationTarget::fact_epoch(graph);
let mut provenance_store = ScopeProvenanceStore::new();
provenance_store.resize_to(GraphMutationTarget::scope_arena(graph).slot_count());
for (scope_id, scope) in GraphMutationTarget::scope_arena(graph).iter() {
let file_path = GraphMutationTarget::files(graph)
.resolve(scope.file)
.expect("scope file must be registered in the file registry");
let file_stable = FileStableId::from_registry_path(&file_path);
let content_hash = GraphMutationTarget::files(graph)
.file_provenance(scope.file)
.map(|v| *v.content_hash)
.unwrap_or([0u8; 32]);
let stable =
compute_scope_stable_id(file_stable, content_hash, scope.kind, scope.byte_span);
provenance_store.insert(
scope_id,
ScopeProvenance {
first_seen_epoch: fact_epoch,
last_seen_epoch: fact_epoch,
file_stable_id: file_stable,
stable_id: stable,
},
);
}
log::debug!(
"Phase 4e: stamped {} scope provenance records (epoch {})",
provenance_store.len(),
fact_epoch
);
graph.set_scope_provenance_store(provenance_store);
stats
}
#[cfg(all(test, feature = "rebuild-internals"))]
mod phase2_rebuild_tests {
use std::collections::{HashMap, HashSet};
use std::path::Path;
use super::*;
use crate::graph::unified::bind::alias::{AliasEntry, AliasTable};
use crate::graph::unified::bind::scope::provenance::ScopeProvenanceStore;
use crate::graph::unified::bind::scope::{ScopeArena, ScopeId, ScopeKind};
use crate::graph::unified::bind::shadow::{ShadowEntry, ShadowTable};
use crate::graph::unified::concurrent::CodeGraph;
use crate::graph::unified::edge::EdgeKind;
use crate::graph::unified::node::NodeKind;
use crate::graph::unified::storage::NodeEntry;
use crate::graph::unified::string::id::StringId;
struct SeedHandles {
_module: NodeId,
_function: NodeId,
_import: NodeId,
_var_outer: NodeId,
_var_inner: NodeId,
alias_local_name: StringId,
alias_target_name: StringId,
shadow_symbol: StringId,
shadow_outer_offset: u32,
shadow_inner_offset: u32,
}
fn seed<G: GraphMutationTarget>(graph: &mut G, file_suffix: &str) -> SeedHandles {
let file_path = format!("/virtual/bind_{file_suffix}.rs");
let file_id = graph
.files_mut()
.register(Path::new(&file_path))
.expect("register test file");
let mod_name = graph.strings_mut().intern("my_mod").unwrap();
let fn_name = graph.strings_mut().intern("my_fn").unwrap();
let import_local = graph.strings_mut().intern("baz").unwrap();
let import_target = graph.strings_mut().intern("foo::bar").unwrap();
let shadow_symbol = graph.strings_mut().intern("x").unwrap();
let mut mod_entry = NodeEntry::new(NodeKind::Module, mod_name, file_id);
mod_entry.qualified_name = Some(mod_name);
mod_entry.start_byte = 0;
mod_entry.end_byte = 200;
mod_entry.start_line = 1;
mod_entry.end_line = 20;
let mod_id = graph.nodes_mut().alloc(mod_entry).unwrap();
let mut fn_entry = NodeEntry::new(NodeKind::Function, fn_name, file_id);
fn_entry.qualified_name = Some(fn_name);
fn_entry.start_byte = 20;
fn_entry.end_byte = 150;
fn_entry.start_line = 2;
fn_entry.end_line = 15;
let fn_id = graph.nodes_mut().alloc(fn_entry).unwrap();
let mut import_entry = NodeEntry::new(NodeKind::Import, import_local, file_id);
import_entry.qualified_name = Some(import_target);
import_entry.start_byte = 2;
import_entry.end_byte = 18;
import_entry.start_line = 1;
import_entry.end_line = 1;
let import_id = graph.nodes_mut().alloc(import_entry).unwrap();
let mut var_outer_entry = NodeEntry::new(NodeKind::Variable, shadow_symbol, file_id);
var_outer_entry.qualified_name = Some(shadow_symbol);
var_outer_entry.start_byte = 30;
var_outer_entry.end_byte = 35;
var_outer_entry.start_line = 3;
var_outer_entry.end_line = 3;
let var_outer_id = graph.nodes_mut().alloc(var_outer_entry).unwrap();
let mut var_inner_entry = NodeEntry::new(NodeKind::Variable, shadow_symbol, file_id);
var_inner_entry.qualified_name = Some(shadow_symbol);
var_inner_entry.start_byte = 80;
var_inner_entry.end_byte = 85;
var_inner_entry.start_line = 8;
var_inner_entry.end_line = 8;
let var_inner_id = graph.nodes_mut().alloc(var_inner_entry).unwrap();
crate::graph::unified::build::parallel_commit::rebuild_indices(graph);
graph
.edges_mut()
.add_edge(mod_id, fn_id, EdgeKind::Contains, file_id);
graph
.edges_mut()
.add_edge(mod_id, import_id, EdgeKind::Contains, file_id);
graph
.edges_mut()
.add_edge(mod_id, var_outer_id, EdgeKind::Contains, file_id);
graph
.edges_mut()
.add_edge(fn_id, var_outer_id, EdgeKind::Contains, file_id);
graph
.edges_mut()
.add_edge(fn_id, var_inner_id, EdgeKind::Contains, file_id);
graph.edges_mut().add_edge(
mod_id,
import_id,
EdgeKind::Imports {
alias: Some(import_local),
is_wildcard: false,
},
file_id,
);
SeedHandles {
_module: mod_id,
_function: fn_id,
_import: import_id,
_var_outer: var_outer_id,
_var_inner: var_inner_id,
alias_local_name: import_local,
alias_target_name: import_target,
shadow_symbol,
shadow_outer_offset: 30,
shadow_inner_offset: 80,
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Hash)]
enum ScopeIdentityTag {
Invalid = 0,
Stale = 1,
Live = 2,
}
type ScopeIdentity = (ScopeIdentityTag, u8, (u32, u32), u32);
fn scope_identity(id: ScopeId, arena: &ScopeArena) -> ScopeIdentity {
if id.is_invalid() {
return (ScopeIdentityTag::Invalid, 0, (0, 0), 0);
}
match arena.get(id) {
Some(scope) => (
ScopeIdentityTag::Live,
scope.kind.discriminant(),
scope.byte_span,
scope.file.index(),
),
None => (ScopeIdentityTag::Stale, 0, (0, 0), 0),
}
}
fn project_alias_entries(
table: &AliasTable,
arena: &ScopeArena,
) -> Vec<(ScopeIdentity, StringId, StringId, NodeId, bool)> {
let mut v: Vec<_> = table
.entries()
.iter()
.map(|e: &AliasEntry| {
(
scope_identity(e.scope, arena),
e.from_symbol,
e.to_symbol,
e.import_node,
e.is_wildcard,
)
})
.collect();
v.sort();
v
}
fn project_shadow_entries(
table: &ShadowTable,
arena: &ScopeArena,
) -> Vec<(ScopeIdentity, StringId, u32, NodeId)> {
let mut v: Vec<_> = table
.entries()
.iter()
.map(|e: &ShadowEntry| {
(
scope_identity(e.scope, arena),
e.symbol,
e.byte_offset,
e.node,
)
})
.collect();
v.sort();
v
}
fn project_provenance_stable_ids(store: &ScopeProvenanceStore) -> Vec<([u8; 16], [u8; 16])> {
let mut v: Vec<_> = store
.entries()
.map(|(_, prov)| (*prov.file_stable_id.as_bytes(), prov.stable_id.0))
.collect();
v.sort();
v
}
#[test]
fn derive_binding_plane_runs_against_rebuild_graph() {
let mut cg = CodeGraph::new();
let cg_handles = seed(&mut cg, "cg");
let cg_stats = derive_binding_plane_generic(&mut cg);
let cg_scope_count = cg.scope_arena().len();
let cg_alias_table = cg.alias_table();
let cg_shadow_table = cg.shadow_table();
let cg_provenance_store = cg.scope_provenance_store();
assert!(
cg_scope_count > 0,
"baseline: CodeGraph scope arena must be non-empty"
);
assert!(
!cg_alias_table.is_empty(),
"baseline: CodeGraph alias table must be non-empty"
);
assert!(
!cg_shadow_table.is_empty(),
"baseline: CodeGraph shadow table must be non-empty"
);
assert!(
!cg_provenance_store.is_empty(),
"baseline: CodeGraph scope provenance store must be non-empty"
);
let mut rebuild = {
let graph = CodeGraph::new();
graph.clone_for_rebuild()
};
let rb_handles = seed(&mut rebuild, "cg"); let rb_stats = derive_binding_plane_generic(&mut rebuild);
assert_eq!(cg_handles.alias_local_name, rb_handles.alias_local_name);
assert_eq!(cg_handles.alias_target_name, rb_handles.alias_target_name);
assert_eq!(cg_handles.shadow_symbol, rb_handles.shadow_symbol);
assert_eq!(
cg_stats.scopes, rb_stats.scopes,
"scope-count parity across CodeGraph and RebuildGraph"
);
assert_eq!(cg_stats.aliases, rb_stats.aliases);
assert_eq!(cg_stats.shadows, rb_stats.shadows);
let rb_scope_arena = &rebuild.scope_arena;
let rb_alias_table = &rebuild.alias_table;
let rb_shadow_table = &rebuild.shadow_table;
let rb_provenance_store = &rebuild.scope_provenance_store;
assert!(
!rb_scope_arena.is_empty(),
"rebuild.scope_arena must be populated by set_scope_arena"
);
assert!(
!rb_alias_table.is_empty(),
"rebuild.alias_table must be populated by set_alias_table"
);
assert!(
!rb_shadow_table.is_empty(),
"rebuild.shadow_table must be populated by set_shadow_table"
);
assert!(
!rb_provenance_store.is_empty(),
"rebuild.scope_provenance_store must be populated by set_scope_provenance_store"
);
let rb_scope_count = rb_scope_arena.len();
assert_eq!(
cg_scope_count, rb_scope_count,
"CodeGraph vs RebuildGraph scope arena length parity"
);
let cg_scope_kinds: HashSet<ScopeKind> =
cg.scope_arena().iter().map(|(_, s)| s.kind).collect();
let rb_scope_kinds: HashSet<ScopeKind> =
rb_scope_arena.iter().map(|(_, s)| s.kind).collect();
assert_eq!(
cg_scope_kinds, rb_scope_kinds,
"CodeGraph vs RebuildGraph scope arena must contain the same set of scope kinds"
);
assert!(rb_scope_kinds.contains(&ScopeKind::Module));
assert!(rb_scope_kinds.contains(&ScopeKind::Function));
let cg_alias_proj = project_alias_entries(cg_alias_table, cg.scope_arena());
let rb_alias_proj = project_alias_entries(rb_alias_table, rb_scope_arena);
assert_eq!(
cg_alias_proj, rb_alias_proj,
"CodeGraph vs RebuildGraph alias_table must contain the \
same (scope_identity, from_symbol, to_symbol, import_node, \
is_wildcard) tuples. Divergence here indicates \
`set_alias_table` / `alias_table_mut` is not wired to the \
rebuild-local field, OR a `derive_aliases` bug is stamping \
the wrong scope onto rebuild-local entries."
);
let has_expected_alias = rb_alias_table.entries().iter().any(|e| {
e.from_symbol == rb_handles.alias_local_name
&& e.to_symbol == rb_handles.alias_target_name
&& !e.is_wildcard
});
assert!(
has_expected_alias,
"rebuild alias table must contain the expected entry \
(from=`baz`, to=`foo::bar`, wildcard=false)"
);
let cg_shadow_proj = project_shadow_entries(cg_shadow_table, cg.scope_arena());
let rb_shadow_proj = project_shadow_entries(rb_shadow_table, rb_scope_arena);
assert_eq!(
cg_shadow_proj, rb_shadow_proj,
"CodeGraph vs RebuildGraph shadow_table must contain the \
same (scope_identity, symbol, byte_offset, node) tuples. \
Divergence here indicates `set_shadow_table` / \
`shadow_table_mut` is not wired to the rebuild-local field, \
OR a `derive_shadows` bug is stamping the wrong scope onto \
rebuild-local entries."
);
let shadow_offsets_for_x: HashSet<u32> = rb_shadow_table
.entries()
.iter()
.filter(|e| e.symbol == rb_handles.shadow_symbol)
.map(|e| e.byte_offset)
.collect();
assert!(
shadow_offsets_for_x.contains(&rb_handles.shadow_outer_offset),
"rebuild shadow table must contain outer def of `x` at byte {}",
rb_handles.shadow_outer_offset
);
assert!(
shadow_offsets_for_x.contains(&rb_handles.shadow_inner_offset),
"rebuild shadow table must contain inner def of `x` at byte {}",
rb_handles.shadow_inner_offset
);
let cg_prov_proj = project_provenance_stable_ids(cg_provenance_store);
let rb_prov_proj = project_provenance_stable_ids(rb_provenance_store);
assert_eq!(
cg_prov_proj, rb_prov_proj,
"CodeGraph vs RebuildGraph scope_provenance_store must stamp \
the same (file_stable_id, scope_stable_id) pairs. Divergence \
here indicates `set_scope_provenance_store` is not wired to \
the rebuild-local field."
);
assert_eq!(
rb_provenance_store.len(),
rb_scope_count,
"rebuild: one provenance record per scope"
);
let rb_fact_epoch = GraphMutationTarget::fact_epoch(&rebuild);
for (_, prov) in rb_provenance_store.entries() {
assert_eq!(
prov.first_seen_epoch, rb_fact_epoch,
"rebuild provenance first_seen_epoch must equal rebuild.fact_epoch()"
);
assert_eq!(
prov.last_seen_epoch, rb_fact_epoch,
"rebuild provenance last_seen_epoch must equal rebuild.fact_epoch()"
);
}
}
#[test]
fn derive_binding_plane_incremental_runs_against_rebuild_graph() {
use crate::graph::unified::build::phase4e_incremental::derive_binding_plane_incremental_generic;
let mut cg = CodeGraph::new();
let _ = seed(&mut cg, "cg_inc");
let cg_stats =
crate::graph::unified::build::phase4e_incremental::derive_binding_plane_incremental(
&mut cg,
);
let cg_alias_proj = project_alias_entries(cg.alias_table(), cg.scope_arena());
let cg_shadow_proj = project_shadow_entries(cg.shadow_table(), cg.scope_arena());
let cg_prov_proj = project_provenance_stable_ids(cg.scope_provenance_store());
let mut rebuild = {
let graph = CodeGraph::new();
graph.clone_for_rebuild()
};
let handles = seed(&mut rebuild, "cg_inc"); let rb_stats = derive_binding_plane_incremental_generic(&mut rebuild);
assert!(rb_stats.scopes > 0, "incremental deriver populated scopes");
assert_eq!(
cg_stats.scopes, rb_stats.scopes,
"incremental CodeGraph vs RebuildGraph scope-count parity"
);
assert_eq!(
cg_stats.aliases, rb_stats.aliases,
"incremental alias-count parity"
);
assert_eq!(
cg_stats.shadows, rb_stats.shadows,
"incremental shadow-count parity"
);
let rb_scope_arena = &rebuild.scope_arena;
let rb_alias_table = &rebuild.alias_table;
let rb_shadow_table = &rebuild.shadow_table;
let rb_provenance_store = &rebuild.scope_provenance_store;
assert!(
!rb_scope_arena.is_empty(),
"incremental: rebuild.scope_arena must be populated"
);
assert!(
!rb_alias_table.is_empty(),
"incremental: rebuild.alias_table must be populated by set_alias_table"
);
assert!(
!rb_shadow_table.is_empty(),
"incremental: rebuild.shadow_table must be populated by set_shadow_table"
);
assert!(
!rb_provenance_store.is_empty(),
"incremental: rebuild.scope_provenance_store must be populated \
by set_scope_provenance_store"
);
assert_eq!(
cg_alias_proj,
project_alias_entries(rb_alias_table, rb_scope_arena),
"incremental alias_table semantic equivalence (scope-inclusive)"
);
assert_eq!(
cg_shadow_proj,
project_shadow_entries(rb_shadow_table, rb_scope_arena),
"incremental shadow_table semantic equivalence (scope-inclusive)"
);
assert_eq!(
cg_prov_proj,
project_provenance_stable_ids(rb_provenance_store),
"incremental scope_provenance_store semantic equivalence"
);
assert!(
rb_alias_table.entries().iter().any(|e| {
e.from_symbol == handles.alias_local_name
&& e.to_symbol == handles.alias_target_name
&& !e.is_wildcard
}),
"incremental: rebuild alias table has expected `baz` → `foo::bar` entry"
);
let shadow_nodes_for_x: HashMap<u32, NodeId> = rb_shadow_table
.entries()
.iter()
.filter(|e| e.symbol == handles.shadow_symbol)
.map(|e| (e.byte_offset, e.node))
.collect();
assert!(
shadow_nodes_for_x.contains_key(&handles.shadow_outer_offset),
"incremental: outer `x` def at byte {} present",
handles.shadow_outer_offset
);
assert!(
shadow_nodes_for_x.contains_key(&handles.shadow_inner_offset),
"incremental: inner `x` def at byte {} present",
handles.shadow_inner_offset
);
}
fn corrupt_alias_scope(table: &AliasTable, corrupt_to: ScopeId) -> AliasTable {
let mut entries: Vec<AliasEntry> = table.entries().to_vec();
assert!(
!entries.is_empty(),
"corrupt_alias_scope precondition: table must have at least one entry"
);
entries[0].scope = corrupt_to;
let bytes = postcard::to_allocvec(&entries).expect("serialize alias entries");
postcard::from_bytes::<AliasTable>(&bytes).expect("deserialize corrupted alias table")
}
fn corrupt_shadow_scope(table: &ShadowTable, corrupt_to: ScopeId) -> ShadowTable {
let mut entries: Vec<ShadowEntry> = table.entries().to_vec();
assert!(
!entries.is_empty(),
"corrupt_shadow_scope precondition: table must have at least one entry"
);
entries[0].scope = corrupt_to;
let bytes = postcard::to_allocvec(&entries).expect("serialize shadow entries");
postcard::from_bytes::<ShadowTable>(&bytes).expect("deserialize corrupted shadow table")
}
#[test]
fn scope_corruption_in_alias_table_is_caught_by_phase4e_tests() {
let mut cg = CodeGraph::new();
let _ = seed(&mut cg, "corrupt_baseline");
let _ = derive_binding_plane_generic(&mut cg);
let baseline_alias_proj = project_alias_entries(cg.alias_table(), cg.scope_arena());
let baseline_shadow_proj = project_shadow_entries(cg.shadow_table(), cg.scope_arena());
let mut rebuild = {
let graph = CodeGraph::new();
graph.clone_for_rebuild()
};
let _ = seed(&mut rebuild, "corrupt_baseline");
let _ = derive_binding_plane_generic(&mut rebuild);
let clean_alias_proj = project_alias_entries(&rebuild.alias_table, &rebuild.scope_arena);
let clean_shadow_proj = project_shadow_entries(&rebuild.shadow_table, &rebuild.scope_arena);
assert_eq!(
baseline_alias_proj, clean_alias_proj,
"pre-corruption: alias projections must match across CodeGraph and RebuildGraph"
);
assert_eq!(
baseline_shadow_proj, clean_shadow_proj,
"pre-corruption: shadow projections must match across CodeGraph and RebuildGraph"
);
assert!(
!rebuild.alias_table.is_empty(),
"meta-test precondition: rebuild alias table must have ≥1 entry"
);
assert!(
!rebuild.shadow_table.is_empty(),
"meta-test precondition: rebuild shadow table must have ≥1 entry"
);
let corrupted_alias = corrupt_alias_scope(&rebuild.alias_table, ScopeId::INVALID);
let corrupted_alias_proj = project_alias_entries(&corrupted_alias, &rebuild.scope_arena);
assert_ne!(
baseline_alias_proj, corrupted_alias_proj,
"SCOPE-INVALID corruption on alias_table[0].scope MUST cause the \
projection to diverge from the CodeGraph baseline. If this ever \
becomes equal, the Codex iter-2 finding is re-opened — the \
strengthened `project_alias_entries` is NOT including scope in \
its tuple, and a real setter bug that stamps INVALID onto a \
rebuild-local entry would silently pass the main test."
);
let (first_entry_scope_id, _) = rebuild
.shadow_table
.entries()
.first()
.map(|e| (e.scope, e.symbol))
.expect("shadow table has at least one entry");
let original_identity = scope_identity(first_entry_scope_id, &rebuild.scope_arena);
let wrong_live_scope: ScopeId = rebuild
.scope_arena
.iter()
.find_map(|(id, _)| {
let candidate_identity = scope_identity(id, &rebuild.scope_arena);
(candidate_identity != original_identity).then_some(id)
})
.expect("seed must produce ≥2 distinct scopes (Module + Function)");
assert_ne!(
scope_identity(first_entry_scope_id, &rebuild.scope_arena),
scope_identity(wrong_live_scope, &rebuild.scope_arena),
"corruption precondition: picked a scope with a distinct intrinsic identity"
);
let corrupted_shadow = corrupt_shadow_scope(&rebuild.shadow_table, wrong_live_scope);
let corrupted_shadow_proj = project_shadow_entries(&corrupted_shadow, &rebuild.scope_arena);
assert_ne!(
baseline_shadow_proj, corrupted_shadow_proj,
"WRONG-LIVE-SCOPE corruption on shadow_table[0].scope MUST cause \
the projection to diverge from the CodeGraph baseline. If this \
ever becomes equal, the Codex iter-2 finding is re-opened — the \
strengthened `project_shadow_entries` is NOT including scope in \
its tuple, and a real setter bug that swaps scopes between \
entries would silently pass the main test."
);
let stale_scope = ScopeId::new(u32::MAX - 1, u64::MAX);
assert!(
!stale_scope.is_invalid(),
"stale_scope precondition: distinct from ScopeId::INVALID"
);
assert!(
rebuild.scope_arena.get(stale_scope).is_none(),
"stale_scope precondition: must NOT resolve in the arena"
);
let corrupted_alias_stale = corrupt_alias_scope(&rebuild.alias_table, stale_scope);
let corrupted_alias_stale_proj =
project_alias_entries(&corrupted_alias_stale, &rebuild.scope_arena);
assert_ne!(
baseline_alias_proj, corrupted_alias_stale_proj,
"STALE-HANDLE corruption on alias_table[0].scope MUST cause the \
projection to diverge from the CodeGraph baseline."
);
}
#[test]
fn broken_alias_table_setter_is_caught_by_phase4e_tests() {
let mut rebuild = {
let graph = CodeGraph::new();
graph.clone_for_rebuild()
};
let _ = seed(&mut rebuild, "broken");
let _ = derive_binding_plane_generic(&mut rebuild);
assert!(!rebuild.alias_table.is_empty());
assert!(!rebuild.shadow_table.is_empty());
assert!(!rebuild.scope_provenance_store.is_empty());
assert!(!rebuild.scope_arena.is_empty());
rebuild.alias_table = AliasTable::new();
assert!(
rebuild.alias_table.is_empty(),
"broken-setter simulation: alias_table cleared"
);
let _ = derive_binding_plane_generic(&mut rebuild);
assert!(
!rebuild.alias_table.is_empty(),
"re-deriving after clear must refill alias_table via set_alias_table; \
if THIS assertion fails in production tests, the Codex iter-1 gap \
is re-opened — set_alias_table is a no-op"
);
rebuild.shadow_table = ShadowTable::new();
assert!(rebuild.shadow_table.is_empty());
let _ = derive_binding_plane_generic(&mut rebuild);
assert!(
!rebuild.shadow_table.is_empty(),
"re-deriving after clear must refill shadow_table via set_shadow_table"
);
rebuild.scope_provenance_store = ScopeProvenanceStore::new();
assert!(rebuild.scope_provenance_store.is_empty());
let _ = derive_binding_plane_generic(&mut rebuild);
assert!(
!rebuild.scope_provenance_store.is_empty(),
"re-deriving after clear must refill scope_provenance_store via \
set_scope_provenance_store"
);
}
}