From 9388fcf2f0b82daffec93308f42949a612b07330 Mon Sep 17 00:00:00 2001 From: "sorokin.oleg.edu" Date: Thu, 24 Sep 2026 16:16:45 -0700 Subject: [PATCH] Plan complete mixed source renames against whole target models --- .../mixed/project/ordinary_index.rs | 2 + .../mixed/project/ordinary_index/rename.rs | 491 +++++++++ .../project/ordinary_index/rename/tests.rs | 952 ++++++++++++++++++ ...xed-rename-plan-implementation-20260924.md | 19 + 4 files changed, 1464 insertions(+) create mode 100644 crates/oa-core/src/language/analysis_source/mixed/project/ordinary_index/rename.rs create mode 100644 crates/oa-core/src/language/analysis_source/mixed/project/ordinary_index/rename/tests.rs create mode 100644 harness/analysis-mixed-rename-plan-implementation-20260924.md diff --git a/crates/oa-core/src/language/analysis_source/mixed/project/ordinary_index.rs b/crates/oa-core/src/language/analysis_source/mixed/project/ordinary_index.rs index 16164a2a..6daea0a3 100644 --- a/crates/oa-core/src/language/analysis_source/mixed/project/ordinary_index.rs +++ b/crates/oa-core/src/language/analysis_source/mixed/project/ordinary_index.rs @@ -429,3 +429,5 @@ fn word(unit: &FileSyntax<'_>, at: Span, expected: &str) -> Result<(), TypedErro #[cfg(test)] mod tests; + +pub(in crate::language::analysis_source) mod rename; diff --git a/crates/oa-core/src/language/analysis_source/mixed/project/ordinary_index/rename.rs b/crates/oa-core/src/language/analysis_source/mixed/project/ordinary_index/rename.rs new file mode 100644 index 00000000..7adc671e --- /dev/null +++ b/crates/oa-core/src/language/analysis_source/mixed/project/ordinary_index/rename.rs @@ -0,0 +1,491 @@ +//! Complete-target rename planning only; no output emission or admission. +use super::*; +use crate::source_project::SourceBundle; +use lower::Target; + +pub(in crate::language::analysis_source) struct RenamePlan<'s, 't> { + source: AuthoredMixedProject<'s>, + desired: &'t Project, + names: BTreeMap, + edits: BTreeMap, +} +impl<'s> AuthoredMixedProject<'s> { + pub fn plan_rename<'t>(mut self, desired: &'t Project) -> Result, Failure> { + let mut failure_file = None; + let result = (|| { + let resolved = &mut self.indexed.resolved; + let e = resolved.expressions.len(); + resolved.storage.reserve( + &resolved.syntax.budget, + Slots::entries(e), + Span::default(), + )?; + let equal = same_project(&resolved.project, desired); + resolved.storage.release(Slots::entries(e)); + if !equal { + return Err(error( + Span::default(), + "Rename target changes non-name project semantics.", + )); + } + let mut names = BTreeMap::new(); + let mut bytes = 0; + visit_names(desired, &mut |target, name| { + let old = self.indexed.data.names.get(&target).ok_or_else(|| { + error( + Span::default(), + "Rename target is absent from the original catalog.", + ) + })?; + if old != name { + bytes = add(bytes, name.len(), Span::default())?; + self.indexed + .resolved + .syntax + .budget + .index_bytes(bytes, Span::default())?; + if !crate::model::valid_alias(name) { + return Err(error(Span::default(), "Invalid rename alias.")); + } + self.indexed.resolved.storage.reserve( + &self.indexed.resolved.syntax.budget, + Slots::entries(1), + Span::default(), + )?; + if names.insert(target, name).is_some() { + return Err(error(Span::default(), "Duplicate rename target.")); + } + } + Ok(()) + })?; + if !names.is_empty() { + let mut namespace = BTreeSet::new(); + for (target, old) in &self.indexed.data.names { + let scope = match target { + Target::Global(_) => None, + Target::Parameter { calculation, .. } => Some(*calculation), + }; + let name = names.get(target).copied().unwrap_or(old); + self.indexed.resolved.storage.reserve( + &self.indexed.resolved.syntax.budget, + Slots::entries(1), + Span::default(), + )?; + if !namespace.insert((scope, name)) { + return Err(error( + Span::default(), + "Rename aliases collide in their final namespace.", + )); + } + } + let q = namespace.len(); + drop(namespace); + self.indexed.resolved.storage.release(Slots::entries(q)); + } + let mut edits = BTreeMap::new(); + for (key, location) in &self.indexed.data.occurrences { + if names.contains_key(&key.target) { + failure_file = Some(location.file); + insert_edit( + &mut self.indexed.resolved.storage, + &self.indexed.resolved.syntax.budget, + &mut edits, + (location.file, location.span.start, location.span.end), + key.target, + )?; + } + } + for (&key, occurrence) in &self.ordinary { + let target = Target::Global(occurrence.target); + if names.contains_key(&target) { + failure_file = Some(key.0); + insert_edit( + &mut self.indexed.resolved.storage, + &self.indexed.resolved.syntax.budget, + &mut edits, + key, + target, + )?; + } + } + Ok((names, edits)) + })(); + let (names, edits) = result.map_err(|error| { + Failure::index( + &self.indexed.resolved.syntax, + lower::Failure { + file: failure_file, + error, + }, + ) + })?; + let plan = RenamePlan { + source: self, + desired, + names, + edits, + }; + plan.validate()?; + Ok(plan) + } +} +fn insert_edit( + storage: &mut Ledger, + budget: &Budget, + edits: &mut BTreeMap, + key: OrdinaryKey, + target: Target, +) -> Result<(), TypedError> { + let at = Span { + start: key.1, + end: key.2, + }; + if edits.contains_key(&key) { + return Err(error(at, "Duplicate rename physical token.")); + } + storage.reserve(budget, Slots::entries(1), at)?; + edits.insert(key, target); + Ok(()) +} +impl<'s, 't> RenamePlan<'s, 't> { + pub fn source(&self) -> &AuthoredMixedProject<'s> { + &self.source + } + pub fn desired(&self) -> &'t Project { + self.desired + } + pub fn changed_names(&self) -> &BTreeMap { + &self.names + } + pub fn edits(&self) -> &BTreeMap { + &self.edits + } + pub fn validate_current(&self, current: &SourceBundle) -> Result<(), Failure> { + let old = self.source.bundle(); + if old.entry != current.entry || old.files != current.files { + return Err(self.failure( + None, + error( + Span::default(), + "Current complete bundle differs from the rename snapshot.", + ), + )); + } + Ok(()) + } + fn failure(&self, file: Option, error: TypedError) -> Failure { + Failure::index( + &self.source.indexed.resolved.syntax, + lower::Failure { file, error }, + ) + } + fn validate(&self) -> Result<(), Failure> { + let mut previous: Option = None; + for (&key, target) in &self.edits { + let at = Span { + start: key.1, + end: key.2, + }; + let checked = (|| { + let unit = self + .source + .indexed + .resolved + .syntax + .files + .get(key.0) + .ok_or_else(|| error(at, "Rename token has no original file."))?; + let original = self + .source + .names() + .get(target) + .ok_or_else(|| error(at, "Rename token has no original target."))?; + if !self.names.contains_key(target) { + return Err(error(at, "Rename token targets an unchanged name.")); + } + if at.start >= at.end { + return Err(error(at, "Rename token must be nonempty.")); + } + word(unit, at, original)?; + if previous.is_some_and(|p| p.0 == key.0 && p.2 > key.1) { + return Err(error(at, "Rename token ranges overlap.")); + } + Ok(()) + })(); + checked.map_err(|e| self.failure(Some(key.0), e))?; + previous = Some(key); + } + let mut required = 0; + for (key, location) in self.source.typed_occurrences() { + self.require( + (location.file, location.span.start, location.span.end), + key.target, + &mut required, + )?; + } + for (&key, occurrence) in self.source.ordinary_occurrences() { + self.require(key, Target::Global(occurrence.target), &mut required)?; + } + if required != self.edits.len() { + return Err(self.failure( + None, + error(Span::default(), "Rename edit set contains extra tokens."), + )); + } + self.output_bytes()?; + Ok(()) + } + fn require( + &self, + key: OrdinaryKey, + target: Target, + required: &mut usize, + ) -> Result<(), Failure> { + if self.names.contains_key(&target) { + let at = Span { + start: key.1, + end: key.2, + }; + if self.edits.get(&key) != Some(&target) { + return Err(self.failure( + Some(key.0), + error(at, "Rename edit set is missing a required target token."), + )); + } + *required = add(*required, 1, at).map_err(|e| self.failure(Some(key.0), e))?; + } + Ok(()) + } + pub fn output_bytes(&self) -> Result { + let result = (|| { + let mut total = 0; + let mut edits = self.edits.iter().peekable(); + for (file, _, original) in self.source.files() { + let mut removed = 0; + let mut replaced = 0; + while let Some((&key, target)) = edits.peek().copied() { + if key.0 != file { + break; + } + let at = Span { + start: key.1, + end: key.2, + }; + removed = add( + removed, + key.2 + .checked_sub(key.1) + .ok_or_else(|| error(at, "Invalid rename token size."))?, + at, + )?; + replaced = add( + replaced, + self.names + .get(target) + .ok_or_else(|| error(at, "Rename token has no replacement."))? + .len(), + at, + )?; + edits.next(); + } + let size = original.len().checked_sub(removed).ok_or_else(|| { + error(Span::default(), "Rename removals exceed original file.") + })?; + total = add( + total, + add(size, replaced, Span::default())?, + Span::default(), + )?; + } + if edits.next().is_some() { + return Err(error( + Span::default(), + "Rename edit file is outside original bundle.", + )); + } + self.source + .indexed + .resolved + .syntax + .budget + .index_bytes(total, Span::default())?; + Ok(total) + })(); + result.map_err(|e| self.failure(None, e)) + } +} + +// Exhaustive exclusion patterns make every new field an explicit gate decision. +macro_rules! fixed { + ($old:expr, $new:expr, $ty:path, [$($field:ident),*], [$($excluded:ident),*]) => {{ + let $ty { $($field,)* $($excluded: _,)* } = $old; + true $(&& $field == &$new.$field)* + }}; +} +fn map_same( + old: &BTreeMap, + new: &BTreeMap, + same: impl Fn(&T, &T) -> bool, +) -> bool { + old.len() == new.len() + && old + .iter() + .zip(new) + .all(|((a, x), (b, y))| a == b && same(x, y)) +} +fn same_project(a: &Project, b: &Project) -> bool { + fixed!( + a, + b, + Project, + [schema_version, id, name, description], + [ + alias, + definitions, + elements, + relations, + diagrams, + artifact_links, + traces + ] + ) && map_same(&a.definitions, &b.definitions, |a, b| { + fixed!( + a, + b, + analysis::Definition, + [id, name, description], + [alias, body] + ) && match (&a.body, &b.body) { + ( + analysis::DefinitionBody::Calculation(a), + analysis::DefinitionBody::Calculation(b), + ) => { + a.parameters.len() == b.parameters.len() + && a.parameters + .iter() + .zip(&b.parameters) + .all(|(a, b)| fixed!(a, b, analysis::Parameter, [id, value_type], [name])) + && fixed!( + a, + b, + analysis::CalculationDefinition, + [result, body], + [parameters] + ) + } + ( + analysis::DefinitionBody::QuantityKind(a), + analysis::DefinitionBody::QuantityKind(b), + ) => a == b, + (analysis::DefinitionBody::Unit(a), analysis::DefinitionBody::Unit(b)) => a == b, + _ => false, + } + }) && map_same(&a.elements, &b.elements, |a, b| { + fixed!( + a, + b, + crate::Element, + [id, name, description, parent, body], + [alias] + ) + }) && map_same(&a.relations, &b.relations, |a, b| { + fixed!( + a, + b, + crate::Relation, + [id, name, description, body], + [alias] + ) + }) && map_same(&a.diagrams, &b.diagrams, |a, b| { + fixed!( + a, + b, + crate::Diagram, + [ + id, + name, + description, + layer, + projection, + view_options, + routes + ], + [alias, nodes] + ) && map_same(&a.nodes, &b.nodes, |a, b| { + fixed!( + a, + b, + crate::NodePlacement, + [id, element, position, size, collapsed, port_anchors], + [alias] + ) + }) + }) && map_same(&a.artifact_links, &b.artifact_links, |a, b| { + fixed!( + a, + b, + crate::ArtifactLink, + [ + id, + name, + description, + owner, + relation, + uri, + revision, + fragment + ], + [alias] + ) + }) && map_same(&a.traces, &b.traces, |a, b| { + fixed!( + a, + b, + crate::Trace, + [id, name, description, oa, sa, kind, baseline], + [alias] + ) + }) +} +fn visit_names<'t>( + project: &'t Project, + f: &mut impl FnMut(Target, &'t str) -> Result<(), TypedError>, +) -> Result<(), TypedError> { + f(Target::Global(project.id), &project.alias)?; + for d in project.definitions.values() { + f(Target::Global(d.id), &d.alias)?; + if let analysis::DefinitionBody::Calculation(c) = &d.body { + for p in &c.parameters { + f( + Target::Parameter { + calculation: d.id, + parameter: p.id, + }, + &p.name, + )?; + } + } + } + for e in project.elements.values() { + f(Target::Global(e.id), &e.alias)?; + } + for r in project.relations.values() { + f(Target::Global(r.id), &r.alias)?; + } + for d in project.diagrams.values() { + f(Target::Global(d.id), &d.alias)?; + for n in d.nodes.values() { + f(Target::Global(n.id), &n.alias)?; + } + } + for a in project.artifact_links.values() { + f(Target::Global(a.id), &a.alias)?; + } + for t in project.traces.values() { + f(Target::Global(t.id), &t.alias)?; + } + Ok(()) +} + +#[cfg(test)] +mod tests; diff --git a/crates/oa-core/src/language/analysis_source/mixed/project/ordinary_index/rename/tests.rs b/crates/oa-core/src/language/analysis_source/mixed/project/ordinary_index/rename/tests.rs new file mode 100644 index 00000000..111e2f17 --- /dev/null +++ b/crates/oa-core/src/language/analysis_source/mixed/project/ordinary_index/rename/tests.rs @@ -0,0 +1,952 @@ +use super::super::super::tests::{accepted, id, rich_bundle, rich_model, single}; +use super::*; +use crate::language::analysis_source::budget::Limits; +use crate::language::analysis_source::mixed::parse_with_profile; +use crate::source_project::ProjectLimits; +use crate::{Layer, analysis as a}; + +fn authored(bundle: &SourceBundle) -> AuthoredMixedProject<'_> { + accepted(bundle).index().unwrap().index_ordinary().unwrap() +} +fn calculation(p: &mut Project, n: usize) -> &mut a::CalculationDefinition { + let a::DefinitionBody::Calculation(c) = &mut p.definitions.get_mut(&id(n)).unwrap().body else { + panic!("calculation") + }; + c +} +fn renamed(layer: Layer) -> Project { + let mut p = rich_model(layer); + p.alias = "w".into(); + p.elements.get_mut(&id(2)).unwrap().alias = "p".into(); + p.elements.get_mut(&id(3)).unwrap().alias = "n".into(); + let d = p.diagrams.get_mut(&id(5)).unwrap(); + d.alias = "z".into(); + d.nodes.get_mut(&id(4)).unwrap().alias = "v".into(); + for (n, name) in [(10, "g"), (11, "f")] { + p.definitions.get_mut(&id(n)).unwrap().alias = name.into(); + let c = calculation(&mut p, n); + c.parameters[0].name = "y".into(); + c.parameters[1].name = "x".into(); + } + if layer == Layer::Sa { + p.elements.get_mut(&id(6)).unwrap().alias = "u".into(); + } + p +} +fn rejected(bundle: &SourceBundle, desired: &Project, contains: &str) { + let e = match authored(bundle).plan_rename(desired) { + Ok(_) => panic!("unexpected accepted target"), + Err(e) => e, + }; + assert_eq!(e.diagnostics.len(), 1); + assert!( + e.diagnostics[0].diagnostic.message.contains(contains), + "{:?}", + e + ); +} +#[test] +fn noop_borrows_exact_target_and_preserves_source_usage_and_storage() { + let bundle = rich_bundle(Layer::Oa); + let source = authored(&bundle); + let desired = rich_model(Layer::Oa); + let before = source.indexed.resolved.storage.live; + let usage = source.indexed.resolved.syntax.budget.usage; + let numeric = source.indexed.resolved.syntax.budget.numeric_units(); + let plan = source.plan_rename(&desired).unwrap(); + assert!(std::ptr::eq(plan.desired(), &desired)); + assert!(std::ptr::eq(plan.source().bundle(), &bundle)); + assert!(plan.edits().is_empty()); + assert!(plan.changed_names().is_empty()); + assert_eq!(plan.source.indexed.resolved.storage.live, before); + assert_eq!(plan.source.indexed.resolved.syntax.budget.usage, usage); + assert_eq!( + plan.source.indexed.resolved.syntax.budget.numeric_units(), + numeric + ); + assert_eq!(desired, rich_model(Layer::Oa)); + assert_eq!(plan.source.project(), &desired); + assert_eq!(plan.output_bytes().unwrap(), 900); +} +#[test] +fn final_names_reject_unchanged_collisions_invalid_names_and_scoped_collisions() { + let bundle = rich_bundle(Layer::Oa); + for alias in ["w", "", "雨", "project", "bad-name"] { + let mut desired = rich_model(Layer::Oa); + desired.alias = alias.into(); + rejected( + &bundle, + &desired, + if alias == "w" { "collide" } else { "Invalid" }, + ); + } + let mut desired = rich_model(Layer::Oa); + calculation(&mut desired, 10).parameters[0].name = "y".into(); + rejected(&bundle, &desired, "collide"); + let mut desired = rich_model(Layer::Oa); + calculation(&mut desired, 10).parameters[0].name = "p".into(); + calculation(&mut desired, 11).parameters[0].name = "p".into(); + let plan = authored(&bundle).plan_rename(&desired).unwrap(); + assert_eq!(plan.changed_names().len(), 2); +} +#[test] +fn complete_model_gate_rejects_identity_collection_parameter_and_payload_changes() { + let bundle = rich_bundle(Layer::Oa); + type Mutation = Box; + let mutations: Vec = vec![ + Box::new(|p| p.schema_version = 7), + Box::new(|p| p.id = id(99)), + Box::new(|p| p.name.push('!')), + Box::new(|p| p.description.push('!')), + Box::new(|p| { + p.elements.remove(&id(2)); + }), + Box::new(|p| { + let e = p.elements.remove(&id(2)).unwrap(); + p.elements.insert(id(99), e); + }), + Box::new(|p| p.elements.get_mut(&id(2)).unwrap().id = id(99)), + Box::new(|p| p.elements.get_mut(&id(2)).unwrap().name.push('!')), + Box::new(|p| p.elements.get_mut(&id(2)).unwrap().description.push('!')), + Box::new(|p| p.elements.get_mut(&id(2)).unwrap().parent = Some(id(3))), + Box::new(|p| { + p.elements.get_mut(&id(2)).unwrap().body = + crate::ElementBody::Sa(crate::SaBody::Function) + }), + Box::new(|p| p.definitions.get_mut(&id(10)).unwrap().id = id(99)), + Box::new(|p| p.definitions.get_mut(&id(10)).unwrap().name.push('!')), + Box::new(|p| { + p.definitions + .get_mut(&id(10)) + .unwrap() + .description + .push('!') + }), + Box::new(|p| calculation(p, 10).parameters.swap(0, 1)), + Box::new(|p| { + calculation(p, 10).parameters.pop(); + }), + Box::new(|p| { + let q = calculation(p, 10).parameters[0].clone(); + calculation(p, 10).parameters.push(q); + }), + Box::new(|p| calculation(p, 10).parameters[0].id = a::ParameterId(id(99))), + Box::new(|p| { + calculation(p, 10).parameters[0].value_type = + a::AnalysisType::Scalar(a::ScalarType::Boolean) + }), + Box::new(|p| calculation(p, 10).result = a::AnalysisType::Scalar(a::ScalarType::Boolean)), + Box::new(|p| calculation(p, 10).body = a::Binding::Unbound), + Box::new(|p| p.diagrams.get_mut(&id(5)).unwrap().id = id(99)), + Box::new(|p| p.diagrams.get_mut(&id(5)).unwrap().name.push('!')), + Box::new(|p| p.diagrams.get_mut(&id(5)).unwrap().description.push('!')), + Box::new(|p| p.diagrams.get_mut(&id(5)).unwrap().layer = Layer::Sa), + Box::new(|p| { + p.diagrams + .get_mut(&id(5)) + .unwrap() + .view_options + .hidden_edges + .push(id(99)) + }), + Box::new(|p| { + p.diagrams.get_mut(&id(5)).unwrap().nodes.clear(); + }), + Box::new(|p| { + p.diagrams + .get_mut(&id(5)) + .unwrap() + .nodes + .get_mut(&id(4)) + .unwrap() + .id = id(99) + }), + Box::new(|p| { + p.diagrams + .get_mut(&id(5)) + .unwrap() + .nodes + .get_mut(&id(4)) + .unwrap() + .element = id(2) + }), + Box::new(|p| { + p.diagrams + .get_mut(&id(5)) + .unwrap() + .nodes + .get_mut(&id(4)) + .unwrap() + .position + .x = 1. + }), + Box::new(|p| { + p.diagrams + .get_mut(&id(5)) + .unwrap() + .nodes + .get_mut(&id(4)) + .unwrap() + .size + .width = 1. + }), + Box::new(|p| { + p.diagrams + .get_mut(&id(5)) + .unwrap() + .nodes + .get_mut(&id(4)) + .unwrap() + .collapsed = true + }), + ]; + for mutate in mutations { + let mut p = rich_model(Layer::Oa); + mutate(&mut p); + rejected(&bundle, &p, "non-name"); + } +} +#[test] +fn complete_current_bundle_rejects_entry_paths_missing_extra_and_untouched_bytes() { + let bundle = rich_bundle(Layer::Oa); + let mut desired = rich_model(Layer::Oa); + desired.alias = "renamed".into(); + let plan = authored(&bundle).plan_rename(&desired).unwrap(); + plan.validate_current(&bundle).unwrap(); + for case in 0..6 { + let mut current = bundle.clone(); + match case { + 0 => current.entry = "a.arcadia".into(), + 1 => { + current.files.remove("a.arcadia"); + } + 2 => { + current.files.insert("extra.arcadia".into(), String::new()); + } + 3 => current.files.get_mut("a.arcadia").unwrap().push(' '), + 4 => current.files.get_mut("main.arcadia").unwrap().push(' '), + _ => { + let s = current.files.remove("a.arcadia").unwrap(); + current.files.insert("renamed.arcadia".into(), s); + } + } + assert!(plan.validate_current(¤t).is_err()); + } +} +#[test] +fn exact_merged_map_cap_passes_all_predecessors_and_retains_r_plus_m() { + let bundle = rich_bundle(Layer::Oa); + let desired = renamed(Layer::Oa); + let baseline = authored(&bundle).indexed.resolved.storage.live; + for (extra, fits) in [(37, false), (38, true)] { + let profile = Limits { + entries: baseline.entries + extra, + ..Limits::source8(language::ParseLimits::UNLIMITED) + }; + let source = + resolve(parse_with_profile(&bundle, ProjectLimits::default(), profile).unwrap()) + .unwrap() + .index() + .unwrap() + .index_ordinary() + .unwrap(); + assert_eq!(source.indexed.resolved.storage.live, baseline); + let result = source.plan_rename(&desired); + if fits { + let plan = result.unwrap(); + assert_eq!( + plan.source.indexed.resolved.storage.live, + baseline.add(Slots::entries(38), Span::default()).unwrap() + ); + } else { + let e = match result { + Ok(_) => panic!("cap accepted"), + Err(e) => e, + }; + assert_eq!( + e.diagnostics[0].diagnostic.message, + "Source8 lowering entry limit exceeded." + ); + assert_eq!(e.diagnostics[0].path, "main.arcadia"); + assert_eq!( + e.diagnostics[0].diagnostic.span, + Span { + start: 412, + end: 413 + } + ); + } + } +} +#[test] +fn damaged_complete_edits_fail_missing_extra_wrong_target_and_utf8_authority() { + let mut bundle = rich_bundle(Layer::Oa); + for s in bundle.files.values_mut() { + *s = format!("// 雨\r\n{s}"); + } + let desired = renamed(Layer::Oa); + for case in 0..7 { + let mut plan = authored(&bundle).plan_rename(&desired).unwrap(); + let key = *plan.edits.keys().next().unwrap(); + match case { + 0 => { + plan.edits.remove(&key); + } + 1 => { + plan.edits.insert((0, 0, 1), Target::Global(id(10))); + } + 2 => { + plan.edits.insert(key, Target::Global(id(11))); + } + 3 => { + plan.edits.insert((0, 4, 5), Target::Global(id(10))); + } + 4 => { + plan.edits + .insert((99, key.1, key.2), Target::Global(id(10))); + } + 5 => { + plan.edits + .insert((0, key.1, key.2 + 1), Target::Global(id(10))); + } + _ => { + plan.edits.insert((0, key.1, key.1), Target::Global(id(10))); + } + } + let failure = plan.validate().unwrap_err(); + assert_eq!(failure.diagnostics.len(), 1); + if case == 3 || case == 4 { + assert_eq!(failure.diagnostics[0].diagnostic.span, Span::default()); + assert_eq!( + failure.diagnostics[0].path, + if case == 3 { + "a.arcadia" + } else { + "main.arcadia" + } + ); + } + if case == 0 { + assert_eq!(failure.diagnostics[0].path, "a.arcadia"); + assert_eq!( + failure.diagnostics[0].diagnostic.span, + Span { start: 20, end: 21 } + ); + assert_eq!( + failure.diagnostics[0].diagnostic.message, + "Rename edit set is missing a required target token." + ); + } + } +} + +fn categories() -> SourceBundle { + SourceBundle { + entry: "main.arcadia".into(), + files: BTreeMap::from([( + "main.arcadia".into(), + include_str!("../tests/fixtures/categories.arcadia").into(), + )]), + } +} +#[test] +fn every_ordinary_record_field_group_is_fixed_including_historical_payloads() { + let bundle = categories(); + let original = authored(&bundle).project().clone(); + type Mutation = Box; + let changes: Vec = vec![ + Box::new(|p| p.relations.get_mut(&id(6)).unwrap().id = id(99)), + Box::new(|p| p.relations.get_mut(&id(6)).unwrap().name.push('!')), + Box::new(|p| p.relations.get_mut(&id(6)).unwrap().description.push('!')), + Box::new(|p| { + let body = p.relations[&id(15)].body.clone(); + p.relations.get_mut(&id(6)).unwrap().body = body; + }), + Box::new(|p| { + p.relations.remove(&id(6)); + }), + Box::new(|p| { + p.diagrams.get_mut(&id(7)).unwrap().projection = crate::ProjectionKind::Allocation + }), + Box::new(|p| p.diagrams.get_mut(&id(7)).unwrap().routes.clear()), + Box::new(|p| { + p.diagrams.remove(&id(7)); + }), + Box::new(|p| p.artifact_links.get_mut(&id(8)).unwrap().id = id(99)), + Box::new(|p| p.artifact_links.get_mut(&id(8)).unwrap().name.push('!')), + Box::new(|p| { + p.artifact_links + .get_mut(&id(8)) + .unwrap() + .description + .push('!') + }), + Box::new(|p| p.artifact_links.get_mut(&id(8)).unwrap().owner = id(3)), + Box::new(|p| { + p.artifact_links.get_mut(&id(8)).unwrap().relation = crate::ArtifactRelation::Verifies + }), + Box::new(|p| p.artifact_links.get_mut(&id(8)).unwrap().uri.push('!')), + Box::new(|p| p.artifact_links.get_mut(&id(8)).unwrap().revision = Some("changed".into())), + Box::new(|p| p.artifact_links.get_mut(&id(8)).unwrap().fragment = Some("changed".into())), + Box::new(|p| { + p.artifact_links.remove(&id(8)); + }), + Box::new(|p| p.traces.get_mut(&id(9)).unwrap().id = id(99)), + Box::new(|p| p.traces.get_mut(&id(9)).unwrap().name.push('!')), + Box::new(|p| p.traces.get_mut(&id(9)).unwrap().description.push('!')), + Box::new(|p| p.traces.get_mut(&id(9)).unwrap().oa = id(4)), + Box::new(|p| p.traces.get_mut(&id(9)).unwrap().sa = id(4)), + Box::new(|p| p.traces.get_mut(&id(9)).unwrap().kind = crate::TraceKind::Refinement), + Box::new(|p| p.traces.get_mut(&id(9)).unwrap().baseline = None), + Box::new(|p| { + p.traces + .get_mut(&id(9)) + .unwrap() + .baseline + .as_mut() + .unwrap() + .semantic = None + }), + Box::new(|p| { + p.traces + .get_mut(&id(9)) + .unwrap() + .baseline + .as_mut() + .unwrap() + .oa + .name + .push('!') + }), + Box::new(|p| { + p.traces.remove(&id(9)); + }), + ]; + for change in changes { + let mut p = original.clone(); + change(&mut p); + rejected(&bundle, &p, "non-name"); + } + let source = authored(&bundle); + assert_eq!(source.indexed.resolved.expressions.len(), 0); + drop(source.plan_rename(&original).unwrap()); +} +#[test] +fn combined_shrink_offsets_typed_growth_and_counts_untouched_files() { + let mut bundle = rich_bundle(Layer::Oa); + let main = bundle.files.get_mut("main.arcadia").unwrap(); + *main = main.replace("node n ", &format!("node {} ", "n".repeat(31))); + assert_eq!(bundle.files.values().map(String::len).sum::(), 930); + for (name, expected) in [("renamed_f", Some(924)), ("renamed_func", None)] { + let profile = Limits { + bytes: 930, + ..Limits::source8(language::ParseLimits::UNLIMITED) + }; + let source = + resolve(parse_with_profile(&bundle, ProjectLimits::default(), profile).unwrap()) + .unwrap() + .index() + .unwrap() + .index_ordinary() + .unwrap(); + let mut desired = source.project().clone(); + desired + .diagrams + .get_mut(&id(5)) + .unwrap() + .nodes + .get_mut(&id(4)) + .unwrap() + .alias = "n".into(); + desired.definitions.get_mut(&id(10)).unwrap().alias = name.into(); + let result = source.plan_rename(&desired); + if let Some(bytes) = expected { + let plan = result.unwrap(); + assert_eq!(plan.output_bytes().unwrap(), bytes); + assert_eq!(plan.edits().len(), 4); + } else { + let e = match result { + Ok(_) => panic!("oversize accepted"), + Err(e) => e, + }; + assert_eq!( + e.diagnostics[0].diagnostic.message, + "Source8 index/output byte limit exceeded." + ); + } + } +} +#[test] +fn deep_and_wide_equality_and_late_refusal_drop_on_small_stack() { + std::thread::Builder::new() + .stack_size(128 * 1024) + .spawn(|| { + for (expression, result, nodes) in [ + ( + format!("{}1{}", "abs(".repeat(127), ")".repeat(127)), + "Number", + 128, + ), + ( + format!("[{}]", vec!["1"; 128].join(", ")), + "List(Number)", + 129, + ), + ] { + let bundle = single(&format!( + "calculation c \"C\" id \"{}\" {{ result {result}; body {expression}; }}", + id(2) + )); + let source = authored(&bundle); + assert_eq!(source.indexed.resolved.expressions.len(), nodes); + let original = source.project().clone(); + drop(source.plan_rename(&original).unwrap()); + let mut desired = original.clone(); + desired.definitions.get_mut(&id(2)).unwrap().alias = "p".into(); + rejected(&bundle, &desired, "collide"); + let mut desired = original.clone(); + calculation(&mut desired, 2).body = a::Binding::Unbound; + rejected(&bundle, &desired, "non-name"); + } + }) + .unwrap() + .join() + .unwrap(); +} +#[test] +fn zero_expression_original_rejects_target_only_deep_and_wide_variants() { + let bundle = single(&format!( + "calculation c \"C\" id \"{}\" {{ result Number; body unbound; }}", + id(2) + )); + let source = authored(&bundle); + assert_eq!(source.indexed.resolved.expressions.len(), 0); + let original = source.project().clone(); + let mut deep = a::Expr::Boolean(true); + for _ in 0..2048 { + deep = a::Expr::Unary { + op: a::UnaryOp::Not, + operand: Box::new(deep), + }; + } + for expression in [deep, a::Expr::List(vec![a::Expr::Boolean(true); 4096])] { + let mut desired = original.clone(); + calculation(&mut desired, 2).body = a::Binding::Expression(expression); + rejected(&bundle, &desired, "non-name"); + } +} +#[test] +fn signed_zero_geometry_uses_existing_semantic_equality() { + let bundle = rich_bundle(Layer::Oa); + let mut desired = rich_model(Layer::Oa); + desired + .diagrams + .get_mut(&id(5)) + .unwrap() + .nodes + .get_mut(&id(4)) + .unwrap() + .position + .x = -0.; + assert!( + authored(&bundle) + .plan_rename(&desired) + .unwrap() + .edits() + .is_empty() + ); +} + +fn parameter(c: usize, p: usize) -> Target { + Target::Parameter { + calculation: id(c), + parameter: a::ParameterId(id(p)), + } +} +#[test] +fn independent_entire_rich_maps_oa_sa_unicode_crlf_and_borrowed_names() { + for layer in [Layer::Oa, Layer::Sa] { + for crlf in [false, true] { + let mut bundle = rich_bundle(layer); + if crlf { + for source in bundle.files.values_mut() { + *source = format!("// 雨\r\n{}", source.replace('\n', "\r\n")); + } + } + let desired = renamed(layer); + let source = authored(&bundle); + let before = source.indexed.resolved.storage.live; + let usage = source.indexed.resolved.syntax.budget.usage; + let plan = source.plan_rename(&desired).unwrap(); + let mut names = BTreeMap::from([ + (Target::Global(id(1)), "w"), + (Target::Global(id(2)), "p"), + (Target::Global(id(3)), "n"), + (Target::Global(id(4)), "v"), + (Target::Global(id(5)), "z"), + (Target::Global(id(10)), "g"), + (Target::Global(id(11)), "f"), + (parameter(10, 1), "y"), + (parameter(10, 12), "x"), + (parameter(11, 1), "y"), + (parameter(11, 12), "x"), + ]); + if layer == Layer::Sa { + names.insert(Target::Global(id(6)), "u"); + } + let mut edits = BTreeMap::new(); + let offsets = if crlf { + [20, 77, 138, 214, 219] + } else { + [12, 68, 128, 203, 208] + }; + for (file, calculation) in [(0, 10), (1, 11)] { + for (start, target) in offsets.into_iter().zip([ + Target::Global(id(calculation)), + parameter(calculation, 1), + parameter(calculation, 12), + parameter(calculation, 1), + parameter(calculation, 12), + ]) { + edits.insert((file, start, start + 1), target); + } + } + let offsets = if crlf { + [ + 28, 133, 188, 200, 218, 281, 303, 305, 311, 315, 317, 323, 333, 335, 341, 363, + 426, + ] + } else { + [ + 19, 122, 177, 189, 206, 269, 291, 293, 299, 303, 305, 311, 321, 323, 329, 350, + 412, + ] + }; + let g = |n| Target::Global(id(n)); + for (start, target) in offsets.into_iter().zip([ + g(1), + g(4), + g(5), + g(3), + g(3), + g(2), + g(10), + parameter(10, 12), + parameter(10, 1), + g(11), + parameter(11, 1), + parameter(11, 12), + g(10), + parameter(10, 1), + parameter(10, 12), + g(2), + g(5), + ]) { + edits.insert((2, start, start + 1), target); + } + if layer == Layer::Sa { + let start = if crlf { 505 } else { 490 }; + edits.insert((2, start, start + 1), g(6)); + } + assert_eq!(plan.changed_names(), &names); + assert_eq!(plan.edits(), &edits); + let delta = if layer == Layer::Oa { 38 } else { 40 }; + assert_eq!( + plan.source.indexed.resolved.storage.live, + before.add(Slots::entries(delta), Span::default()).unwrap() + ); + assert_eq!(plan.source.indexed.resolved.syntax.budget.usage, usage); + assert!(std::ptr::eq(plan.desired(), &desired)); + assert_eq!(desired, renamed(layer)); + assert_eq!(plan.source.project(), &rich_model(layer)); + assert!(std::ptr::eq( + plan.names[&g(1)].as_ptr(), + desired.alias.as_ptr() + )); + assert_eq!( + plan.output_bytes().unwrap(), + match (layer, crlf) { + (Layer::Oa, false) => 900, + (Layer::Oa, true) => 943, + (Layer::Sa, false) => 974, + (Layer::Sa, true) => 1017, + } + ); + } + } +} +#[test] +fn independent_entire_category_map_includes_comment_separated_refs_and_excludes_history() { + let bundle = categories(); + let source = authored(&bundle); + let mut desired = source.project().clone(); + let replacements = [ + "a", "p", "implicit", "child", "link", "s", "note", "d", "q", "t", "use", "sub", "right", + "left", "ctx", + ]; + desired.alias = replacements[0].into(); + for e in desired.elements.values_mut() { + e.alias = replacements[(1..=15).find(|n| id(*n) == e.id).unwrap() - 1].into(); + } + for r in desired.relations.values_mut() { + r.alias = replacements[(1..=15).find(|n| id(*n) == r.id).unwrap() - 1].into(); + } + desired.diagrams.get_mut(&id(7)).unwrap().alias = "note".into(); + desired.artifact_links.get_mut(&id(8)).unwrap().alias = "d".into(); + desired.traces.get_mut(&id(9)).unwrap().alias = "q".into(); + let before = source.indexed.resolved.storage.live; + assert_eq!(source.indexed.resolved.expressions.len(), 0); + let plan = source.plan_rename(&desired).unwrap(); + let spans: &[(usize, &[(usize, usize)])] = &[ + (1, &[(19, 20)]), + (2, &[(168, 169), (344, 345)]), + (3, &[(249, 254), (625, 630), (992, 997), (1741, 1746)]), + (4, &[(369, 377), (640, 648), (1882, 1890)]), + (5, &[(487, 488), (1003, 1004)]), + (6, &[(561, 565), (741, 745), (789, 793), (885, 889)]), + (7, &[(664, 665)]), + (8, &[(818, 822)]), + (9, &[(925, 926)]), + ( + 10, + &[(1328, 1329), (1577, 1578), (1855, 1856), (2004, 2005)], + ), + (11, &[(1422, 1425), (1617, 1620), (1712, 1715)]), + (12, &[(1391, 1394), (1514, 1517), (2029, 2032)]), + (13, &[(1487, 1491), (1648, 1652), (2047, 2051)]), + (14, &[(1397, 1402), (1789, 1794), (2092, 2097)]), + (15, &[(1935, 1942)]), + ]; + let expected: BTreeMap<_, _> = spans + .iter() + .flat_map(|(n, spans)| { + spans + .iter() + .map(move |&(start, end)| ((0, start, end), Target::Global(id(*n)))) + }) + .collect(); + assert_eq!(plan.edits(), &expected); + let names: BTreeMap<_, _> = replacements + .iter() + .enumerate() + .map(|(i, &name)| (Target::Global(id(i + 1)), name)) + .collect(); + assert_eq!(plan.changed_names(), &names); + assert_eq!(plan.output_bytes().unwrap(), 2145); + assert_eq!( + plan.source.indexed.resolved.storage.live, + before.add(Slots::entries(51), Span::default()).unwrap() + ); +} + +#[test] +fn signed_zero_scalar_bounds_defaults_and_historical_facets_preserve_original_bytes() { + let bundle = single(&format!( + r#" + oa_data_type a "A" id "{}" {{ definition {{ Real {{ minimum 0.0; maximum 1.0; }}; }}; default {{ Real 0.0; }}; }} + sa_data_type b "B" id "{}" {{ definition {{ Real {{ minimum 0.0; maximum 1.0; }}; }}; default {{ Real 0.0; }}; }} + trace t "T" id "{}" {{ oa @a; sa @b; kind Item; baseline {{ + oa {{ name "A"; description ""; }}; sa {{ name "B"; description ""; }}; + semantic {{ facets [{{ Data {{ RealMinimum {{ oa {{ Value 0.0; }}; sa {{ Value 0.0; }}; }}; }}; }}]; }}; + }}; }} + "#, + id(2), + id(3), + id(4) + )); + let source = authored(&bundle); + let mut desired = source.project().clone(); + for e in desired.elements.values_mut() { + let body = match &mut e.body { + crate::ElementBody::Oa(crate::OaBody::Shared(b)) + | crate::ElementBody::Sa(crate::SaBody::Shared(b)) => b, + _ => panic!("data"), + }; + let crate::SharedBody::DataType { + definition, + default, + .. + } = body + else { + panic!("data") + }; + let crate::DataTypeDefinition::Real { minimum, .. } = definition else { + panic!("real") + }; + *minimum = Some(-0.0); + *default = Some(crate::DataValue::Real(-0.0)); + } + let facet = &mut desired + .traces + .get_mut(&id(4)) + .unwrap() + .baseline + .as_mut() + .unwrap() + .semantic + .as_mut() + .unwrap() + .facets[0]; + let crate::semantic::FacetReview::Data(crate::semantic::DataReview::RealMinimum(review)) = + facet + else { + panic!("historical real") + }; + review.oa = crate::semantic::Observed::Value(Some(-0.0)); + review.sa = crate::semantic::Observed::Value(Some(-0.0)); + let plan = source.plan_rename(&desired).unwrap(); + assert!(plan.edits().is_empty()); + assert!(std::ptr::eq(plan.source.bundle(), &bundle)); +} +#[test] +fn nonzero_expression_lists_calls_and_definition_payloads_reject_changes() { + let bundle = single(&format!( + "calculation c \"C\" id \"{}\" {{ result List(Number); body [1,2]; }}", + id(2) + )); + let original = authored(&bundle).project().clone(); + for bigger in [false, true] { + let mut desired = original.clone(); + let a::Binding::Expression(a::Expr::List(items)) = &mut calculation(&mut desired, 2).body + else { + panic!("list") + }; + if bigger { + items.push(a::Expr::Boolean(true)); + } else { + items.pop(); + } + rejected(&bundle, &desired, "non-name"); + } + let bundle = rich_bundle(Layer::Oa); + let mut desired = rich_model(Layer::Oa); + let crate::ElementBody::Oa(crate::OaBody::Shared(crate::SharedBody::EngineeringProperty { + base: a::Binding::Expression(a::Expr::Binary { left, .. }), + .. + })) = &mut desired.elements.get_mut(&id(3)).unwrap().body + else { + panic!("property") + }; + let a::Expr::Call { arguments, .. } = left.as_mut() else { + panic!("call") + }; + arguments.remove(&a::ParameterId(id(1))); + rejected(&bundle, &desired, "non-name"); + let mut desired = rich_model(Layer::Oa); + desired + .diagrams + .get_mut(&id(5)) + .unwrap() + .nodes + .get_mut(&id(4)) + .unwrap() + .port_anchors + .push(crate::PortAnchor { + port: id(99), + side: crate::PortAnchorSide::Left, + offset: 0., + }); + rejected(&bundle, &desired, "non-name"); + // Opaque quantity/unit payload comparison is tested directly, independently + // of admission's semantic dimension rules; these are gate-level controls. + let mut old = rich_model(Layer::Oa); + old.definitions.get_mut(&id(10)).unwrap().body = + a::DefinitionBody::QuantityKind(a::QuantityKindDefinition { + dimension: crate::analysis_numeric::Dimension::ZERO, + algebra: a::QuantityAlgebra::Linear, + }); + let mut changed = old.clone(); + let a::DefinitionBody::QuantityKind(q) = + &mut changed.definitions.get_mut(&id(10)).unwrap().body + else { + unreachable!() + }; + q.algebra = a::QuantityAlgebra::Affine { + difference: a::DefinitionRef::Local(id(11)), + }; + assert!(!same_project(&old, &changed)); + old.definitions.get_mut(&id(10)).unwrap().body = a::DefinitionBody::Unit(a::UnitDefinition { + kind: a::DefinitionRef::Local(id(11)), + scale: crate::analysis_numeric::Exact::from_integer(1), + offset: crate::analysis_numeric::Exact::from_integer(0), + symbol: "u".into(), + }); + let mut changed = old.clone(); + let a::DefinitionBody::Unit(u) = &mut changed.definitions.get_mut(&id(10)).unwrap().body else { + unreachable!() + }; + u.symbol.push('x'); + assert!(!same_project(&old, &changed)); + assert!(!same_project(&old, &rich_model(Layer::Oa))); +} + +#[test] +fn same_spelling_different_callee_and_unchanged_target_corruptions_are_rejected() { + let bundle = rich_bundle(Layer::Oa); + let desired = renamed(Layer::Oa); + let mut plan = authored(&bundle).plan_rename(&desired).unwrap(); + plan.edits.insert((2, 299, 300), parameter(11, 1)); + let e = plan.validate().unwrap_err(); + assert_eq!(e.diagnostics[0].path, "main.arcadia"); + assert_eq!( + e.diagnostics[0].diagnostic.span, + Span { + start: 299, + end: 300 + } + ); + assert_eq!( + e.diagnostics[0].diagnostic.message, + "Rename edit set is missing a required target token." + ); + let mut desired = rich_model(Layer::Oa); + desired.alias = "new_project".into(); + let mut plan = authored(&bundle).plan_rename(&desired).unwrap(); + plan.edits.insert((2, 122, 123), Target::Global(id(4))); + let e = plan.validate().unwrap_err(); + assert_eq!( + e.diagnostics[0].diagnostic.span, + Span { + start: 122, + end: 123 + } + ); + assert_eq!( + e.diagnostics[0].diagnostic.message, + "Rename token targets an unchanged name." + ); +} +#[test] +fn included_file_last_reservation_preserves_its_canonical_diagnostic() { + let mut bundle = rich_bundle(Layer::Oa); + let main = bundle.files.get_mut("main.arcadia").unwrap(); + let start = main.find("diagram d ").unwrap(); + let end = start + main[start..].find('\n').unwrap() + 1; + let diagram = main[start..end].to_owned(); + main.replace_range(start..end, "include \"z.arcadia\";\n"); + bundle.files.insert("z.arcadia".into(), diagram); + let baseline = authored(&bundle).indexed.resolved.storage.live; + let desired = renamed(Layer::Oa); + // The unchanged-profile pipeline must clear every predecessor. The final + // ordinary header is now in the canonical last include, at d[8..9). + let profile = Limits { + entries: baseline.entries + 37, + ..Limits::source8(language::ParseLimits::UNLIMITED) + }; + let source = resolve(parse_with_profile(&bundle, ProjectLimits::default(), profile).unwrap()) + .unwrap() + .index() + .unwrap() + .index_ordinary() + .unwrap(); + let e = match source.plan_rename(&desired) { + Ok(_) => panic!("cap accepted"), + Err(e) => e, + }; + assert_eq!(e.diagnostics[0].path, "z.arcadia"); + assert_eq!(e.diagnostics[0].diagnostic.span, Span { start: 8, end: 9 }); + assert_eq!( + e.diagnostics[0].diagnostic.message, + "Source8 lowering entry limit exceeded." + ); +} diff --git a/harness/analysis-mixed-rename-plan-implementation-20260924.md b/harness/analysis-mixed-rename-plan-implementation-20260924.md new file mode 100644 index 00000000..68cb0686 --- /dev/null +++ b/harness/analysis-mixed-rename-plan-implementation-20260924.md @@ -0,0 +1,19 @@ +# Private complete-target mixed rename planning + +2026-09-24. Descendant implementation of base `ee500908e3d77182ddd54e12524f9b6c6eb43b60`, under accepted parent contract `dc87babf` (`harness/analysis-mixed-rename-plan-contract-20260924.md` on the evidence branch). This note describes source and authored tests, not completed runtime acceptance. + +`AuthoredMixedProject::plan_rename` consumes the complete admitted source witness and borrows an immutable whole desired Project. `RenamePlan` retains that witness, desired borrow, changed qualified names and one original-coordinate physical edit map. Read-only methods expose source, desired, names, edits, full-current-bundle validation and combined complete output-byte sizing. There is no emitter, rewritten-output admission, public activation or legacy adapter. + +The whole-target gate uses exhaustive record patterns, collection keys/counts and parameter position/identity/type checks. Existing PartialEq semantics govern all unchanged payloads, including signed floating zero. Its expression work vectors run under an E-entry reservation, where E is the retained raw expression-site count. Current expression roots are calculation bindings, engineering-property base, case overrides and check predicates; historical semantic fields have no analysis Expr. E scratch is released before changed names are collected; the final namespace set is dropped before edits are collected. Successful retained growth is R+M entries, zero occurrence slots and zero copied text. Parsing, numeric and frame Usage remain unchanged. + +Both authenticated occurrence families feed one map. Validation checks original token spelling/UTF-8 authority, whole Word spans, physical nonoverlap and exact required key/qualified-target membership. Bundle validation includes entry, every path/file and untouched bytes. Sizing subtracts the sum of removed bytes before adding replacements per file, then includes all files in the inherited byte check. Emission/re-admission lifecycle and aggregate output storage remain required successors. + +Independent source review found and prompted two pre-compiler repairs: preserve canonical file attribution for edit-reservation failures, and check ordered parameter shape before calculation-body equality. No runtime failure claim is attached to these source findings. Existing historical mixed/ordinary failures and prior baseline results remain unchanged; this work does not overwrite their records. + +The 17 authored tests cover independently specified complete rich OA/SA and Unicode/CRLF maps and final-name sets; all36 category edits including real comment-separated references and historical UUID exclusion; identity/map/parameter/non-name field rejections; QuantityKind/Unit payload gate controls; simultaneous global/scoped swaps and collisions; immutable exact target/source borrowing; no-op; unchanged Usage and R+M retention; stale touched/untouched/path/entry/file-set controls; safe corrupted UTF-8/file authority and same-spelling wrong-callee identity; signed-zero geometry, scalar bounds/defaults and historical real facets; deep/wide old equality and target-only trees with small-stack success/drop and late refusal. + +Storage oracle: rich OA B=resolved+65, E15/Q11/R11/M27; added peak38 and retention38. Inherited B+37/B+38 tests run the entire predecessor pipeline before planning, and the negative checks the last ordinary diagram header main[412..413). A separate diagram moved to canonical last include z.arcadia checks precise included-file allocation diagnostics, contingent on its complete predecessor pipeline fitting the unchanged profile. No test replaces/tightens a live owner's Budget to manufacture a later-phase failure. Wide/deep equality tests assert authored E128/E129; no unsupported isolated E-cap claim is made. + +Combined-size oracle uses original930 bytes: ordinary31-byte node alias shrinks to1 while three typed f aliases grow to9; complete924 fits although typed-only intermediate954 would not. A12-byte replacement produces933 and must fail under the same inherited930-byte cap, including the untouched213-byte file. This tests planned sizing only, never output correctness. + +All source/tests require parent compilation, focused runtime tests and existing broader gates. Direct rustfmt is the only executed Rust tool during authoring. Parent owns commit/publication and verification evidence; no Cargo/build/test execution was performed by this implementation agent.