Compare commits

..
Author SHA1 Message Date
Codex-ytr83r 4fcb65c0d3 Record boundary ordering comparison and native review 2026-09-24 22:19:18 -07:00
Codex-ytr83r 3e21c1bca2 Bound complete member displacement across large origins 2026-09-24 21:52:38 -07:00
Codex-ytr83r 42f595a5d6 Probe dependency-aware ordering across container boundaries 2026-09-24 21:50:15 -07:00
Codex-ytr83r c02a03c553 Declare dependency-aware boundary ordering comparison 2026-09-24 21:48:07 -07:00
Codex-ytr83r f4fa0c11f2 Record SCC placement evidence and bounded transfer correction 2026-09-24 21:37:03 -07:00
Codex-ytr83r 2426d28648 Preserve requested container minima after placement transfer 2026-09-24 21:24:57 -07:00
Codex-ytr83r 4dd764fa6c Use mutable reachability rows in isolated SCC probe 2026-09-24 21:17:11 -07:00
Codex-ytr83r ea18adc061 fix(experiment): score occupied perimeter cells including one-column cycles 2026-09-24 21:11:19 -07:00
Codex-ytr83r 5300a8c803 experiment(layout): keep cyclic quotient groups compact in condensation order 2026-09-24 21:09:04 -07:00
Codex-ytr83r ae9dad54b7 Record bounded placement clearance result and preserved quality regressions 2026-09-24 20:57:17 -07:00
Codex-ytr83r 074051261a test(layout): use valid record types for non-port lead fixtures 2026-09-24 20:40:41 -07:00
Codex-ytr83r 2be8c7f292 experiment(layout): diagnose mandatory leads and test bounded flat separation 2026-09-24 20:38:33 -07:00
Codex-ytr83r 2850b915d6 Record placement reference failures and port-clearance requirements 2026-09-24 20:26:23 -07:00
Codex-ytr83r fc5c22c903 Preserve authored sizes during identity placement transfer 2026-09-24 20:18:39 -07:00
Codex-ytr83r 8a2010d8a6 Add bounded external placement comparison and preservation adapter 2026-09-24 20:12:01 -07:00
Codex-ytr83r ac5d7ef34a Describe recorded evidence without implying blocked publication 2026-09-24 19:16:59 -07:00
Codex-ytr83r ae86111f22 Record independent routing corpus decision and preserved evidence 2026-09-24 19:12:27 -07:00
Codex-ytr83r 4fb3cdc721 Observe released router 715036e1 in frozen common harness 2026-09-24 18:53:15 -07:00
Codex-ytr83r 3ea0e23b70 Observe j router 08656b18 in frozen common harness 2026-09-24 18:46:28 -07:00
Codex-ytr83r 095ab3b358 Split geometry serialization to stay within macro recursion depth 2026-09-24 18:44:56 -07:00
Codex-ytr83r f674e36c8b Freeze byte-identical independent routing inputs and review 2026-09-24 18:37:26 -07:00
Codex-ytr83r f0eae74912 Make secondary corpus placement aliases deterministic 2026-09-24 18:36:18 -07:00
Codex-ytr83r a879341cfc Define frozen routing corpus and isolated timing protocol 2026-09-24 18:33:37 -07:00
Codex-ytr83r f72aa2ca5c Record lane tradeoffs, verification and runtime concern 2026-09-24 18:20:21 -07:00
Codex-ytr83r e8ec6dbb31 Isolate parallel lanes with the original overlap coefficient 2026-09-24 18:13:07 -07:00
Codex-ytr83r d0be9ff067 Probe bounded parallel lanes after capped penalty comparison 2026-09-24 18:09:48 -07:00
Codex-ytr83r 3d7adff923 Experiment with fixed capped overlap penalty at unchanged geometry 2026-09-24 18:03:48 -07:00
Codex-ytr83r 9c02100d23 Preserve terminal scoring experiment with ordinary-scene regressions 2026-09-24 18:02:45 -07:00
Codex-ytr83r 08656b18bf Resolve diagnostic probe strict Clippy findings 2026-09-24 17:59:36 -07:00
Codex-ytr83r 497c1ad8b3 Account for target joins when selecting route legs 2026-09-24 17:53:47 -07:00
Codex-ytr83r 427078461a Record first automatic layout experiment and shared-route regressions 2026-09-24 17:49:29 -07:00
Codex-ytr83r e7deea7039 experiment: compare automatic layout against independent geometry metrics 2026-09-24 17:40:49 -07:00
790 changed files with 30069 additions and 97385 deletions
-5
View File
@@ -1,5 +1,4 @@
* text=auto eol=lf
*.rs text eol=lf
*.toml text eol=lf
*.lock text eol=lf
@@ -90,7 +89,3 @@ harness/experiments/analysis-source-s2a-windows-20260913/** -text whitespace=-bl
# Preserve the pinned Iced dependency bytes and upstream manifest.
crates/vendor/iced_widget/** -text whitespace=cr-at-eol
# Final overrides preserve original validation bytes after generic text rules.
harness/experiments/navigation-validation-20261002/** -text whitespace=-blank-at-eol,-blank-at-eof,cr-at-eol
harness/reviews/navigation-validation-packet-20261002/** -text whitespace=-blank-at-eol,-blank-at-eof,cr-at-eol
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,131 @@
//! Independent structural falsifiers for incoming boundary ordering.
use super::*;
fn pair(f: &mut Builder, name: &str, x: f32) -> (Id, Id, Id) {
let parent = f.function(name, None, x, 40.0);
let a = f.function(&format!("{name} A"), Some(parent), x + 60.0, 130.0);
let b = f.function(&format!("{name} B"), Some(parent), x + 60.0, 330.0);
(parent, a, b)
}
pub(super) fn fixtures() -> Vec<(&'static str, Builder)> {
let mut all = Vec::new();
let mut f = Builder::new("boundary_opposing_parents");
let (_, la, lb) = pair(&mut f, "Left", 40.0);
let (_, ra, rb) = pair(&mut f, "Right", 820.0);
for (name, a, b) in [
("Reverse upper", la, rb),
("Reverse lower", lb, ra),
("Return upper", ra, la),
("Return lower", rb, lb),
] {
f.exchange(name, a, b);
}
all.push(("boundary_opposing_parents", f));
let mut f = Builder::new("boundary_three_parent_chain");
let (_, la, lb) = pair(&mut f, "Left", 40.0);
let (middle, ma, mb) = pair(&mut f, "Middle", 820.0);
let (_, ra, rb) = pair(&mut f, "Right", 1600.0);
for (name, a, b) in [
("Left upper", la, mb),
("Left lower", lb, ma),
("Middle upper", mb, ra),
("Middle lower", ma, rb),
] {
f.exchange(name, a, b);
}
let mut translated = f.clone();
for element in [middle, ma, mb] {
let node = translated.node(element);
let position = &mut translated
.project
.diagrams
.get_mut(&translated.diagram)
.unwrap()
.nodes
.get_mut(&node)
.unwrap()
.position;
position.x += 300.0;
position.y += 500.0;
}
let mut permuted = f.clone();
let ids: Vec<_> = permuted.project.relations.keys().copied().collect();
for (i, id) in ids.iter().enumerate() {
reidentify(
&mut permuted.project,
*id,
Id::from_u128(1_000_000 + i as u128),
)
.unwrap();
}
for (i, id) in ids.iter().rev().enumerate() {
reidentify(
&mut permuted.project,
Id::from_u128(1_000_000 + i as u128),
*id,
)
.unwrap();
}
all.push(("boundary_three_parent_chain", f));
all.push(("boundary_three_parent_translated", translated));
all.push(("boundary_three_parent_edge_ids_reversed", permuted));
let mut f = Builder::new("boundary_two_local_sccs");
let (_, sa, sb) = pair(&mut f, "Source", 40.0);
let target = f.function("Target", None, 820.0, 40.0);
let x1 = f.function("X1", Some(target), 880.0, 130.0);
let x2 = f.function("X2", Some(target), 1200.0, 130.0);
let y1 = f.function("Y1", Some(target), 880.0, 330.0);
let y2 = f.function("Y2", Some(target), 1200.0, 330.0);
let z = f.function("Z", Some(target), 1520.0, 230.0);
for (name, a, b) in [
("X forward", x1, x2),
("X back", x2, x1),
("Y forward", y1, y2),
("Y back", y2, y1),
("X result", x2, z),
("Y result", y2, z),
("Upper boundary", sa, y1),
("Lower boundary", sb, x1),
] {
f.exchange(name, a, b);
}
all.push(("boundary_two_local_sccs", f));
let mut f = super::fixtures()
.into_iter()
.find(|(name, _)| *name == "two_containers_reversed_children")
.unwrap()
.1;
let relations: Vec<_> = f.project.relations.values().cloned().collect();
for relation in relations {
if let RelationBody::FunctionalExchange {
source_port,
target_port,
..
} = relation.body
{
for (port, side, offset) in [
(source_port, PortAnchorSide::Right, 0.3),
(target_port, PortAnchorSide::Left, 0.7),
] {
let owner = f.project.elements[&port].parent.unwrap();
let node = f.node(owner);
f.project = prepare_command(
&f.project,
&Command::SetPortAnchor {
diagram: f.diagram,
node,
anchor: PortAnchor { port, side, offset },
},
)
.unwrap()
.project;
}
}
}
all.push(("boundary_two_containers_anchored", f));
all
}
@@ -0,0 +1,279 @@
//! Bounded flat-placement experiment. Empty lead diagnostics are necessary only,
//! never a claim that routes, labels, or drawing quality are acceptable.
use oa_core::{Id, ModelError, Project, projection, routing};
use std::collections::BTreeMap;
const ROUNDS: usize = 4;
const WORK_LIMIT: usize = 10_000_000;
const MAX_SPAN: f32 = 100_000.0;
const MARGIN: f32 = 1.0;
fn fail(message: &str) -> ModelError {
ModelError(format!("Clearance probe: {message}"))
}
fn spend(work: &mut usize, amount: usize) -> Result<(), ModelError> {
*work = work.saturating_add(amount);
if *work > WORK_LIMIT {
return Err(fail("work limit exceeded"));
}
Ok(())
}
// A boundary constraint means start[to] >= start[from] + extent[from] + gap.
// Adjacent total-order constraints below separately preserve start ordering.
type Constraints = BTreeMap<(usize, usize), f32>;
fn solve(
baseline: &[f32],
extents: &[f32],
order: &[usize],
constraints: &Constraints,
work: &mut usize,
) -> Result<Vec<f32>, ModelError> {
let mut result = baseline.to_vec();
let mut previous = None;
for &to in order {
if let Some(from) = previous {
result[to] = result[to].max(result[from]);
}
for (&(from, target), &gap) in constraints {
spend(work, 1)?;
if target == to {
result[to] = result[to].max(result[from] + extents[from] + gap);
}
}
previous = Some(to);
}
let minimum = baseline.iter().copied().fold(f32::INFINITY, f32::min);
for (i, &value) in result.iter().enumerate() {
if !value.is_finite() || value + extents[i] - minimum > MAX_SPAN {
return Err(fail("coordinate/span bound exceeded"));
}
}
Ok(result)
}
/// Input is an already admitted placement candidate. Never modifies its routes,
/// authored anchors, sizes or semantics. Error drops the private clone.
pub fn repair(project: &Project, diagram: Id) -> Result<Project, ModelError> {
let original = project
.diagrams
.get(&diagram)
.ok_or_else(|| fail("missing diagram"))?;
if original.projection.scenario().is_some() {
return Err(fail("Scenario unsupported"));
}
let baseline = projection::project_diagram(project, original);
if baseline.nodes.len() > 256 || baseline.edges.len() > 1024 {
return Err(fail("graph bound exceeded"));
}
if baseline.nodes.len() != original.nodes.len()
|| baseline
.nodes
.iter()
.any(|n| n.collapsed || n.visual_parent.is_some())
{
return Err(fail("nested, folded or omitted placements unsupported"));
}
let mut nodes = baseline.nodes.clone();
nodes.sort_by_key(|n| n.id);
if nodes.iter().any(|n| {
!n.position.x.is_finite()
|| !n.position.y.is_finite()
|| !n.size.width.is_finite()
|| !n.size.height.is_finite()
|| n.size.width <= 0.0
|| n.size.height <= 0.0
}) {
return Err(fail("invalid initial geometry"));
}
let indices: BTreeMap<_, _> = nodes.iter().enumerate().map(|(i, n)| (n.id, i)).collect();
let coordinates = [
nodes.iter().map(|n| n.position.x).collect::<Vec<_>>(),
nodes.iter().map(|n| n.position.y).collect::<Vec<_>>(),
];
let extents = [
nodes.iter().map(|n| n.size.width).collect::<Vec<_>>(),
nodes.iter().map(|n| n.size.height).collect::<Vec<_>>(),
];
for axis in 0..2 {
let minimum = coordinates[axis]
.iter()
.copied()
.fold(f32::INFINITY, f32::min);
if coordinates[axis]
.iter()
.zip(&extents[axis])
.any(|(&start, &extent)| {
!(start + extent).is_finite() || start + extent - minimum > MAX_SPAN
})
{
return Err(fail("initial coordinate/span bound exceeded"));
}
}
let mut orders = [Vec::new(), Vec::new()];
let mut ranks = [vec![0; nodes.len()], vec![0; nodes.len()]];
for axis in 0..2 {
orders[axis] = (0..nodes.len()).collect();
orders[axis].sort_by(|&a, &b| {
coordinates[axis][a]
.total_cmp(&coordinates[axis][b])
.then(nodes[a].id.cmp(&nodes[b].id))
});
for (rank, &i) in orders[axis].iter().enumerate() {
ranks[axis][i] = rank;
}
}
let mut constraints = [Constraints::new(), Constraints::new()];
let mut work = 0;
// Preserve one existing disjoint axis per pair, X before Y. No initially
// overlapping pair is silently treated as successfully repaired.
for a in 0..nodes.len() {
for b in a + 1..nodes.len() {
spend(&mut work, 1)?;
let mut chosen = None;
for axis in 0..2 {
for (from, to) in [(a, b), (b, a)] {
let gap = coordinates[axis][to] - coordinates[axis][from] - extents[axis][from];
if gap >= 0.0 {
chosen = Some((axis, from, to, gap));
break;
}
}
if chosen.is_some() {
break;
}
}
let (axis, from, to, gap) =
chosen.ok_or_else(|| fail("initial node overlap unsupported"))?;
constraints[axis].insert((from, to), gap);
}
}
let mut result = project.clone();
for round in 0..=ROUNDS {
let current = &result.diagrams[&diagram];
let view = projection::project_diagram(&result, current);
if view.nodes.len() != nodes.len()
|| view.nodes.iter().any(|n| {
indices.get(&n.id).is_none_or(|&i| {
n.size != nodes[i].size || n.collapsed || n.visual_parent.is_some()
})
})
{
return Err(fail("reprojection changed node identity/size/topology"));
}
// Charge work units proportional to node count and at most sixteen
// endpoint pairs. This is not an exact operation count or a bound on
// all projection work; graph, round and span limits apply separately.
spend(
&mut work,
view.edges
.len()
.saturating_mul(nodes.len().saturating_add(1))
.saturating_mul(16),
)?;
let failures = routing::lead_incompatibilities(&result, current, &view);
if failures.is_empty() {
return Ok(result);
}
if round == ROUNDS {
return Err(fail("unresolved mandatory leads after four adjustments"));
}
let mut strengthened = false;
for failure in failures {
if failure.allowed_pair_count != 1
|| failure.source.len() != 1
|| failure.target.len() != 1
{
return Err(fail("ambiguous endpoint choices unsupported"));
}
for endpoint in failure.source.iter().chain(&failure.target) {
let owner = *indices
.get(&endpoint.owner)
.ok_or_else(|| fail("unknown endpoint owner"))?;
let dx = endpoint.stub.x - endpoint.tip.x;
let dy = endpoint.stub.y - endpoint.tip.y;
if endpoint.blockers.is_empty() {
continue;
}
let (axis, forward) = match (dx, dy) {
(x, 0.0) if x != 0.0 => (0, x > 0.0),
(0.0, y) if y != 0.0 => (1, y > 0.0),
_ => return Err(fail("noncardinal/degenerate blocked lead")),
};
for blocker in &endpoint.blockers {
spend(&mut work, 1)?;
let other = *indices
.get(&blocker.id)
.ok_or_else(|| fail("unknown blocker"))?;
let (from, to) = if forward {
(owner, other)
} else {
(other, owner)
};
if ranks[axis][from] >= ranks[axis][to] {
return Err(fail("lead constraint opposes frozen spatial order"));
}
let required = routing::PORT_STUB + routing::CLEARANCE + MARGIN;
let old = constraints[axis].entry((from, to)).or_insert(0.0);
if *old < required {
*old = required;
strengthened = true;
}
}
}
}
if !strengthened {
return Err(fail(
"unresolved lead without a stronger monotone constraint",
));
}
let x = solve(
&coordinates[0],
&extents[0],
&orders[0],
&constraints[0],
&mut work,
)?;
let y = solve(
&coordinates[1],
&extents[1],
&orders[1],
&constraints[1],
&mut work,
)?;
let target = result.diagrams.get_mut(&diagram).unwrap();
for (i, node) in nodes.iter().enumerate() {
let placed = target.nodes.get_mut(&node.id).unwrap();
placed.position.x = x[i];
placed.position.y = y[i];
}
}
unreachable!("bounded loop returns on final round")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn separation_propagates_to_neighbor_without_moving_predecessor() {
let constraints = BTreeMap::from([((0, 1), 31.0), ((1, 2), 10.0)]);
let actual = solve(
&[-20.0, 100.0, 210.0],
&[100.0; 3],
&[0, 1, 2],
&constraints,
&mut 0,
)
.unwrap();
assert_eq!(actual, vec![-20.0, 111.0, 221.0]);
}
#[test]
fn extent_bound_rejects_constraint_expansion() {
let constraints = BTreeMap::from([((0, 1), MAX_SPAN)]);
assert!(solve(&[0.0, 20.0], &[10.0; 2], &[0, 1], &constraints, &mut 0).is_err());
}
}
@@ -0,0 +1,654 @@
//! Isolated SCC-block placement experiment; no production-layout acceptance.
use super::{PlacementNode, PlacementPlan, import_placement};
use oa_core::{Id, ModelError, Position, Project, Size, projection, routing};
use std::collections::{BTreeMap, BTreeSet};
const LIMIT: usize = 20_000_000;
const PAD: f32 = 32.0;
const SPAN: f32 = 100_000.0;
#[path = "condensed/boundary.rs"]
mod boundary;
fn error(text: &str) -> ModelError {
ModelError(format!("SCC placement: {text}"))
}
fn charge(work: &mut usize, count: usize) -> Result<(), ModelError> {
*work = work.saturating_add(count);
if *work > LIMIT {
Err(error("work limit exceeded"))
} else {
Ok(())
}
}
fn components(arcs: &[BTreeSet<usize>], work: &mut usize) -> Result<Vec<Vec<usize>>, ModelError> {
let n = arcs.len();
let mut reach = vec![vec![false; n]; n];
for (start, reachable) in reach.iter_mut().enumerate() {
let mut todo = vec![start];
reachable[start] = true;
while let Some(at) = todo.pop() {
for &next in &arcs[at] {
charge(work, 1)?;
if !reachable[next] {
reachable[next] = true;
todo.push(next);
}
}
}
}
let mut used = vec![false; n];
let mut result = Vec::new();
for i in 0..n {
if used[i] {
continue;
}
let mut group = Vec::new();
for j in i..n {
charge(work, 1)?;
if !used[j] && reach[i][j] && reach[j][i] {
used[j] = true;
group.push(j);
}
}
result.push(group);
}
Ok(result)
}
fn perimeter_slots(n: usize, rows: usize, cols: usize) -> Vec<(usize, usize)> {
let mut slots = Vec::new();
if rows == 1 {
slots.extend((0..cols).map(|col| (col, 0)));
} else if cols == 1 {
slots.extend((0..rows).map(|row| (0, row)));
} else {
slots.extend((0..cols).map(|col| (col, 0)));
slots.extend((1..rows).map(|row| (cols - 1, row)));
slots.extend((0..cols - 1).rev().map(|col| (col, rows - 1)));
slots.extend((1..rows - 1).rev().map(|row| (0, row)));
}
slots.truncate(n);
slots
}
// Score occupied maximum-member cells, excluding unused perimeter slots.
// Heterogeneous members may occupy less than this nominal cell envelope.
fn perimeter(
n: usize,
cell: Size,
gap: Size,
work: &mut usize,
) -> Result<(Vec<Position>, usize, usize), ModelError> {
type GridScore = (bool, f32, f32, usize, usize);
let mut best: Option<(GridScore, usize, usize)> = None;
for rows in 1..=n {
for cols in 1..=n {
charge(work, 1)?;
let capacity = if rows == 1 || cols == 1 {
rows * cols
} else {
2 * rows + 2 * cols - 4
};
if capacity < n {
continue;
}
charge(work, capacity)?;
let slots = perimeter_slots(n, rows, cols);
let max_x = slots.iter().map(|p| p.0).max().unwrap();
let max_y = slots.iter().map(|p| p.1).max().unwrap();
let width = (max_x + 1) as f32 * cell.width + max_x as f32 * gap.width;
let height = (max_y + 1) as f32 * cell.height + max_y as f32 * gap.height;
let aspect = (width / height).max(height / width);
let key = (
aspect > 2.0,
if aspect <= 2.0 {
width * height
} else {
aspect
},
width * height,
rows,
cols,
);
if best.as_ref().is_none_or(|(old, _, _)| key < *old) {
best = Some((key, rows, cols));
}
}
}
let (_, rows, cols) = best.ok_or_else(|| error("no perimeter grid"))?;
let slots = perimeter_slots(n, rows, cols);
Ok((
slots
.into_iter()
.take(n)
.map(|(x, y)| Position {
x: x as f32 * (cell.width + gap.width),
y: y as f32 * (cell.height + gap.height),
})
.collect(),
rows,
cols,
))
}
fn lift(
id: Id,
parent: Option<Id>,
parents: &BTreeMap<Id, Option<Id>>,
work: &mut usize,
) -> Result<Option<Id>, ModelError> {
let mut at = id;
for _ in 0..=parents.len() {
charge(work, 1)?;
let Some(&up) = parents.get(&at) else {
return Err(error("missing visual ancestor"));
};
if up == parent {
return Ok(Some(at));
}
let Some(next) = up else {
return Ok(None);
};
at = next;
}
Err(error("cyclic visual ancestry"))
}
/// Frozen projected geometry is the minimum; folded nodes remain opaque.
pub fn candidate(project: &Project, diagram: Id) -> Result<Project, ModelError> {
candidate_with_diagnostics(project, diagram).map(|(result, _)| result)
}
pub fn candidate_with_diagnostics(
project: &Project,
diagram: Id,
) -> Result<(Project, serde_json::Value), ModelError> {
candidate_policy(project, diagram, false)
}
pub fn boundary_candidate_with_diagnostics(
project: &Project,
diagram: Id,
) -> Result<(Project, serde_json::Value), ModelError> {
candidate_policy(project, diagram, true)
}
fn candidate_policy(
project: &Project,
diagram: Id,
order_boundaries: bool,
) -> Result<(Project, serde_json::Value), ModelError> {
let d = project
.diagrams
.get(&diagram)
.ok_or_else(|| error("missing diagram"))?;
if d.projection.scenario().is_some() {
return Err(error("Scenario unsupported"));
}
if d.nodes.len() > 256 {
return Err(error("stored-node bound exceeded"));
}
let view = projection::project_diagram(project, d);
if view.edges.len() > 1024 {
return Err(error("visible-edge bound exceeded"));
}
if view.nodes.iter().any(|n| {
!n.size.width.is_finite()
|| !n.size.height.is_finite()
|| n.size.width <= 0.0
|| n.size.height <= 0.0
}) {
return Err(error("invalid projected minima"));
}
let parents: BTreeMap<_, _> = view.nodes.iter().map(|n| (n.id, n.visual_parent)).collect();
let names: BTreeMap<_, _> = view
.nodes
.iter()
.map(|n| (n.id, (project.elements[&n.element].alias.clone(), n.id)))
.collect();
let mut children: BTreeMap<Option<Id>, Vec<Id>> = BTreeMap::new();
for n in &view.nodes {
children.entry(n.visual_parent).or_default().push(n.id);
}
for ids in children.values_mut() {
ids.sort_by_key(|id| names[id].clone());
}
let mut work = 0;
let mut depth = BTreeMap::new();
for n in &view.nodes {
let mut at = n.id;
let mut count = 0;
let mut seen = BTreeSet::new();
while let Some(up) = *parents.get(&at).ok_or_else(|| error("missing parent"))? {
charge(&mut work, 1)?;
if !seen.insert(at) {
return Err(error("cyclic hierarchy"));
}
count += 1;
at = up;
}
depth.insert(n.id, count);
}
let routed = routing::route_diagram(project, d, &view);
let mut sizes: BTreeMap<_, _> = view.nodes.iter().map(|n| (n.id, n.size)).collect();
let mut offsets = BTreeMap::new();
let mut diagnostics = Vec::new();
let mut boundary_groups = Vec::new();
let mut groups: Vec<_> = children.keys().copied().collect();
groups.sort_by_key(|p| std::cmp::Reverse(p.map_or(0, |id| depth[&id] + 1)));
for parent in groups {
let ids = &children[&parent];
let index: BTreeMap<_, _> = ids.iter().enumerate().map(|(i, id)| (*id, i)).collect();
let mut arcs = vec![BTreeSet::new(); ids.len()];
let mut links = Vec::new();
let mut undirected = BTreeSet::new();
for edge in &view.edges {
charge(&mut work, 1)?;
let (Some(a), Some(b)) = (
lift(edge.source, parent, &parents, &mut work)?,
lift(edge.target, parent, &parents, &mut work)?,
) else {
continue;
};
if a == b {
continue;
}
let (a, b) = (index[&a], index[&b]);
if edge.oriented {
arcs[a].insert(b);
} else {
undirected.insert((ids[a].min(ids[b]), ids[a].max(ids[b])));
}
let routed_edge = routed
.edges
.get(&edge.id)
.ok_or_else(|| error("missing estimated edge label"))?;
let mut label = routed_edge.label.bounds.size;
if let Some(relation) = project.relations.get(&edge.id) {
for end in routing::association_end_geometry(relation, routed_edge) {
label.width = label.width.max(end.label.bounds.size.width);
label.height = label.height.max(end.label.bounds.size.height);
}
}
if !label.width.is_finite() || !label.height.is_finite() {
return Err(error("invalid estimated label"));
}
links.push((a, b, label));
}
let comps = components(&arcs, &mut work)?;
let mut owner = vec![0; ids.len()];
for (c, members) in comps.iter().enumerate() {
for &i in members {
owner[i] = c;
}
}
let mut dag = vec![BTreeSet::new(); comps.len()];
for (a, targets) in arcs.iter().enumerate() {
for &b in targets {
if owner[a] != owner[b] {
dag[owner[a]].insert(owner[b]);
}
}
}
let mut ranks = vec![0usize; comps.len()];
let mut done = BTreeSet::new();
while done.len() < comps.len() {
let next = (0..comps.len())
.find(|c| {
!done.contains(c)
&& (0..comps.len()).all(|p| !dag[p].contains(c) || done.contains(&p))
})
.ok_or_else(|| error("condensation is cyclic"))?;
charge(&mut work, comps.len() * comps.len())?;
for &to in &dag[next] {
ranks[to] = ranks[to].max(ranks[next] + 1);
}
done.insert(next);
}
let mut blocks = Vec::new();
let mut component_diagnostics = Vec::new();
let mut member_offsets = BTreeMap::new();
for (c, members) in comps.iter().enumerate() {
let mut gap = Size {
width: PAD,
height: PAD,
};
for &(a, b, label) in &links {
if owner[a] == c && owner[b] == c {
gap.width = gap.width.max(label.width + PAD);
gap.height = gap.height.max(label.height + PAD);
}
}
let mut order = Vec::new();
let mut seen = BTreeSet::new();
for &start in members {
let mut stack = vec![start];
while let Some(i) = stack.pop() {
charge(&mut work, 1)?;
if !seen.insert(i) {
continue;
}
order.push(i);
for &j in arcs[i].iter().rev() {
if owner[j] == c && !seen.contains(&j) {
stack.push(j);
}
}
}
}
let cell = Size {
width: members
.iter()
.map(|&i| sizes[&ids[i]].width)
.fold(0.0, f32::max),
height: members
.iter()
.map(|&i| sizes[&ids[i]].height)
.fold(0.0, f32::max),
};
let (slots, rows, cols) = if members.len() == 1 {
(vec![Position { x: 0.0, y: 0.0 }], 1, 1)
} else {
perimeter(members.len(), cell, gap, &mut work)?
};
component_diagnostics.push(serde_json::json!({"component":c,"rank":ranks[c],"members":members.iter().map(|&i|ids[i]).collect::<Vec<_>>(),"walk":order.iter().map(|&i|ids[i]).collect::<Vec<_>>(),"grid_rows":rows,"grid_cols":cols,"gap":gap}));
let mut size = Size {
width: 0.0,
height: 0.0,
};
for (i, pos) in order.into_iter().zip(slots) {
size.width = size.width.max(pos.x + sizes[&ids[i]].width);
size.height = size.height.max(pos.y + sizes[&ids[i]].height);
member_offsets.insert(i, pos);
}
blocks.push(size);
}
let max_rank = ranks.iter().copied().max().unwrap_or(0);
charge(
&mut work,
links
.len()
.saturating_mul(comps.len().saturating_add(1))
.saturating_mul(comps.len().saturating_add(1)),
)?;
let mut block_positions = vec![Position { x: 0.0, y: 0.0 }; comps.len()];
let mut x = 0.0;
let mut total = Size {
width: 0.0,
height: 0.0,
};
let mut prior_order = vec![0usize; comps.len()];
let mut bands = Vec::new();
for rank in 0..=max_rank {
let mut layer: Vec<_> = (0..comps.len()).filter(|&c| ranks[c] == rank).collect();
// Fixed one-pass proximity order, including undirected links, without
// inventing direction or changing condensation ranks.
layer.sort_by_key(|&c| {
let neighbors: Vec<_> = links
.iter()
.filter_map(|&(a, b, _)| {
let (a, b) = (owner[a], owner[b]);
if a == c && ranks[b] < rank {
Some(prior_order[b])
} else if b == c && ranks[a] < rank {
Some(prior_order[a])
} else {
None
}
})
.collect();
(
if neighbors.is_empty() {
c
} else {
neighbors.iter().sum::<usize>() / neighbors.len()
},
c,
)
});
// Keep same-rank undirected neighbours contiguous in one canonical
// breadth-first walk; these links never become directed rank arcs.
let mut proximity = Vec::new();
let mut remaining: BTreeSet<_> = layer.iter().copied().collect();
for &start in &layer {
if !remaining.remove(&start) {
continue;
}
let mut queue = std::collections::VecDeque::from([start]);
while let Some(c) = queue.pop_front() {
proximity.push(c);
for &next in &layer {
charge(&mut work, links.len() + 1)?;
if remaining.contains(&next)
&& links.iter().any(|&(a, b, _)| {
(owner[a] == c && owner[b] == next)
|| (owner[b] == c && owner[a] == next)
})
{
remaining.remove(&next);
queue.push_back(next);
}
}
}
}
layer = proximity;
let mut cut = Size {
width: PAD,
height: PAD,
};
for &(a, b, label) in &links {
if owner[a] != owner[b] && (ranks[owner[a]] == rank || ranks[owner[b]] == rank) {
cut.width = cut.width.max(label.width + PAD);
cut.height = cut.height.max(label.height + PAD);
}
}
let mut y = 0.0;
let mut width = 0.0_f32;
if order_boundaries && parent.is_some() {
bands.push(boundary::Band {
components: layer.clone(),
gap: cut.height,
});
}
for (i, c) in layer.into_iter().enumerate() {
prior_order[c] = i;
block_positions[c] = Position { x, y };
y += blocks[c].height + cut.height;
width = width.max(blocks[c].width);
}
total.width = total.width.max(x + width);
total.height = total.height.max((y - cut.height).max(0.0));
x += width + cut.width;
}
let header = if let Some(id) = parent {
let n = view.nodes.iter().find(|n| n.id == id).unwrap();
if n.collapsed {
return Err(error("folded node has projected children"));
}
if d.projection.state_machine().is_some() {
projection::state_header_height(project, n.element)
} else {
64.0
}
} else {
0.0
};
for (i, id) in ids.iter().enumerate() {
let b = block_positions[owner[i]];
let m = member_offsets[&i];
offsets.insert(
*id,
Position {
x: b.x + m.x + if parent.is_some() { PAD } else { 0.0 },
y: b.y + m.y + if parent.is_some() { header + PAD } else { 0.0 },
},
);
}
if let Some(id) = parent {
let size = sizes.get_mut(&id).unwrap();
size.width = size.width.max(total.width + 2.0 * PAD);
size.height = size.height.max(total.height + header + 2.0 * PAD);
if order_boundaries {
boundary_groups.push(boundary::Group {
parent: id,
blocks: comps
.iter()
.enumerate()
.map(|(c, members)| boundary::Block {
members: members.iter().map(|&i| ids[i]).collect(),
size: blocks[c],
position: block_positions[c],
})
.collect(),
bands,
undirected: undirected
.iter()
.map(|(a, b)| (owner[index[a]], owner[index[b]]))
.collect(),
});
}
}
let lifted: Vec<_> = arcs
.iter()
.enumerate()
.flat_map(|(a, targets)| targets.iter().map(move |&b| (ids[a], ids[b])))
.collect();
diagnostics.push(serde_json::json!({"visible_parent":parent,"components":component_diagnostics,"directed_lifted_edges":lifted,"undirected_lifted_pairs":undirected,"block_positions":block_positions,"bounds":total,"policy":"SCCs of sibling-subtree quotient, not global semantic SCCs. Undirected edges affect local gap and deterministic proximity only; no rank direction invented."}));
}
let boundary_report = if order_boundaries {
let report = boundary::reorder(
&mut boundary_groups,
&parents,
&mut offsets,
&sizes,
&view.edges,
&mut work,
)?;
for group in &boundary_groups {
let diagnostic = diagnostics
.iter_mut()
.find(|entry| entry["visible_parent"] == serde_json::json!(group.parent))
.unwrap();
diagnostic["block_positions"] =
serde_json::json!(group.blocks.iter().map(|b| b.position).collect::<Vec<_>>());
}
Some(report)
} else {
None
};
let mut plans = Vec::new();
for n in &view.nodes {
let mut position = offsets[&n.id];
let mut parent = n.visual_parent;
while let Some(id) = parent {
charge(&mut work, 1)?;
let p = offsets[&id];
position.x += p.x;
position.y += p.y;
parent = parents[&id];
}
position.x += 40.0;
position.y += 40.0;
let size = sizes[&n.id];
if !position.x.is_finite()
|| !position.y.is_finite()
|| position.x + size.width > SPAN
|| position.y + size.height > SPAN
{
return Err(error("extent bound exceeded"));
}
plans.push(PlacementNode {
id: n.id,
position,
size,
});
}
let result = import_placement(
project,
diagram,
PlacementPlan {
schema: 1,
nodes: plans,
},
)?;
let final_view = projection::project_view(&result, diagram);
charge(
&mut work,
final_view.edges.len() * (final_view.nodes.len() + 1) * 16,
)?;
if !routing::lead_incompatibilities(&result, &result.diagrams[&diagram], &final_view).is_empty()
{
return Err(error(
"final mandatory port lead incompatible; no repair attempted",
));
}
let mut report = serde_json::json!({"groups":diagnostics,"work_charged":work,"work_limit":LIMIT,"scope":"Projected-forest quotient heuristic; actual final reprojection and necessary lead guard checked. No full-route or readability acceptance."});
if let Some(boundary_report) = boundary_report {
report["boundary_order"] = boundary_report;
}
Ok((result, report))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn directed_cycles_remain_separate_across_one_way_bridge() {
let arcs = vec![
BTreeSet::from([1]),
BTreeSet::from([0, 2]),
BTreeSet::from([3]),
BTreeSet::from([2]),
];
assert_eq!(
components(&arcs, &mut 0).unwrap(),
vec![vec![0, 1], vec![2, 3]]
);
}
#[test]
fn perimeter_places_ten_unique_cells_without_overlap() {
let (positions, _, _) = perimeter(
10,
Size {
width: 224.0,
height: 132.0,
},
Size {
width: 32.0,
height: 32.0,
},
&mut 0,
)
.unwrap();
assert_eq!(positions.len(), 10);
for (i, a) in positions.iter().enumerate() {
for b in &positions[i + 1..] {
assert!((a.x - b.x).abs() >= 256.0 || (a.y - b.y).abs() >= 164.0);
}
}
}
#[test]
fn two_node_perimeter_scores_occupied_cells_instead_of_an_unused_row() {
let (positions, rows, cols) = perimeter(
2,
Size {
width: 224.0,
height: 160.0,
},
Size {
width: 250.0,
height: 56.0,
},
&mut 0,
)
.unwrap();
assert_eq!((rows, cols), (2, 1));
assert_eq!(
positions,
vec![Position { x: 0.0, y: 0.0 }, Position { x: 0.0, y: 216.0 }]
);
}
}
@@ -0,0 +1,686 @@
//! One deterministic boundary-order pass; dependency cycles use frozen scores.
use super::{charge, components, error, lift};
use oa_core::{Id, ModelError, Position, Size, projection::ProjectedEdge};
use std::collections::{BTreeMap, BTreeSet};
pub(super) struct Block {
pub members: Vec<Id>,
pub size: Size,
pub position: Position,
}
pub(super) struct Band {
pub components: Vec<usize>,
pub gap: f32,
}
pub(super) struct Group {
pub parent: Id,
pub blocks: Vec<Block>,
pub bands: Vec<Band>,
pub undirected: BTreeSet<(usize, usize)>,
}
#[derive(Clone)]
struct Unit {
blocks: Vec<usize>,
sources: BTreeSet<Id>,
hints: Vec<(Id, Id, Id)>,
}
fn center(
id: Id,
parents: &BTreeMap<Id, Option<Id>>,
offsets: &BTreeMap<Id, Position>,
sizes: &BTreeMap<Id, Size>,
work: &mut usize,
) -> Result<f64, ModelError> {
let mut at = Some(id);
let mut seen = BTreeSet::new();
let mut y = 0.0_f32;
while let Some(node) = at {
charge(work, 1)?;
if !seen.insert(node) {
return Err(error("boundary cyclic ancestry"));
}
let pos = offsets
.get(&node)
.ok_or_else(|| error("boundary missing offset"))?;
if !pos.x.is_finite() || !pos.y.is_finite() {
return Err(error("boundary nonfinite offset"));
}
if seen.len() == 1 {
y = pos.y;
} else {
y += pos.y;
}
if !y.is_finite() {
return Err(error("boundary composed position overflow"));
}
at = *parents
.get(&node)
.ok_or_else(|| error("boundary missing parent"))?;
}
let size = sizes
.get(&id)
.ok_or_else(|| error("boundary missing source size"))?;
y += 40.0;
let center = f64::from(y) + f64::from(size.height) / 2.0;
if !center.is_finite() {
return Err(error("boundary nonfinite center"));
}
Ok(center)
}
fn translate(value: f32, old_origin: f32, new_origin: f32) -> Result<f32, ModelError> {
// Bound the complete displacement, including origin subtraction. A rounded
// f32 delta can hide lost relative precision even when its addition is exact.
let intended = f64::from(value) + (f64::from(new_origin) - f64::from(old_origin));
let next = intended as f32;
if !next.is_finite() || (f64::from(next) - intended).abs() > 0.001 {
return Err(error("boundary member translation precision exceeded"));
}
Ok(next)
}
fn score(
unit: &Unit,
parents: &BTreeMap<Id, Option<Id>>,
offsets: &BTreeMap<Id, Position>,
sizes: &BTreeMap<Id, Size>,
work: &mut usize,
) -> Result<Option<f64>, ModelError> {
if unit.sources.is_empty() {
return Ok(None);
}
let mut total = 0.0;
for &source in &unit.sources {
total += center(source, parents, offsets, sizes, work)?;
}
let value = total / unit.sources.len() as f64;
if !value.is_finite() {
return Err(error("boundary nonfinite mean"));
}
Ok(Some(value))
}
fn ordered_units(scores: &[Option<f64>]) -> Vec<usize> {
let mut anchored: Vec<_> = (0..scores.len()).filter(|&i| scores[i].is_some()).collect();
anchored.sort_by(|&a, &b| {
scores[a]
.unwrap()
.total_cmp(&scores[b].unwrap())
.then(a.cmp(&b))
});
let mut next = anchored.into_iter();
(0..scores.len())
.map(|i| {
if scores[i].is_some() {
next.next().unwrap()
} else {
i
}
})
.collect()
}
fn packing(
blocks: &[Block],
before: &[usize],
after: &[usize],
gap: f32,
) -> Result<Vec<f32>, ModelError> {
const EPS: f64 = 0.001;
let origin = f64::from(blocks[before[0]].position.y);
let mut canonical = origin;
for &c in before {
if (f64::from(blocks[c].position.y) - canonical).abs() > EPS {
return Err(error("boundary non-prefix canonical band"));
}
canonical += f64::from(blocks[c].size.height) + f64::from(gap);
}
let mut next = origin;
let mut ys = Vec::new();
for &c in after {
let y = next as f32;
if !y.is_finite() || (f64::from(y) - next).abs() > EPS {
return Err(error("boundary prefix precision bound exceeded"));
}
ys.push(y);
next += f64::from(blocks[c].size.height) + f64::from(gap);
}
if (next - canonical).abs() > EPS {
return Err(error("boundary envelope changed"));
}
if before == after {
Ok(before.iter().map(|&c| blocks[c].position.y).collect())
} else {
Ok(ys)
}
}
pub(super) fn reorder(
groups: &mut [Group],
parents: &BTreeMap<Id, Option<Id>>,
offsets: &mut BTreeMap<Id, Position>,
sizes: &BTreeMap<Id, Size>,
edges: &[ProjectedEdge],
work: &mut usize,
) -> Result<serde_json::Value, ModelError> {
let mut group_order: Vec<_> = (0..groups.len()).collect();
group_order.sort_by_key(|&i| groups[i].parent);
let mut offset_owner = BTreeMap::new();
let mut parent_seen = BTreeSet::new();
let mut units: Vec<Vec<Vec<Unit>>> = vec![Vec::new(); groups.len()];
let mut active = BTreeSet::new();
for &g in &group_order {
let group = &groups[g];
if !parent_seen.insert(group.parent) || !parents.contains_key(&group.parent) {
return Err(error("boundary duplicate/missing group parent"));
}
let mut child_block = BTreeMap::new();
for (c, block) in group.blocks.iter().enumerate() {
if !block.size.width.is_finite()
|| !block.size.height.is_finite()
|| block.size.width <= 0.0
|| block.size.height <= 0.0
|| !block.position.x.is_finite()
|| !block.position.y.is_finite()
{
return Err(error("boundary invalid block geometry"));
}
for &id in &block.members {
if parents.get(&id) != Some(&Some(group.parent))
|| !offsets.contains_key(&id)
|| child_block.insert(id, c).is_some()
|| offset_owner.insert(id, g).is_some()
{
return Err(error("boundary invalid child membership"));
}
}
}
let mut covered = BTreeSet::new();
for band in &group.bands {
if !band.gap.is_finite() || band.gap < 0.0 || band.components.is_empty() {
return Err(error("boundary invalid band"));
}
let local: BTreeMap<_, _> = band
.components
.iter()
.enumerate()
.map(|(i, &c)| (c, i))
.collect();
for &c in &band.components {
if c >= group.blocks.len() || !covered.insert(c) {
return Err(error("boundary duplicate/unknown band block"));
}
}
let mut arcs = vec![BTreeSet::new(); band.components.len()];
for &(a, b) in &group.undirected {
charge(work, 1)?;
if let (Some(&a), Some(&b)) = (local.get(&a), local.get(&b)) {
arcs[a].insert(b);
arcs[b].insert(a);
}
}
let bundles = components(&arcs, work)?;
let mut band_units = Vec::new();
for bundle in bundles {
if bundle.windows(2).any(|v| v[1] != v[0] + 1) {
return Err(error("boundary undirected bundle not contiguous"));
}
let blocks: Vec<_> = bundle.iter().map(|&i| band.components[i]).collect();
let mut unit = Unit {
blocks,
sources: BTreeSet::new(),
hints: Vec::new(),
};
for edge in edges {
charge(work, 1)?;
if !edge.oriented {
continue;
}
let target = lift(edge.target, Some(group.parent), parents, work)?;
let Some(target) = target else {
continue;
};
if !unit.blocks.contains(
child_block
.get(&target)
.ok_or_else(|| error("boundary missing lifted target"))?,
) {
continue;
}
if lift(edge.source, Some(group.parent), parents, work)?.is_none() {
unit.sources.insert(edge.source);
unit.hints.push((edge.id, edge.source, edge.target));
}
}
unit.hints.sort();
band_units.push(unit);
}
if band_units.iter().filter(|u| !u.sources.is_empty()).count() >= 2 {
active.insert(g);
}
units[g].push(band_units);
}
if covered.len() != group.blocks.len() {
return Err(error("boundary incomplete band inventory"));
}
}
let active_order: Vec<_> = group_order
.into_iter()
.filter(|g| active.contains(g))
.collect();
let active_index: BTreeMap<_, _> = active_order
.iter()
.enumerate()
.map(|(i, &g)| (g, i))
.collect();
let mut dependencies = vec![BTreeSet::new(); active_order.len()];
let mut dependency_evidence = BTreeSet::new();
for (&g, &gi) in &active_index {
for unit in units[g].iter().flatten() {
for &source in &unit.sources {
let mut at = Some(source);
let mut seen = BTreeSet::new();
while let Some(node) = at {
charge(work, 1)?;
if !seen.insert(node) {
return Err(error("boundary dependency ancestry cycle"));
}
if let Some(&owner) = offset_owner.get(&node)
&& let Some(&oi) = active_index.get(&owner)
{
dependencies[oi].insert(gi);
dependency_evidence.insert((
groups[owner].parent,
groups[g].parent,
source,
node,
));
}
at = *parents
.get(&node)
.ok_or_else(|| error("boundary missing dependency ancestor"))?;
}
}
}
}
let sccs = components(&dependencies, work)?;
let mut scc_owner = vec![0; active_order.len()];
for (s, members) in sccs.iter().enumerate() {
for &i in members {
scc_owner[i] = s;
}
}
let mut dag = vec![BTreeSet::new(); sccs.len()];
for (a, targets) in dependencies.iter().enumerate() {
for &b in targets {
if scc_owner[a] != scc_owner[b] {
dag[scc_owner[a]].insert(scc_owner[b]);
}
}
}
let mut done = BTreeSet::new();
let mut records = Vec::new();
let mut processed = Vec::new();
while done.len() < sccs.len() {
charge(work, sccs.len() * sccs.len())?;
let s = (0..sccs.len())
.find(|s| {
!done.contains(s)
&& (0..sccs.len()).all(|p| !dag[p].contains(s) || done.contains(&p))
})
.ok_or_else(|| error("boundary condensation cycle"))?;
// Score the entire dependency SCC before modifying any offsets.
let mut snapshots = Vec::new();
for &i in &sccs[s] {
let g = active_order[i];
for (b, band_units) in units[g].iter().enumerate() {
let scores = band_units
.iter()
.map(|u| score(u, parents, offsets, sizes, work))
.collect::<Result<Vec<_>, _>>()?;
snapshots.push((g, b, scores));
}
}
for (g, b, scores) in snapshots {
let band_units = &units[g][b];
let order = ordered_units(&scores);
let before = groups[g].bands[b].components.clone();
let gap = groups[g].bands[b].gap;
let after: Vec<_> = order
.iter()
.flat_map(|&u| band_units[u].blocks.iter().copied())
.collect();
let ys = packing(&groups[g].blocks, &before, &after, gap)?;
let mut changed = Vec::new();
for (&c, y) in after.iter().zip(ys) {
charge(work, groups[g].blocks[c].members.len() + 1)?;
let old = groups[g].blocks[c].position.y;
if !y.is_finite() {
return Err(error("boundary packing overflow"));
}
if y != old {
changed.push(c);
}
for id in &groups[g].blocks[c].members {
let p = offsets
.get_mut(id)
.ok_or_else(|| error("boundary lost member offset"))?;
p.y = translate(p.y, old, y)?;
}
groups[g].blocks[c].position.y = y;
}
groups[g].bands[b].components = after.clone();
let changed_units: Vec<_> = band_units
.iter()
.enumerate()
.filter(|(_, u)| u.blocks.iter().any(|c| changed.contains(c)))
.map(|(i, _)| i)
.collect();
records.push(serde_json::json!({"parent":groups[g].parent,"band":b,"before_blocks":before,"after_blocks":after,"unit_order":order,"scores":scores,"changed_blocks":changed,"changed_units":changed_units,"envelope_epsilon":0.001,"units":band_units.iter().map(|u|serde_json::json!({"blocks":u.blocks,"source_owners":u.sources,"edge_hints":u.hints})).collect::<Vec<_>>()}));
}
processed.push(serde_json::json!({"parents":sccs[s].iter().map(|&i|groups[active_order[i]].parent).collect::<Vec<_>>(),"cyclic":sccs[s].len()>1||sccs[s].iter().any(|&i|dependencies[i].contains(&i)),"policy":"all scores snapshotted before one simultaneous group-permutation pass"}));
done.insert(s);
}
Ok(
serde_json::json!({"active_parents":active_order.iter().map(|&g|groups[g].parent).collect::<Vec<_>>(),"dependencies":dependency_evidence,"dependency_components":processed,"bands":records,"limitation":"One pass; cyclic dependencies use a common snapshot, not a fixed-point solution. Root unchanged; no quality or repeat-stability acceptance."}),
)
}
#[cfg(test)]
mod tests {
use super::*;
fn id(n: u128) -> Id {
Id::from_u128(n)
}
fn block(n: u128, y: f32, height: f32) -> Block {
Block {
members: vec![id(n)],
size: Size {
width: 10.0,
height,
},
position: Position { x: 0.0, y },
}
}
fn edge(n: u128, source: u128, target: u128) -> ProjectedEdge {
ProjectedEdge {
id: id(n),
source: id(source),
target: id(target),
trace: false,
oriented: true,
source_port: None,
target_port: None,
}
}
#[test]
fn anchored_sort_preserves_unanchored_ordinal_slot() {
assert_eq!(
ordered_units(&[Some(90.0), None, Some(10.0), Some(10.0)]),
vec![2, 1, 3, 0]
);
}
#[test]
fn heterogeneous_blocks_use_new_prefix_not_old_slots() {
let blocks = vec![
Block {
members: vec![],
size: Size {
width: 10.0,
height: 100.0,
},
position: Position { x: 0.0, y: 0.0 },
},
Block {
members: vec![],
size: Size {
width: 10.0,
height: 20.0,
},
position: Position { x: 0.0, y: 132.0 },
},
];
assert_eq!(
packing(&blocks, &[0, 1], &[1, 0], 32.0).unwrap(),
vec![0.0, 52.0]
);
}
#[test]
fn center_includes_grandparent_offset_change() {
let (a, b, c) = (Id::from_u128(1), Id::from_u128(2), Id::from_u128(3));
let parents = BTreeMap::from([(a, None), (b, Some(a)), (c, Some(b))]);
let mut offsets = BTreeMap::from([
(a, Position { x: 0.0, y: 100.0 }),
(b, Position { x: 0.0, y: 20.0 }),
(c, Position { x: 0.0, y: 5.0 }),
]);
let sizes = BTreeMap::from([(
c,
Size {
width: 10.0,
height: 10.0,
},
)]);
assert_eq!(
center(c, &parents, &offsets, &sizes, &mut 0).unwrap(),
170.0
);
offsets.get_mut(&a).unwrap().y += 300.0;
assert_eq!(
center(c, &parents, &offsets, &sizes, &mut 0).unwrap(),
470.0
);
}
#[test]
fn ancestor_dependency_updates_downstream_scores_before_its_permutation() {
// Downstream parent1 sorts before upstream20, so an absent ancestor
// dependency would leave downstream order unchanged and fail this test.
let mut groups = vec![
Group {
parent: id(1),
blocks: vec![block(2, 0.0, 100.0), block(3, 132.0, 100.0)],
bands: vec![Band {
components: vec![0, 1],
gap: 32.0,
}],
undirected: BTreeSet::new(),
},
Group {
parent: id(20),
blocks: vec![block(21, 0.0, 100.0), block(22, 132.0, 100.0)],
bands: vec![Band {
components: vec![0, 1],
gap: 32.0,
}],
undirected: BTreeSet::new(),
},
];
let parents = BTreeMap::from([
(id(1), None),
(id(2), Some(id(1))),
(id(3), Some(id(1))),
(id(20), None),
(id(21), Some(id(20))),
(id(22), Some(id(20))),
(id(23), Some(id(21))),
(id(30), None),
(id(31), None),
(id(32), None),
]);
let mut offsets: BTreeMap<_, _> = [
(1, 0.0),
(2, 0.0),
(3, 132.0),
(20, 0.0),
(21, 0.0),
(22, 132.0),
(23, 10.0),
(30, 300.0),
(31, 0.0),
(32, 50.0),
]
.into_iter()
.map(|(n, y)| (id(n), Position { x: 0.0, y }))
.collect();
let sizes = parents
.keys()
.map(|&n| {
(
n,
Size {
width: 10.0,
height: 10.0,
},
)
})
.collect();
let edges = vec![
edge(101, 30, 21),
edge(102, 31, 22),
edge(103, 23, 2),
edge(104, 32, 3),
];
let report = reorder(&mut groups, &parents, &mut offsets, &sizes, &edges, &mut 0).unwrap();
assert_eq!(groups[1].bands[0].components, vec![1, 0]);
assert_eq!(groups[0].bands[0].components, vec![1, 0]);
assert_eq!(offsets[&id(21)].y, 132.0);
assert_eq!(offsets[&id(23)].y, 10.0); // descendant is not directly moved
assert_eq!(
center(id(23), &parents, &offsets, &sizes, &mut 0).unwrap(),
187.0
);
assert_eq!(
report["dependency_components"][0]["parents"][0],
serde_json::json!(id(20))
);
}
#[test]
fn undirected_bundle_stays_whole_around_unanchored_unit() {
let mut groups = vec![Group {
parent: id(100),
blocks: vec![
block(101, 0.0, 20.0),
block(102, 30.0, 30.0),
block(103, 70.0, 40.0),
block(104, 120.0, 10.0),
],
bands: vec![Band {
components: vec![0, 1, 2, 3],
gap: 10.0,
}],
undirected: BTreeSet::from([(0, 1)]),
}];
let parents = BTreeMap::from([
(id(100), None),
(id(101), Some(id(100))),
(id(102), Some(id(100))),
(id(103), Some(id(100))),
(id(104), Some(id(100))),
(id(110), None),
(id(111), None),
]);
let mut offsets: BTreeMap<_, _> = [
(100, 0.0),
(101, 0.0),
(102, 30.0),
(103, 70.0),
(104, 120.0),
(110, 300.0),
(111, 0.0),
]
.into_iter()
.map(|(n, y)| (id(n), Position { x: 0.0, y }))
.collect();
let sizes = parents
.keys()
.map(|&n| {
(
n,
Size {
width: 10.0,
height: 10.0,
},
)
})
.collect();
let report = reorder(
&mut groups,
&parents,
&mut offsets,
&sizes,
&[
edge(201, 110, 101),
edge(202, 110, 102),
edge(203, 111, 104),
],
&mut 0,
)
.unwrap();
assert_eq!(groups[0].bands[0].components, vec![3, 2, 0, 1]);
assert_eq!(
[
offsets[&id(104)].y,
offsets[&id(103)].y,
offsets[&id(101)].y,
offsets[&id(102)].y
],
[0.0, 20.0, 70.0, 100.0]
);
assert_eq!(
report["bands"][0]["units"][0]["source_owners"]
.as_array()
.unwrap()
.len(),
1
);
}
#[test]
fn center_uses_final_plan_f32_composition_and_translation_has_error_bound() {
let parents = BTreeMap::from([(id(1), None), (id(2), Some(id(1))), (id(3), Some(id(2)))]);
let offsets = BTreeMap::from([
(
id(1),
Position {
x: 0.0,
y: -100000.0,
},
),
(
id(2),
Position {
x: 0.0,
y: 100000.0,
},
),
(id(3), Position { x: 0.0, y: 0.1 }),
]);
let sizes = BTreeMap::from([(
id(3),
Size {
width: 10.0,
height: 10.0,
},
)]);
assert_eq!(
center(id(3), &parents, &offsets, &sizes, &mut 0).unwrap(),
45.1015625
);
assert_eq!(translate(10.125, 0.0, 0.375).unwrap(), 10.5);
assert!(translate(32768.0, 0.0, 0.002).is_err());
}
#[test]
fn member_offset_survives_large_origin_subtraction() {
let blocks = vec![
block(1, 0.0, 65404.0),
block(2, 65436.0, 68.0),
block(3, 65536.0, 100.0),
block(4, 65668.0, 68.002),
];
let ys = packing(&blocks, &[0, 1, 2, 3], &[3, 2, 1, 0], 32.0).unwrap();
let member = translate(65568.0, 65536.0, ys[1]).unwrap();
assert!((f64::from(member) - f64::from(ys[1]) - 32.0).abs() <= 0.001);
}
}
@@ -0,0 +1,380 @@
//! Isolated #32 experiment, not a production layout engine.
//! Uses existing Layout sizing, three deterministic ordering passes, and one
//! core-estimated-label spacing pass. Automatic elbows remain derived. Folded/omitted
//! node topology is explicitly unsupported; filtered edges retain their metadata.
use oa_core::{Command, Id, ModelError, Position, Project, Size, projection, routing};
use std::collections::{BTreeMap, BTreeSet};
const WORK_LIMIT: usize = 200_000;
const NODE_LIMIT: usize = 256;
const EDGE_LIMIT: usize = 1024;
fn spend(work: &mut usize) -> Result<(), ModelError> {
*work += 1;
if *work > WORK_LIMIT {
return Err(ModelError("Layered probe work limit exceeded.".into()));
}
Ok(())
}
/// Return a new Project; caller owns comparison, history and serialization.
pub fn candidate(project: &Project, diagram: Id) -> Result<Project, ModelError> {
let original = project
.diagrams
.get(&diagram)
.ok_or_else(|| ModelError("Missing probe diagram.".into()))?;
let view = projection::project_diagram(project, original);
if view.nodes.len() > NODE_LIMIT || view.edges.len() > EDGE_LIMIT {
return Err(ModelError("Layered probe graph limit exceeded.".into()));
}
if view.nodes.len() != original.nodes.len() || view.nodes.iter().any(|n| n.collapsed) {
return Err(ModelError(
"Layered probe does not support folded/omitted node topology.".into(),
));
}
let routed = routing::route_diagram(project, original, &view);
let eligible: BTreeSet<_> = view
.edges
.iter()
.filter(|edge| {
projection::edge_endpoint_placements(project, original, edge.id)
== Some((edge.source, edge.target))
&& routed
.edges
.get(&edge.id)
.is_some_and(|route| !route.custom_suspended)
})
.map(|edge| edge.id)
.collect();
let mut result = oa_core::prepare_command(project, &Command::Layout(diagram))?.project;
// Discard baseline route translation: final routes are reconstructed directly
// from original geometry, avoiding two translations and preserving list order.
result.diagrams.get_mut(&diagram).unwrap().routes = original.routes.clone();
for route in &mut result.diagrams.get_mut(&diagram).unwrap().routes {
if eligible.contains(&route.edge) {
route.waypoints.clear();
route.label = None;
}
}
let baseline = projection::project_view(&result, diagram);
let labels = routing::route_diagram(&result, &result.diagrams[&diagram], &baseline);
let mut gap_x = 64.0_f32;
let mut gap_y = 32.0_f32;
for edge in labels.edges.values() {
gap_x = gap_x.max(edge.label.bounds.size.width + 24.0);
gap_y = gap_y.max(edge.label.bounds.size.height + 24.0);
if let Some(relation) = result.relations.get(&edge.id) {
for end in routing::association_end_geometry(relation, edge) {
gap_x = gap_x.max(end.label.bounds.size.width + 24.0);
gap_y = gap_y.max(end.label.bounds.size.height + 24.0);
}
}
}
if !gap_x.is_finite() || !gap_y.is_finite() {
return Err(ModelError("Nonfinite estimated label bound.".into()));
}
let parents: BTreeMap<_, _> = baseline
.nodes
.iter()
.map(|n| (n.id, n.visual_parent))
.collect();
let mut children: BTreeMap<Option<Id>, Vec<Id>> = BTreeMap::new();
for node in &baseline.nodes {
children
.entry(node.visual_parent)
.or_default()
.push(node.id);
}
let names: BTreeMap<_, _> = baseline
.nodes
.iter()
.map(|n| (n.id, (result.elements[&n.element].alias.clone(), n.id)))
.collect();
for siblings in children.values_mut() {
siblings.sort_by_key(|id| names[id].clone());
}
let minima: BTreeMap<_, _> = baseline.nodes.iter().map(|n| (n.id, n.size)).collect();
let headers: BTreeMap<_, _> = baseline
.nodes
.iter()
.map(|n| {
(
n.id,
if original.projection.state_machine().is_some() {
projection::state_header_height(&result, n.element)
} else {
64.0
},
)
})
.collect();
let mut positions: BTreeMap<_, _> = baseline.nodes.iter().map(|n| (n.id, n.position)).collect();
let mut sizes = minima.clone();
let mut work = 0;
let mut group_order: Vec<_> = children.keys().copied().collect();
group_order.sort_by_key(|parent| {
parent.map_or(0, |id| {
baseline.nodes.iter().find(|n| n.id == id).unwrap().depth + 1
})
});
let mut groups = BTreeMap::new();
for pass in 0..3 {
for parent in &group_order {
let siblings = &children[parent];
let mut arcs = BTreeSet::new();
for edge in &baseline.edges {
spend(&mut work)?;
let a = child_under(edge.source, *parent, &parents, &mut work)?;
let b = child_under(edge.target, *parent, &parents, &mut work)?;
if edge.oriented
&& let (Some(a), Some(b)) = (a, b)
&& a != b
{
arcs.insert((a, b));
}
}
let ranks = ranks(siblings, &arcs, &mut work)?;
let mut layers: BTreeMap<usize, Vec<Id>> = BTreeMap::new();
for id in siblings {
layers.entry(ranks[id]).or_default().push(*id);
}
// Boundary neighbours participate even though they are not siblings.
// Incoming preference anchors the downstream order to its upstream
// counterpart instead of swapping both sides simultaneously.
let mut scores = BTreeMap::new();
for id in siblings {
let mut incoming = Vec::new();
for edge in &baseline.edges {
spend(&mut work)?;
let a = child_under(edge.source, *parent, &parents, &mut work)?;
let b = child_under(edge.target, *parent, &parents, &mut work)?;
if edge.oriented && b == Some(*id) && a != Some(*id) {
incoming.push(positions[&edge.source].y + sizes[&edge.source].height / 2.0);
}
}
// Do not also chase outgoing boundary neighbours: simultaneous
// swaps in two connected containers can preserve the crossing.
let values = &incoming;
let score = if values.is_empty() {
positions[id].y
} else {
values.iter().sum::<f32>() / values.len() as f32
};
scores.insert(*id, score);
}
for layer in layers.values_mut() {
// First pass starts from canonical alias order; subsequent passes
// retain a deterministic alias tie break, never hash iteration.
if pass > 0 {
layer.sort_by(|a, b| {
scores[a]
.total_cmp(&scores[b])
.then_with(|| names[a].cmp(&names[b]))
});
}
}
groups.insert(*parent, layers.into_values().collect::<Vec<_>>());
}
// Recompute from frozen minima, never add spacing to last pass's size.
sizes = minima.clone();
measure(None, &groups, &headers, &mut sizes, gap_x, gap_y, &mut work)?;
place(
None,
Position { x: 40.0, y: 40.0 },
&groups,
&headers,
&sizes,
&mut positions,
gap_x,
gap_y,
&mut work,
)?;
}
let d = result.diagrams.get_mut(&diagram).unwrap();
for (id, node) in &mut d.nodes {
node.position = positions[id];
node.size = sizes[id];
}
let deltas: BTreeMap<_, _> = original
.nodes
.iter()
.map(|(id, node)| {
(
*id,
Position {
x: positions[id].x - node.position.x,
y: positions[id].y - node.position.y,
},
)
})
.collect();
let translated: BTreeMap<_, _> = projection::translated_routes(project, original, &deltas)
.into_iter()
.map(|r| (r.edge, r))
.collect();
d.routes = original
.routes
.iter()
.map(|route| {
let mut route = translated.get(&route.edge).unwrap_or(route).clone();
if eligible.contains(&route.edge) {
route.waypoints.clear();
route.label = None;
}
route
})
.collect();
if d.nodes.values().any(|n| {
!n.position.x.is_finite()
|| !n.position.y.is_finite()
|| !n.size.width.is_finite()
|| !n.size.height.is_finite()
|| n.size.width <= 0.0
|| n.size.height <= 0.0
}) {
return Err(ModelError("Invalid layered probe geometry.".into()));
}
oa_core::validate_candidate(&result)?;
Ok(result)
}
fn child_under(
mut id: Id,
parent: Option<Id>,
parents: &BTreeMap<Id, Option<Id>>,
work: &mut usize,
) -> Result<Option<Id>, ModelError> {
for _ in 0..=parents.len() {
spend(work)?;
let Some(actual) = parents.get(&id) else {
return Ok(None);
};
if *actual == parent {
return Ok(Some(id));
}
let Some(next) = actual else { return Ok(None) };
id = *next;
}
Err(ModelError("Layered probe containment cycle.".into()))
}
fn ranks(
ids: &[Id],
arcs: &BTreeSet<(Id, Id)>,
work: &mut usize,
) -> Result<BTreeMap<Id, usize>, ModelError> {
let mut remaining: BTreeSet<_> = ids.iter().copied().collect();
let mut ranks: BTreeMap<_, usize> = BTreeMap::new();
while !remaining.is_empty() {
let mut ready = None;
for id in ids.iter().filter(|id| remaining.contains(*id)) {
let mut blocked = false;
for (a, b) in arcs {
spend(work)?;
blocked |= b == id && remaining.contains(a);
}
if !blocked {
ready = Some(*id);
break;
}
}
// Explicit cycle fallback: break the first canonical remaining vertex's
// incoming ranking constraints. Actual relation direction is unchanged.
let id = ready.unwrap_or_else(|| *ids.iter().find(|id| remaining.contains(*id)).unwrap());
let mut rank = 0;
for (a, b) in arcs {
spend(work)?;
if *b == id
&& let Some(previous) = ranks.get(a)
{
rank = rank.max(previous + 1);
}
}
ranks.insert(id, rank);
remaining.remove(&id);
}
Ok(ranks)
}
type Groups = BTreeMap<Option<Id>, Vec<Vec<Id>>>;
fn measure(
parent: Option<Id>,
groups: &Groups,
headers: &BTreeMap<Id, f32>,
sizes: &mut BTreeMap<Id, Size>,
gx: f32,
gy: f32,
work: &mut usize,
) -> Result<Size, ModelError> {
let Some(layers) = groups.get(&parent) else {
return Ok(Size {
width: 0.0,
height: 0.0,
});
};
let mut width = 0.0;
let mut height = 0.0_f32;
for (i, layer) in layers.iter().enumerate() {
let mut lw = 0.0_f32;
let mut lh = 0.0;
for (j, id) in layer.iter().enumerate() {
spend(work)?;
let content = measure(Some(*id), groups, headers, sizes, gx, gy, work)?;
let size = sizes.get_mut(id).unwrap();
if groups.contains_key(&Some(*id)) {
size.width = size.width.max(content.width + 48.0);
size.height = size.height.max(content.height + headers[id] + 24.0);
}
lw = lw.max(size.width);
lh += size.height + if j == 0 { 0.0 } else { gy };
}
width += lw + if i == 0 { 0.0 } else { gx };
height = height.max(lh);
}
Ok(Size { width, height })
}
#[allow(clippy::too_many_arguments)]
fn place(
parent: Option<Id>,
origin: Position,
groups: &Groups,
headers: &BTreeMap<Id, f32>,
sizes: &BTreeMap<Id, Size>,
positions: &mut BTreeMap<Id, Position>,
gx: f32,
gy: f32,
work: &mut usize,
) -> Result<(), ModelError> {
let Some(layers) = groups.get(&parent) else {
return Ok(());
};
let mut x = origin.x;
for layer in layers {
let mut y = origin.y;
let mut width = 0.0_f32;
for id in layer {
spend(work)?;
positions.insert(*id, Position { x, y });
place(
Some(*id),
Position {
x: x + 24.0,
y: y + headers[id],
},
groups,
headers,
sizes,
positions,
gx,
gy,
work,
)?;
y += sizes[id].height + gy;
width = width.max(sizes[id].width);
}
x += width + gx;
}
Ok(())
}
@@ -0,0 +1,192 @@
//! Frozen-corpus route-only timing. Protocol: harness/experiments/routing-independent-20260925/timing-protocol.md.
use oa_core::{Id, Project, projection, routing, validate_candidate};
use serde::Deserialize;
use serde_json::json;
use std::hint::black_box;
use std::path::{Component, Path, PathBuf};
use std::time::{Duration, Instant};
const ROUNDS: usize = 7;
const SLOW_CALL: Duration = Duration::from_secs(5);
#[derive(Deserialize)]
struct Manifest {
schema: u32,
cases: Vec<Case>,
#[serde(flatten)]
metadata: std::collections::BTreeMap<String, serde_json::Value>,
}
#[derive(Deserialize)]
struct Case {
name: String,
project_file: PathBuf,
diagram: Id,
description: String,
#[serde(flatten)]
metadata: std::collections::BTreeMap<String, serde_json::Value>,
}
fn timed_route(
project: &Project,
diagram: &oa_core::Diagram,
view: &projection::ProjectedView,
) -> (routing::RoutedView, Duration) {
// Input black_box is outside the clock. Result consumption is inside it;
// allocation is part of routing, result destruction is outside the clock.
let project = black_box(project);
let diagram = black_box(diagram);
let view = black_box(view);
let started = Instant::now();
let result = black_box(routing::route_diagram(project, diagram, view));
let elapsed = started.elapsed();
(result, elapsed)
}
fn point_digest(route: &routing::RoutedView) -> String {
// Stable FNV-1a64 over ID bytes, point count and IEEE754 coordinate bits,
// little endian. Diagnostic only, not a cryptographic integrity guarantee.
let mut hash = 0xcbf29ce484222325_u64;
let mut feed = |bytes: &[u8]| {
for byte in bytes {
hash ^= u64::from(*byte);
hash = hash.wrapping_mul(0x100000001b3);
}
};
for (id, edge) in &route.edges {
feed(id.as_bytes());
feed(&(edge.points.len() as u64).to_le_bytes());
for point in &edge.points {
feed(&point.x.to_bits().to_le_bytes());
feed(&point.y.to_bits().to_le_bytes());
}
}
format!("{hash:016x}")
}
fn relative_project(root: &Path, file: &Path) -> Result<PathBuf, Box<dyn std::error::Error>> {
if file.as_os_str().is_empty()
|| !file
.components()
.all(|part| matches!(part, Component::Normal(_)))
{
return Err(
"project_file must be a nonempty relative path without parent traversal".into(),
);
}
let path = root.join(file).canonicalize()?;
if !path.starts_with(root) {
return Err("project_file resolves outside the manifest directory".into());
}
Ok(path)
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut args = std::env::args_os().skip(1);
let manifest_path = PathBuf::from(
args.next()
.ok_or("usage: routing_cost_probe MANIFEST.json")?,
)
.canonicalize()?;
if args.next().is_some() {
return Err(
"protocol is fixed: one warmup and seven measured calls; no extra arguments".into(),
);
}
let manifest: Manifest = serde_json::from_slice(&std::fs::read(&manifest_path)?)?;
if manifest.schema != 1 || manifest.cases.is_empty() {
return Err("expected schema1 with at least one case".into());
}
let root = manifest_path
.parent()
.ok_or("manifest has no parent directory")?;
println!(
"{}",
json!({
"protocol": "routing_cost_probe_v1", "manifest_schema": manifest.schema,
"warmup_calls": 1, "measured_calls": ROUNDS,
"slow_call_limit_ns": SLOW_CALL.as_nanos(),
"debug_assertions": cfg!(debug_assertions),
"os": std::env::consts::OS, "arch": std::env::consts::ARCH,
"case_count": manifest.cases.len(),
"corpus_metadata": manifest.metadata,
"clock_scope": "route_diagram plus result black_box; excludes projection, JSON, equality, digest, output and result destruction",
"digest": "FNV-1a64 IDs/counts/f32 point bits; diagnostic, not cryptographic",
"acceptance": "none; external receipts must bind source, optimized build, binary and corpus"
})
);
let mut names = std::collections::BTreeSet::new();
for case in manifest.cases {
if !names.insert(case.name.clone()) {
return Err("duplicate case name".into());
}
let bytes = std::fs::read(relative_project(root, &case.project_file)?)?;
let project: Project = serde_json::from_slice(&bytes)?;
validate_candidate(&project)?;
let original = project.clone();
let diagram = project
.diagrams
.get(&case.diagram)
.ok_or("case diagram is absent")?;
let view = projection::project_diagram(&project, diagram);
let (first, warmup) = timed_route(&project, diagram, &view);
let mut samples = Vec::with_capacity(ROUNDS);
let mut equality = Vec::with_capacity(ROUNDS);
let mut endpoints = Vec::with_capacity(ROUNDS);
let mut censored = warmup > SLOW_CALL;
if !censored {
for _ in 0..ROUNDS {
let (route, elapsed) = timed_route(&project, diagram, &view);
samples.push(elapsed.as_nanos());
equality.push(route == first);
endpoints.push(
route.edges.len() == first.edges.len()
&& route.edges.iter().all(|(id, edge)| {
first.edges.get(id).is_some_and(|other| {
edge.points.first() == other.points.first()
&& edge.points.last() == other.points.last()
})
}),
);
if elapsed > SLOW_CALL {
censored = true;
break;
}
}
}
let mut ordered = samples.clone();
ordered.sort_unstable();
let complete = !censored && samples.len() == ROUNDS;
let median = complete.then(|| ordered[ROUNDS / 2]);
let warnings: usize = first.edges.values().map(|edge| edge.warnings.len()).sum();
let project_unchanged = project == original;
let stable = equality.iter().all(|same| *same);
println!(
"{}",
json!({
"case": case.name, "description": case.description,
"corpus_case_metadata": case.metadata,
"project_file": case.project_file, "diagram": case.diagram,
"project_bytes": bytes.len(), "nodes": view.nodes.len(),
"edges": view.edges.len(), "ports": view.ports.len(),
"routed_edges": first.edges.len(), "warnings": warnings,
"route_points": first.edges.values().map(|edge| edge.points.len()).sum::<usize>(),
"route_point_digest": point_digest(&first),
"warmup_ns": warmup.as_nanos(), "samples_ns": samples,
"median_ns": median, "max_observed_measured_ns": ordered.last(),
"complete": complete, "censored": censored,
"censor_reason": if censored { Some("single call exceeded five seconds; remaining repeats skipped") } else { None },
"repeat_full_routed_view_equal_warmup": equality,
"repeat_endpoints_equal_warmup": endpoints,
"stability_observed": !samples.is_empty(),
"project_unchanged": project_unchanged
})
);
if !stable || !project_unchanged {
return Err(
"routing stability or Project preservation failed; receipt above retained".into(),
);
}
}
Ok(())
}
@@ -0,0 +1,555 @@
//! Source-first inputs for independent routing experiments. Never invokes layout/routing.
use oa_core::*;
use serde_json::{Value, json};
use std::{collections::BTreeMap, error::Error, fs, path::PathBuf};
struct Case {
name: &'static str,
description: &'static str,
project: Project,
diagram: Id,
trace_edges: Vec<Id>,
metadata: Value,
}
struct Builder {
project: Project,
diagram: Id,
base: u128,
next: u128,
edges: Vec<Id>,
}
impl Builder {
fn new(name: &str, base: u128) -> Self {
let mut project = Project::empty();
project.id = Id::from_u128(base);
project.alias = name.into();
project.name = name.into();
let old = project.ensure_diagram(Layer::Sa);
let diagram = Id::from_u128(base + 1);
reidentify(&mut project, old, diagram).unwrap();
let d = project.diagrams.get_mut(&diagram).unwrap();
d.alias = format!("{name}_main");
d.name = format!("{name} main view");
d.projection = ProjectionKind::FunctionalDataflow;
Self {
project,
diagram,
base,
next: base + 100,
edges: Vec::new(),
}
}
fn stable(&mut self, old: Id) -> Id {
let id = Id::from_u128(self.next);
self.next += 1;
reidentify(&mut self.project, old, id).unwrap();
id
}
fn function(&mut self, name: &str, x: f32, y: f32) -> Id {
let old = self
.project
.add_element(Kind::Function, Layer::Sa, name, None)
.unwrap();
let id = self.stable(old);
let old = self.project.place(self.diagram, id, Position { x, y });
self.stable(old);
id
}
fn exchange(
&mut self,
name: &str,
source: Id,
target: Id,
source_port: PortChoice,
target_port: PortChoice,
) -> Id {
let id = Id::from_u128(self.base + 10_000 + self.edges.len() as u128);
let request = FunctionalExchangeRequest {
id,
alias: format!("flow_{:02}", self.edges.len()),
name: name.into(),
description: String::new(),
source,
target,
source_port,
target_port,
items: vec![],
};
let commands = ports::plan_functional_exchange(&self.project, &request).unwrap();
self.project = execute_batch(&self.project, &commands).unwrap();
self.edges.push(id);
id
}
fn port_pair(&self, edge: Id) -> (Id, Id) {
let RelationBody::FunctionalExchange {
source_port,
target_port,
..
} = self.project.relations[&edge].body
else {
unreachable!()
};
(source_port, target_port)
}
fn anchor(&mut self, owner: Id, port: Id, side: PortAnchorSide, offset: f32) {
let node = self.project.diagrams[&self.diagram]
.nodes
.values()
.find(|n| n.element == owner)
.unwrap()
.id;
self.project = prepare_command(
&self.project,
&Command::SetPortAnchor {
diagram: self.diagram,
node,
anchor: PortAnchor { port, side, offset },
},
)
.unwrap()
.project;
}
}
fn sentinel(edge: Id, x: f32, y: f32) -> EdgeRoute {
EdgeRoute {
edge,
source_attachment: None,
target_attachment: None,
waypoints: vec![Position { x, y }],
label: Some(EdgeLabel {
fraction: 0.4,
offset: Position { x: -30., y: -70. },
}),
}
}
fn workshop() -> Vec<Case> {
let original =
persistence::decode(include_bytes!("../../../examples/routing-workshop.json")).unwrap();
let diagram = original
.diagrams
.values()
.find(|d| d.alias == "sa_system_boundary_and_functions")
.unwrap()
.id;
assert_eq!(
original.diagrams[&diagram].projection,
ProjectionKind::Allocation
);
assert_eq!(original.diagrams[&diagram].nodes.len(), 12);
let container = original
.elements
.values()
.find(|e| e.alias == "rescue_coordination_system")
.unwrap()
.id;
let placement = original.diagrams[&diagram]
.nodes
.values()
.find(|n| n.element == container)
.unwrap()
.id;
let traces: Vec<_> = [
"sa_report_to_relay",
"sa_direct_advice",
"sa_incident_decision",
]
.iter()
.map(|alias| {
original
.relations
.values()
.find(|r| r.alias == *alias)
.unwrap()
.id
})
.collect();
let mut folded = original.clone();
assert!(!folded.diagrams[&diagram].nodes[&placement].collapsed);
folded
.diagrams
.get_mut(&diagram)
.unwrap()
.nodes
.get_mut(&placement)
.unwrap()
.collapsed = true;
let mut restored = folded.clone();
restored
.diagrams
.get_mut(&diagram)
.unwrap()
.nodes
.get_mut(&placement)
.unwrap()
.collapsed = false;
assert_eq!(restored, original);
vec![
Case {
name: "workshop_sa_original",
description: "Saved coastal rescue Allocation view, all source records retained.",
project: original,
diagram,
trace_edges: traces.clone(),
metadata: json!({"source":"examples/routing-workshop.json","view_alias":"sa_system_boundary_and_functions"}),
},
Case {
name: "workshop_sa_folded",
description: "Same saved Project with only rescue_coordination_system placement folded.",
project: folded,
diagram,
trace_edges: traces,
metadata: json!({"source":"examples/routing-workshop.json","folded_placement":placement,
"folded_element":container,"unfold_restores_original_exact":true}),
},
]
}
fn mixed() -> Vec<Case> {
let mut b = Builder::new("mixed_dispatch", 100_000);
let nodes: Vec<_> = [
("Sensor A", 40., 90.),
("Sensor B", 40., 510.),
("Ingest", 380., 250.),
("Validate", 720., 80.),
("Archive", 720., 510.),
("Correlate", 1060., 270.),
("Plan", 1400., 80.),
("Dispatch", 1740., 270.),
("Actuator", 1400., 680.),
("Operator", 1740., 680.),
]
.into_iter()
.map(|(name, x, y)| b.function(name, x, y))
.collect();
let e0 = b.exchange(
"Sensor A observations",
nodes[0],
nodes[2],
PortChoice::Create,
PortChoice::Create,
);
let ingest_input = b.port_pair(e0).1;
let e1 = b.exchange(
"Sensor B observations",
nodes[1],
nodes[2],
PortChoice::Create,
PortChoice::Reuse(ingest_input),
);
let e2 = b.exchange(
"Validate incoming observations",
nodes[2],
nodes[3],
PortChoice::Create,
PortChoice::Create,
);
let ingest_output = b.port_pair(e2).0;
let e3 = b.exchange(
"Archive incoming observations",
nodes[2],
nodes[4],
PortChoice::Reuse(ingest_output),
PortChoice::Create,
);
b.exchange(
"Validated observations",
nodes[3],
nodes[5],
PortChoice::Create,
PortChoice::Create,
);
b.exchange(
"Archived context",
nodes[4],
nodes[5],
PortChoice::Create,
PortChoice::Create,
);
b.exchange(
"Correlated situation",
nodes[5],
nodes[6],
PortChoice::Create,
PortChoice::Create,
);
b.exchange(
"Planned actions",
nodes[6],
nodes[7],
PortChoice::Create,
PortChoice::Create,
);
let e8 = b.exchange(
"Actuator dispatch",
nodes[7],
nodes[8],
PortChoice::Create,
PortChoice::Create,
);
let dispatch_output = b.port_pair(e8).0;
let e9 = b.exchange(
"Operator dispatch copy",
nodes[7],
nodes[9],
PortChoice::Reuse(dispatch_output),
PortChoice::Create,
);
b.exchange(
"Operator acknowledgement",
nodes[9],
nodes[8],
PortChoice::Create,
PortChoice::Create,
);
b.exchange(
"Archived actuator policy",
nodes[4],
nodes[8],
PortChoice::Create,
PortChoice::Create,
);
b.exchange(
"Sensor B archive stream",
nodes[1],
nodes[4],
PortChoice::Create,
PortChoice::Create,
);
let feedback = b.exchange(
"Sensor A sampling feedback",
nodes[6],
nodes[0],
PortChoice::Create,
PortChoice::Create,
);
let (feedback_output, feedback_input) = b.port_pair(feedback);
b.anchor(nodes[0], b.port_pair(e0).0, PortAnchorSide::Right, 0.25);
b.anchor(nodes[2], ingest_input, PortAnchorSide::Left, 0.3);
b.anchor(nodes[2], ingest_output, PortAnchorSide::Bottom, 0.6);
b.anchor(nodes[6], feedback_output, PortAnchorSide::Top, 0.5);
b.anchor(nodes[0], feedback_input, PortAnchorSide::Left, 0.6);
b.anchor(nodes[7], dispatch_output, PortAnchorSide::Right, 0.5);
b.project
.diagrams
.get_mut(&b.diagram)
.unwrap()
.routes
.push(sentinel(feedback, 300., -100.));
let secondary = Id::from_u128(b.base + 2);
let mut other = b.project.diagrams[&b.diagram].clone();
other.id = secondary;
other.alias = "mixed_dispatch_secondary".into();
other.name = "Feedback review sentinel".into();
other.description = "Unaffected other-view preservation sentinel".into();
other.nodes.clear();
other.routes = vec![sentinel(feedback, 460., 480.)];
b.project.diagrams.insert(secondary, other);
for (i, element) in [nodes[0], nodes[2], nodes[6]].into_iter().enumerate() {
let old = b.project.place(
secondary,
element,
Position {
x: 100. + i as f32 * 340.,
y: 100.,
},
);
let stable = b.stable(old);
b.project
.diagrams
.get_mut(&secondary)
.unwrap()
.nodes
.get_mut(&stable)
.unwrap()
.alias = format!("feedback_review_{i}");
}
assert_eq!(b.edges.len(), 14);
assert_eq!(b.project.diagrams[&b.diagram].nodes.len(), 10);
let originals = b.edges.clone();
let replacements: Vec<_> = originals.iter().rev().copied().collect();
let mut permuted = b.project.clone();
for (i, id) in originals.iter().enumerate() {
reidentify(&mut permuted, *id, Id::from_u128(1_000_000 + i as u128)).unwrap();
}
for (i, id) in replacements.iter().enumerate() {
reidentify(&mut permuted, Id::from_u128(1_000_000 + i as u128), *id).unwrap();
}
let mut restored = permuted.clone();
for (i, id) in replacements.iter().enumerate() {
reidentify(&mut restored, *id, Id::from_u128(1_000_000 + i as u128)).unwrap();
}
for (i, id) in originals.iter().enumerate() {
reidentify(&mut restored, Id::from_u128(1_000_000 + i as u128), *id).unwrap();
}
assert_eq!(restored, b.project);
let correspondence: BTreeMap<_, _> = originals.iter().copied().zip(replacements).collect();
let traces = vec![e1, e3, e9, feedback];
let permuted_traces = traces.iter().map(|id| correspondence[id]).collect();
let metadata = json!({"shared_input":ingest_input,"shared_outputs":[ingest_output,dispatch_output],
"sentinel_edge":feedback,"secondary_diagram":secondary,"fixed_anchor_records":6,
"authored_waypoint_count_main":1,"authored_label_count_main":1,
"declared_feedback_cycle":[nodes[0],nodes[2],nodes[3],nodes[5],nodes[6],nodes[0]]});
vec![
Case {
name: "mixed_dispatch",
description: "Ten-function sensing/decision/dispatch network, explicit port sharing and fixed anchors.",
project: b.project,
diagram: b.diagram,
trace_edges: traces,
metadata,
},
Case {
name: "mixed_dispatch_edge_ids_reversed",
description: "Same mixed Project with fixed reversed relation-ID permutation, remapped references.",
project: permuted,
diagram: b.diagram,
trace_edges: permuted_traces,
metadata: json!({"paired_case":"mixed_dispatch","edge_id_correspondence":correspondence,
"inverse_recovers_original_exact":true,"secondary_diagram":secondary}),
},
]
}
fn sparse() -> Case {
let mut b = Builder::new("subsystems_40_sparse", 200_000);
let names = [
"Sensing",
"Communications",
"Assessment",
"Planning",
"Delivery",
];
let stages = [
"Acquire",
"Decode",
"Validate",
"Enrich",
"Aggregate",
"Decide",
"Package",
"Publish",
];
let mut nodes = Vec::new();
for (group, name) in names.iter().enumerate() {
let row: Vec<_> = stages
.iter()
.enumerate()
.map(|(stage, label)| {
b.function(
&format!("{name} {label}"),
80. + stage as f32 * 300.,
80. + group as f32 * 240.,
)
})
.collect();
nodes.push(row);
}
for group in 0..5 {
for stage in 0..7 {
b.exchange(
&format!(
"{} {} to {}",
names[group],
stages[stage],
stages[stage + 1]
),
nodes[group][stage],
nodes[group][stage + 1],
PortChoice::Create,
PortChoice::Create,
);
}
}
let mut trace_edges = Vec::new();
for group in 0..5 {
for stage in [1, 3, 5, 7] {
let next = (group + 1) % 5;
let id = b.exchange(
&format!(
"{} {} handoff to {} {}",
names[group],
stages[stage],
names[next],
stages[stage - 1]
),
nodes[group][stage],
nodes[next][stage - 1],
PortChoice::Create,
PortChoice::Create,
);
if (group == 0 && stage == 1) || (group == 4 && stage == 7) {
trace_edges.push(id);
}
}
}
assert_eq!(b.edges.len(), 55);
assert_eq!(b.project.diagrams[&b.diagram].nodes.len(), 40);
Case {
name: "subsystems_40_sparse",
description: "Five eight-stage subsystem chains with twenty directed cross-subsystem handoffs; all ports distinct.",
project: b.project,
diagram: b.diagram,
trace_edges,
metadata: json!({"groups":names,"stages":stages,"chain_edges":35,"cross_edges":20,
"all_exchange_ports_distinct":true,"grid_columns":8,"grid_rows":5}),
}
}
fn main() -> Result<(), Box<dyn Error>> {
let mut args = std::env::args_os().skip(1);
let output = PathBuf::from(
args.next()
.ok_or("Usage: routing_eval_corpus NEW_DIRECTORY")?,
);
if args.next().is_some() {
return Err("Expected exactly one new output directory".into());
}
if output.exists() {
return Err("Output directory already exists; refusing overwrite".into());
}
let mut cases = workshop();
cases.extend(mixed());
cases.push(sparse());
let mut files = Vec::new();
let mut entries = Vec::new();
for case in cases {
validate_candidate(&case.project)?;
let file = format!("{}.json", case.name);
let traces: Vec<_> = case
.trace_edges
.iter()
.map(|id| {
let relation = &case.project.relations[id];
json!({"edge":id,"alias":relation.alias,"name":relation.name,
"source":relation.source(),"target":relation.target()})
})
.collect();
entries.push(json!({"name":case.name,"project_file":file,"diagram":case.diagram,
"description":case.description,"metadata":case.metadata,"trace_tasks":traces,
"source_counts":{"elements":case.project.elements.len(),"relations":case.project.relations.len(),
"views":case.project.diagrams.len(),"selected_view_placements":case.project.diagrams[&case.diagram].nodes.len(),
"selected_view_authored_routes":case.project.diagrams[&case.diagram].routes.len()}}));
files.push((file, serde_json::to_vec_pretty(&case.project)?));
}
let manifest = json!({"schema":1,"cases":entries,"definition_baseline":"f72aa2ca",
"construction":"Source-authored positions/topology, no Layout/projection/routing calls; validate all before writing.",
"invariants":"Routing is read-only. Preserve all semantic records, port identities, fixed anchors, authored routes/labels, fold flags, and other views. ID permutation correspondence is explicit. No quality or automatic endpoint-side expectation.",
"generation_in_routing_timing":false});
let manifest_bytes = serde_json::to_vec_pretty(&manifest)?;
fs::create_dir(&output)?;
for (name, bytes) in files {
fs::write(output.join(name), bytes)?;
}
fs::write(output.join("manifest.json"), manifest_bytes)?;
println!("{}", output.join("manifest.json").display());
Ok(())
}
+6 -60
View File
@@ -340,18 +340,6 @@ pub(crate) fn diagnostic(source: &str, error: Error) -> Diagnostic {
},
}
}
pub(crate) fn diagnostic_with_issue(
source: &str,
error: Error,
issue: Option<validation::Issue>,
) -> Diagnostic {
let mut result = diagnostic(source, error);
if let Some(issue) = issue {
result.object_id = issue.element;
result.field = issue.field;
}
result
}
fn reserved(word: &str) -> bool {
crate::reserved_alias(word)
|| declaration_kind(word).is_some()
@@ -634,7 +622,6 @@ fn parse_document(source: &str, limits: ParseLimits) -> Result<ParsedDocument, V
if tokens.get(1).is_some_and(|t| t.text(source) == "2") {
return v2_source::parse(source, tokens, limits);
}
let mut migration_issue = None;
Parser {
source,
tokens,
@@ -642,8 +629,8 @@ fn parse_document(source: &str, limits: ParseLimits) -> Result<ParsedDocument, V
value_depth: 0,
limits,
}
.document(&mut migration_issue)
.map_err(|e| vec![diagnostic_with_issue(source, e, migration_issue)])
.document()
.map_err(|e| vec![diagnostic(source, e)])
}
fn offset_for(source: &str, line: usize, column: usize) -> usize {
let mut offset = 0;
@@ -1462,10 +1449,7 @@ impl Parser<'_> {
},
}
}
fn document(
&mut self,
migration_issue: &mut Option<validation::Issue>,
) -> Result<ParsedDocument, Error> {
fn document(&mut self) -> Result<ParsedDocument, Error> {
let unit = self.unit(true, false)?;
let header = unit.header.expect("entry header");
let mut aliases = BTreeMap::from([(header.project.alias.clone(), header.project.id)]);
@@ -1492,14 +1476,9 @@ impl Parser<'_> {
.map(|(key, span)| ((d.id, key.clone()), span.start)),
);
}
let decoded = resolve_declarations(
header.project,
&unit.declarations,
&aliases,
self.limits,
migration_issue,
)
.map_err(|(_, error)| error)?;
let decoded =
resolve_declarations(header.project, &unit.declarations, &aliases, self.limits)
.map_err(|(_, error)| error)?;
Ok(ParsedDocument {
project: decoded.project,
migration: decoded.migration,
@@ -1703,7 +1682,6 @@ pub(crate) fn resolve_declarations(
declarations: &[Declaration],
aliases: &BTreeMap<String, Id>,
limits: ParseLimits,
migration_issue: &mut Option<validation::Issue>,
) -> Result<crate::persistence::DecodeResult, (usize, Error)> {
let legacy = project.schema_version == 3;
let mut legacy_elements = serde_json::Map::new();
@@ -2011,38 +1989,6 @@ pub(crate) fn resolve_declarations(
crate::persistence::migrate_v6(project, limits)
})
.map_err(|error| {
// These frozen admission checks formerly ran in final validation.
// Keep their diagnostic code and reference location after moving
// them before schema normalization.
if let crate::persistence::FileError::Model { issues, .. } = &error
&& let Some(issue) = issues.first()
&& let Some(code) = match issue.code.as_str() {
"diagram_layer" => Some("diagram_layer"),
"view_options" => Some("view_options"),
_ => None,
}
{
let index = issue
.element
.and_then(|id| declarations.iter().position(|d| d.id == id))
.unwrap_or(usize::MAX);
let offset = declarations.get(index).map_or(0, |d| {
issue
.field
.as_ref()
.and_then(|field| d.field_spans.get(field))
.map_or(d.offset, |span| span.start)
});
*migration_issue = Some(issue.clone());
return (
index,
Error {
code,
offset,
message: issue.message.clone(),
},
);
}
(
usize::MAX,
Error {
+3 -9
View File
@@ -69,8 +69,8 @@ impl Project4 {
}
crate::persistence::check_pre6_relations(self.relations.values())?;
crate::persistence::check_pre6_elements(self.elements.values())?;
let mut project = Project {
schema_version: self.schema_version,
Ok(Project {
schema_version: SCHEMA_VERSION,
id: self.id,
alias: self.alias,
name: self.name,
@@ -84,11 +84,7 @@ impl Project4 {
.into_iter()
.map(|(id, trace)| (id, trace.into()))
.collect(),
};
// Schema3's adapter also enters here, without calling `finish`.
crate::persistence::check_pre7_diagram_admission(&project)?;
project.schema_version = SCHEMA_VERSION;
Ok(project)
})
}
}
@@ -108,8 +104,6 @@ pub(crate) fn review_defaults(project: &Project, report: &mut MigrationReport) {
}
pub(crate) fn finish(mut project: Project, limits: ParseLimits) -> Result<DecodeResult, FileError> {
// Source4 enters here directly rather than through `Project4`.
crate::persistence::check_pre7_diagram_admission(&project)?;
crate::persistence::check_pre6_relations(project.relations.values())?;
crate::persistence::check_pre6_elements(project.elements.values())?;
project.schema_version = SCHEMA_VERSION;
-46
View File
@@ -341,50 +341,6 @@ pub(crate) fn check_pre6_baseline(baseline: &SemanticBaseline) -> Result<(), Fil
}
Ok(())
}
/// Frozen sources 3–6 admit opposite-layer placements and visibility references
/// only in Traceability. Check before migration discards the advertised version;
/// current ordinary projections may retain these records as latent presentation.
pub(crate) fn check_pre7_diagram_admission(project: &Project) -> Result<(), FileError> {
let mut issues = Vec::new();
for diagram in project.diagrams.values() {
let option_issues = if diagram.projection == ProjectionKind::Traceability {
crate::view_options::validate_options_cross_layer(project, &diagram.view_options)
} else {
crate::view_options::validate_options(project, diagram.layer, &diagram.view_options)
};
for issue in option_issues {
validation::error(
&mut issues,
diagram.id,
"view_options",
issue.field,
issue.message,
);
}
for node in diagram.nodes.values() {
if !project.elements.get(&node.element).is_some_and(|element| {
diagram.projection == ProjectionKind::Traceability
|| element.layer() == diagram.layer
}) {
validation::error(
&mut issues,
node.id,
"diagram_layer",
"element",
"Diagram nodes reference existing elements in the selected layer.",
);
}
}
}
if let Some(issue) = issues.into_iter().next() {
return Err(FileError::Model {
message: issue.message.clone(),
issues: vec![issue],
});
}
Ok(())
}
pub(crate) fn migrate_v5(
mut project: Project,
limits: crate::language::ParseLimits,
@@ -392,7 +348,6 @@ pub(crate) fn migrate_v5(
if project.schema_version != 5 {
return Err(FileError::Invalid("Expected schema_version 5.".into()));
}
check_pre7_diagram_admission(&project)?;
check_pre6_relations(project.relations.values())?;
check_pre6_elements(project.elements.values())?;
for baseline in project
@@ -465,7 +420,6 @@ pub(crate) fn migrate_v6(
if project.schema_version != 6 {
return Err(FileError::Invalid("Expected schema_version 6.".into()));
}
check_pre7_diagram_admission(&project)?;
reject_source7_fields(&serde_json::to_value(&project)?).map_err(FileError::Invalid)?;
limits.check_model(&project).map_err(FileError::Limit)?;
project.schema_version = SCHEMA_VERSION;
-1
View File
@@ -85,7 +85,6 @@ pub(crate) fn parents(p: &Project, d: &Diagram) -> BTreeMap<Id, Option<Id>> {
.filter(|n| {
p.elements.get(&n.element).is_some_and(|e| {
e.kind() != Kind::Port
&& (d.projection == ProjectionKind::Traceability || e.layer() == d.layer)
&& d.projection.includes(e.kind())
&& d.projection
.state_machine()
+76 -40
View File
@@ -6,7 +6,9 @@ use crate::{
RelationBody, RelationKind, Size,
};
mod attachments;
mod leads;
pub use attachments::{attachment_from_point, attachment_point, semantic_port_endpoint};
pub use leads::{LeadBlocker, LeadEndpoint, LeadIncompatibility, lead_incompatibilities};
use std::cmp::Ordering;
use std::collections::{BTreeMap, BTreeSet, BinaryHeap};
use std::fmt;
@@ -420,6 +422,73 @@ fn endpoints(
.collect()
}
fn effective_route<'a>(
project: &Project,
diagram: &Diagram,
edge: &ProjectedEdge,
stored: Option<&'a crate::EdgeRoute>,
) -> (
bool,
Option<&'a crate::EdgeRoute>,
[Option<EdgeAttachment>; 2],
) {
let promoted = projection::edge_endpoint_placements(project, diagram, edge.id)
.is_some_and(|(source, target)| source != edge.source || target != edge.target);
let route = if promoted { None } else { stored };
let attachments = [EdgeEnd::Source, EdgeEnd::Target].map(|end| {
semantic_port_endpoint(project, edge.id, end)
.is_none()
.then(|| route.and_then(|r| r.attachment(end)))
.flatten()
});
(promoted, route, attachments)
}
#[allow(clippy::too_many_arguments)]
fn edge_endpoints(
project: &Project,
diagram: &Diagram,
edge: &ProjectedEdge,
source: &ProjectedNode,
target: &ProjectedNode,
view: &ProjectedView,
attachments: [Option<EdgeAttachment>; 2],
) -> [Vec<Endpoint>; 2] {
[
endpoints(
project,
diagram,
source,
semantic_port_endpoint(project, edge.id, EdgeEnd::Source).or(edge.source_port),
view,
attachments[0],
),
endpoints(
project,
diagram,
target,
semantic_port_endpoint(project, edge.id, EdgeEnd::Target).or(edge.target_port),
view,
attachments[1],
),
]
}
fn allowed_endpoint_pair(edge: &ProjectedEdge, a: Endpoint, b: Endpoint, coincident: bool) -> bool {
!(edge.source == edge.target
&& edge.source_port.is_none()
&& edge.target_port.is_none()
&& a.point == b.point
&& a.stub == b.stub
&& !coincident)
}
fn lead_pair_clear(edge: &ProjectedEdge, a: Endpoint, b: Endpoint, obstacles: &[Rect]) -> bool {
!obstacles
.iter()
.any(|r| r.id != edge.source && r.crosses(a.point, a.stub))
&& !obstacles
.iter()
.any(|r| r.id != edge.target && r.crosses(b.point, b.stub))
}
fn ancestors(id: Id, nodes: &BTreeMap<Id, &ProjectedNode>, out: &mut BTreeSet<Id>) {
let mut current = nodes.get(&id).and_then(|n| n.visual_parent);
let mut seen = BTreeSet::new();
@@ -512,16 +581,8 @@ fn route_diagram_impl(
continue;
};
let stored = authored.get(&edge.id).copied();
let promoted = projection::edge_endpoint_placements(project, diagram, edge.id)
.is_some_and(|(source, target)| source != edge.source || target != edge.target);
let custom_suspended = promoted;
let route = if promoted { None } else { stored };
let attachments = [EdgeEnd::Source, EdgeEnd::Target].map(|end| {
semantic_port_endpoint(project, edge.id, end)
.is_none()
.then(|| route.and_then(|r| r.attachment(end)))
.flatten()
});
let (custom_suspended, route, attachments) =
effective_route(project, diagram, edge, stored);
let pins = route.map_or_else(Vec::new, |r| r.waypoints.clone());
let obstacles = edge_obstacles(edge, &nodes, &rectangles, &headers);
@@ -559,24 +620,11 @@ fn route_diagram_impl(
});
}
}
let ends = edge_endpoints(project, diagram, edge, source, target, view, attachments);
let (points, pin_lengths, failure) = route_edge(
edge,
&endpoints(
project,
diagram,
source,
semantic_port_endpoint(project, edge.id, EdgeEnd::Source).or(edge.source_port),
view,
attachments[0],
),
&endpoints(
project,
diagram,
target,
semantic_port_endpoint(project, edge.id, EdgeEnd::Target).or(edge.target_port),
view,
attachments[1],
),
&ends[0],
&ends[1],
&pins,
&obstacles,
&previous,
@@ -793,13 +841,7 @@ fn route_edge(
for &b in target_ends {
// A real self-relation must leave and return at distinct anchors.
// Reusing one anchor makes the cheapest path a degenerate stub.
if edge.source == edge.target
&& edge.source_port.is_none()
&& edge.target_port.is_none()
&& a.point == b.point
&& a.stub == b.stub
&& !coincident
{
if !allowed_endpoint_pair(edge, a, b, coincident) {
continue;
}
let estimate = manhattan(a.stub, pins.first().copied().unwrap_or(b.stub))
@@ -823,13 +865,7 @@ fn route_edge(
}) {
continue;
}
if obstacles
.iter()
.any(|r| r.id != edge.source && r.crosses(a.point, a.stub))
|| obstacles
.iter()
.any(|r| r.id != edge.target && r.crosses(b.point, b.stub))
{
if !lead_pair_clear(edge, a, b, obstacles) {
continue;
}
let mut points = vec![a.point, a.stub];
+173
View File
@@ -0,0 +1,173 @@
//! Necessary terminal-lead checks shared with experimental placement consumers.
use super::*;
#[derive(Debug, Clone, PartialEq)]
pub struct LeadBlocker {
pub id: Id,
/// The actual routing obstacle: clearance inflated, ancestor body excluded.
pub bounds: RoutingBounds,
}
#[derive(Debug, Clone, PartialEq)]
pub struct LeadEndpoint {
pub owner: Id,
pub tip: Position,
pub stub: Position,
pub blockers: Vec<LeadBlocker>,
/// Separate information only; not part of the router's early lead guard.
pub stub_containers: Vec<LeadBlocker>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct LeadIncompatibility {
pub edge: Id,
pub source: Vec<LeadEndpoint>,
pub target: Vec<LeadEndpoint>,
pub allowed_pair_count: usize,
pub lead_clear_pair_count: usize,
}
fn blocker(r: &Rect) -> LeadBlocker {
LeadBlocker {
id: r.id,
bounds: RoutingBounds {
position: pos(r.left, r.top),
size: Size {
width: r.right - r.left,
height: r.bottom - r.top,
},
},
}
}
fn describe(owner: Id, endpoint: Endpoint, obstacles: &[Rect]) -> LeadEndpoint {
LeadEndpoint {
owner,
tip: endpoint.point,
stub: endpoint.stub,
blockers: obstacles
.iter()
.filter(|r| r.id != owner && r.crosses(endpoint.point, endpoint.stub))
.map(blocker)
.collect(),
stub_containers: obstacles
.iter()
.filter(|r| r.contains(endpoint.stub))
.map(blocker)
.collect(),
}
}
/// Return only edges for which every allowed endpoint pair fails the router's
/// existing mandatory-lead guard. Empty output is NECESSARY ONLY: it does not
/// establish a clear route, clear pins, successful search, or label clearance.
/// Uses the supplied final projection; does not modify the Project or choose
/// different ports/attachments. Stub containment is reported separately and
/// does not change the lead guard or its historical pinned/degenerate behavior.
pub fn lead_incompatibilities(
project: &Project,
diagram: &Diagram,
view: &ProjectedView,
) -> Vec<LeadIncompatibility> {
let nodes: BTreeMap<_, _> = view.nodes.iter().map(|n| (n.id, n)).collect();
let rectangles: Vec<_> = nodes.values().map(|n| Rect::node(n)).collect();
let authored: BTreeMap<_, _> = diagram.routes.iter().map(|r| (r.edge, r)).collect();
let headers: BTreeMap<_, _> = nodes
.values()
.map(|node| {
(
node.id,
if diagram.projection.state_machine().is_some() && !node.collapsed {
projection::state_header_height(project, node.element)
} else {
HEADER
},
)
})
.collect();
let mut ordered: Vec<_> = view.edges.iter().collect();
ordered.sort_by_key(|e| e.id);
let mut result = Vec::new();
for edge in ordered {
let (Some(source), Some(target)) = (nodes.get(&edge.source), nodes.get(&edge.target))
else {
continue;
};
let (_, _, attachments) =
effective_route(project, diagram, edge, authored.get(&edge.id).copied());
let ends = edge_endpoints(project, diagram, edge, source, target, view, attachments);
let obstacles = edge_obstacles(edge, &nodes, &rectangles, &headers);
let coincident = edge.source == edge.target
&& ends[0].len() == 1
&& ends[1].len() == 1
&& ends[0][0].point == ends[1][0].point
&& ends[0][0].stub == ends[1][0].stub;
let mut allowed_pair_count = 0;
let mut lead_clear_pair_count = 0;
for &a in &ends[0] {
for &b in &ends[1] {
if !allowed_endpoint_pair(edge, a, b, coincident) {
continue;
}
allowed_pair_count += 1;
if lead_pair_clear(edge, a, b, &obstacles) {
lead_clear_pair_count += 1;
}
}
}
if lead_clear_pair_count == 0 {
result.push(LeadIncompatibility {
edge: edge.id,
source: ends[0]
.iter()
.map(|&e| describe(edge.source, e, &obstacles))
.collect(),
target: ends[1]
.iter()
.map(|&e| describe(edge.target, e, &obstacles))
.collect(),
allowed_pair_count,
lead_clear_pair_count,
});
}
}
result
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn coincident_selfloop_exception_and_stub_containment_remain_separate() {
let owner = Id::from_u128(1);
let edge = ProjectedEdge {
id: Id::from_u128(2),
source: owner,
target: owner,
source_port: None,
target_port: None,
trace: false,
oriented: true,
};
let endpoint = Endpoint {
point: pos(0., 0.),
stub: pos(18., 0.),
};
assert!(!allowed_endpoint_pair(&edge, endpoint, endpoint, false));
assert!(allowed_endpoint_pair(&edge, endpoint, endpoint, true));
let obstacles = [Rect {
id: owner,
left: -1.,
right: 20.,
top: -1.,
bottom: 1.,
}];
assert!(lead_pair_clear(&edge, endpoint, endpoint, &obstacles));
let diagnostic = describe(owner, endpoint, &obstacles);
assert!(diagnostic.blockers.is_empty());
assert_eq!(diagnostic.stub_containers.len(), 1);
// This diagnostic must not be folded into the historical early guard.
assert_eq!(diagnostic.stub_containers[0].id, owner);
}
}
-11
View File
@@ -589,17 +589,6 @@ fn apply_command(
.map(|d| d.id)
.ok_or_else(missing)?;
if source != *diagram {
let destination = &p.diagrams[diagram];
let element = p.diagrams[&source].nodes[node].element;
if destination.projection != ProjectionKind::Traceability
&& p.elements
.get(&element)
.is_none_or(|e| e.layer() != destination.layer)
{
return Err(ModelError(
"Move the placement to a view in its layer or a traceability view.".into(),
));
}
let placement = p
.diagrams
.get_mut(&source)
+10 -20
View File
@@ -573,26 +573,16 @@ fn compile_once(
span,
}),
};
let mut migration_issue = None;
let decoded = language::resolve_declarations(
header.project,
&declarations,
&aliases,
limits.model,
&mut migration_issue,
)
.map_err(|(index, error)| {
let path = declaration_paths.get(index).unwrap_or(&bundle.entry);
vec![SourceDiagnostic {
path: path.clone(),
diagnostic: language::diagnostic_with_issue(
&bundle.files[path],
error,
migration_issue,
),
related: Vec::new(),
}]
})?;
let decoded =
language::resolve_declarations(header.project, &declarations, &aliases, limits.model)
.map_err(|(index, error)| {
let path = declaration_paths.get(index).unwrap_or(&bundle.entry);
vec![SourceDiagnostic {
path: path.clone(),
diagnostic: language::diagnostic(&bundle.files[path], error),
related: Vec::new(),
}]
})?;
let diagnostics = model_diagnostics(bundle, &index, &decoded.project);
let errors: Vec<_> = diagnostics
.iter()
+7 -12
View File
@@ -620,14 +620,11 @@ pub fn validate(p: &Project) -> Vec<Issue> {
text(&mut out, d.id, "name", &d.name);
text(&mut out, d.id, "description", &d.description);
crate::scenario_layout::validate_diagram(p, d, &mut out);
// Ordinary projections are reversible filters over the same authored view.
// Retain opposite-layer visibility choices when leaving Traceability.
let option_issues =
if d.projection.state_machine().is_none() && d.projection.scenario().is_none() {
crate::view_options::validate_options_cross_layer(p, &d.view_options)
} else {
crate::view_options::validate_options(p, d.layer, &d.view_options)
};
let option_issues = if d.projection == ProjectionKind::Traceability {
crate::view_options::validate_options_cross_layer(p, &d.view_options)
} else {
crate::view_options::validate_options(p, d.layer, &d.view_options)
};
for issue in option_issues {
error(&mut out, d.id, "view_options", issue.field, issue.message);
}
@@ -801,10 +798,8 @@ pub fn validate(p: &Project) -> Vec<Issue> {
);
}
match p.elements.get(&n.element) {
Some(e)
if (d.projection.state_machine().is_none()
&& d.projection.scenario().is_none())
|| e.layer() == d.layer => {}
Some(e) if d.projection == ProjectionKind::Traceability || e.layer() == d.layer => {
}
_ => error(
&mut out,
n.id,
+1 -3
View File
@@ -218,9 +218,7 @@ pub fn validate_options(p: &Project, layer: Layer, options: &ViewOptions) -> Vec
validate_options_in(p, Some(layer), options)
}
/// Ordinary saved views retain references from either architecture layer so
/// projection changes preserve authored visibility choices. Projection decides
/// which layer is visible; typed State/Scenario views use layer-scoped validation.
/// Traceability views admit references from either architecture layer.
pub fn validate_options_cross_layer(p: &Project, options: &ViewOptions) -> Vec<OptionError> {
validate_options_in(p, None, options)
}
+238
View File
@@ -0,0 +1,238 @@
use oa_core::{projection, routing, *};
struct Fixture {
p: Project,
d: Id,
edge: Id,
source: Id,
target: Id,
blocker: Id,
}
fn fixture(ports: bool, gap: f32) -> Fixture {
let mut p = Project::empty();
let kind = if ports {
Kind::Function
} else {
Kind::DataType
};
let a = p.add_element(kind, Layer::Sa, "Source", None).unwrap();
let b = p.add_element(kind, Layer::Sa, "Target", None).unwrap();
let c = p.add_element(kind, Layer::Sa, "Blocker", None).unwrap();
for element in p.elements.values_mut() {
if let Some(SharedBody::DataType { definition, .. }) = element.shared_mut() {
*definition = DataTypeDefinition::Record { supertype: None };
}
}
let edge = p.relate(
if ports {
RelationKind::FunctionalExchange
} else {
RelationKind::DataAssociation
},
"Flow",
a,
b,
);
let d = p.ensure_diagram(Layer::Sa);
let source = p.place(d, a, Position { x: 0., y: 0. });
let target = p.place(
d,
b,
Position {
x: 500.,
y: 180. + gap,
},
);
let blocker = p.place(d, c, Position { x: 500., y: 20. });
for n in p.diagrams.get_mut(&d).unwrap().nodes.values_mut() {
n.size = Size {
width: 224.,
height: 160.,
};
}
if ports {
for (owner, end, side) in [
(source, EdgeEnd::Source, PortAnchorSide::Right),
(target, EdgeEnd::Target, PortAnchorSide::Top),
] {
let port = routing::semantic_port_endpoint(&p, edge, end).unwrap();
p.diagrams
.get_mut(&d)
.unwrap()
.nodes
.get_mut(&owner)
.unwrap()
.port_anchors
.push(PortAnchor {
port,
side,
offset: 0.5,
});
}
}
validate_candidate(&p).unwrap();
Fixture {
p,
d,
edge,
source,
target,
blocker,
}
}
fn report(f: &Fixture) -> Vec<routing::LeadIncompatibility> {
routing::lead_incompatibilities(
&f.p,
&f.p.diagrams[&f.d],
&projection::project_view(&f.p, f.d),
)
}
fn attachment_route(edge: Id) -> EdgeRoute {
EdgeRoute {
edge,
source_attachment: Some(EdgeAttachment {
side: PortAnchorSide::Top,
offset: 0.25,
}),
target_attachment: Some(EdgeAttachment {
side: PortAnchorSide::Bottom,
offset: 0.75,
}),
waypoints: vec![Position { x: 300., y: 400. }],
label: None,
}
}
#[test]
fn exact_twenty_gap_is_blocked_thirty_plus_clears_without_mutation() {
let f = fixture(true, 20.);
let original = f.p.clone();
let r = report(&f);
assert_eq!(r.len(), 1);
assert_eq!(
(
r[0].edge,
r[0].allowed_pair_count,
r[0].lead_clear_pair_count
),
(f.edge, 1, 0)
);
assert_eq!(r[0].source[0].owner, f.source);
let end = &r[0].target[0];
assert_eq!(end.owner, f.target);
assert_eq!(end.tip, Position { x: 612., y: 200. });
assert_eq!(end.stub, Position { x: 612., y: 182. });
assert_eq!(end.blockers.len(), 1);
assert_eq!(end.blockers[0].id, f.blocker);
assert_eq!(
end.blockers[0].bounds,
routing::RoutingBounds {
position: Position { x: 488., y: 8. },
size: Size {
width: 248.,
height: 184.
},
}
);
assert_eq!(end.stub_containers, end.blockers);
assert_eq!(f.p, original);
assert!(report(&fixture(true, 30.)).is_empty());
assert!(report(&fixture(true, 31.)).is_empty());
}
#[test]
fn semantic_manual_ports_override_dormant_route_attachments_and_pins() {
let mut f = fixture(true, 20.);
let expected = report(&f);
f.p.diagrams
.get_mut(&f.d)
.unwrap()
.routes
.push(attachment_route(f.edge));
assert_eq!(report(&f), expected);
let view = projection::project_view(&f.p, f.d);
let routed = routing::route_diagram(&f.p, &f.p.diagrams[&f.d], &view);
assert_eq!(
routed.edges[&f.edge].points.last(),
Some(&expected[0].target[0].tip)
);
assert!(
routed.edges[&f.edge]
.warnings
.contains(&routing::RouteWarning::NoClearPath)
);
}
#[test]
fn unconstrained_nonport_alternative_is_not_hard_blocked() {
let mut f = fixture(false, 20.);
assert!(report(&f).is_empty());
let mut route = attachment_route(f.edge);
route.source_attachment = Some(EdgeAttachment {
side: PortAnchorSide::Right,
offset: 0.5,
});
route.target_attachment = Some(EdgeAttachment {
side: PortAnchorSide::Top,
offset: 0.5,
});
f.p.diagrams.get_mut(&f.d).unwrap().routes.push(route);
let r = report(&f);
assert_eq!(r.len(), 1);
assert_eq!(r[0].allowed_pair_count, 1);
assert_eq!(r[0].target[0].blockers[0].id, f.blocker);
}
#[test]
fn nested_endpoint_body_is_traversable_and_promoted_attachments_are_suspended() {
let mut f = fixture(false, 20.);
// DataType containment is not required for this routing-only view witness:
// construct the supplied projected forest explicitly, retaining real owners.
let mut view = projection::project_view(&f.p, f.d);
let parent = Id::new_v4();
let source = view.nodes.iter_mut().find(|n| n.id == f.source).unwrap();
source.visual_parent = Some(parent);
source.position = Position { x: 40., y: 100. };
let element = source.element;
view.nodes.push(projection::ProjectedNode {
id: parent,
element,
position: Position { x: 0., y: 0. },
size: Size {
width: 300.,
height: 300.,
},
visual_parent: None,
depth: 0,
collapsed: false,
allocation_summary: false,
});
let mut route = attachment_route(f.edge);
route.source_attachment = Some(EdgeAttachment {
side: PortAnchorSide::Right,
offset: 0.5,
});
route.target_attachment = Some(EdgeAttachment {
side: PortAnchorSide::Top,
offset: 0.5,
});
f.p.diagrams.get_mut(&f.d).unwrap().routes.push(route);
let r = routing::lead_incompatibilities(&f.p, &f.p.diagrams[&f.d], &view);
assert_eq!(r.len(), 1);
assert!(
r[0].source[0].blockers.is_empty(),
"Ancestor body must not block source lead"
);
// Promotion changes actual projected owner: custom attachments no longer apply.
view.edges
.iter_mut()
.find(|e| e.id == f.edge)
.unwrap()
.source = parent;
let with_dormant = routing::lead_incompatibilities(&f.p, &f.p.diagrams[&f.d], &view);
f.p.diagrams.get_mut(&f.d).unwrap().routes.clear();
assert_eq!(
with_dormant,
routing::lead_incompatibilities(&f.p, &f.p.diagrams[&f.d], &view)
);
}
@@ -1,205 +0,0 @@
//! Current latent presentation must not broaden frozen source/JSON admission.
use oa_core::{view_options::*, *};
fn legacy_json(project: &Project, version: u32) -> Vec<u8> {
let mut value = serde_json::to_value(project).unwrap();
value["schema_version"] = version.into();
for diagram in value["diagrams"].as_object_mut().unwrap().values_mut() {
let fields = diagram.as_object_mut().unwrap();
fields.remove("layout");
if version < 6 {
fields.remove("view_options");
}
}
serde_json::to_vec(&value).unwrap()
}
fn model_issue(error: persistence::FileError) -> validation::Issue {
match error {
persistence::FileError::Model { mut issues, .. } => issues.remove(0),
other => panic!("Expected a located model issue, got {other:?}"),
}
}
#[test]
fn frozen_3_through_6_reject_cross_layer_nodes_but_keep_mixed_traceability() {
let node_id = Id::parse_str("00000000-0000-4000-8000-000000000004").unwrap();
for version in 3..=6 {
let source = format!(
"arcadia {version}; project compat \"Compatibility\" id \"00000000-0000-4000-8000-000000000001\" {{\n\
sa_function work \"Counterpart\" id \"00000000-0000-4000-8000-000000000002\" {{}}\n\
diagram view \"OA concern\" id \"00000000-0000-4000-8000-000000000003\" {{ layer Oa; projection Traceability; }}\n\
node occurrence id \"00000000-0000-4000-8000-000000000004\" {{ diagram @view; element @work; }}\n}}"
);
let mixed = language::parse(&source).unwrap();
assert_eq!(
persistence::decode(&legacy_json(&mixed, version)).unwrap(),
mixed
);
let ordinary = source.replace("projection Traceability", "projection Overview");
let same_layer = ordinary.replace("layer Oa", "layer Sa");
let ordinary_project = language::parse(&same_layer).unwrap();
assert_eq!(
persistence::decode(&legacy_json(&ordinary_project, version)).unwrap(),
ordinary_project
);
let errors = language::parse(&ordinary).unwrap_err();
// Source3 already reported migration errors with the offending location.
let code = if version == 3 {
"migration_error"
} else {
"diagram_layer"
};
assert!(
errors.iter().any(|error| error.code == code
&& error.line == 4
&& error.message.contains("selected layer")),
"{version}: {errors:?}"
);
let mut latent = mixed.clone();
let diagram = latent.diagrams.values_mut().next().unwrap();
diagram.projection = ProjectionKind::Overview;
let node = diagram.nodes.values().next().unwrap().id;
assert_eq!(node, node_id);
if version >= 4 {
let diagnostic = errors
.iter()
.find(|error| error.code == "diagram_layer")
.unwrap();
assert_eq!(diagnostic.object_id, Some(node_id));
assert_eq!(diagnostic.field.as_deref(), Some("element"));
let bundle = source_project::SourceBundle {
entry: "model.arcadia".into(),
files: std::collections::BTreeMap::from([("model.arcadia".into(), ordinary)]),
};
let errors =
source_project::compile_bundle(&bundle, source_project::ProjectLimits::default())
.unwrap_err();
let diagnostic = &errors
.iter()
.find(|error| error.diagnostic.code == "diagram_layer")
.unwrap()
.diagnostic;
assert_eq!(diagnostic.object_id, Some(node_id));
assert_eq!(diagnostic.field.as_deref(), Some("element"));
}
let issue = model_issue(persistence::decode(&legacy_json(&latent, version)).unwrap_err());
assert_eq!(issue.code, "diagram_layer");
assert_eq!(issue.element, Some(node));
assert_eq!(issue.field.as_deref(), Some("element"));
// The exact same record is valid under the current presentation contract.
assert_eq!(language::parse(&language::format(&latent)).unwrap(), latent);
assert_eq!(
persistence::decode(&serde_json::to_vec(&latent).unwrap()).unwrap(),
latent
);
let mut dangling = mixed;
dangling
.diagrams
.values_mut()
.next()
.unwrap()
.nodes
.values_mut()
.next()
.unwrap()
.element = Id::parse_str("00000000-0000-4000-8000-000000000005").unwrap();
let issue = model_issue(persistence::decode(&legacy_json(&dangling, version)).unwrap_err());
assert_eq!(issue.code, "diagram_layer");
assert_eq!(issue.element, Some(node));
assert_eq!(issue.field.as_deref(), Some("element"));
}
}
#[test]
fn frozen_6_checks_every_layer_sensitive_saved_selector_before_upgrade() {
let mut project = Project::empty();
let a = project
.add_element(Kind::Activity, Layer::Oa, "A", None)
.unwrap();
let b = project
.add_element(Kind::Activity, Layer::Oa, "B", None)
.unwrap();
let edge = project.relate(RelationKind::Interaction, "Interaction", a, b);
let category = project
.add_element(Kind::ExchangeCategory, Layer::Oa, "Work category", None)
.unwrap();
let participant_category = project
.add_element(
Kind::ExchangeCategory,
Layer::Oa,
"Participant category",
None,
)
.unwrap();
*project
.elements
.get_mut(&participant_category)
.unwrap()
.shared_mut()
.unwrap() = SharedBody::ExchangeCategory {
family: ExchangeFamily::Participant,
exchanges: vec![],
};
let context = project
.add_element(Kind::Process, Layer::Oa, "Process", None)
.unwrap();
let diagram = project.ensure_diagram(Layer::Sa);
for (field, options) in [
(
"hidden_edges",
ViewOptions {
hidden_edges: vec![edge],
..Default::default()
},
),
(
"work_categories",
ViewOptions {
work_categories: CategorySelection::Any(vec![category]),
..Default::default()
},
),
(
"participant_categories",
ViewOptions {
participant_categories: CategorySelection::Any(vec![participant_category]),
..Default::default()
},
),
(
"work_contexts",
ViewOptions {
work_contexts: ContextSelection::Any(vec![context]),
..Default::default()
},
),
] {
project.diagrams.get_mut(&diagram).unwrap().view_options = options;
let issue = model_issue(persistence::decode(&legacy_json(&project, 6)).unwrap_err());
assert_eq!(issue.code, "view_options");
assert_eq!(issue.element, Some(diagram));
assert_eq!(issue.field.as_deref(), Some(field));
let current_source = language::format(&project);
assert_eq!(language::parse(&current_source).unwrap(), project);
let source6 = current_source.replacen("arcadia 7;", "arcadia 6;", 1);
let errors = language::parse(&source6).unwrap_err();
let diagnostic = errors
.iter()
.find(|error| error.code == "view_options")
.unwrap();
assert_eq!(diagnostic.object_id, Some(diagram));
assert_eq!(diagnostic.field.as_deref(), Some(field));
project.diagrams.get_mut(&diagram).unwrap().projection = ProjectionKind::Traceability;
assert_eq!(
persistence::decode(&legacy_json(&project, 6)).unwrap(),
project
);
let source6 = language::format(&project).replacen("arcadia 7;", "arcadia 6;", 1);
assert_eq!(language::parse(&source6).unwrap(), project);
project.diagrams.get_mut(&diagram).unwrap().projection = ProjectionKind::Overview;
}
}
@@ -1,304 +0,0 @@
//! Populated presentation survives mixed-layer projection switches.
//! Complements projection_switching.rs without external fixture dependencies.
use oa_core::{
projection::*, routing::route_diagram, source_project::*, view_options::ViewIndex, *,
};
struct Populated {
project: Project,
diagram: Id,
groups: [Id; 2],
children: [[Id; 2]; 2],
edges: [Id; 2],
ports: [Id; 2],
trace: Id,
}
fn xy(x: f32, y: f32) -> Position {
Position { x, y }
}
fn apply(project: &mut Project, command: Command) {
*project = prepare_command(project, &command).unwrap().project;
}
fn fixture(layer: Layer, collapsed: bool) -> Populated {
let mut p = Project::empty();
let oa_group = p
.add_element(Kind::Activity, Layer::Oa, "Operational response", None)
.unwrap();
let oa_send = p
.add_element(Kind::Activity, Layer::Oa, "Request repair", Some(oa_group))
.unwrap();
let oa_receive = p
.add_element(Kind::Activity, Layer::Oa, "Perform repair", Some(oa_group))
.unwrap();
let sa_group = p
.add_element(Kind::Function, Layer::Sa, "System response", None)
.unwrap();
let sa_send = p
.add_element(Kind::Function, Layer::Sa, "Dispatch repair", Some(sa_group))
.unwrap();
let sa_receive = p
.add_element(Kind::Function, Layer::Sa, "Execute repair", Some(sa_group))
.unwrap();
let oa_edge = p.relate(
RelationKind::Interaction,
"Repair request",
oa_send,
oa_receive,
);
let sa_edge = p.relate(
RelationKind::FunctionalExchange,
"Repair dispatch",
sa_send,
sa_receive,
);
let RelationBody::FunctionalExchange {
source_port,
target_port,
..
} = p.relations[&sa_edge].body
else {
unreachable!()
};
let trace = Id::new_v4();
let refinement = Trace {
id: trace,
alias: p.fresh_alias("dispatch_refinement", "trace", trace),
name: "Dispatch refines repair request".into(),
description: "Keep this cross-layer trace and its authored route.".into(),
oa: oa_send,
sa: sa_send,
kind: TraceKind::Refinement,
baseline: Some(TraceBaseline {
oa: p.properties(oa_send).unwrap(),
sa: p.properties(sa_send).unwrap(),
semantic: None,
}),
};
apply(&mut p, Command::CreateTrace(refinement));
let d = p.ensure_diagram(layer);
p.diagrams.get_mut(&d).unwrap().projection = ProjectionKind::Traceability;
let oa_group_node = p.place(d, oa_group, xy(0., 0.));
let oa_send_node = p.place(d, oa_send, xy(40., 120.));
let oa_receive_node = p.place(d, oa_receive, xy(360., 120.));
let sa_group_node = p.place(d, sa_group, xy(0., 500.));
let sa_send_node = p.place(d, sa_send, xy(40., 620.));
let sa_receive_node = p.place(d, sa_receive, xy(360., 620.));
for (node, port, side, offset) in [
(sa_send_node, source_port, PortAnchorSide::Bottom, 0.25),
(sa_receive_node, target_port, PortAnchorSide::Top, 0.75),
] {
apply(
&mut p,
Command::SetPortAnchor {
diagram: d,
node,
anchor: PortAnchor { port, side, offset },
},
);
}
for (edge, y, port_backed) in [
(oa_edge, 260., false),
(sa_edge, 860., true),
(trace, 420., false),
] {
apply(
&mut p,
Command::SetEdgeRoute {
diagram: d,
route: EdgeRoute {
edge,
source_attachment: (!port_backed).then_some(EdgeAttachment {
side: PortAnchorSide::Right,
offset: 0.3,
}),
target_attachment: (!port_backed).then_some(EdgeAttachment {
side: PortAnchorSide::Left,
offset: 0.7,
}),
waypoints: vec![xy(680., y), xy(740., y)],
label: Some(EdgeLabel {
fraction: 0.4,
offset: xy(13., -27.),
}),
},
},
);
}
for node in [oa_group_node, sa_group_node] {
let mut placement = p.diagrams[&d].nodes[&node].clone();
placement.collapsed = collapsed;
placement.size = Size {
width: 310.,
height: 170.,
};
apply(
&mut p,
Command::EditNode {
diagram: d,
original: node,
node: placement,
},
);
}
validate_candidate(&p).unwrap();
Populated {
project: p,
diagram: d,
groups: [oa_group_node, sa_group_node],
children: [
[oa_send_node, oa_receive_node],
[sa_send_node, sa_receive_node],
],
edges: [oa_edge, sa_edge],
ports: [source_port, target_port],
trace,
}
}
fn assert_round_trips(p: &Project) {
assert_eq!(language::parse(&language::format(p)).unwrap(), *p);
assert_eq!(
persistence::decode(&serde_json::to_vec(p).unwrap()).unwrap(),
*p
);
assert_eq!(
compile_bundle(&format_bundle(p), ProjectLimits::default())
.unwrap()
.decoded
.project,
*p
);
}
fn assert_layer_only(p: &Project, d: Id, view: &ProjectedView) {
let layer = p.diagrams[&d].layer;
for node in &view.nodes {
assert_eq!(p.elements[&node.element].layer(), layer);
if let Some(parent) = node.visual_parent {
let parent = view.nodes.iter().find(|n| n.id == parent).unwrap();
assert_eq!(p.elements[&parent.element].layer(), layer);
}
}
for port in &view.ports {
assert_eq!(p.elements[&port.element].layer(), layer);
let owner = view.nodes.iter().find(|n| n.id == port.owner).unwrap();
assert_eq!(p.elements[&owner.element].layer(), layer);
}
for edge in &view.edges {
assert!(
!edge.trace,
"cross-layer trace leaked into ordinary projection"
);
assert_eq!(p.layer_of(edge.id), Some(layer));
for endpoint in [edge.source, edge.target] {
let owner = view.nodes.iter().find(|n| n.id == endpoint).unwrap();
assert_eq!(p.elements[&owner.element].layer(), layer);
}
for port in [edge.source_port, edge.target_port].into_iter().flatten() {
assert_eq!(p.elements[&port].layer(), layer);
}
}
}
#[test]
fn populated_mixed_projection_switch_conserves_authorship_and_restores_geometry() {
for layer in [Layer::Oa, Layer::Sa] {
for collapsed in [false, true] {
let f = fixture(layer, collapsed);
let baseline = &f.project;
let d = f.diagram;
let before_view = project_view(baseline, d);
let before_routes = route_diagram(baseline, &baseline.diagrams[&d], &before_view);
// Guard against a vacuous fixture: Traceability deliberately flattens parents.
assert_eq!(before_view.nodes.len(), 6);
assert!(before_view.nodes.iter().all(|n| n.visual_parent.is_none()));
for id in [f.edges[0], f.edges[1], f.trace] {
assert!(before_view.edges.iter().any(|e| e.id == id));
let route = baseline.diagrams[&d]
.routes
.iter()
.find(|r| r.edge == id)
.unwrap();
assert_eq!(route.waypoints.len(), 2);
assert!(route.label.is_some());
}
for port in f.ports {
assert!(before_view.ports.iter().any(|p| p.element == port));
}
assert_round_trips(baseline);
for projection in [
ProjectionKind::Overview,
ProjectionKind::Decomposition,
ProjectionKind::Allocation,
ProjectionKind::FunctionalDataflow,
ProjectionKind::Capabilities,
] {
let mut expected = baseline.clone();
expected.diagrams.get_mut(&d).unwrap().projection = projection;
let mut session = Session::new(baseline.clone());
session
.execute(Command::EditDiagram(d, expected.diagrams[&d].clone()))
.unwrap();
assert_eq!(session.project(), &expected);
assert_round_trips(session.project());
let view = project_view(session.project(), d);
assert_eq!(
view,
project_view_with_index(&expected, d, &ViewIndex::new(&expected))
);
assert_layer_only(session.project(), d, &view);
if projection == ProjectionKind::Overview {
let own = if layer == Layer::Oa { 0 } else { 1 };
assert!(view.nodes.iter().any(|n| n.id == f.groups[own]));
for child in f.children[own] {
if collapsed {
assert!(!view.nodes.iter().any(|n| n.id == child));
} else {
assert_eq!(
view.nodes
.iter()
.find(|n| n.id == child)
.unwrap()
.visual_parent,
Some(f.groups[own])
);
}
}
if !collapsed {
assert!(view.edges.iter().any(|e| e.id == f.edges[own]));
if layer == Layer::Sa {
assert_eq!(view.ports.len(), 2);
}
}
}
session.undo();
assert_eq!(session.project(), baseline);
assert!(
!session.can_undo(),
"projection change must be one model history step"
);
assert_round_trips(session.project());
session.redo();
assert_eq!(session.project(), &expected);
session
.execute(Command::EditDiagram(d, baseline.diagrams[&d].clone()))
.unwrap();
assert_eq!(session.project(), baseline);
let restored = project_view(session.project(), d);
assert_eq!(restored, before_view);
assert_eq!(
route_diagram(
session.project(),
&session.project().diagrams[&d],
&restored
),
before_routes
);
assert_round_trips(session.project());
}
}
}
}
@@ -1,156 +0,0 @@
use oa_core::{source_project::*, view_options::*, *};
fn fixture(layer: Layer) -> (Project, Id, Id, Id, Id) {
let mut p = Project::empty();
let oa = p
.add_element(Kind::Activity, Layer::Oa, "Assess alarm", None)
.unwrap();
let oa2 = p
.add_element(Kind::Activity, Layer::Oa, "Respond", None)
.unwrap();
let sa = p
.add_element(Kind::Function, Layer::Sa, "Assess alarm", None)
.unwrap();
let edge = p.relate(RelationKind::Interaction, "Alarm", oa, oa2);
let category = p
.add_element(Kind::ExchangeCategory, Layer::Oa, "Alarm data", None)
.unwrap();
*p.elements.get_mut(&category).unwrap().shared_mut().unwrap() = SharedBody::ExchangeCategory {
family: ExchangeFamily::Work,
exchanges: vec![edge],
};
let context = p
.add_element(Kind::Process, Layer::Oa, "Alarm handling", None)
.unwrap();
let d = p.ensure_diagram(layer);
p.diagrams.get_mut(&d).unwrap().projection = ProjectionKind::Traceability;
for (i, element) in [oa, oa2, sa].into_iter().enumerate() {
p.place(
d,
element,
Position {
x: 300. * i as f32,
y: 40.,
},
);
}
p.diagrams.get_mut(&d).unwrap().view_options = ViewOptions {
hidden_edges: vec![edge],
work_categories: CategorySelection::Any(vec![category]),
work_contexts: ContextSelection::Any(vec![context]),
..Default::default()
};
validate_candidate(&p).unwrap();
(p, d, oa, sa, category)
}
#[test]
fn projection_switching_preserves_complete_model_history_and_all_serializations() {
for layer in [Layer::Oa, Layer::Sa] {
let (baseline, diagram, _, _, _) = fixture(layer);
for projection in [
ProjectionKind::Overview,
ProjectionKind::Decomposition,
ProjectionKind::Allocation,
ProjectionKind::FunctionalDataflow,
ProjectionKind::Capabilities,
] {
let mut expected = baseline.clone();
expected.diagrams.get_mut(&diagram).unwrap().projection = projection;
let mut session = Session::new(baseline.clone());
session
.execute(Command::EditDiagram(
diagram,
expected.diagrams[&diagram].clone(),
))
.unwrap();
assert_eq!(session.project(), &expected);
let view = projection::project_view(session.project(), diagram);
assert!(
view.nodes
.iter()
.all(|n| expected.elements[&n.element].layer() == layer)
);
assert_eq!(
view,
projection::project_view_with_index(&expected, diagram, &ViewIndex::new(&expected))
);
let source = language::format(&expected);
assert_eq!(language::parse(&source).unwrap(), expected);
assert_eq!(
persistence::decode(&serde_json::to_vec(&expected).unwrap()).unwrap(),
expected
);
let bundle = format_bundle(&expected);
assert_eq!(
compile_bundle(&bundle, ProjectLimits::default())
.unwrap()
.decoded
.project,
expected
);
session.undo();
assert_eq!(session.project(), &baseline);
assert!(!session.can_undo());
session.redo();
assert_eq!(session.project(), &expected);
session
.execute(Command::EditDiagram(
diagram,
baseline.diagrams[&diagram].clone(),
))
.unwrap();
assert_eq!(session.project(), &baseline);
assert_eq!(
projection::project_view(session.project(), diagram),
projection::project_view(&baseline, diagram)
);
}
}
}
#[test]
fn projection_switching_does_not_admit_missing_elements_or_invalid_filter_kinds() {
let (mut p, d, oa, _, category) = fixture(Layer::Sa);
p.diagrams.get_mut(&d).unwrap().projection = ProjectionKind::Allocation;
validate_candidate(&p).unwrap();
let valid = p.clone();
let node = p.diagrams[&d]
.nodes
.values()
.find(|n| n.element == oa)
.unwrap()
.id;
p.diagrams
.get_mut(&d)
.unwrap()
.nodes
.get_mut(&node)
.unwrap()
.element = Id::new_v4();
assert!(validate_candidate(&p).is_err());
p = valid.clone();
p.diagrams.get_mut(&d).unwrap().view_options.work_categories = CategorySelection::Any(vec![oa]);
assert!(validate_candidate(&p).is_err());
p = valid;
p.elements.remove(&category);
assert!(validate_candidate(&p).is_err());
}
#[test]
fn projection_switching_keeps_typed_state_membership_strict() {
let (mut p, d, _, _, _) = fixture(Layer::Sa);
let system = p
.add_element(Kind::System, Layer::Sa, "System", None)
.unwrap();
let machine = p
.add_element(Kind::StateMachine, Layer::Sa, "Modes", Some(system))
.unwrap();
p.diagrams.get_mut(&d).unwrap().projection = ProjectionKind::StateMachine { machine };
assert!(
validation::validate(&p)
.iter()
.any(|i| i.code == "state_diagram_member")
);
assert!(validate_candidate(&p).is_err());
}
+1 -12
View File
@@ -67,18 +67,7 @@ fn source6_traceability_default_keeps_both_layers_and_indexed_projection_matches
.any(|e| e.id == interaction)
);
p.diagrams.get_mut(&d).unwrap().projection = ProjectionKind::Overview;
validate_candidate(&p).unwrap();
let ordinary = projection::project_view_with_index(&p, d, &index);
assert!(
ordinary
.nodes
.iter()
.all(|n| p.elements[&n.element].layer() == Layer::Oa)
);
assert!(ordinary.edges.iter().any(|e| e.id == interaction));
assert!(!ordinary.edges.iter().any(|e| e.id == fe));
assert_eq!(p.diagrams[&d].nodes.len(), 4);
assert_eq!(p.diagrams[&d].view_options.hidden_edges, vec![fe]);
assert!(validate_candidate(&p).is_err());
p.diagrams.get_mut(&d).unwrap().projection = ProjectionKind::Traceability;
p.diagrams.get_mut(&d).unwrap().view_options.relation_kinds = RelationKinds::Only(vec![]);
assert!(
+42 -208
View File
@@ -4,12 +4,8 @@ pub(crate) mod architecture_authoring;
mod architecture_authoring_tests;
#[cfg(test)]
mod behavior_removal_tests;
#[cfg(test)]
mod canvas_selection_tests;
mod checkpoints;
#[cfg(test)]
mod connection_begin_regressions;
#[cfg(test)]
mod diagram_focus_tests;
pub(crate) mod diagram_removal;
#[cfg(test)]
@@ -23,8 +19,6 @@ mod hosted;
pub(crate) mod multiselect;
#[cfg(test)]
mod multiselect_tests;
#[cfg(test)]
mod placement_relocation_regressions;
mod reference_authoring;
#[cfg(test)]
mod review_navigation_tests;
@@ -387,7 +381,6 @@ pub enum Message {
Behavior(crate::behavior::Event),
Layer(Layer),
Select(Id),
CanvasSelect(crate::route_edit::Context, Id),
EditProperties(Id),
SelectNode(Id),
DiagramSelection(multiselect::Event),
@@ -954,16 +947,6 @@ impl App {
return true;
}
let original = self.selected;
let canvas_surface = self.architecture_surface();
let placement_owner = self.draft.as_ref().and_then(|draft| match draft.kind {
crate::forms::RecordKind::Node(owner) => self
.project()
.diagrams
.get(&owner)
.filter(|diagram| diagram.nodes.contains_key(&draft.original))
.map(|diagram| diagram.id),
_ => None,
});
let result = self
.draft
.as_ref()
@@ -1018,16 +1001,13 @@ impl App {
if let Some(layer) = self.project().layer_of(id) {
self.layer = layer;
}
let selected_surface = self
if let Some((diagram, layer, node)) = self
.project()
.diagrams
.values()
.find(|d| d.id == id || d.nodes.contains_key(&id))
.map(|d| (d.id, d.layer, d.nodes.contains_key(&id).then_some(id)));
let relocated_placement = placement_owner
.zip(selected_surface.and_then(|(diagram, _, node)| node.map(|_| diagram)))
.is_some_and(|(before, after)| before != after);
if let Some((diagram, layer, node)) = selected_surface {
.map(|d| (d.id, d.layer, d.nodes.contains_key(&id).then_some(id)))
{
self.layer = layer;
if self.page == Page::Behavior
&& let Some(machine) =
@@ -1041,20 +1021,6 @@ impl App {
self.selected_node = node;
}
}
// Ordinary Properties edits retain their saved architecture view.
// Follow an explicitly edited diagram identity/layer, never an
// inferred replacement for an unrelated queued canvas click.
// An accepted placement move explicitly chooses its new view.
// Same-owner edits (including a new UUID) still retain inspection.
if !relocated_placement && let Some((diagram, _, _)) = canvas_surface {
let diagram = if diagram == original { id } else { diagram };
if let Some(view) = self.project().diagrams.get(&diagram)
&& view.projection.is_architecture()
{
self.layer = view.layer;
self.active_diagram = Some(diagram);
}
}
self.sync();
self.status = "Properties applied".into();
true
@@ -1205,13 +1171,7 @@ impl App {
if id != self.selected {
self.selected_attachment = None;
}
if self.selected == id
&& !process_preview
&& self
.project()
.layer_of(id)
.is_none_or(|layer| layer == self.layer)
{
if self.selected == id && !process_preview {
return;
}
if !self.apply() {
@@ -1257,59 +1217,6 @@ impl App {
}
self.refresh_draft();
}
/// The saved architecture surface, independent of the Inspector's object.
fn architecture_surface(&self) -> Option<(Id, Layer, ProjectionKind)> {
if self.page != Page::Diagram {
return None;
}
let diagram = self.project().diagrams.get(&self.active_diagram?)?;
diagram.projection.is_architecture().then_some((
diagram.id,
diagram.layer,
diagram.projection,
))
}
fn canvas_target_visible(&self, diagram: Id, id: Id) -> bool {
let view = project_view(self.project(), diagram);
view.nodes
.iter()
.any(|node| node.id == id || node.element == id)
|| view.ports.iter().any(|port| port.element == id)
|| view.edges.iter().any(|edge| edge.id == id)
}
/// Inspect a contextual target without following its model layer. An edit
/// may invalidate a previously visible occurrence; accept the edit but
/// refuse that old hit. Invisible Show-in-view targets remain intentional.
fn inspect_canvas_target(&mut self, id: Id) -> bool {
if self.selected == id {
return true;
}
let surface = self.architecture_surface();
let accepted_draft = self.draft_dirty();
let was_visible = accepted_draft
&& surface.is_some_and(|(diagram, _, _)| self.canvas_target_visible(diagram, id));
if !self.project().contains(id) || !self.apply() {
return false;
}
if !self.project().contains(id)
|| (was_visible
&& (self.architecture_surface() != surface
|| !surface
.is_some_and(|(diagram, _, _)| self.canvas_target_visible(diagram, id))))
{
if accepted_draft {
self.status =
"Properties applied. The view changed; select a visible target again.".into();
}
return false;
}
self.selected = id;
self.selected_node = None;
self.selected_attachment = None;
self.selected_bend = None;
self.refresh_draft();
true
}
fn reveal_explorer_selection(&mut self) {
if let Some(group) = self.explorer_index.group(self.selected) {
self.explorer_groups_collapsed.remove(&group);
@@ -1456,11 +1363,6 @@ impl App {
{
return Task::none();
}
if let Message::CanvasSelect(captured, _) = &message
&& (self.page != Page::Diagram || self.route_context() != Some(*captured))
{
return Task::none();
}
// Selection can change without advancing the captured canvas context.
if let Message::Route(
crate::route_edit::Event::Commit(captured, changed, ..)
@@ -2216,7 +2118,6 @@ impl App {
Message::Command(Command::Layout(diagram)) => self.saved_scenario_diagram(*diagram),
Message::Projection(projection) => active || projection.scenario().is_some(),
Message::SelectNode(_)
| Message::CanvasSelect(..)
| Message::PlaceSelected
| Message::HideSelected
| Message::ToggleCollapse
@@ -2817,26 +2718,10 @@ impl App {
e.kind() != Kind::Port && d.projection.includes(e.kind())
})
});
// Draw acts on the visible current view even when opened from
// Explorer; explicit Select/Open actions still navigate.
let draw_in_current_view = match &*action {
Message::Connection(crate::connection_edit::Event::Begin(tool, source)) => {
*source == menu.request.object
&& self.active_diagram.is_some_and(|diagram| {
crate::connection_edit::can_begin(
self.project(),
diagram,
*tool,
*source,
)
})
}
_ => false,
};
if show_in_mixed_view || draw_in_current_view || menu.request.canvas.is_some() {
if !self.inspect_canvas_target(menu.request.object) {
return Task::none();
}
if show_in_mixed_view {
self.selected = menu.request.object;
self.selected_attachment = None;
self.refresh_draft();
} else {
self.select(menu.request.object);
}
@@ -2848,15 +2733,7 @@ impl App {
}
self.reconcile_multiselect();
self.selected_bend = menu.request.bend.map(|i| (menu.request.object, i));
let action = match *action {
Message::EditProperties(id)
if menu.request.canvas.is_some() && id == menu.request.object =>
{
Message::InspectorTab(crate::navigation::InspectorTab::Properties)
}
action => action,
};
return self.update(action);
return self.update(*action);
}
Message::CreateContextElement(kind, child) => {
self.create_kind = kind;
@@ -3012,14 +2889,6 @@ impl App {
self.status = "Property draft reverted".into();
}
Message::Select(id) => self.select(id),
Message::CanvasSelect(context, id) => {
let view = project_view(self.project(), context.diagram);
if view.ports.iter().any(|port| port.element == id)
|| view.edges.iter().any(|edge| edge.id == id)
{
self.inspect_canvas_target(id);
}
}
Message::DiagramSelection(event) => self.multiselect_event(event),
Message::EditProperties(id) => {
self.select(id);
@@ -3039,23 +2908,10 @@ impl App {
return Task::none();
}
let original = self.selected;
let surface = self.architecture_surface();
let accepted_draft = self.draft_dirty();
if !self.apply() {
return Task::none();
}
let id = if id == original { self.selected } else { id };
if let Some((diagram, _, _)) = surface
&& (self.architecture_surface() != surface
|| !self.canvas_target_visible(diagram, id))
{
if accepted_draft {
self.status =
"Properties applied. The view changed; select a visible target again."
.into();
}
return Task::none();
}
if let Some(d) = self
.active_diagram
.and_then(|d| self.project().diagrams.get(&d))
@@ -3480,7 +3336,7 @@ impl App {
self.workflow = Some(crate::workflow::Workflow::relationship_kind(
self.project(),
self.selected,
self.project().layer_of(self.selected).unwrap_or(self.layer),
self.layer,
kind,
incoming,
));
@@ -4506,18 +4362,13 @@ impl App {
self.status = "Select an SA function or participant, then choose a typed port action in Properties.".into();
return;
}
let creation_layer = if child {
self.project().layer_of(self.selected).unwrap_or(self.layer)
} else {
self.layer
};
let allocated = child
&& self.create_kind.is_work()
&& self
.project()
.elements
.get(&self.selected)
.is_some_and(|e| e.layer() == creation_layer && e.kind().is_performer());
.is_some_and(|e| e.layer() == self.layer && e.kind().is_performer());
let parent = if child && !allocated {
Some(self.selected)
} else {
@@ -4525,7 +4376,7 @@ impl App {
};
let Some(e) = self.project().make_element(
self.create_kind,
creation_layer,
self.layer,
&format!("New {}", self.create_kind.label().to_lowercase()),
parent,
) else {
@@ -4554,16 +4405,16 @@ impl App {
.project()
.diagrams
.values()
.find(|d| d.layer == creation_layer && d.projection == preferred)
.find(|d| d.layer == self.layer && d.projection == preferred)
.map(|d| d.id);
if diagram_target.is_none() {
let view = Id::new_v4();
commands.push(Command::CreateDiagram(Diagram {
id: view,
alias: self.project().fresh_alias("engineering_view", "view", view),
name: format!("{} {preferred}", creation_layer),
name: format!("{} {preferred}", self.layer),
description: String::new(),
layer: creation_layer,
layer: self.layer,
projection: preferred,
layout: None,
routes: Vec::new(),
@@ -4696,13 +4547,7 @@ impl App {
));
}
if self.execute(Command::Batch(commands)) {
let changed_view = self.active_diagram != diagram_target;
self.active_diagram = diagram_target;
if changed_view
&& let Some(view) = diagram_target.and_then(|id| self.project().diagrams.get(&id))
{
self.layer = view.layer;
}
self.selected = id;
self.inspector_tab = crate::navigation::InspectorTab::Properties;
self.refresh_draft();
@@ -4716,7 +4561,7 @@ impl App {
self.workflow = Some(crate::workflow::Workflow::relationship(
self.project(),
self.selected,
self.project().layer_of(self.selected).unwrap_or(self.layer),
self.layer,
));
}
fn add_trace(&mut self) {
@@ -5137,30 +4982,8 @@ impl App {
epoch: self.canvas_epoch,
})
}
// Admission belongs to the caller: palette choice, contextual source, or
// an already admitted session whose complete context is still current.
fn arm_connection(
&mut self,
context: crate::route_edit::Context,
tool: crate::connection_tool::Tool,
) -> crate::route_edit::Context {
self.canvas_epoch = self.canvas_epoch.wrapping_add(1);
let context = crate::route_edit::Context {
epoch: self.canvas_epoch,
..context
};
self.connection = Some(crate::connection_edit::Session::new(
self.project(),
&project_view(self.project(), context.diagram),
context,
tool,
));
self.selected_bend = None;
self.status = tool.hint().into();
context
}
fn edit_connection(&mut self, event: crate::connection_edit::Event) {
use crate::connection_edit::Event;
use crate::connection_edit::{Event, Session};
let Some(context) = self.route_context() else {
return;
};
@@ -5176,13 +4999,10 @@ impl App {
}
match event {
Event::Begin(tool, source) => {
if !crate::connection_edit::can_begin(self.project(), context.diagram, tool, source)
{
self.status = "Choose a visible compatible source in this diagram.".into();
return;
self.edit_connection(Event::Choose(Some(tool)));
if let Some(context) = self.route_context() {
self.edit_connection(Event::Endpoint(context, source));
}
let context = self.arm_connection(context, tool);
self.edit_connection(Event::Endpoint(context, source));
}
Event::Choose(Some(tool)) => {
if !crate::connection_tool::Tool::for_layer(self.layer).contains(&tool) {
@@ -5196,7 +5016,16 @@ impl App {
{
return;
}
self.arm_connection(context, tool);
self.canvas_epoch = self.canvas_epoch.wrapping_add(1);
let context = self.route_context().unwrap();
self.connection = Some(Session::new(
self.project(),
&project_view(self.project(), context.diagram),
context,
tool,
));
self.selected_bend = None;
self.status = tool.hint().into();
}
Event::Endpoint(captured, id) => {
if captured != context {
@@ -5257,9 +5086,17 @@ impl App {
&project_view(self.project(), context.diagram),
id,
);
if !self.inspect_canvas_target(id) {
self.connection = Some(session);
return;
if session.tool == crate::connection_tool::Tool::Allocation
&& self.project().diagrams[&context.diagram].projection
== ProjectionKind::Traceability
{
// Mixed views can allocate either perspective. Choosing
// a visible source must not navigate to its preferred view.
self.selected = id;
self.selected_attachment = None;
self.refresh_draft();
} else {
self.select(id);
}
self.status = if session.candidates.is_empty() {
"No compatible targets are visible. Expand containers or show a compatible endpoint in this view; Esc cancels drawing.".into()
@@ -5275,11 +5112,8 @@ impl App {
Event::Restart(captured) => {
if captured == context
&& let Some(session) = &self.connection
&& session.context == context
{
// A contextual mixed-layer tool need not be in this layer's
// palette. Its unchanged session context retains admission.
self.arm_connection(context, session.tool);
self.edit_connection(Event::Choose(Some(session.tool)));
}
}
Event::Choose(None) => {}
@@ -89,10 +89,7 @@ async fn refused_click_keeps_field(mounted: &mut Mounted, f: &mut Fixture, field
);
assert!(!messages.iter().any(|m| matches!(
m,
Message::Select(_)
| Message::CanvasSelect(..)
| Message::SelectNode(_)
| Message::MoveNode(..)
Message::Select(_) | Message::SelectNode(_) | Message::MoveNode(..)
)));
assert_eq!(f.app.document.bundle(), &before);
assert_eq!((f.app.selected, f.app.selected_node), selected);
@@ -237,10 +237,7 @@ fn mounted_dirty_property_press_does_not_select_apply_or_destroy_draft() {
));
assert!(!events.iter().any(|m| matches!(
m,
Message::Select(_)
| Message::CanvasSelect(..)
| Message::SelectNode(_)
| Message::MoveNode(..)
Message::Select(_) | Message::SelectNode(_) | Message::MoveNode(..)
)));
send(&mut f.app, events);
assert_eq!((f.app.selected, f.app.selected_node), selection);
@@ -1,6 +1,5 @@
//! Compact neutral tool palette. Tool state never changes its allocated width.
use super::*;
use crate::connection_tool::Tool;
use iced::{
Point, Rectangle, Size, Theme, mouse,
widget::{
@@ -12,7 +11,7 @@ use iced::{
#[derive(Clone, Copy)]
enum Icon {
Element(Kind),
Connection(Tool),
Arrow,
Existing,
Select,
Collapse(bool),
@@ -77,59 +76,10 @@ impl canvas::Program<Message> for Icon {
);
}
}
Self::Connection(connection) => match connection {
// The OA and SA work-flow tools share a visual family; they
// never coexist in a palette. Keep their small glyph legible.
Tool::Interaction | Tool::FunctionalExchange => {
for x in [1., 11.] {
f.stroke(
&Path::rounded_rectangle(
Point::new(x, 5.),
Size::new(4., 6.),
1.5.into(),
),
stroke,
);
}
f.stroke(&line(&[(5., 8.), (10., 8.)]), stroke);
f.stroke(&line(&[(7.5, 5.5), (10., 8.), (7.5, 10.5)]), stroke);
}
Tool::CommunicationMean => {
// Participant endpoints, without implying a fixed direction.
for x in [1., 11.] {
f.stroke(
&Path::rectangle(Point::new(x, 4.), Size::new(4., 8.)),
stroke,
);
}
f.stroke(&line(&[(5., 8.), (11., 8.)]), stroke);
}
Tool::BoundaryExchange => {
// Ports on facing participant boundaries. Standard exchanges
// are unoriented, so the generic tool has no arrowhead.
for x in [2., 14.] {
f.stroke(&line(&[(x, 2.), (x, 14.)]), stroke);
}
for x in [1., 12.] {
f.stroke(
&Path::rectangle(Point::new(x, 6.), Size::new(3., 4.)),
stroke,
);
}
f.stroke(&line(&[(4., 8.), (12., 8.)]), stroke);
}
Tool::Allocation => {
// Work enters a performer: allocation can appear as nesting
// rather than a relationship line in allocated-work views.
f.stroke(&line(&[(1., 2.), (1., 14.), (15., 14.), (15., 2.)]), stroke);
f.stroke(
&Path::rounded_rectangle(Point::new(5., 9.), Size::new(6., 3.), 1.into()),
stroke,
);
f.stroke(&line(&[(8., 1.), (8., 7.)]), stroke);
f.stroke(&line(&[(5., 4.), (8., 7.), (11., 4.)]), stroke);
}
},
Self::Arrow => {
f.stroke(&line(&[(1., 8.), (14., 8.), (10., 4.)]), stroke);
f.stroke(&line(&[(14., 8.), (10., 12.)]), stroke);
}
Self::Existing => {
f.stroke(
&Path::rectangle(Point::new(1., 1.), Size::new(10., 10.)),
@@ -284,7 +234,7 @@ pub fn view(app: &App) -> Element<'_, Message> {
}
content = content.push(tool(
app,
Icon::Connection(*connection),
Icon::Arrow,
connection.label(),
Message::Connection(crate::connection_edit::Event::Choose(Some(*connection))),
app.connection
@@ -1,904 +0,0 @@
//! Canvas inspection retains the displayed mixed-layer surface; navigation follows the model.
use super::*;
use crate::diagram_usage::Target;
use crate::model_menu::{Event as MenuEvent, Request};
struct Fixture {
app: App,
diagram: Id,
process: Id,
placement: Id,
edge: Id,
}
fn fixture() -> Fixture {
let mut p = Project::empty();
let process = p
.add_element(Kind::Process, Layer::Oa, "Operational process", None)
.unwrap();
let capability = p
.add_element(Kind::Capability, Layer::Oa, "Operational capability", None)
.unwrap();
let edge = p.relate(
RelationKind::ChainInvolvement,
"Enables",
process,
capability,
);
p.ensure_diagram(Layer::Oa);
let diagram = p.ensure_diagram(Layer::Sa);
p.diagrams.get_mut(&diagram).unwrap().projection = ProjectionKind::Traceability;
let placement = p.place(diagram, process, Position { x: 0., y: 0. });
p.place(diagram, capability, Position { x: 500., y: 0. });
let mut app = App::with_project(p);
app.page = Page::Diagram;
app.layer = Layer::Sa;
app.active_diagram = Some(diagram);
app.selected = diagram;
app.refresh_draft();
app.reconcile_multiselect();
assert!(
project_view(app.project(), diagram)
.edges
.iter()
.any(|e| e.id == edge)
);
Fixture {
app,
diagram,
process,
placement,
edge,
}
}
fn assert_surface(f: &Fixture) {
assert_eq!(f.app.page, Page::Diagram);
assert_eq!(f.app.layer, Layer::Sa);
assert_eq!(f.app.active_diagram, Some(f.diagram));
}
#[test]
fn mixed_projection_switch_restores_all_authored_content_and_one_step_history() {
let mut f = fixture();
let before = f.app.document.snapshot();
let mut expected = before.decoded.project.clone();
expected.diagrams.get_mut(&f.diagram).unwrap().projection = ProjectionKind::Allocation;
let _ = f
.app
.update(Message::Projection(ProjectionKind::Allocation));
assert_surface(&f);
assert_eq!(f.app.project(), &expected);
assert!(project_view(f.app.project(), f.diagram).nodes.is_empty());
assert_eq!(f.app.project().diagrams[&f.diagram].nodes.len(), 2);
let switched = f.app.document.snapshot();
let _ = f.app.update(Message::Undo);
assert_eq!(f.app.project(), &before.decoded.project);
assert_eq!(f.app.document.bundle(), &before.bundle);
assert!(!f.app.document.can_undo());
let _ = f.app.update(Message::Redo);
assert_eq!(f.app.project(), &switched.decoded.project);
assert_eq!(f.app.document.bundle(), &switched.bundle);
let _ = f
.app
.update(Message::Projection(ProjectionKind::Traceability));
assert_surface(&f);
assert_eq!(f.app.project(), &before.decoded.project);
assert!(
project_view(f.app.project(), f.diagram)
.edges
.iter()
.any(|e| e.id == f.edge)
);
}
#[test]
fn mixed_canvas_edge_and_group_inspection_preserve_surface_and_history() {
for grouped in [false, true] {
let mut f = fixture();
let before = f.app.document.snapshot();
let revision = f.app.document.revision();
if grouped {
let _ = f
.app
.update(Message::DiagramSelection(multiselect::Event::Choose(
f.app.multiselect_context().unwrap(),
Target::Placement(f.placement),
multiselect::Choice::Replace,
)));
}
let message = if grouped {
Message::DiagramSelection(multiselect::Event::Choose(
f.app.multiselect_context().unwrap(),
Target::Edge(f.edge),
multiselect::Choice::Add,
))
} else {
Message::CanvasSelect(f.app.route_context().unwrap(), f.edge)
};
let _ = f.app.update(message);
assert_surface(&f);
assert_eq!(f.app.selected, f.edge);
assert_eq!(f.app.draft.as_ref().unwrap().original, f.edge);
assert_eq!(
f.app.multiselect_snapshot().unwrap().primary(),
Some(&Target::Edge(f.edge))
);
assert_eq!(
f.app.multiselect_snapshot().unwrap().targets().len(),
if grouped { 2 } else { 1 }
);
assert_eq!(f.app.project(), &before.decoded.project);
assert_eq!(f.app.document.bundle(), &before.bundle);
assert_eq!(f.app.document.revision(), revision);
assert!(!f.app.document.can_undo());
}
}
fn invoke(f: &mut Fixture, object: Id, placement: Option<Id>, action: Message) {
let mut request = Request::object(object, iced::Point::ORIGIN);
request.canvas = f.app.route_context();
request.placement = placement;
let _ = f.app.update(Message::Menu(MenuEvent::Open(request)));
let token = f.app.model_menu.as_ref().unwrap().token;
let _ = f
.app
.update(Message::Menu(MenuEvent::Invoke(token, Box::new(action))));
}
#[test]
fn mixed_canvas_context_properties_and_hide_keep_the_captured_surface() {
let mut f = fixture();
let edge = f.edge;
invoke(&mut f, edge, None, Message::EditProperties(edge));
assert_surface(&f);
assert_eq!(f.app.selected, edge);
assert!(!f.app.document.can_undo());
let before = f.app.document.snapshot();
let process = f.process;
let placement = f.placement;
invoke(&mut f, process, Some(placement), Message::HideSelected);
assert_surface(&f);
assert!(
!f.app.project().diagrams[&f.diagram]
.nodes
.contains_key(&placement)
);
assert!(f.app.project().elements.contains_key(&process));
let _ = f.app.update(Message::Undo);
assert_eq!(f.app.project(), &before.decoded.project);
assert_eq!(f.app.document.bundle(), &before.bundle);
}
#[test]
fn mixed_canvas_explicit_navigation_still_follows_the_target_layer() {
let mut f = fixture();
let process = f.process;
let edge = f.edge;
invoke(&mut f, edge, None, Message::Select(process));
assert_eq!(f.app.selected, process);
assert_eq!(f.app.layer, Layer::Oa);
assert_ne!(f.app.active_diagram, Some(f.diagram));
}
#[test]
fn explicit_navigation_to_the_inspected_cross_layer_object_follows_its_layer() {
let mut f = fixture();
let _ = f.app.update(Message::CanvasSelect(
f.app.route_context().unwrap(),
f.edge,
));
assert_surface(&f);
assert_eq!(f.app.selected, f.edge);
let before = f.app.document.snapshot();
let _ = f.app.update(Message::Select(f.edge));
assert_eq!(f.app.selected, f.edge);
assert_eq!(f.app.layer, Layer::Oa);
assert_ne!(f.app.active_diagram, Some(f.diagram));
assert_eq!(f.app.project(), &before.decoded.project);
assert_eq!(f.app.document.bundle(), &before.bundle);
assert!(!f.app.document.can_undo());
}
#[test]
fn mixed_canvas_invalid_draft_and_stale_packets_preserve_selection() {
let mut f = fixture();
let context = f.app.route_context().unwrap();
let _ = f.app.update(Message::Field("/id".into(), "-".into()));
let draft = serde_json::to_value(f.app.draft.as_ref().unwrap().recovery_snapshot()).unwrap();
let _ = f.app.update(Message::CanvasSelect(context, f.edge));
assert_eq!(f.app.selected, f.diagram);
assert_surface(&f);
assert_eq!(
serde_json::to_value(f.app.draft.as_ref().unwrap().recovery_snapshot()).unwrap(),
draft
);
let _ = f
.app
.update(Message::DiagramSelection(multiselect::Event::Choose(
f.app.multiselect_context().unwrap(),
Target::Edge(f.edge),
multiselect::Choice::Replace,
)));
assert_eq!(f.app.selected, f.diagram);
assert_eq!(
serde_json::to_value(f.app.draft.as_ref().unwrap().recovery_snapshot()).unwrap(),
draft
);
let _ = f.app.update(Message::RevertProperties);
f.app.canvas_epoch += 1;
let _ = f.app.update(Message::CanvasSelect(context, f.edge));
assert_eq!(f.app.selected, f.diagram);
assert!(!f.app.document.can_undo());
}
#[test]
fn cross_layer_port_inspection_and_connection_start_retain_the_view() {
let mut p = Project::empty();
let owner = p
.add_element(Kind::Function, Layer::Sa, "Send", None)
.unwrap();
let port = Id::new_v4();
let element = make_port(
&p,
port,
owner,
"Output",
PortContract::FunctionOutput {
outgoing_items: vec![],
},
)
.unwrap();
p.elements.insert(port, element);
p.ensure_diagram(Layer::Sa);
let diagram = p.ensure_diagram(Layer::Oa);
p.diagrams.get_mut(&diagram).unwrap().projection = ProjectionKind::Traceability;
let placement = p.place(diagram, owner, Position { x: 0., y: 0. });
let mut app = App::with_project(p);
app.page = Page::Diagram;
app.layer = Layer::Oa;
app.active_diagram = Some(diagram);
app.selected = diagram;
app.refresh_draft();
app.reconcile_multiselect();
assert!(
project_view(app.project(), diagram)
.ports
.iter()
.any(|p| p.element == port)
);
let _ = app.update(Message::CanvasSelect(app.route_context().unwrap(), port));
assert_eq!(app.selected, port);
assert_eq!(app.active_diagram, Some(diagram));
assert_eq!(app.layer, Layer::Oa);
assert_eq!(
app.multiselect_snapshot().unwrap().primary(),
Some(&Target::Port { port, placement })
);
// Reset inspection so the endpoint path cannot pass by selecting the same ID.
app.selected = diagram;
app.refresh_draft();
let context = app.route_context().unwrap();
// Mixed canvases can retain a tool from either perspective.
app.connection = Some(crate::connection_edit::Session::new(
app.project(),
&project_view(app.project(), diagram),
context,
crate::connection_tool::Tool::FunctionalExchange,
));
let _ = app.update(Message::Connection(
crate::connection_edit::Event::Endpoint(context, port),
));
assert_eq!(app.selected, port);
assert_eq!(app.layer, Layer::Oa);
assert_eq!(app.active_diagram, Some(diagram));
assert_eq!(app.connection.as_ref().unwrap().source, Some(port));
assert!(!app.document.can_undo());
}
fn authoring_fixture(performer: bool) -> (Fixture, Id) {
let mut f = fixture();
let mut project = f.app.project().clone();
let owner = project
.add_element(
if performer {
Kind::Entity
} else {
Kind::Activity
},
Layer::Oa,
"Operational owner",
None,
)
.unwrap();
let target = project
.add_element(Kind::Activity, Layer::Oa, "Operational target", None)
.unwrap();
let placement = project.place(f.diagram, owner, Position { x: 0., y: 400. });
project.place(f.diagram, target, Position { x: 500., y: 400. });
f.app = App::with_project(project);
f.app.page = Page::Diagram;
f.app.layer = Layer::Sa;
f.app.active_diagram = Some(f.diagram);
f.app.selected = f.diagram;
f.app.refresh_draft();
f.app.reconcile_multiselect();
f.process = owner;
f.placement = placement;
(f, target)
}
fn assert_source_pair(app: &App) {
let compiled = oa_core::source_project::compile_bundle(
app.document.bundle(),
oa_core::source_project::ProjectLimits::default(),
)
.unwrap();
assert_eq!(&compiled.decoded.project, app.project());
assert_eq!(&compiled.bundle, app.document.bundle());
}
#[test]
fn mixed_canvas_relationship_actions_keep_operational_source_and_commit_one_step() {
for context_action in [false, true] {
let (mut f, target) = authoring_fixture(false);
let before = f.app.document.snapshot();
let owner = f.process;
let placement = f.placement;
if context_action {
invoke(
&mut f,
owner,
Some(placement),
Message::AddRelationKind(RelationKind::Interaction, false),
);
} else {
let _ = f.app.update(Message::SelectNode(placement));
let _ = f.app.update(Message::AddRelation);
}
assert_surface(&f);
let Some(crate::workflow::Workflow::Relation(relation)) = &f.app.workflow else {
panic!("relationship form did not open: {}", f.app.status);
};
assert_eq!(
relation.source.as_ref().map(|source| source.id),
Some(owner)
);
assert_eq!(relation.kind, RelationKind::Interaction);
let saved = f
.app
.workflow
.as_ref()
.unwrap()
.recovery_snapshot()
.unwrap();
assert_eq!(saved["Relation"]["layer"], "Oa");
let _ = f
.app
.update(Message::Workflow(crate::workflow::Event::Target(Choice {
id: target,
label: "Operational target".into(),
})));
let _ = f
.app
.update(Message::Workflow(crate::workflow::Event::Apply));
assert!(f.app.workflow.is_none(), "{}", f.app.status);
let created: Vec<_> = f
.app
.project()
.relations
.values()
.filter(|relation| !before.decoded.project.relations.contains_key(&relation.id))
.collect();
assert_eq!(created.len(), 1);
assert_eq!(
(created[0].kind(), created[0].source(), created[0].target()),
(RelationKind::Interaction, owner, target)
);
assert_source_pair(&f.app);
let _ = f.app.update(Message::Undo);
assert_eq!(f.app.project(), &before.decoded.project);
assert_eq!(f.app.document.bundle(), &before.bundle);
assert!(!f.app.document.can_undo());
}
}
#[test]
fn mixed_canvas_add_child_command_uses_parent_layer_for_containment_and_allocation() {
for performer in [false, true] {
let (mut f, _) = authoring_fixture(performer);
let before = f.app.document.snapshot();
let owner = f.process;
let placement = f.placement;
invoke(
&mut f,
owner,
Some(placement),
Message::CreateContextElement(Kind::Activity, true),
);
assert_surface(&f);
let created: Vec<_> = f
.app
.project()
.elements
.values()
.filter(|element| !before.decoded.project.elements.contains_key(&element.id))
.collect();
assert_eq!(created.len(), 1, "{}", f.app.status);
let child = created[0].id;
assert_eq!(created[0].kind(), Kind::Activity);
assert_eq!(created[0].layer(), Layer::Oa);
assert_eq!(
created[0].parent,
if performer { None } else { Some(owner) }
);
let allocations: Vec<_> = f
.app
.project()
.relations
.values()
.filter(|relation| !before.decoded.project.relations.contains_key(&relation.id))
.collect();
assert_eq!(allocations.len(), usize::from(performer));
if performer {
assert_eq!(
(
allocations[0].kind(),
allocations[0].source(),
allocations[0].target()
),
(RelationKind::Allocation, owner, child)
);
}
assert!(
f.app.project().diagrams[&f.diagram]
.nodes
.values()
.any(|node| node.element == child)
);
assert_source_pair(&f.app);
let _ = f.app.update(Message::Undo);
assert_eq!(f.app.project(), &before.decoded.project);
assert_eq!(f.app.document.bundle(), &before.bundle);
assert!(!f.app.document.can_undo());
}
}
#[test]
fn mixed_canvas_root_creation_still_uses_the_active_perspective() {
let (mut f, _) = authoring_fixture(false);
let _ = f.app.update(Message::SelectNode(f.placement));
let before = f.app.document.snapshot();
let _ = f
.app
.update(Message::CreateContextElement(Kind::Function, false));
assert_surface(&f);
let created: Vec<_> = f
.app
.project()
.elements
.values()
.filter(|element| !before.decoded.project.elements.contains_key(&element.id))
.collect();
assert_eq!(created.len(), 1, "{}", f.app.status);
assert_eq!(created[0].kind(), Kind::Function);
assert_eq!(created[0].layer(), Layer::Sa);
assert_eq!(created[0].parent, None);
assert_source_pair(&f.app);
let _ = f.app.update(Message::Undo);
assert_eq!(f.app.project(), &before.decoded.project);
assert_eq!(f.app.document.bundle(), &before.bundle);
}
#[test]
fn child_creation_navigates_to_a_parent_layer_preferred_view_only_when_needed() {
for existing_preferred in [false, true] {
let (mut f, _) = authoring_fixture(false);
let mut project = f.app.project().clone();
for diagram in project
.diagrams
.values_mut()
.filter(|d| d.layer == Layer::Oa)
{
diagram.projection = if existing_preferred {
ProjectionKind::Allocation
} else {
ProjectionKind::Overview
};
}
project.diagrams.get_mut(&f.diagram).unwrap().projection = ProjectionKind::Capabilities;
f.app = App::with_project(project);
let _ = f.app.update(Message::Select(f.process));
let _ = f.app.update(Message::Diagram(Choice {
id: f.diagram,
label: "System capabilities".into(),
}));
assert_eq!(f.app.selected, f.process);
assert_eq!(f.app.layer, Layer::Sa);
assert_eq!(f.app.active_diagram, Some(f.diagram));
let before = f.app.document.snapshot();
let _ = f
.app
.update(Message::CreateContextElement(Kind::Activity, true));
assert_eq!(f.app.layer, Layer::Oa, "{}", f.app.status);
let diagram = f.app.active_diagram.unwrap();
assert_ne!(diagram, f.diagram);
assert_eq!(f.app.project().diagrams[&diagram].layer, Layer::Oa);
assert_eq!(
f.app.project().diagrams[&diagram].projection,
ProjectionKind::Allocation
);
assert_eq!(
f.app.project().diagrams.len(),
before.decoded.project.diagrams.len() + usize::from(!existing_preferred)
);
assert_eq!(
f.app.project().elements[&f.app.selected].parent,
Some(f.process)
);
assert_source_pair(&f.app);
let _ = f.app.update(Message::Undo);
assert_eq!(f.app.project(), &before.decoded.project);
assert_eq!(f.app.document.bundle(), &before.bundle);
}
}
// Regression scope: a contextual click may accept a valid property draft, but
// must not inherit the draft object's semantic-layer navigation. If acceptance
// changes the originating diagram identity or hides the clicked target, retain
// that one accepted edit and refuse the old click. Do not infer a new target or
// reinterpret an old surface packet on a replacement diagram.
fn draft_click_fixture() -> (Fixture, Id) {
let mut f = fixture();
let mut project = f.app.project().clone();
let capability = project
.add_element(Kind::Capability, Layer::Oa, "Second capability", None)
.unwrap();
let other_edge = project.relate(
RelationKind::ChainInvolvement,
"Also enables",
f.process,
capability,
);
project.place(f.diagram, capability, Position { x: 500., y: 300. });
f.app = App::with_project(project);
f.app.page = Page::Diagram;
f.app.layer = Layer::Sa;
f.app.active_diagram = Some(f.diagram);
f.app.selected = f.diagram;
f.app.refresh_draft();
f.app.reconcile_multiselect();
assert!(
project_view(f.app.project(), f.diagram)
.edges
.iter()
.any(|edge| edge.id == other_edge)
);
(f, other_edge)
}
fn expected_draft_commit(app: &App, command: Command) -> Document {
let mut expected = Document::from_bundle(
app.document.bundle().clone(),
oa_core::source_project::ProjectLimits::default(),
)
.unwrap();
expected.execute(command).unwrap();
expected
}
fn assert_accepted_draft(app: &App, expected: &Document, revision: u64) {
assert_eq!(app.project(), expected.project());
assert_eq!(app.document.bundle(), expected.bundle());
assert_eq!(app.document.revision(), revision + 1);
assert!(!app.draft_dirty());
assert!(app.document.can_undo());
assert!(!app.document.can_redo());
assert_source_pair(app);
}
fn assert_one_draft_history(
app: &mut App,
before: &oa_core::source_project::CompiledSourceProject,
expected: &Document,
) {
let _ = app.update(Message::Undo);
assert_eq!(app.project(), &before.decoded.project);
assert_eq!(app.document.bundle(), &before.bundle);
assert!(
!app.document.can_undo(),
"the click must not add a second edit"
);
assert!(app.document.can_redo());
let _ = app.update(Message::Redo);
assert_eq!(app.project(), expected.project());
assert_eq!(app.document.bundle(), expected.bundle());
assert!(!app.document.can_redo());
}
fn pending_edge_name(f: &mut Fixture) {
let context = f.app.route_context().unwrap();
let _ = f.app.update(Message::CanvasSelect(context, f.edge));
assert_surface(f);
assert_eq!(f.app.selected, f.edge);
let _ = f
.app
.update(Message::Field("/name".into(), "Reviewed enables".into()));
assert!(f.app.draft_dirty());
assert!(f.app.draft.as_ref().unwrap().command().is_ok());
}
#[test]
fn valid_cross_layer_draft_then_canvas_edge_retains_surface_and_one_edit() {
let (mut f, other_edge) = draft_click_fixture();
let before = f.app.document.snapshot();
let revision = f.app.document.revision();
let expected =
expected_draft_commit(&f.app, Command::Rename(f.edge, "Reviewed enables".into()));
pending_edge_name(&mut f);
// Capture after drafting, so this exercises successful draft application,
// not refusal of a packet made stale by the property edit itself.
let context = f.app.route_context().unwrap();
let _ = f.app.update(Message::CanvasSelect(context, other_edge));
assert_accepted_draft(&f.app, &expected, revision);
assert_surface(&f);
assert_eq!(f.app.selected, other_edge);
assert_eq!(
f.app.multiselect_snapshot().unwrap().primary(),
Some(&Target::Edge(other_edge))
);
assert_one_draft_history(&mut f.app, &before, &expected);
}
#[test]
fn valid_cross_layer_draft_then_node_click_retains_surface_and_one_edit() {
let (mut f, _) = draft_click_fixture();
let before = f.app.document.snapshot();
let revision = f.app.document.revision();
let expected =
expected_draft_commit(&f.app, Command::Rename(f.edge, "Reviewed enables".into()));
pending_edge_name(&mut f);
let _ = f.app.update(Message::SelectNode(f.placement));
assert_accepted_draft(&f.app, &expected, revision);
assert_surface(&f);
assert_eq!(f.app.selected, f.process);
assert_eq!(f.app.selected_node, Some(f.placement));
assert_eq!(
f.app.multiselect_snapshot().unwrap().primary(),
Some(&Target::Placement(f.placement))
);
assert_one_draft_history(&mut f.app, &before, &expected);
}
#[test]
fn diagram_reidentity_draft_commits_but_refuses_the_old_surface_click() {
let (mut f, other_edge) = draft_click_fixture();
let before = f.app.document.snapshot();
let revision = f.app.document.revision();
let replacement = Id::new_v4();
let expected = expected_draft_commit(&f.app, Command::Reidentify(f.diagram, replacement));
let _ = f
.app
.update(Message::Field("/id".into(), replacement.to_string()));
assert!(f.app.draft_dirty());
let context = f.app.route_context().unwrap();
let _ = f.app.update(Message::CanvasSelect(context, other_edge));
assert_accepted_draft(&f.app, &expected, revision);
assert!(!f.app.project().diagrams.contains_key(&context.diagram));
assert!(
f.app
.active_diagram
.is_some_and(|id| f.app.project().diagrams.contains_key(&id)),
"never restore a removed diagram identity"
);
assert_ne!(f.app.selected, other_edge, "refuse the old surface click");
assert!(
f.app
.multiselect_snapshot()
.is_none_or(|s| !s.targets().contains(&Target::Edge(other_edge)))
);
assert_eq!(
f.app.status,
"Properties applied. The view changed; select a visible target again."
);
assert_one_draft_history(&mut f.app, &before, &expected);
}
#[test]
fn projection_draft_commits_but_refuses_the_now_hidden_canvas_target() {
let (mut f, other_edge) = draft_click_fixture();
let before = f.app.document.snapshot();
let revision = f.app.document.revision();
let mut diagram = f.app.project().diagrams[&f.diagram].clone();
diagram.projection = ProjectionKind::Allocation;
let expected = expected_draft_commit(&f.app, Command::EditDiagram(f.diagram, diagram));
let _ = f.app.update(Message::FieldShape(
"/projection".into(),
serde_json::to_value(ProjectionKind::Allocation).unwrap(),
));
assert!(f.app.draft_dirty());
let context = f.app.route_context().unwrap();
let _ = f.app.update(Message::CanvasSelect(context, other_edge));
assert_accepted_draft(&f.app, &expected, revision);
assert_surface(&f);
assert!(
!project_view(f.app.project(), f.diagram)
.edges
.iter()
.any(|edge| edge.id == other_edge)
);
assert_ne!(
f.app.selected, other_edge,
"do not inspect a no-longer-visible hit"
);
assert!(
f.app
.multiselect_snapshot()
.is_none_or(|s| !s.targets().contains(&Target::Edge(other_edge)))
);
assert_eq!(
f.app.status,
"Properties applied. The view changed; select a visible target again."
);
assert_one_draft_history(&mut f.app, &before, &expected);
}
#[test]
fn invalid_cross_layer_draft_blocks_contextual_clicks_without_changing_history() {
for click in 0..3 {
let (mut f, other_edge) = draft_click_fixture();
let context = f.app.route_context().unwrap();
let _ = f.app.update(Message::CanvasSelect(context, f.edge));
let before = f.app.document.snapshot();
let revision = f.app.document.revision();
let _ = f
.app
.update(Message::Field("/id".into(), "not-a-uuid".into()));
assert!(f.app.draft_dirty());
let expected_error = f.app.draft.as_ref().unwrap().command().unwrap_err();
let draft =
serde_json::to_value(f.app.draft.as_ref().unwrap().recovery_snapshot()).unwrap();
let selection = f.app.multiselect_snapshot().unwrap();
let message = match click {
0 => Message::CanvasSelect(f.app.route_context().unwrap(), other_edge),
1 => Message::SelectNode(f.placement),
_ => Message::DiagramSelection(multiselect::Event::Choose(
f.app.multiselect_context().unwrap(),
Target::Edge(other_edge),
multiselect::Choice::Add,
)),
};
let _ = f.app.update(message);
if click < 2 {
assert_eq!(f.app.status, expected_error);
} else {
assert_eq!(
f.app.status,
"Finish or cancel the active edit before changing the diagram selection."
);
}
assert_surface(&f);
assert_eq!(f.app.selected, f.edge);
assert_eq!(f.app.project(), &before.decoded.project);
assert_eq!(f.app.document.bundle(), &before.bundle);
assert_eq!(f.app.document.revision(), revision);
assert!(!f.app.document.can_undo());
assert!(!f.app.document.can_redo());
assert_eq!(
serde_json::to_value(f.app.draft.as_ref().unwrap().recovery_snapshot()).unwrap(),
draft
);
assert_eq!(
f.app.multiselect_snapshot().unwrap().targets(),
selection.targets()
);
assert_eq!(
f.app.multiselect_snapshot().unwrap().primary(),
selection.primary()
);
if click == 0 {
// Pre-dispatch stale rejection applies no draft and must not emit
// either success feedback or a new validation message.
let stale = f.app.route_context().unwrap();
f.app.canvas_epoch += 1;
f.app.status = "Keep previous feedback".into();
let _ = f.app.update(Message::CanvasSelect(stale, other_edge));
assert_eq!(f.app.status, "Keep previous feedback");
assert_eq!(f.app.project(), &before.decoded.project);
assert_eq!(f.app.document.bundle(), &before.bundle);
assert_eq!(f.app.document.revision(), revision);
assert_eq!(
serde_json::to_value(f.app.draft.as_ref().unwrap().recovery_snapshot()).unwrap(),
draft
);
}
}
}
#[test]
fn explicit_select_after_valid_cross_layer_draft_still_navigates_and_commits_once() {
let (mut f, other_edge) = draft_click_fixture();
let before = f.app.document.snapshot();
let revision = f.app.document.revision();
let expected =
expected_draft_commit(&f.app, Command::Rename(f.edge, "Reviewed enables".into()));
pending_edge_name(&mut f);
let _ = f.app.update(Message::Select(other_edge));
assert_accepted_draft(&f.app, &expected, revision);
assert_eq!(f.app.selected, other_edge);
assert_eq!(f.app.layer, Layer::Oa);
assert_ne!(f.app.active_diagram, Some(f.diagram));
assert_one_draft_history(&mut f.app, &before, &expected);
}
#[test]
fn properties_apply_on_cross_layer_edge_retains_surface_and_one_edit() {
let (mut f, _) = draft_click_fixture();
let before = f.app.document.snapshot();
let revision = f.app.document.revision();
let expected =
expected_draft_commit(&f.app, Command::Rename(f.edge, "Reviewed enables".into()));
pending_edge_name(&mut f);
let _ = f.app.update(Message::Apply);
assert_accepted_draft(&f.app, &expected, revision);
assert_eq!(f.app.status, "Properties applied");
assert_surface(&f);
assert_eq!(f.app.selected, f.edge);
assert_eq!(f.app.draft.as_ref().unwrap().original, f.edge);
assert_eq!(
f.app.multiselect_snapshot().unwrap().primary(),
Some(&Target::Edge(f.edge))
);
assert_one_draft_history(&mut f.app, &before, &expected);
}
#[test]
fn properties_apply_honors_an_intentional_active_diagram_layer_edit() {
let (mut f, _) = draft_click_fixture();
let before = f.app.document.snapshot();
let revision = f.app.document.revision();
let mut diagram = f.app.project().diagrams[&f.diagram].clone();
diagram.layer = Layer::Oa;
let expected = expected_draft_commit(&f.app, Command::EditDiagram(f.diagram, diagram));
let _ = f.app.update(Message::FieldShape(
"/layer".into(),
serde_json::to_value(Layer::Oa).unwrap(),
));
assert!(f.app.draft_dirty());
let _ = f.app.update(Message::Apply);
assert_accepted_draft(&f.app, &expected, revision);
assert_eq!(f.app.page, Page::Diagram);
assert_eq!(
f.app.layer,
Layer::Oa,
"honor the edited diagram's own layer"
);
assert_eq!(f.app.active_diagram, Some(f.diagram));
assert_eq!(f.app.selected, f.diagram);
assert_eq!(f.app.draft.as_ref().unwrap().original, f.diagram);
assert_one_draft_history(&mut f.app, &before, &expected);
}
#[test]
fn properties_apply_follows_the_known_active_diagram_reidentity_once() {
let (mut f, _) = draft_click_fixture();
let before = f.app.document.snapshot();
let revision = f.app.document.revision();
let replacement = Id::new_v4();
let expected = expected_draft_commit(&f.app, Command::Reidentify(f.diagram, replacement));
let _ = f
.app
.update(Message::Field("/id".into(), replacement.to_string()));
assert!(f.app.draft_dirty());
let _ = f.app.update(Message::Apply);
assert_accepted_draft(&f.app, &expected, revision);
assert_eq!(f.app.page, Page::Diagram);
assert_eq!(f.app.layer, Layer::Sa);
// This identity was explicitly authored in the diagram's own draft. No
// queued click is being remapped or inferred from semantic similarity.
assert_eq!(f.app.active_diagram, Some(replacement));
assert_eq!(f.app.selected, replacement);
assert_eq!(f.app.draft.as_ref().unwrap().original, replacement);
assert!(!f.app.project().diagrams.contains_key(&f.diagram));
assert_one_draft_history(&mut f.app, &before, &expected);
}
@@ -1,366 +0,0 @@
// Test-only proposal pinned to peer 6520a89dc042dd433454f6cc63cb23b0e6df7911.
// Independent App test module; real popup messages retain menu tokens.
use super::*;
use crate::model_menu::{Event as MenuEvent, Request};
struct BeginFixture {
app: App,
diagram: Id,
surface_layer: Layer,
source: Id,
target: Id,
wrong_layer: Id,
hidden: Id,
tool: crate::connection_tool::Tool,
}
fn begin_fixture(source_layer: Layer, allocation: bool, mixed: bool) -> BeginFixture {
use crate::connection_tool::Tool;
let mut p = Project::empty();
let (work, performer, exchange) = match source_layer {
Layer::Oa => (Kind::Activity, Kind::Entity, Tool::Interaction),
Layer::Sa => (Kind::Function, Kind::System, Tool::FunctionalExchange),
};
let opposite = if source_layer == Layer::Oa {
Layer::Sa
} else {
Layer::Oa
};
let surface_layer = if mixed { opposite } else { source_layer };
let source = p
.add_element(
if allocation { performer } else { work },
source_layer,
"Source",
None,
)
.unwrap();
let target = p.add_element(work, source_layer, "Target", None).unwrap();
let hidden = p
.add_element(work, source_layer, "Hidden target", None)
.unwrap();
let other_work = if opposite == Layer::Oa {
Kind::Activity
} else {
Kind::Function
};
let wrong_layer = p
.add_element(other_work, opposite, "Wrong layer target", None)
.unwrap();
p.ensure_diagram(source_layer);
let diagram = p.ensure_diagram(surface_layer);
p.diagrams.get_mut(&diagram).unwrap().projection = if mixed {
ProjectionKind::Traceability
} else {
ProjectionKind::Capabilities
};
p.place(diagram, source, Position { x: 0., y: 0. });
p.place(diagram, target, Position { x: 400., y: 0. });
if mixed {
p.place(diagram, wrong_layer, Position { x: 800., y: 0. });
}
let mut app = App::with_project(p);
app.page = Page::Diagram;
app.layer = surface_layer;
app.active_diagram = Some(diagram);
app.selected = diagram;
app.refresh_draft();
app.reconcile_multiselect();
assert!(!app.document.can_undo());
BeginFixture {
app,
diagram,
surface_layer,
source,
target,
wrong_layer,
hidden,
tool: if allocation {
Tool::Allocation
} else {
exchange
},
}
}
fn begin_surface(f: &BeginFixture) {
assert_eq!(f.app.page, Page::Diagram);
assert_eq!(f.app.layer, f.surface_layer);
assert_eq!(f.app.active_diagram, Some(f.diagram));
}
fn begin_popup_action(f: &mut BeginFixture) -> Option<Message> {
begin_popup_action_from(f, true)
}
fn begin_popup_action_from(f: &mut BeginFixture, canvas: bool) -> Option<Message> {
let mut request = Request::object(f.source, iced::Point::ORIGIN);
if canvas {
request.canvas = f.app.route_context();
request.placement = f.app.project().diagrams[&f.diagram]
.nodes
.values()
.find(|node| node.element == f.source)
.map(|node| node.id);
}
let _ = f.app.update(Message::Menu(MenuEvent::Open(request)));
let popup = crate::model_menu::popup(&f.app).expect("real model popup");
let expected = format!("Draw {}", f.tool.label().to_lowercase());
for entry in popup.entries {
if let crate::context_menu::Entry::Action { label, message, .. } = entry
&& label == expected
{
if let Some(Message::Menu(MenuEvent::Invoke(token, _))) = &message {
assert_eq!(*token, popup.token);
} else {
assert!(message.is_none(), "Draw must use the menu token envelope");
}
return message;
}
}
None // Omission is also a valid unavailable-action policy.
}
fn begin_from_popup(f: &mut BeginFixture) -> Message {
begin_from_popup_origin(f, true)
}
fn begin_from_popup_origin(f: &mut BeginFixture, canvas: bool) -> Message {
let before = f.app.document.snapshot();
let revision = f.app.document.revision();
let action =
begin_popup_action_from(f, canvas).expect("visible semantic source offers enabled Draw");
let _ = f.app.update(action.clone());
begin_surface(f);
assert_eq!(f.app.project(), &before.decoded.project);
assert_eq!(f.app.document.bundle(), &before.bundle);
assert_eq!(f.app.document.revision(), revision);
assert!(!f.app.document.can_undo());
let session = f
.app
.connection
.as_ref()
.expect("menu Draw must arm its tool");
assert_eq!(session.tool, f.tool);
assert_eq!(session.source, Some(f.source));
assert_eq!(session.context, f.app.route_context().unwrap());
assert!(session.candidates.contains(&f.target));
assert!(!session.candidates.contains(&f.wrong_layer));
assert!(!session.candidates.contains(&f.hidden));
action
}
fn begin_complete_and_check_history(f: &mut BeginFixture) {
let before = f.app.document.snapshot();
let revision = f.app.document.revision();
let context = f.app.route_context().unwrap();
let _ = f.app.update(Message::Connection(
crate::connection_edit::Event::Endpoint(context, f.target),
));
begin_surface(f);
assert!(f.app.connection.is_none());
assert_eq!(f.app.document.revision(), revision + 1);
let relations: Vec<_> = f
.app
.project()
.relations
.values()
.filter(|r| !before.decoded.project.relations.contains_key(&r.id))
.collect();
assert_eq!(relations.len(), 1);
let expected = match f.tool {
crate::connection_tool::Tool::Interaction => RelationKind::Interaction,
crate::connection_tool::Tool::FunctionalExchange => RelationKind::FunctionalExchange,
crate::connection_tool::Tool::Allocation => RelationKind::Allocation,
_ => unreachable!(),
};
assert_eq!(relations[0].kind(), expected);
assert_eq!(
(relations[0].source(), relations[0].target()),
(f.source, f.target)
);
// Random identities are accepted only for the precise command delta.
// Everything else, including traces, metadata and view options, stays exact.
let mut expected_project = before.decoded.project.clone();
let created = relations[0];
assert!(
expected_project
.relations
.insert(created.id, created.clone())
.is_none()
);
if let RelationBody::FunctionalExchange {
source_port,
target_port,
items,
..
} = &created.body
{
assert_eq!(f.tool, crate::connection_tool::Tool::FunctionalExchange);
assert!(items.is_empty());
assert_ne!(source_port, target_port);
let output = &f.app.project().elements[source_port];
let input = &f.app.project().elements[target_port];
assert_eq!(
(output.kind(), output.layer(), output.parent),
(Kind::Port, Layer::Sa, Some(f.source))
);
assert_eq!(
(input.kind(), input.layer(), input.parent),
(Kind::Port, Layer::Sa, Some(f.target))
);
assert!(
matches!(port_contract(output), Some(PortContract::FunctionOutput { outgoing_items }) if outgoing_items.is_empty())
);
assert!(
matches!(port_contract(input), Some(PortContract::FunctionInput { incoming_items }) if incoming_items.is_empty())
);
assert!(!before.decoded.project.elements.contains_key(source_port));
assert!(!before.decoded.project.elements.contains_key(target_port));
assert_eq!(output.id, *source_port);
assert_eq!(input.id, *target_port);
expected_project
.elements
.insert(*source_port, output.clone());
expected_project
.elements
.insert(*target_port, input.clone());
assert_eq!(
f.app.project().elements.len(),
before.decoded.project.elements.len() + 2
);
} else {
assert_ne!(f.tool, crate::connection_tool::Tool::FunctionalExchange);
assert_eq!(f.app.project().elements, before.decoded.project.elements);
}
assert_eq!(f.app.project(), &expected_project);
assert_eq!(f.app.project().diagrams, before.decoded.project.diagrams);
for (id, element) in &before.decoded.project.elements {
assert_eq!(&f.app.project().elements[id], element);
}
begin_assert_source_pair(&f.app);
let after = f.app.document.snapshot();
let _ = f.app.update(Message::Undo);
assert_eq!(f.app.project(), &before.decoded.project);
assert_eq!(f.app.document.bundle(), &before.bundle);
assert!(
!f.app.document.can_undo(),
"creation is a single history step"
);
let _ = f.app.update(Message::Redo);
assert_eq!(f.app.project(), &after.decoded.project);
assert_eq!(f.app.document.bundle(), &after.bundle);
begin_assert_source_pair(&f.app);
}
#[test]
fn contextual_begin_exchange_uses_actual_menu_in_both_mixed_directions() {
for layer in [Layer::Oa, Layer::Sa] {
let mut f = begin_fixture(layer, false, true);
begin_from_popup(&mut f);
begin_complete_and_check_history(&mut f);
}
}
#[test]
fn contextual_begin_cancel_restart_and_stale_menu_preserve_session_ownership() {
for layer in [Layer::Oa, Layer::Sa] {
let mut f = begin_fixture(layer, false, true);
let original = f.app.document.snapshot();
let stale_menu = begin_from_popup(&mut f);
let first = f.app.route_context().unwrap();
let _ = f
.app
.update(Message::Connection(crate::connection_edit::Event::Restart(
first,
)));
begin_surface(&f);
let restarted = f
.app
.connection
.as_ref()
.expect("restart retains contextual tool");
assert_eq!(restarted.tool, f.tool);
assert_eq!(restarted.source, None);
assert!(restarted.candidates.contains(&f.source));
let current = restarted.context;
assert_ne!(current, first);
let _ = f.app.update(stale_menu);
let _ = f.app.update(Message::Connection(
crate::connection_edit::Event::Endpoint(first, f.target),
));
assert_eq!(f.app.connection.as_ref().unwrap().context, current);
assert_eq!(f.app.connection.as_ref().unwrap().source, None);
let _ = f.app.update(Message::Connection(
crate::connection_edit::Event::Endpoint(current, f.source),
));
assert_eq!(f.app.connection.as_ref().unwrap().source, Some(f.source));
let _ = f.app.update(Message::Escape);
let _ = f.app.update(Message::Connection(
crate::connection_edit::Event::Endpoint(current, f.target),
));
assert!(f.app.connection.is_none());
begin_surface(&f);
assert_eq!(f.app.project(), &original.decoded.project);
assert_eq!(f.app.document.bundle(), &original.bundle);
assert!(!f.app.document.can_undo());
}
}
#[test]
fn contextual_begin_allocation_uses_actual_menu_in_both_mixed_directions() {
for layer in [Layer::Oa, Layer::Sa] {
let mut f = begin_fixture(layer, true, true);
begin_from_popup(&mut f);
begin_complete_and_check_history(&mut f);
}
}
#[test]
fn contextual_begin_allocation_is_not_enabled_when_projection_hides_work() {
for layer in [Layer::Oa, Layer::Sa] {
let mut f = begin_fixture(layer, true, false);
let view = project_view(f.app.project(), f.diagram);
assert!(view.nodes.iter().any(|n| n.element == f.source));
assert!(!view.nodes.iter().any(|n| n.element == f.target));
assert!(!f.tool.available_in(layer, ProjectionKind::Capabilities));
let before = f.app.document.snapshot();
assert!(
begin_popup_action(&mut f).is_none(),
"Draw allocation must be disabled or omitted when work is not projected"
);
let _ = f.app.update(Message::Menu(MenuEvent::Close));
// Defensive handler refusal complements actual-menu enabledness above.
let _ = f
.app
.update(Message::Connection(crate::connection_edit::Event::Begin(
f.tool, f.source,
)));
assert!(f.app.connection.is_none());
begin_surface(&f);
assert_eq!(f.app.project(), &before.decoded.project);
assert_eq!(f.app.document.bundle(), &before.bundle);
assert!(!f.app.document.can_undo());
}
}
fn begin_assert_source_pair(app: &App) {
let compiled = oa_core::source_project::compile_bundle(
app.document.bundle(),
oa_core::source_project::ProjectLimits::default(),
)
.unwrap();
assert_eq!(&compiled.decoded.project, app.project());
assert_eq!(&compiled.bundle, app.document.bundle());
}
#[test]
fn contextual_begin_explorer_draw_retains_visible_mixed_diagram_in_both_directions() {
for layer in [Layer::Oa, Layer::Sa] {
let mut f = begin_fixture(layer, false, true);
// A real Explorer Request::object has no canvas/placement envelope,
// but its generated Draw action still targets the visible current view.
begin_from_popup_origin(&mut f, false);
begin_complete_and_check_history(&mut f);
}
}
@@ -1,339 +0,0 @@
// Proposed tests against c7065db76ecca3faa733ccb5f09b87fd0a0958be.
use super::*;
struct RelocationFixture {
app: App,
source: Id,
target: Id,
node: Id,
other_node: Id,
edge: Id,
}
fn relocation_fixture(source_layer: Layer, target_layer: Layer) -> RelocationFixture {
let mut p = Project::empty();
let work = p
.add_element(Kind::Activity, Layer::Oa, "Observed work", None)
.unwrap();
let other = p
.add_element(Kind::Activity, Layer::Oa, "Other work", None)
.unwrap();
let edge = p.relate(RelationKind::Interaction, "Observed exchange", work, other);
let source = p.ensure_diagram(source_layer);
p.diagrams.get_mut(&source).unwrap().projection = ProjectionKind::Traceability;
let node = p.place(source, work, Position { x: 20., y: 20. });
let other_node = p.place(source, other, Position { x: 500., y: 20. });
let mut destination = p.diagrams[&source].clone();
destination.id = Id::new_v4();
destination.alias = "relocation_destination".into();
destination.name = "Relocation destination".into();
destination.layer = target_layer;
destination.nodes.clear();
let target = destination.id;
p.diagrams.insert(target, destination);
let mut app = App::with_project(p);
app.page = Page::Diagram;
app.layer = source_layer;
app.active_diagram = Some(source);
app.selected = node;
app.refresh_draft();
app.reconcile_multiselect();
assert!(
matches!(app.draft.as_ref().unwrap().kind, crate::forms::RecordKind::Node(d) if d == source)
);
assert!(!app.document.can_undo());
RelocationFixture {
app,
source,
target,
node,
other_node,
edge,
}
}
fn relocation_field(app: &mut App, path: &str, value: impl ToString) {
let _ = app.update(Message::Field(path.into(), value.to_string()));
}
fn relocation_source_pair(app: &App) {
let compiled = oa_core::source_project::compile_bundle(
app.document.bundle(),
oa_core::source_project::ProjectLimits::default(),
)
.unwrap();
assert_eq!(&compiled.decoded.project, app.project());
assert_eq!(&compiled.bundle, app.document.bundle());
}
fn relocation_selection(app: &App, diagram: Id, placement: Id) {
assert_eq!(app.page, Page::Diagram);
assert_eq!(app.active_diagram, Some(diagram));
assert_eq!(app.layer, app.project().diagrams[&diagram].layer);
assert_eq!(app.selected, placement);
assert_eq!(app.selected_node, Some(placement));
let draft = app.draft.as_ref().unwrap();
assert_eq!(draft.original, placement);
assert!(matches!(draft.kind, crate::forms::RecordKind::Node(d) if d == diagram));
}
fn relocation_expected(app: &App, command: Command) -> Document {
let mut expected = Document::from_bundle(
app.document.bundle().clone(),
oa_core::source_project::ProjectLimits::default(),
)
.unwrap();
expected.execute(command).unwrap();
expected
}
fn relocation_history(
app: &mut App,
before: &oa_core::source_project::CompiledSourceProject,
expected: &Document,
) {
assert_eq!(app.project(), expected.project());
assert_eq!(app.document.bundle(), expected.bundle());
assert!(app.document.can_undo());
assert!(!app.document.can_redo());
let _ = app.update(Message::Undo);
assert_eq!(app.project(), &before.decoded.project);
assert_eq!(app.document.bundle(), &before.bundle);
assert!(!app.document.can_undo());
assert!(app.document.can_redo());
let _ = app.update(Message::Redo);
assert_eq!(app.project(), expected.project());
assert_eq!(app.document.bundle(), expected.bundle());
assert!(!app.document.can_redo());
relocation_source_pair(app);
}
#[test]
fn explicit_placement_relocation_follows_actual_owner_with_optional_new_identity() {
for layer in [Layer::Oa, Layer::Sa] {
for reidentify in [false, true] {
let mut f = relocation_fixture(Layer::Oa, layer);
let before = f.app.document.snapshot();
let before_project = before.decoded.project.clone();
let revision = f.app.document.revision();
let id = if reidentify { Id::new_v4() } else { f.node };
let mut expected = before_project.clone();
let mut placement = expected
.diagrams
.get_mut(&f.source)
.unwrap()
.nodes
.remove(&f.node)
.unwrap();
placement.id = id;
expected
.diagrams
.get_mut(&f.target)
.unwrap()
.nodes
.insert(id, placement);
let expected_document = relocation_expected(
&f.app,
Command::Batch(vec![
Command::EditNode {
diagram: f.source,
original: f.node,
node: expected.diagrams[&f.target].nodes[&id].clone(),
},
Command::RelocateNode {
node: id,
diagram: f.target,
},
]),
);
assert_eq!(expected_document.project(), &expected);
relocation_field(&mut f.app, "/diagram", f.target);
if reidentify {
relocation_field(&mut f.app, "/id", id);
}
assert!(f.app.apply(), "{}", f.app.status);
assert_eq!(f.app.project(), &expected);
assert_eq!(f.app.document.revision(), revision + 1);
relocation_selection(&f.app, f.target, id);
relocation_source_pair(&f.app);
assert_eq!(f.app.document.bundle(), expected_document.bundle());
relocation_history(&mut f.app, &before, &expected_document);
}
}
}
#[test]
fn same_owner_placement_position_alias_and_identity_keep_mixed_surface() {
for different_current_view in [false, true] {
for field in ["/position/x", "/alias", "/id"] {
let mut f = relocation_fixture(Layer::Sa, Layer::Oa);
// Ordinary Select plus the diagram picker can retain an independent
// placement Inspector while showing a different saved view. This is
// an App-handler workflow, not a claim of native menu execution.
let _ = f.app.update(Message::Select(f.app.project().id));
let _ = f.app.update(Message::Select(f.node));
let current = if different_current_view {
f.target
} else {
f.source
};
if different_current_view {
let _ = f.app.update(Message::Diagram(Choice {
id: current,
label: "Other current view".into(),
}));
}
assert_eq!(f.app.selected, f.node);
assert_eq!(f.app.active_diagram, Some(current));
assert!(
matches!(f.app.draft.as_ref().unwrap().kind, crate::forms::RecordKind::Node(d) if d == f.source)
);
let before = f.app.document.snapshot();
let revision = f.app.document.revision();
let mut expected = before.decoded.project.clone();
let mut placement = expected
.diagrams
.get_mut(&f.source)
.unwrap()
.nodes
.remove(&f.node)
.unwrap();
let value = match field {
"/position/x" => {
placement.position.x = 123.;
"123".to_owned()
}
"/alias" => {
placement.alias = "renamed_placement".into();
placement.alias.clone()
}
_ => {
placement.id = Id::new_v4();
placement.id.to_string()
}
};
let id = placement.id;
let expected_document = relocation_expected(
&f.app,
Command::EditNode {
diagram: f.source,
original: f.node,
node: placement.clone(),
},
);
expected
.diagrams
.get_mut(&f.source)
.unwrap()
.nodes
.insert(id, placement);
assert_eq!(expected_document.project(), &expected);
relocation_field(&mut f.app, field, value);
assert!(f.app.apply(), "{}", f.app.status);
assert_eq!(f.app.project(), &expected);
assert_eq!(f.app.document.bundle(), expected_document.bundle());
assert_eq!(f.app.document.revision(), revision + 1);
assert_eq!(f.app.page, Page::Diagram);
assert_eq!(f.app.active_diagram, Some(current));
assert_eq!(f.app.layer, f.app.project().diagrams[&current].layer);
assert_eq!(f.app.selected, id);
assert_eq!(
f.app.selected_node,
if different_current_view {
None
} else {
Some(id)
}
);
let draft = f.app.draft.as_ref().unwrap();
assert_eq!(draft.original, id);
assert!(matches!(draft.kind, crate::forms::RecordKind::Node(d) if d == f.source));
relocation_source_pair(&f.app);
relocation_history(&mut f.app, &before, &expected_document);
}
}
}
#[test]
fn accepted_relocation_refuses_queued_old_canvas_target_with_feedback() {
for edge_hit in [false, true] {
let mut f = relocation_fixture(Layer::Oa, Layer::Sa);
let before = f.app.document.snapshot();
let expected_document = relocation_expected(
&f.app,
Command::Batch(vec![
Command::EditNode {
diagram: f.source,
original: f.node,
node: f.app.project().diagrams[&f.source].nodes[&f.node].clone(),
},
Command::RelocateNode {
node: f.node,
diagram: f.target,
},
]),
);
let mut expected = before.decoded.project.clone();
let placement = expected
.diagrams
.get_mut(&f.source)
.unwrap()
.nodes
.remove(&f.node)
.unwrap();
expected
.diagrams
.get_mut(&f.target)
.unwrap()
.nodes
.insert(f.node, placement);
let revision = f.app.document.revision();
let captured = f.app.route_context().unwrap();
relocation_field(&mut f.app, "/diagram", f.target);
let message = if edge_hit {
Message::CanvasSelect(captured, f.edge)
} else {
Message::SelectNode(f.other_node)
};
let _ = f.app.update(message);
assert_eq!(f.app.project(), &expected);
assert_eq!(f.app.document.revision(), revision + 1);
relocation_selection(&f.app, f.target, f.node);
assert_eq!(
f.app.status,
"Properties applied. The view changed; select a visible target again."
);
assert!(
f.app.multiselect_snapshot().is_none_or(|snapshot| {
!snapshot.targets().iter().any(|target| {
matches!(target, crate::diagram_usage::Target::Placement(id) if *id == f.other_node)
|| matches!(target, crate::diagram_usage::Target::Edge(id) if *id == f.edge)
})
}),
"the rejected old hit must not enter the destination selection"
);
assert_eq!(expected_document.project(), &expected);
assert_eq!(f.app.document.bundle(), expected_document.bundle());
relocation_source_pair(&f.app);
relocation_history(&mut f.app, &before, &expected_document);
}
}
#[test]
fn rejected_placement_destination_preserves_owner_model_source_and_draft() {
let mut f = relocation_fixture(Layer::Oa, Layer::Sa);
let before = f.app.document.snapshot();
let revision = f.app.document.revision();
relocation_field(&mut f.app, "/diagram", Id::new_v4());
let draft = serde_json::to_value(f.app.draft.as_ref().unwrap().recovery_snapshot()).unwrap();
assert!(!f.app.apply());
assert_eq!(f.app.project(), &before.decoded.project);
assert_eq!(f.app.document.bundle(), &before.bundle);
assert_eq!(f.app.document.revision(), revision);
assert!(!f.app.document.can_undo());
relocation_selection(&f.app, f.source, f.node);
assert_eq!(
serde_json::to_value(f.app.draft.as_ref().unwrap().recovery_snapshot()).unwrap(),
draft
);
}
-18
View File
@@ -39,24 +39,6 @@ impl Session {
}
}
/// Contextual drawing follows the visible semantic source, not the palette layer.
/// The caller separately validates the menu/context lifetime and pending drafts.
pub fn can_begin(project: &Project, diagram: Id, tool: Tool, source: Id) -> bool {
let Some(diagram) = project.diagrams.get(&diagram) else {
return false;
};
let Some(element) = project.elements.get(&source) else {
return false;
};
diagram.projection.is_architecture()
&& (element.layer() == diagram.layer
|| diagram.projection == oa_core::ProjectionKind::Traceability)
&& Tool::for_layer(element.layer()).contains(&tool)
&& tool.can_start(project, source)
&& (tool != Tool::Allocation || tool.available_in(element.layer(), diagram.projection))
&& visible(&oa_core::projection::project_view(project, diagram.id)).any(|id| id == source)
}
pub fn visible(view: &ProjectedView) -> impl Iterator<Item = Id> + '_ {
view.nodes
.iter()
+6 -9
View File
@@ -993,10 +993,7 @@ impl canvas::Program<Message> for DiagramCanvas<'_> {
world,
));
}
return Some(
Action::publish(Message::CanvasSelect(self.context, port.element))
.and_capture(),
);
return Some(Action::publish(Message::Select(port.element)).and_capture());
}
if let Some(id) = self.hit(view, p) {
let position = self
@@ -1013,7 +1010,7 @@ impl canvas::Program<Message> for DiagramCanvas<'_> {
.hit_label(&scene.routed, p)
.or_else(|| self.hit_association_label(&scene.routed, p))
{
Some(Action::publish(Message::CanvasSelect(self.context, id)).and_capture())
Some(Action::publish(Message::Select(id)).and_capture())
} else if let Some((id, segment)) = self.hit_edge(&scene.routed, p) {
if id == self.selected
&& self.editable
@@ -1029,7 +1026,7 @@ impl canvas::Program<Message> for DiagramCanvas<'_> {
self.begin_route(state, world, Handle::Bend(index), route);
Some(Action::capture())
} else {
Some(Action::publish(Message::CanvasSelect(self.context, id)).and_capture())
Some(Action::publish(Message::Select(id)).and_capture())
}
} else {
state.pan_start = Some((mouse::Button::Left, p));
@@ -2444,7 +2441,7 @@ mod tests {
.unwrap()
.into_inner()
.0;
assert!(matches!(selected,Some(Message::CanvasSelect(_, id)) if id==port.element));
assert!(matches!(selected,Some(Message::Select(id)) if id==port.element));
let request = canvas.context_request(
&state,
click,
@@ -3057,7 +3054,7 @@ mod tests {
let mut state = State::default();
assert_eq!(canvas.hit_association_label(&routed, at), Some(edge));
assert!(
matches!(pointer(&canvas,&mut state,mouse::Event::ButtonPressed(mouse::Button::Left),at),Some(Message::CanvasSelect(_, id)) if id==edge)
matches!(pointer(&canvas,&mut state,mouse::Event::ButtonPressed(mouse::Button::Left),at),Some(Message::Select(id)) if id==edge)
);
assert!(
state.route_drag.is_none(),
@@ -3107,7 +3104,7 @@ mod tests {
let center = scene.routed.edges[&edge].label.center;
let screen = point(center) + canvas.pan;
assert!(
matches!(pointer(&canvas,&mut state,mouse::Event::ButtonPressed(mouse::Button::Left),screen),Some(Message::CanvasSelect(_, id)) if id==edge)
matches!(pointer(&canvas,&mut state,mouse::Event::ButtonPressed(mouse::Button::Left),screen),Some(Message::Select(id)) if id==edge)
);
canvas.selected = edge;
pointer(
@@ -179,7 +179,7 @@ fn coalesced_port_drag_commits_once_through_app_and_source_history() {
zoom,
);
assert_eq!(messages.len(), 2);
assert!(matches!(&messages[0],Message::CanvasSelect(_, id)if *id==output));
assert!(matches!(&messages[0],Message::Select(id)if *id==output));
assert!(
matches!(&messages[1],Message::PortAnchor(port_edit::Event::Commit(_,_,anchor))if anchor.port==output&&anchor.side==PortAnchorSide::Top)
);
@@ -251,7 +251,7 @@ fn menu_captured_motion_must_not_turn_following_click_into_old_port_drag() {
b,
);
let picked = match &press {
Message::CanvasSelect(_, id) => Some(*id),
Message::Select(id) => Some(*id),
_ => None,
};
let release_debug = format!("{release:?}");
@@ -300,7 +300,7 @@ fn drag_from_already_selected_stationary_port_keeps_source_between_batches() {
.filter_map(|e| event(&c, &mut state, e, b))
.collect();
assert!(
matches!(messages.first(),Some(Message::CanvasSelect(_, id)) if *id==output),
matches!(messages.first(),Some(Message::Select(id)) if *id==output),
"Stationary source was lost between batches: {messages:?}"
);
assert!(
@@ -165,7 +165,7 @@ fn nested_owner_palette_selection_drag_and_context_menu_pick_the_same_port() {
.unwrap()
.into_inner()
.0;
assert!(matches!(press, Some(Message::CanvasSelect(_, id)) if id == port.element));
assert!(matches!(press, Some(Message::Select(id)) if id == port.element));
assert_eq!(state.port_drag.as_ref().unwrap().anchor.port, port.element);
let menu = canvas.context_request(&state, at, bounds);
assert_eq!(menu.object, port.element);
+1 -1
View File
@@ -375,7 +375,7 @@ mod tests {
messages.extend(message);
}
}
assert!(matches!(messages.first(), Some(Message::CanvasSelect(_, id)) if *id == port));
assert!(matches!(messages.first(), Some(Message::Select(id)) if *id == port));
assert!(
matches!(messages.last(), Some(Message::PortAnchor(crate::port_edit::Event::Commit(_, _, anchor))) if anchor.port == port && anchor.side == PortAnchorSide::Top)
);
-2
View File
@@ -322,8 +322,6 @@ fn article(topic: Topic) -> Article {
"Show a correspondence between OA and SA",
&[
"Choose OA ↔ SA traceability in View content. Changing this setting does not create counterparts or add their placements, so an existing single-layer diagram can initially look unchanged.",
"Switching back to Architecture or another single-layer projection shows only that view's layer. The other layer's placements, routing and saved visibility remain stored; return to OA ↔ SA traceability to show them again, subject to the view's filters. Selecting a displayed card, port or line and opening its Properties keeps the current view and perspective. Applying ordinary Properties edits keeps that saved architecture view open. Changing a placement's Diagram explicitly moves it and follows its destination. Changes you make to the view's own perspective or identity still take effect. If the app asks you to select again, choose a visible target.",
"In a mixed traceability view, use Draw from a visible source's canvas menu or the Model explorer sidebar on the Architecture page to connect from that source's OA or SA layer without leaving the view. Choose a highlighted compatible visible target; Escape cancels. The palette offers tools for the view's perspective. Opening an endpoint or selecting a record in the Model explorer sidebar follows that record's perspective. Contextual relationship forms keep the view while opening; applying the reviewed relationship can navigate to its result.",
"Use Review layer traces to inspect named Operational and System endpoints and the meaning of their link. This opens the model's layer-trace list with no search restriction; traces between relationships are reviewed here rather than drawn between element cards.",
"In the chosen architecture view, use Add existing elements. The chooser offers compatible elements from both layers. Select an existing endpoint and click its position in the canvas; repeat for the other end if needed. This adds a placement, not a new semantic element or trace. For port correspondences, place the ports' owners.",
"The view shows correspondence lines alongside ordinary relationships. The shown-line count reflects the current projection and visibility settings, not trace coverage. If a line is hidden, use View visibility and inspect Saved view visibility; placing an endpoint does not remove explicit hides or filters.",
+4 -1
View File
@@ -557,7 +557,10 @@ pub fn popup(app: &App) -> Option<Menu<Message>> {
&format!("Draw {}", tool.label().to_lowercase()),
Message::Connection(crate::connection_edit::Event::Begin(*tool, id)),
app.active_diagram.is_some_and(|diagram| {
crate::connection_edit::can_begin(p, diagram, *tool, id)
crate::connection_edit::visible(&projection::project_view(
p, diagram,
))
.any(|element| element == id)
}),
));
}
@@ -372,150 +372,3 @@ async fn source6_http_explicit_communication_ports_preview_apply_and_restart_pre
assert_pair(&reopened);
server.stop().await;
}
#[tokio::test]
async fn mixed_traceability_projection_roundtrip_preserves_dormant_http_state() {
let f = fixture();
let mut model = compile_bundle(&f.bundle, ProjectLimits::default())
.unwrap()
.decoded
.project;
let view = model.diagrams.get_mut(&f.diagram).unwrap();
view.layer = Layer::Sa;
view.projection = ProjectionKind::Traceability;
// The OA placements and category are intentionally opposite the view's layer.
view.view_options.hidden_edges = vec![f.mean];
for (index, element) in f.sa.into_iter().enumerate() {
model.place(
f.diagram,
element,
Position {
x: index as f32 * 400.,
y: 300.,
},
);
}
let mut bundle = f.bundle.clone();
bundle.files.insert(
bundle.entry.clone(),
format!(
"// Authored source-6 engineering rationale\n{}",
oa_core::language::format(&model).replacen(
"{\n",
"{\n include \"notes.arcadia\";\n",
1
)
),
);
let directory = tempfile::tempdir().unwrap();
let server = TestServer::start(directory.path(), None).await;
let original = import(&server, &bundle).await;
assert_pair(&original);
let resource = original.resource_id;
let mixed = oa_core::projection::project_view(&original.model, f.diagram);
assert!(
f.oa.iter()
.chain(&f.sa)
.all(|id| mixed.nodes.iter().any(|n| n.element == *id))
);
let mut ordinary = original.model.diagrams[&f.diagram].clone();
ordinary.projection = ProjectionKind::Allocation;
let response = transact(
&server,
&original,
vec![Command::EditDiagram(f.diagram, ordinary.clone())],
)
.await;
let status = response.status();
let etag = response
.headers()
.get("etag")
.map(|v| v.to_str().unwrap().to_owned());
let result: Value = response.json().await.unwrap();
assert_eq!(status, StatusCode::OK, "{result}");
assert_eq!(result["changed"], true);
assert_eq!(result["dry_run"], false);
assert_eq!(result["revision"], original.revision + 1);
let changed = server.get(resource).await;
assert_eq!(etag.as_deref(), Some(changed.etag().as_str()));
let mut expected = original.model.clone();
expected.diagrams.insert(f.diagram, ordinary.clone());
assert_eq!(
changed.model, expected,
"only the projection changes; dormant state is retained"
);
assert_pair(&changed);
assert_eq!(
changed.source_project.files["notes.arcadia"],
bundle.files["notes.arcadia"]
);
assert!(
changed.source_project.files[&bundle.entry]
.starts_with("// Authored source-6 engineering rationale\n")
);
let projected = oa_core::projection::project_view(&changed.model, f.diagram);
assert!(projected.nodes.iter().all(|n| !f.oa.contains(&n.element)));
assert!(
f.sa.iter()
.all(|id| projected.nodes.iter().any(|n| n.element == *id))
);
// A dormant option still must name an existing category. Rejection must not
// advance the ETag or rewrite either side of the persisted model/source pair.
let disk = directory.path().join(format!("{resource}.json"));
let accepted_bytes = std::fs::read(&disk).unwrap();
ordinary.view_options.participant_categories = CategorySelection::Any(vec![Id::new_v4()]);
let rejected = transact(
&server,
&changed,
vec![Command::EditDiagram(f.diagram, ordinary)],
)
.await;
assert_eq!(rejected.status(), StatusCode::UNPROCESSABLE_ENTITY);
let retained = server.get(resource).await;
assert_eq!(retained.etag(), changed.etag());
assert_eq!(
serde_json::to_value(&retained).unwrap(),
serde_json::to_value(&changed).unwrap()
);
assert_eq!(std::fs::read(&disk).unwrap(), accepted_bytes);
server.stop().await;
let server = TestServer::start(directory.path(), None).await;
let reopened = server.get(resource).await;
assert_eq!(
serde_json::to_value(&reopened).unwrap(),
serde_json::to_value(&changed).unwrap()
);
assert_pair(&reopened);
let response = transact(
&server,
&reopened,
vec![Command::EditDiagram(
f.diagram,
original.model.diagrams[&f.diagram].clone(),
)],
)
.await;
let status = response.status();
let result: Value = response.json().await.unwrap();
assert_eq!(status, StatusCode::OK, "{result}");
assert_eq!(result["changed"], true);
assert_eq!(result["revision"], changed.revision + 1);
let restored = server.get(resource).await;
assert_eq!(restored.model, original.model);
assert_pair(&restored);
assert_eq!(
restored.source_project.files["notes.arcadia"],
bundle.files["notes.arcadia"]
);
let projected = oa_core::projection::project_view(&restored.model, f.diagram);
assert!(
f.oa.iter()
.chain(&f.sa)
.all(|id| projected.nodes.iter().any(|n| n.element == *id))
);
assert!(!projected.edges.iter().any(|edge| edge.id == f.mean));
server.stop().await;
}
+1 -1
View File
@@ -147,7 +147,7 @@ schemas["ViewOptions"] = obj({
"coverage": {**ref("Coverage"), "default": "All"},
"work_contexts": {**ref("ContextSelection"), "default": "All"},
"exchange_labels": {**ref("ExchangeLabels"), "default": "Name"}}, [])
schemas["ViewOptions"]["description"] = "Presentation only. Independent predicates intersect; category members union within a family. Only([]) hides all relations and traces. Any([]) remains an explicit empty selection. String projections preserve both-layer hidden/category/context references and latent placements; only Traceability renders both layers. Typed StateMachine/Scenario views retain layer-scoped admission. Filtering preserves unfiltered automatic port positions."
schemas["ViewOptions"]["description"] = "Presentation only. Independent predicates intersect; category members union within a family. Only([]) hides all relations and traces. Any([]) remains an explicit empty selection. Traceability permits both-layer hidden/category/context references; ordinary projections require the diagram layer. Filtering preserves unfiltered automatic port positions."
schemas["Diagram"]["properties"]["view_options"] = {**ref("ViewOptions"), "default": {}}
def upsert_variant(variants, name, value):
+2 -9
View File
@@ -570,15 +570,8 @@ retain their identities and contracts; migration does not silently replace them.
``coverage`` and ``work_contexts``, and ``Name`` for ``exchange_labels``.
``{"Only":[]}`` hides all relation and trace lines; ``{"Any":[]}`` preserves
an explicit empty category/context selection. Category members union within
each family, and independent filters intersect. String projections permit saved
references and placements from both layers. Only ``Traceability`` renders both;
other string projections filter to the diagram's declared layer. ``EditDiagram``
may change between these projections without deleting latent placements, routes
or visibility choices. Returning to Traceability restores their applicability.
Typed StateMachine/Scenario views retain their strict membership and layer checks.
Missing objects, wrong reference kinds/families and duplicate selections remain
invalid. Frozen versions 1–6 keep their prior layer admission before migration;
older version-7 implementations may reject these newly accepted ordinary views.
each family, and independent filters intersect. ``Traceability`` permits
references from both layers; ordinary projections require the diagram's layer.
These presentation choices preserve semantic records, routes, placements and
the automatic port positions derived from unfiltered eligible exchanges.
+2 -20
View File
@@ -409,12 +409,8 @@ filters rather than widening them to All.
Traceability views retain their established two-layer projection: default options
preserve eligible OA and SA lines. Their hidden-edge, category and context
references may therefore select either layer. Ordinary string projections retain
these references from either layer as saved presentation choices; they still
render only their declared layer. Switching projections MUST preserve those
choices, including explicit empty selections. Existing-object, reference-kind,
family and uniqueness constraints still apply. Typed StateMachine and Scenario
views retain layer-scoped reference admission. A filter never hides the other
references may therefore select either layer. All ordinary projections require
these references in the declared diagram layer. A filter never hides the other
layer merely because the Traceability diagram has an OA or SA layer label.
Versions 1–5 reject the new relation, category constructor, diagram field (even
@@ -1077,20 +1073,6 @@ capabilities with their involvement, exploitation, include, extend and
generalization links. Decomposition and Overview retain general containment;
Overview also exposes all relationships. These string projections hide
inapplicable placements without deleting them or changing semantic records.
This includes opposite-layer placements retained after leaving Traceability.
Changing between string projections changes only the projection value; placement
identities, positions, sizes, collapsed state, port anchors, routes and visibility
choices remain authored. Returning to Traceability restores their applicability.
Ordinary views MUST NOT render opposite-layer nodes, parents, ports or lines.
This current-version admission correction does not broaden frozen source/JSON
versions 1–6: their layer-scoped placement and visibility checks remain required
before migration. Earlier version-7 implementations can reject ordinary views
containing these retained opposite-layer records.
The explicit RelocateNode operation moves a placement to a different diagram
only when its element belongs to the destination layer or the destination is
Traceability. Retaining an existing hidden placement through a projection change
does not authorize a relocation that immediately hides it in another layer.
A StateMachine projection MUST reference an existing StateMachine in the
diagram's layer. Its placements MUST refer to that machine, its Regions, or
+1 -1
View File
@@ -12490,7 +12490,7 @@
},
"required": [],
"additionalProperties": false,
"description": "Presentation only. Independent predicates intersect; category members union within a family. Only([]) hides all relations and traces. Any([]) remains an explicit empty selection. String projections preserve both-layer hidden/category/context references and latent placements; only Traceability renders both layers. Typed StateMachine/Scenario views retain layer-scoped admission. Filtering preserves unfiltered automatic port positions."
"description": "Presentation only. Independent predicates intersect; category members union within a family. Only([]) hides all relations and traces. Any([]) remains an explicit empty selection. Traceability permits both-layer hidden/category/context references; ordinary projections require the diagram layer. Filtering preserves unfiltered automatic port positions."
},
"CommunicationPortChoice": {
"oneOf": [
+98 -26
View File
@@ -1,31 +1,5 @@
# Architectural and product decisions
## Canvas inspection, contextual drawing and explicit relocation — 2026-10-02
An ordinary Properties Apply retains the current Architecture diagram, layer and
projection. An accepted edit to the active Diagram itself uses its resulting
identity, layer and projection. A queued canvas target is revalidated against that retained surface
after Apply; an obsolete target is refused with feedback instead of navigating
or selecting hidden content. Explicit Select/Open still follows the requested
model target. An accepted change to a placement's actual owning diagram is also
explicit navigation: it follows the committed destination and resulting UUID.
Changing only geometry, alias or UUID in the same owning diagram retains the
inspection surface. Invalid or missing ownership never grants this exception.
Contextual Draw uses the invoked menu target as its semantic source and the current
diagram as its editing surface. Shared admission checks the diagram projection,
visibility, family and semantic endpoint roles before arming. Restart clears the
pending source while retaining an admitted tool. Explicit model navigation and
post-review creation navigation retain their separate behavior. This avoids
silently conflating an opposite-layer inspection with a request to change views.
The source repairs at c706 and 6018, the failed c706 workspace attempt, the
full-model/source invariants and corrected Windows acceptance are attributed in
[the navigation checkpoint](navigation-validation-20261002.md). The simpler
blanket surface restoration was rejected when the unchanged relocation test
showed that it hid successful explicit moves. This is bounded Architecture
acceptance; diagram label readability and the complete OA/SA goal remain open.
## Private source composition decision — 2026-09-23
Adopt the opaque validated original-span rename batch as the next private
@@ -524,3 +498,101 @@ editing remains separate from semantic reconnect. The [decision and evidence
map](edge-attachments-20260922.md) records alternatives, lifecycle rules,
independent falsifiers, evaluators and the Process/Scenario applicability limits.
This is branch-specific work; it does not establish an integrated release.
## Retain released routing after independent corpus comparison — 2026-09-25
Issue #32's [fixed comparison](experiments/routing-independent-20260925/results.md)
rejects adoption of target-join J and bounded-lane L at this checkpoint. Optimized
timings pass the declared screen, but a fixed edge-ID permutation worsens shared
runs under both candidates; workshop label conflicts also increase. L's large
workshop overlap improvement does not establish native traceability. Keep released
`715036e` routing for the separate explicit Arrange-reset candidate. Preserve the
experiments and stop tuning this corpus. Reconsider adoption only with meaningful
named trace-task improvements, no newly ambiguous task, and appropriate runtime
evidence. This is a branch-specific decision, not a completed Arrange feature or
integrated release; the linked record retains rejected alternatives and limits.
## Placement must respect derived ports and mandatory exits — 2026-09-25
Branch-specific issue32 research, not an integrated release. The bounded
[placement comparison](experiments/connection-layout-reference-20260925/results.md)
shows that a node/label layout followed by independent port selection and routing
is not a faithful transfer of a port-constrained engine result. Automatic ports
change sides/slots after positions move even though authored anchors stay exact.
The sparse reference's20-unit gap cannot contain the released router's18-unit
port lead plus12-unit obstacle clearance. The sole endpoint pair is rejected
before path search; increasing search work would not repair that placement.
Keep both candidates experimental. A future candidate needs explicit final-port
and mandatory-lead compatibility checks, compound/undirected semantics, and the
existing preservation/native trace gates. Raw engine paths avoiding uninflated
node interiors do not prove compatibility with our inflated obstacles and leads.
Do not silently shorten those leads, move authored anchors, or persist external
generated bends to make a comparison pass. Revisit engine integration only with
a faithful geometry interface and observed usability benefit.
## Lead compatibility does not establish layout quality — 2026-09-25
The [bounded separation experiment](experiments/placement-clearance-20260925/results.md)
at07405126 fixes the exact sparse mandatory-lead conflict by moving eight nodes
down11 units. It removes NoClearPath and node penetration while preserving model
records, anchors and repeated-call stability. Existing route-control output is
byte-identical to released routing. However crossings20→22, shared length666→761
and label/stroke intersections0→1 prevent a general readability improvement
claim. Keep this flat-only repair isolated; do not connect it to production
Arrange or increase its rounds to tune away the result. Use actual endpoint
compatibility as a necessary placement constraint in a future initial coordinate
assignment with connection ordering and corridor/label space. Compound geometry,
automatic side changes and native tracing remain separate required evidence.
This is branch-specific experimental evidence, not an integrated capability.
## Keep cyclic groups coherent before assigning global ranks — 2026-09-25
The [SCC placement experiment](experiments/scc-placement-20260925/results.md)
at `2426d286` supports condensation of sibling-subtree cycles as a useful
structural step. Independent reconstruction finds four ten-node components in
the sparse case. Compact blocks remove the old layered prototype's shared top
corridor: raw pairwise sharing falls from 40,522 to 32, with fewer crossings and
less area than that prototype. The released router is unchanged. The full
comparison includes original and Arrange-reset controls; this is not a universal
improvement across all measures or scenes.
Preserve effective geometry at the placement-transfer boundary. An old
projected size can depend on children that subsequently move inward; retaining
the old authored size then loses an explicitly requested minimum. The corrected
example importer makes one conditional simultaneous minimum restoration and
still demands exact final projection. All 14 frozen cases now complete; the
13 prior successful outputs remain exactly unchanged, with conservation,
roundtrip and ten-call checks recorded. This is an experimental transfer
contract, not a production command or history integration.
Do not adopt the whole candidate yet. The two-container case still ignores
external-neighbor order within each parent and is worse than the old layered
zero-sharing witness. Sparse residual label conflicts, folded route retracing,
bipartite sharing and area tradeoffs remain distinct issues. The next bounded
comparison should preserve coherent SCC ranks while using actual cross-boundary
neighbors to order siblings. Treat label association separately from route
sharing, preserve the full quality vector, and retain native tracing as a later
acceptance gate. No global gap or routing-penalty sweep follows from this result.
## Incoming boundary order is useful but not sufficient — 2026-09-25
The [boundary-order comparison](experiments/boundary-order-20260925/results.md)
at3e21c1bc preserves SCC blocks/ranks and orders same-rank child blocks using
incoming external source centers. Dependency components provide deterministic
propagation; cycles use one simultaneous snapshot. It fixes the declared
simple two-container tracing witness (sharing164→0) and three-parent chain,
while preserving all ten flat Projects/metrics exactly. All20 candidates pass
independent full-Project conservation. Native saved-Project inspection confirms
the simple trace improvement without establishing a native planner integration.
Retain the mechanism experimentally. Folded workshop sharing726.5→1077.5,
expanded crossing6→7, and anchored/local-SCC label intersections prevent
unconditional adoption. Native anchored labels remain readable while their
backgrounds interrupt the parent border: geometric collision counts require
severity and ownership interpretation, not silent removal. Full member displacement
must be checked in f64 before final f32 rounding; checking only a rounded delta
misses large-origin errors. No further parameter sweep or release change is
justified by this checkpoint. Production selection, label handling and integration
remain open; the user requested a stop after publication.
-21
View File
@@ -1,24 +1,3 @@
# Navigation contribution checkpoint — 2026-10-02 UTC
Verified production source **6018ae30b9296b9f6b37d7fc038175080d15fafb** preserves
ordinary opposite-layer Properties inspection and contextual drawing while
following accepted explicit placement relocation. Windows full workspace tests
pass **2,236 / 0 failures / 8 existing ignores**; formatting, strict workspace
all-targets Clippy and matching desktop/server/CLI builds pass. Native creation,
projection/history, corrected relocation and saved-source reopen evidence retain
their actual c706/6018 attribution and independent reviews.
[Source, commands, portable evidence and limits](navigation-validation-20261002.md)
are authoritative. The earlier c706 workspace failure remains recorded.
This is a contribution checkpoint, not the complete OA/SA application or an
integrated Help/Save release. Label ellipsis/readability and broader usability
remain open. **Linux/WSL is still excluded by the user's explicit pause.**
[Issue48](http://217.177.34.108:3000/sorokin.oleg.edu/ARCADIA-Studio/issues/48)
coordinates the subsequent verified union with delivered Save/Help; ownership,
live status and release decisions belong there. Existing released versions are
unchanged. Earlier integrated and contribution checkpoints follow with their
original source and acceptance boundaries.
# Integrated prerelease source checkpoint — 2026-09-24 UTC
This branch assembles peer contributions for the Windows prerelease in issue37.
@@ -0,0 +1,160 @@
# Automatic layout investigation
Issue [32](http://217.177.34.108:3000/sorokin.oleg.edu/ARCADIA-Studio/issues/32)
owns scope, ownership, milestones and integration status. This is an experiment
record above released `715036e132677dd2bc2fb37ed316d93dc9987aec`, not an accepted
replacement layout contract or a completed fix. User-owned requirements remain
in `PROJECT_GOAL.md` and the issue.
The [first executed experiment](experiments/auto-layout-20260925/results-01.md)
records nine Windows fixtures at `e7deea70`: hierarchy/label improvements,
repeat stability, and two shared-connector regressions. The candidate remains
experimental; successful execution is not acceptance of a replacement command.
The [target-join experiment](experiments/auto-layout-20260925/results-j.md)
isolates a routing score correction with fixed Projects/nodes/ports/endpoints.
Its 185 regression checks pass, but an original-scene overlap regression and
additional bipartite T contacts prevent a general readability acceptance.
The [completed scoring/lane comparison](experiments/auto-layout-20260925/results-lanes.md)
records J/P/PL/L and closes this fixture-tuning experiment. L passes 188 selected
checks and strict core Clippy, but residual ambiguity and a runtime cost signal
require broader, independently selected evidence before production adoption.
The [frozen independent corpus comparison](experiments/routing-independent-20260925/results.md)
passes its optimized timing screen but exposes additional ID-order and label
tradeoffs. Retain released routing for the separate route-reset work; J/L remain
experiments without native traceability acceptance. Its complete inputs, raw
receipts, corrected evaluator and independent reviews are recorded together.
## Question and existing evidence
Can explicit Auto-layout produce a readable arrangement that accounts for
connections and their labels, including removal of stale bends, while preserving
semantic endpoints, containment/allocation, source parity and reversible editing?
The current `session::layout` sizes the containment forest, sorts roots using
directed edges and alias tie breaks, places four roots per row, and translates
authored route points only when both actual endpoints move equally. Its authored
route preservation is deliberate and tested in `tests/release_routing.rs` and
`tests/state_diagrams.rs`; the user's requested automatic bend replacement needs
an explicit amendment, not a silently weakened test.
The [source map](experiments/auto-layout-20260925/source-evaluator-map.md)
identifies the actual callers and preserved input/source/API contracts. The
[primary-source research](experiments/auto-layout-20260925/research.md) compares
Sirius/Capella workflows, ELK and reuse of the existing Rust router. The
[candidate critique](experiments/auto-layout-20260925/candidate-critique.md)
records unresolved design gaps before any production choice.
The existing `routing` module already supplies bounded deterministic orthogonal
search, node/header obstacles, fixed semantic port endpoints, crossing/shared-run
costs and automatic label collision search. Automatic elbows are derived, not
persisted. The investigation should reuse this capability unless experiments
show a concrete reason to replace it. Process topology rendering is a different
path; its reported overlapping routes do not alone prove an Auto-layout defect.
## Candidate decisions, not yet accepted
Compare these materially different choices against visible route/label quality,
semantic conservation, predictable user control, runtime/dependency cost,
determinism, model size and maintainability:
1. Retain node arrangement and only reset effective visible route/label constraints.
This is a useful control, but cannot by itself optimize poor node ordering.
2. Use a hierarchy-aware layered node arrangement and the existing router, with
label-aware spacing and bounded crossing-reduction sweeps. Explicit Auto-layout
can replace effective visible pins/label offsets; ordinary drag keeps its
existing preservation rules. This is a candidate, not a chosen implementation.
3. Adopt a mature external layout engine, accounting for ports, labels, nested
containers, runtime packaging and coordinate conversion. Evidence must justify
the integration/dependency cost over reusing the current router.
Open contract questions include hidden/folded routes, authored attachment and port
anchors, undirected and cyclic graphs, nested cross-boundary edges, repeated
layout stability, and the observable treatment of exhausted search/poor results.
No option may change semantic endpoints, ownership, allocation, expressions,
relation kind or source identity to improve a drawing. Other views must remain
unchanged, and one Undo on clean input must recover the complete pre-layout
project. Existing application behavior applies an unrelated pending property
draft first; that separate Apply is not silently folded into Layout's history.
The initial experiment retains the current command as the baseline and compares
reset-only with a layered prototype isolated under `examples/auto_layout_probe`.
Neither prototype changes production behavior. Effective reset eligibility must
come from the pre-layout projection and actual endpoint identities; merely being
drawn does not make a promoted route's dormant metadata eligible. Explicit label
reset means both fraction and offset, while semantic text and endpoint/port
attachments remain fixed. Production adoption remains conditional on the
critic's hierarchy, stability, direction, measurement and global-work checks.
The first [probe recipe](experiments/auto-layout-20260925/probe-notes.md) covers
nine small SA FunctionalDataflow fixtures. Its [independent source review](
experiments/auto-layout-20260925/probe-review.md) found no blocker to that finite
diagnostic run and lists important evaluator limitations. After that review,
the parent inspected the added ten-call prototype stability loop and formatted
the examples; the loop reports every differing/error result without a quality
pass. No production file was changed. The experiment intentionally does not
establish folded/filtered, State, Allocation, source-bundle or native acceptance.
## Evaluator before implementation
Use independently specified small graphs and real project views. Separate proper
crossings, collinear shared runs, obstacle collisions, label/card collisions,
label/label collisions, routing warnings, bends, length and occupied area. A low
crossing count alone can hide coincident edges or enormous drawings. Exempt only
explicitly documented intentional containment and common endpoint geometry;
never discard a failure because it is inconvenient to the proposed algorithm.
Compare the current command and the reset-only control before choosing a design.
Include a chain longer than the current four-column row, diamond/bipartite cases,
nested cross-boundary exchanges, stale authored detours/labels, fixed ports,
cycles/self-loops and disconnected components. Check deterministic repeat calls,
idempotence, model/source round trips and complete Undo/Redo. A probe is evidence
about its named fixtures, not universal graph quality or native usability.
Actual representative Windows workflows, including meaningful window/zoom sizes,
selection and saved reopen, follow an implemented candidate. Linux remains
excluded by the current user instruction. The separately published State label
candidate in issue36 is not silently treated as integrated into this base.
## Placement/reference compatibility checkpoint
The [fixed external placement comparison](experiments/connection-layout-reference-20260925/results.md)
tests the old native prototype and elkjs0.12.0 positions through the released
router on the same independent corpus. Mixed placement metrics improve, but
automatic ports differ from the engine's assumptions; the sparse candidate
violates mandatory port-exit clearance and falls back before path search.
Both workshop reference cases reject an unsupported undirected edge. Neither
candidate is adopted. The next interface must account for final automatic
ports and mandatory leads, not only node/label boxes. All results remain
branch-specific; Gitea holds publication and integration status.
The follow-up [mandatory-lead separation experiment](experiments/placement-clearance-20260925/results.md)
supplies that necessary check using the router's actual endpoint choices. One
bounded flat repair removes the sparse hard routing failure, but worsens crossing,
shared-run and label/stroke measures. Preserve the compatibility result without
adopting this repair as Arrange. Initial connection ordering and reserved routing/
label space, with compound handling, remain the next planner questions; more
repair iterations or router penalty tuning are not supported by this evidence.
The [coherent cyclic-group experiment](experiments/scc-placement-20260925/results.md)
then isolates the old native candidate's rank-stretching problem. Sparse sharing
falls from 40,522 to 32 with unchanged released routing. A corrected importer
preserves requested effective container minima after child movement; all 14
frozen cases complete and the 13 previously successful results remain exact.
This is useful structural evidence, not an Arrange replacement. Cross-boundary
child ordering, local label conflicts and folded backtracking remain explicit
witnesses. Source, failed/successful runs, conservation and independent reviews
are linked from the result. Gitea remains authoritative for live ownership and
publication status; no integrated checkpoint or release change is implied.
The [incoming boundary-order experiment](experiments/boundary-order-20260925/results.md)
resolves the exact simple two-container tracing defect and preserves all flat
SCC results. Twenty supported cases retain model/anchor/block invariants, but
workshop sharing/crossing and label tradeoffs remain. Saved-Project native
inspection confirms the simple benefit and limits the severity interpretation
of anchored label intersections. This is a published research checkpoint, not
an integrated Arrange replacement. Gitea records handoff and the user-requested
pause; no further experiment is authorized during that pause.
-11
View File
@@ -1,11 +0,0 @@
# Connection tool icons
Branch candidate for [#41](http://217.177.34.108:3000/sorokin.oleg.edu/ARCADIA-Studio/issues/41), based on published Windows preview source `715036e132677dd2bc2fb37ed316d93dc9987aec`. This is not integrated or native acceptance.
The reported collapsed palette repeats one arrow for three different tools. Extend the existing Rust/Iced canvas glyphs at their actual 16×16 size: work endpoints plus an arrow for Interaction/Functional exchange, neutral participant endpoints for Communication means, facing component ports for Boundary exchange, and work entering a performer for Allocation. The first pair never coexist in one perspective. Unoriented generic tools must not imply a fixed direction.
Letter badges were rejected as language-dependent and cramped. Different arrow/dash styles risk confusing tool identity with relation direction. Native endpoint/container shapes fit the existing icon system without font or asset dependencies. Preserve button dimensions, explicit labels/tooltips, ordering, messages, availability and selection rules.
Independent source critique found no blocker and confirmed the tool semantics and Iced API use. Actual-size legibility remains unverified, particularly the exchange arrow, port details and possible download/import interpretation of Allocation. Verify enabled, disabled, hovered and selected palettes in both perspectives, expansion/collapse, tool activation, representative relation creation and cancellation. Existing keyboard/accessibility behavior must be checked, not inferred from hover labels. Reuse semantic connection tests; geometry hashes alone are not an identification or usability evaluator.
Linux remains excluded by the existing user pause. Live ownership and integration status belong to the linked issue.
@@ -0,0 +1,38 @@
# #32 candidate critique — 2026-09-25
Read-only adversarial review of `harness/auto-layout-investigation-20260925.md` in the715036e-based isolated checkout, against the source/evaluator map. This critiques candidate choices; no implementation, execution or acceptance is claimed.
## Recommendation
Use the existing explicit **Command::Layout / Auto-layout / Arrange elements** operation for the new automatic behavior. The user specifically asks that operation to replace bends and consider labels/intersections. A second command/UI choice would preserve the obsolete behavior as a competing default without demonstrated need. Deliberately amend its documented contract and focused preservation tests, retain exact Undo, and retain existing ordinary drag/Arrange-next-drag contracts. A new command becomes warranted only if concrete compatibility/user workflows require both automatic policies, not merely because a test currently asserts old behavior.
Prefer comparing candidate1 reset-only against candidate2 layered siblings plus the existing router. No external engine is justified yet. The existing router already creates derived elbows, so reset does not require writing every solver corner as a persisted hard pin. Keep generated corners derived initially; the saved node placement plus remaining explicit attachments determines their repeatable reconstruction.
Recommended explicit Layout route contract: identify eligible visible, non-promoted edges from the **pre-layout projection**, remove their stored waypoints and label override (fraction and offset together), retain source/target attachments and node port anchors. Retain unrelated/hidden/promoted route records with existing equal-delta endpoint-owner translation. Do not call broad Reset routing if that also clears attachments. Preserve stable route-list order and remove an emptied record only under an explicit canonical no-metadata rule. Layout-generated record churn is not progress.
## Blocking design gaps before accepting candidate2
1. **Eligibility must be frozen once.** Visible edges, semantic endpoint-owner placements and promoted endpoints are different sets. Compute the reset set before node movement using projection visibility plus actual owner identity, not “every stored edge” or endpoints merely present in `d.nodes`. A filtered/hidden edge can retain a valid route; a folded edge may be drawn from an ancestor while its own route is suspended. Recomputing eligibility mid-layout risks erasing metadata when visibility/promotion changes. Small experiment: parallel visible/filtered edges, one folded child endpoint, and one both-endpoints-fold-to-same-anchor edge, all with distinctive pins/labels/attachments. Require only declared reset members to change, and retained pins to translate only through the existing owner-delta rule. Unfold/unfilter must reveal the retained custom route.
2. **Two forests are needed conceptually, not two model hierarchies.** The authored projection-parent forest includes folded descendants; the rendered topology may omit them. Reordering only rendered nodes can leave hidden children outside resized parents or leave future expand geometry broken. Conversely, expanding all hidden content for outer layout wastes space and ignores the actual view. Decide visible sibling arrangement versus hidden subtree placement/reservation explicitly. Lift each rendered inter-subtree edge to the relevant direct children at its lowest common ancestor; do not flatten every edge to roots (loses nested ordering) or count the same edge at every depth as independent demand. Small experiment: nested A/B children with crossed exchanges, folded A, external sibling C and a State entry/exit boundary. Check containment/header/port-footer rules before and after expand, with semantic parents/allocations exact.
3. **Idempotence will fail if spacing compounds current sizes.** Current measure uses each authored `node.size` as a lower bound. If label clearance expands a container and the next call adds the same margin again, repeated Arrange grows indefinitely. Label spacing based on previous routed bounds can similarly oscillate. Decide whether current sizes are immutable minima, or may shrink; the latter exceeds today's behavior and needs justification. Compute desired padding from content minima with `max`, not repeated addition to an already padded size. Small experiment: ten consecutive calls on nested long-label/parallel-edge fixtures, comparing full Projects after first vs second and sizes after tenth; repeat after Save/reparse. A two-call-only test can miss alternating period-two layouts.
4. **Cycle/direction policy is not settled by “stable sweeps”.** Some projected edges are undirected; direction may be semantic or merely an endpoint representation. Use directed topology where justified, deterministic SCC treatment for cycles, and explicit undirected ordering inputs. Do not arbitrarily make a Capability/Traceability edge's serialized orientation a mandatory rank. Freeze alias/identity tie rules; crossing sweeps require a stable acceptance tie-break to avoid ping-pong. Small experiment: directed diamond+return, reversed undirected endpoint records, self-loop, parallel/reversed edges and disconnected isolates. Permute input records; independently require identical output for identical IDs and stable quality under UUID changes (not necessarily identical coordinates under reidentification).
5. **Label reset and label-aware spacing need different evidence.** Clearing authored overrides is a reasonable explicit-auto interpretation but must be announced, since issue32 says “take into account” labels, not explicitly erase them. Retain semantic text/typed references; only the target view's override changes. A stale far-away label is a concrete reset fixture; an intentionally constrained attachment/label case tests whether residual collisions are honestly reported. Base715 label widths are scalar estimates, and State title/multiline geometry is known inaccurate without36. Do not tune spacing to claim accurate measured-State acceptance on715. First isolate ordinary/association labels; use conservative declared bounds for experiments, then rerun State B/W/U once36 integrates. Font-free core/API parity forbids quietly using desktop-only font results inside Command::Layout.
6. **Bounded sweeps do not alone bound runtime.** Each complete route evaluation invokes previous-edge interactions, node obstacles and label comparisons; repeated per-sibling trial routing can multiply this cost by nodes×sweeps. Lifting topology naively can multiply edges×depth. Define global deterministic work budgets for candidate ordering/scoring, not just a fixed iteration count, and retain the best valid candidate on exhaustion. Route only a bounded number of whole candidates; cheap crossing surrogates can rank candidates but final acceptance must inspect actual geometry. Small experiment: a fixed family increasing sibling count/edge density/nesting, recording candidate count, comparison/work counters and wall time; include no-edge/isolate cases so they do not pay quadratic routing costs. No hard latency threshold should be fabricated from one machine.
## Input/source/API contract checks
- Clean whole-view Layout should create one reversible model/source transaction. `app.rs` applies a pending property draft before executing Layout, potentially as a separate transaction; do not accidentally promise one Undo reverses both an unrelated draft Apply and Layout. Use a clean-input one-step Undo oracle and a separate valid/invalid pending-draft admission test. Keep diagram UUID remapping, stale selection handling and disabled/pending form policy.
- `prepare_command`/Document/source bundle/REST dry-run must produce the same Project for the same inputs without GUI fonts, caches or selection. A frontend-only rearrangement would fail this. Roundtrip the stored record representation, check unrelated source files/comments remain exact, and recover the exact old source/model with Undo—not merely visually similar positions.
- Keep Allocation Arrange-next-drag, ordinary group translation, semantic ports, reconnect, Hide/show, collapse flags and other diagrams unchanged. A route reset belongs only to explicit Layout. A failed candidate must not clear history or leave a partially reset diagram.
- Native validation later needs visible controls, Fit/export agreement, actual label containment and route readability, plus Save/reopen/Undo. Do not extend the implementation merely to expose a new State Auto-layout control before a demonstrated workflow need; shared State initialization still needs regression coverage.
## Smallest discriminating experiment set
Freeze six hand-built cases before implementation: (A) stale dogleg+far label with fixed attachments, (B) two-container reversed sibling connections with a known zero-crossing witness, (C) nested folded/filtered route-conservation sentinel, (D) cycle/self-loop/parallel/undirected variants, (E) long ordinary/association labels with feasible compact clearance, (F) dense bounded stress. Compare existing Layout, reset-only, and a layered prototype on the same independent vector: invalid overlap/penetration, label collisions, proper crossings, positive collinear overlap, turns, path length/stretch, area and routing warnings. Include ten-call idempotence, exact other-view/semantic conservation, source roundtrip and Undo/Redo.
Candidate2 earns selection only if it materially improves at least B/E over reset-only without regressing the hard invariants or bounded workload. A tie favors the smaller candidate. A nonplanar or contradictory-fixed-port fixture may retain crossings; zero-crossing-by-overlap, giant detours, hidden edges or clipped labels is not a solution. Failure on a named fixture should revise the exact layer/policy implicated, not trigger an automatic wholesale engine dependency.
@@ -0,0 +1,56 @@
"""Compare fixed Project/node/port/endpoint geometry and report routing changes.
Usage: python compare_probe.py OLD_JSONL NEW_JSONL NEW_OUTPUT_JSON
No layout-quality pass is inferred from equal inputs or successful execution.
"""
from pathlib import Path
import hashlib
import json
import sys
old_path, new_path, out = map(Path, sys.argv[1:])
def read(path):
rows = [json.loads(line) for line in path.read_text(encoding='utf-8').splitlines()]
return {row['fixture']: row for row in rows if 'fixture' in row}
old, new = read(old_path), read(new_path)
assert old.keys() == new.keys()
def variants(row):
yield 'before', row['before_project'], row['before']
yield 'baseline', row['after_project'], row['after']
for name in ['reset_only_control', 'experimental_candidate', 'manual_planar_order_witness']:
if row[name] is not None:
yield name, row[name]['project'], row[name]['metrics']
def endpoints(metrics):
return {e['id']: [e['points'][0], e['points'][-1]] for e in metrics['routed_edges']}
keys = ['proper_crossings', 'pairwise_collinear_shared_length',
'nonterminal_pairwise_shared_length', 'unrelated_node_collisions',
'unrelated_edge_node_penetrations', 'label_node_collisions',
'label_label_collisions', 'unrelated_label_stroke_intersections',
'warning_count', 'route_bends', 'route_length', 'drawing_area']
results = []
for name in old:
lhs, rhs = list(variants(old[name])), list(variants(new[name]))
assert len(lhs) == len(rhs)
for (variant, op, om), (other, np, nm) in zip(lhs, rhs):
assert variant == other
fixed = {'project':op == np, 'nodes':om['projected_nodes'] == nm['projected_nodes'],
'ports':om['projected_ports'] == nm['projected_ports'],
'route_endpoints':endpoints(om) == endpoints(nm)}
results.append({'fixture':name, 'variant':variant, 'fixed_geometry':fixed,
'metrics':{key:{'old':om[key], 'new':nm[key]} for key in keys},
'new_point_contacts':nm.get('point_contacts'),
'new_point_contact_details':nm.get('point_contact_details')})
summary = {'old_sha256':hashlib.sha256(old_path.read_bytes()).hexdigest(),
'new_sha256':hashlib.sha256(new_path.read_bytes()).hexdigest(),
'all_projects_nodes_ports_endpoints_exact':all(all(r['fixed_geometry'].values()) for r in results),
'scope':'Fixed-geometry comparison; quality vector retained, no aggregate quality pass.',
'results':results}
with out.open('x',encoding='utf-8') as f:
json.dump(summary,f,indent=2)
f.write('\n')
print(json.dumps({k:v for k,v in summary.items() if k != 'results'}))
for row in results:
if row['variant'] == 'experimental_candidate':
print(json.dumps({'fixture':row['fixture'], 'fixed_geometry':row['fixed_geometry'],
'changed_metrics':{k:v for k,v in row['metrics'].items() if v['old'] != v['new']},
'point_contacts':row['new_point_contacts']}))
@@ -0,0 +1,46 @@
# L results and four-way routing experiment audit
Recommendation: stop tuning these fixtures. L is a useful experimental witness that route alternatives affect sharing, but its small layered benefit and material runtime signal do not presently justify its per-leg preprocessing as a production default. Keep J as the simpler independently motivated scoring correction candidate; decide L only against frozen independent scenes and route-only timing. No production/native acceptance claimed.
## Verified final L result
Primary J06 pin `08656b18bf8934cf264469cf68fc6dea97c15cbb`; L11 pin `e8ec6dbb3160f62476a5557f14605f68959f65fe`. Both recorded offline dev/gnullvm probe commands exit0, source unchanged, failure null. Verified receipt log hashes. Separate raw-output traversal confirms all37 Projects, projected nodes/ports and per-edge endpoint records exactly equal. All nine candidate supported/repeat-input/idempotence/ten-call/JSON-and-single-source-roundtrip results remain true, with unchanged conservation and baseline history checks. Candidate Command/Undo integration remains absent.
J→L changed scalar rows only:
- Original bipartite raw sharing872→724, nonterminal800→652.
- Layered bipartite raw187→176, nonterminal115→104; drawing area175960→165360.
- Original two-container raw/nonterminal54→12.
- Two-container baseline and reset raw/nonterminal18→12 each.
The other32 rows are unchanged in recorded scalar quality metrics. No changed point-contact, proper-crossing, length, bend, warning or collision counts in J→L. Independently comparing full candidate metric objects confirms all nine L layered metrics exactly equal PL, not merely the headline totals. Remaining layered bipartite104 nonterminal shared units still prohibit a solved-traceability claim.
Test10 receipts record135+7+13+18+2+13=188 passed,0 failed/ignored, exit0, unchanged source. J02 and L10 command argv and entire recorded environment dictionaries compare equal. Their routing_geometry test summary elapsed4.30s versus16.43s (~3.82 times, +12.13s); parent reports no concurrent agent compiler. This is a material cost signal, not a controlled routing benchmark: suite runtime includes varied tests and scheduling effects, with no repeated route-only timing. It must not be dismissed by quoting cargo compilation time or presented as an established scaling law.
Verified37 L schematic image hashes/lengths and manifest input hash. Native/glyph rendering was not exercised. Strict Clippy12 was active when assigned and is outside this audit; no outcome inferred.
## Four-way conclusion
All numbers below are raw pairwise shared length; inspect the preceding audits for contacts/area/turn tradeoffs.
| Scene/variant | J | P | P+L | L |
|---|---:|---:|---:|---:|
| Layered bipartite |187|187|176|176|
| Original bipartite |872|730|724|724|
| Baseline/reset bipartite |276|276|72|276|
| Original two-container |54|6|12|12|
| Baseline/reset two-container |18|18|12|12|
P has no layered benefit. P+L's baseline bipartite72 means only common terminal sharing remains, but proper crossings rise0→1, length+24 and area+19392; L alone does not produce that tradeoff. The strongest ordinary two-container sharing result is P6, at an extra bend,12 length units and one T contact. Neither the coefficient nor lanes dominates all vectors. J diamond's isolated improvement remains valid but does not erase its original bipartite regression.
Next scope should be finite and specified before inspecting outcomes: one realistic nested/folded scene, one mixed fan-in/out with a fixed edge-ID permutation, and one modest sparse scene measured by route-only timing and warning/budget counts. Preserve endpoint/model constraints and evaluate traceability, collisions, bends/length/area jointly. Do not silently extend unsupported layered folded behavior into an acceptance claim. Set practical timing and readability criteria before repeating improvement. If L does not show proportionate independent benefit, omit it; avoid adding configuration/architecture solely to retain every experiment. The explicit Layout reset policy and ordinary input/Undo/Save lifecycle are still separate implementation work.
## Evidence identities
J06 output `f9ac7c1320b1cf026259bfcaf13a39e968ae317463712d8dc793375d9a4a541f`.
L11 output `64232f580600b7a874a6f4ceb128a6188fd5ff72655bc6b93d824ebed27ffb7d`.
L11 result.json `feee84c2fde115c22ea865033ff2d136da2aace1f1c8a7423c8add11358366a8`.
L comparison JSON `e1e23e6c2a50cd384457bb061d5b70a740f586f42767164fad9703ecbb4096a8`.
L contact audit JSON `6f6cc674c27d3f43001fe1751ef3b09332caa9ddca396759f5525f811cbe1aaf`.
Read-only audit of completed records; no build/probe/helper rerun, source edit or publication. Earlier independent J/P/PL source/results notes retain grid-axis, subdivision-score, capped retained coordinates versus unbounded preprocessing, stable-edge-order and approximate-label limitations.
@@ -0,0 +1,20 @@
# Bounded L lane-candidate source review
Reviewed routing.rs SHA256 `af5995af10c8e3b30c5c6fb776a2ac2678eeef59dd58eb0cdb3d5c62cd153f1b` before parent formatting. Static inspection only; no builds or source edits. No blocking implementation finding for the finite nine-fixture P+L / J+L probe comparison.
The code retains all sorted base obstacle coordinates and adds at most64 unique extras EACH axis (128 total), filters nonfinite generated values, deduplicates before ranking, then ranks distance from the start/end axis interval, distance from its midpoint, and numeric value. Final lists return to coordinate order. It derives perpendicular offsets only from nondegenerate settled other-edge segments. The current edge's earlier legs/source stub still contribute occupied scoring but do not generate lanes. Fast/grid receive the identical per-leg RoutingSpace; obstacles, endpoints, authored pins, terminal joins and fallback remain unchanged.
Three new tests cover80 retained base coordinates plus64 extras per axis, previous-segment reorder/duplication invariance, nearest-interval/midpoint selection and nonfinite rejection, and direct calls to fast/grid sharing the same parallel-lane witness. The latter expects min(direct cost,272) and supports both coefficients. No test success is inferred before execution.
Test-scope caveat sent to parent: the existing `unspecified_continuation_keeps_intermediate_leg_choice` test deliberately hardcodes baseline coefficient0.12, path choice and cost255.52. At P3, the shared penalty caps80 and the alternative is cheaper; a whole private suite at P3 can fail that baseline-policy assertion. Preserve the result honestly; restore0.12 for baseline-suite claims or explicitly distinguish experimental tests. This does not block a diagnostic P+L probe.
Limits relevant to interpreting results:
-64 bounds retained coordinates, not scan/sort work. Every endpoint-pair/leg constructs candidates from all previous segments; this preprocessing is outside the search-step budget. No large-scene performance guarantee.
- Added coordinates can make the grid exceed65536 cells even when its original grid fit. Existing exhaustion warning/fallback is the evidence to inspect, not something to suppress.
- Grid scoring is coordinate-subdivision-sensitive: strict-interior crossing tests can miss contacts at inserted grid vertices, and per-segment capped overlap charges can change with subdivision. Therefore a grid result can change due to scoring partition as well as newly offered parallel lanes. Keep independent contact/shared-interval metrics authoritative for quality observations.
- Rank uses one axis and does not filter segment extent on the other; remote lanes can consume the cap. This deterministic heuristic is not proof of optimal lane coverage. The router still uses a feasible fast path before grid, two-axis rather than signed-direction search state, stable sequential edge order, and local hard-pin legs.
Required experiment interpretation: compare P+L to P and J+L to J with exact Project/node/port/endpoints, all quality metrics and warning counts. Retain label collisions, T/bend contacts, length/turn/area regressions; fewer shared units alone is not acceptance. No broad production or native readability conclusion follows from this finite source review.
Current formatted routing SHA256: 3a8af7a44f8f32bd9fb94dddd9184fafcf64c93c510025169c62958566bbaedf. Reread formatted lane construction: filtering/ranking/cap semantics agree with reviewed candidate. No mechanical original-byte whitespace comparison claimed. Parent plans exactly the three lane-filtered private tests for P+L, then restores0.12 for the full baseline suite; that appropriately separates the baseline-policy assertion noted above.
@@ -0,0 +1,35 @@
# P+L results independent audit
P+L demonstrates that additional lanes can remove one scene's nonterminal overlap, but adds a proper crossing and gives only a small layered improvement. It does not yet justify adopting the extra heuristic as a general production default. Static record audit only; no reruns, builds or source edits.
Compared P07 `3d7adff923dee5bed54dfdccc119dbee0c7c2773` with PL09 `d0be9ff0674373fcf9623daf62bbf50dad02c4df`. Both probe receipts exit0, failure null, unchanged source; all logged hashes independently checked. Separate raw-output traversal confirms all37 Projects, projected nodes/ports and route endpoint records exact. All nine candidate support/repeat/idempotence/ten-call/roundtrip results remain true; conservation and baseline Command::Layout history checks match P. Candidate Undo integration remains untested.
Test08 actually ran `cargo test --locked --offline -p oa-core --lib lane`: five passed,130 filtered, zero failed/ignored. This is three new routing lane tests plus two existing scenario-layout tests, not a whole core-suite pass.
All changed numeric-vector rows, P→PL:
| Scene/variant | Sharing raw / nonterminal | Other changes |
|---|---|---|
| bipartite before |730→724 /658→652|contacts10→9; bends5→4; length2076→2064|
| bipartite baseline |276→72 /204→0|proper crossings0→1; contacts6→5; length3284→3308; area300576→319968|
| bipartite reset |276→72 /204→0|same changes as baseline|
| bipartite layered |187→176 /115→104|area175960→165360; contacts9 unchanged|
| two-container before |6→12 /6→12|contacts3→2; bends5→4; length1524→1512|
| two-container baseline |18→12 /18→12|no other scalar change|
| two-container reset |18→12 /18→12|no other scalar change|
Remaining30 rows unchanged; eight other layered vectors unchanged. No new label-collision or warning-count changes appear. The baseline/reset proper crossing is a real quality tradeoff, not an automatic failure: a visible isolated crossing may be easier to trace than coincident routes. Conversely, zero proper crossings in the layered scene still accompanies104 nonterminal shared units. Two-container original sharing doubles6→12 despite fewer contacts/turns. No single scalar orders all these results.
Viewed the baseline and layered bipartite PNGs. Baseline channels separate except for the isolated crossing; layered channels retain shared horizontals. Verified all37 offline image hashes/lengths and manifest input binding. These are schematic core geometry, not actual font/native rendering or a user traceability test.
Performance/complexity: probe command elapsed approximately67.1s(P) and69.7s(PL), but these cargo invocations include compilation; they are not comparable routing benchmarks and establish no overhead bound. The new code scans/sorts all prior segments for each endpoint-pair/leg;64-per-axis limits retained coordinates, not preparation work. It can expand the grid past its existing cell cap. Grid crossing/overlap scoring also changes under coordinate subdivision. None of these limitations is resolved by the tiny fixture set.
Recommendation: finish the already-declared J/P/PL/L grid and stop coefficient/lane-spacing tuning on these fixtures. Retain L as an experimental option unless the final comparison supplies stronger value. The next useful acceptance scope is a frozen, modest set of independent scenes chosen before results: one realistic authored diagram with nested/folded content, one mixed-direction fan-in/out, and one moderately sized sparse diagram for routing time/budget behavior, including a fixed edge-order permutation. Choose a preferred traceability tradeoff explicitly; do not add architectural hooks merely to preserve this heuristic. Prioritize the explicit Layout command/reset contract and production integration lifecycle once a maintainable geometry choice is selected. No #32 closure or native readability acceptance follows here.
Bindings:
- P output SHA256 `13111fe89e4e637d1af572a8a45b8bdd763e94b58a2ec5ec6ca83e40ffa209c7`.
- PL output `04bb9ba4ab551a965cea130510cca6b96d9c1012911f191b14a7aa7efbf563dd`.
- PL result.json `538aa79bcedd4cf646c63d5309ec738be4676e2045715db1035a4f334a536117`.
- PL comparison JSON `81f57e651a8840aaaeacc085df2eb90174b2e494182f6cd40ccd6d50e7bb531e`.
- PL contact audit JSON `60ce7e9735ceef50a92d29a0414aafe3d8fb6174dab99eff6397e2abc88e0afa` (inspected as independent worker evidence, not rerun here).
@@ -0,0 +1,26 @@
# P overlap-penalty experiment independent audit
Confirmed the reported finite result. Static read-only audit; no build, probe rerun, source edit or GUI action. P does not improve any of the nine layered candidate metric vectors and is not a demonstrated solution to their remaining shared routes.
Primary comparison: J06 `08656b18bf8934cf264469cf68fc6dea97c15cbb` versus P07 `3d7adff923dee5bed54dfdccc119dbee0c7c2773`. Inspected the latter commit: only routing.rs changes, overlap coefficient0.12→3.0 plus an experiment comment; cap80 remains unchanged. Both recorded `cargo run --locked --offline -p oa-core --example auto_layout_probe` receipts are Windows gnullvm/dev, exit0, inputs unchanged, failure null. Independently verified every log hash in both receipts.
Independent traversal of the raw outputs confirmed all37 variant Projects, projected nodes, projected ports and emitted route endpoint records match exactly. All nine layered candidates retain supported=true, repeat-input exactness, idempotence, passes2–10 equal first/previous, exact JSON/single-source roundtrips and identical conservation results. The authored-detour route-reset exception remains intentional. Baseline Command::Layout history/Undo checks remain identical; this is still not candidate-command Undo evidence.
Only two variant rows change numeric metrics:
| Original/before scene | Raw sharing J→P | Nonterminal sharing J→P | Bends | Length | Point contacts |
|---|---:|---:|---:|---:|---:|
| crossed_bipartite |872→730|800→658|4→5|2064→2076|9→10|
| two_containers_reversed_children |54→6|54→6|4→5|1512→1524|2→3|
Each extra contact is classified as a T contact in the separately generated independent interval audit: bipartite T3→4 and two-container T1→2; proper crossings remain0. Lower shared length therefore comes with one additional bend,12 length units and one additional point contact in each scene. Bipartite returns to the pre-J shared-length total; this does not establish trace readability. All baseline/reset rows, manual witness and nine layered scalar vectors are unchanged. Exact model/endpoint invariants do not imply identical polylines.
Evidence identities:
- J06 raw output `f9ac7c1320b1cf026259bfcaf13a39e968ae317463712d8dc793375d9a4a541f` (also identical to J03 output).
- P07 raw output `13111fe89e4e637d1af572a8a45b8bdd763e94b58a2ec5ec6ca83e40ffa209c7`.
- P07 result.json `6149122d05d77e02b463edb3dd359b06786bc4468c27d8cd625cbc1bc80c9894`.
- P comparison JSON `9f228fbe78e1888c5d93e87dddd8d2effc01bab723929a87423c61e23f5cad66`.
- P independent contact audit JSON `6f4add5c81f6d16c45efabbefd9e36c9beb637b6823993f0d51adc3e20485ad9`. Its recorded definitions and counts were inspected; this reviewer did not execute that helper again.
Recommendation: archive P as a bounded negative result for the layered overlap objective, preserving the two ordinary-scene tradeoffs. A predefined bounded lane-candidate experiment can now test whether available route geometry, rather than this single coefficient, limits improvement. Retain the same Projects/endpoints and all quality metrics; do not tune indefinitely against these nine fixtures or claim general quality/native acceptance. The cap, sequential edge order, fast-path preference, axis-only grid state and estimated label bounds remain limits.
@@ -0,0 +1,47 @@
# #32 bounded automatic-layout probe
Prepared `crates/oa-core/examples/auto_layout_probe.rs` in `.local/auto-layout-20260925` against reference source `715036e132677dd2bc2fb37ed316d93dc9987aec`. No production edits, compiler, Cargo, GUI or Gitea operation by this evaluator. Rustfmt parsed/formatted only the owned main example (`skip_children=true`). The separately owned `auto_layout_probe/layered.rs` is imported through its agreed `candidate(&Project, Id) -> Result<Project, ModelError>` interface; the evaluator does not use candidate internals to define fixtures, geometry metrics or expected conservation.
## Execution and output
From the worktree, in the parent's configured exclusive Windows toolchain environment:
```powershell
cargo run --locked --offline -p oa-core --example auto_layout_probe > auto-layout-probe.jsonl
```
The parent has `.local/run-auto-layout-check-20260925.py` for pinned receipt-based execution. Prefer that for recorded evidence; its source hash/commit must identify the actual build. The example's `reference_source` metadata is the investigation baseline, not a runtime attestation of checkout identity. Stdout is JSON Lines: one metadata/self-check record and nine fixture records. No output files, network calls or UI operations are performed by the example. Errors/unsupported candidates are explicit fields, not quality passes. There is no aggregate score or threshold; a zero metric is not a readability or hard-safety certification.
Each fixture emits the original and current `Command::Layout` Projects and real projected/routed geometry. It also emits the same observations for a **reset-only control applied after existing Layout**, and for the separately implemented experimental candidate invoked on the original Project. The control clears only waypoints and label placement on visible routed edges whose geometry is not suspended/promoted, retaining endpoint attachments, hidden/promoted records and empty route records. The candidate does not determine this control. Ten successive existing Layout calls and ten successive candidate calls are each compared with their preceding and first result, stopping with an explicit error if needed. Candidate identical-input repeat and one further application are also compared separately. Reset eligibility is measured after baseline Layout; the candidate freezes it before Layout. These all-visible fixtures do not change visibility, so broader eligibility-equivalence claims are deferred.
## Declared fixtures
1. Four-node flat chain, initially left to right.
2. Seven-node chain, initially left to right, exposing four-column row wrapping if present.
3. Diamond with branch/join semantics and declared two-level positions.
4. Crossed bipartite ordering, including both straight and crossed pairings.
5. Parent Function with two child Functions, internal exchange and exchange to an outside Function.
6. Two-node authored high detour with a far label offset; reset-only isolates stale constraint cost.
7. Two-node exchange with explicit source-Left0.3 and target-Right0.7 port anchors.
8. Two parent Functions, each with two children, and reversed inter-parent pairings. A separate predeclared witness swaps only right-child y positions, aligning each source with its target. Its real routing is measured; no zero-crossing result is assumed.
9. Three Functions with long ordinary exchange names, including a bypass; the actual existing summary/name excerpt bounds are observed.
Project, diagram, Function, placement, relation and automatically created port UUIDs are remapped to stable IDs in creation order. Port aliases are reset explicitly. Names and other aliases are unique per fixture, avoiding random-ID tie dependence. The fixture Projects in output allow independent repetition and offline visualization; projected node rectangles/ports and per-edge routed point sequences/label rectangles are also included.
## Metric definitions and exclusions
- **Proper crossings:** unique intersection locations per distinct edge pair, interior to both straight segments. Locations deduplicate at0.0001 world units. Segment endpoints, bend contacts, T junctions and collinear overlaps are excluded from this count. Raw crossings remain the primary count. A separate nonterminal count subtracts only contacts within `PORT_STUB` of a physically equal endpoint with the same projected owner, where both tested segments are terminal. Entire compacted first/last segments are never exempt.
- **Shared runs:** summed pairwise collinear overlap length, reported separately from crossings. Raw length is primary. Common exact endpoint terminal overlap gets a separately reported exemption bounded by `PORT_STUB` per common endpoint, capped by the overlap. Three overlapping edges contribute three pairwise overlaps; retraced segments may contribute multiplicity. This is not union length.
- **Unrelated node collisions:** positive rectangle intersection area for distinct nodes; ancestor/descendant pairs are exempt from this count. No inferred intent is attached to a reported collision.
- **Label/node collisions:** positive intersection of real routed label bounds with projected node rectangles, including endpoint nodes. A label wholly inside an endpoint ancestor's *body below the header* is exempt; overlap with its header remains reported. Ordinary header height is64; dedicated State height uses the core helper. Border crossings are retained. These conservative boxes are not glyph measurements.
- **Edge/node penetration:** per edge/node pair, positive clipped segment length in unrelated node interiors. Endpoint-owner nodes are exempt; endpoint ancestors contribute only their header obstacles, so intentional passage through container bodies/boundaries is not counted. This does not detect re-entry into an edge's own endpoint node.
- **Label/stroke intersection:** positive clipped length of another edge's stroke inside a label rectangle, one record per label-edge/stroke-edge pair. Own-edge strokes are excluded; an intentional anchor/label association is not declared a defect. Stroke thickness, arrowheads and antialiasing are unmodeled.
- **Other observations:** actual routing warnings and details; routed total length; direction-changing bends including reversal; total drawing bounding-box area (including labels/nodes); real node/port/edge geometry. Area is scale-sensitive and is not minimized at the expense of crossings or readability.
Tiny analytic assertions check proper crossing versus endpoint contact, collinear overlap versus separated lines, rectangle contact, and line/interior clipping. They will run with the example, but have not been executed by this author. Geometry tolerance is0.0001 world units, while proper-intersection parameter/parallel checks use1e-9.
## Conservation and limits
The current command is exercised through `Session`, with exact one-step Undo/Redo and history/no-op checks. Full JSON decode and formatted single-source parse round trips are compared to the complete Project before/after; these do not prove original multi-file comments/byte preservation. Semantic records, target-view nongeometry fields, other views, per-node identity/visibility/port anchors and authored routes are reported separately. Reset/candidate route equality may intentionally be false and must be assessed against their stated reset policy, not silently treated as semantic loss.
Folded/filter/hidden-route preservation is implemented in the reset eligibility check but not independently falsified by these nine fixtures. Dedicated State #36 native typography, dense stress, cycles, disconnected large sets, non-planar crossing lower bounds, self-crossings, self-loops, full stroke geometry and interactive cognitive load are absent. The candidate may explicitly reject unsupported cases. No algorithm can promise crossing-free output for arbitrary non-planar graphs; the paired-container witness only establishes a concrete alternative ordering to examine. Use vector observations and the output geometry to diagnose mechanisms before selecting a candidate; this is a bounded experiment, not a production layout-quality framework or native acceptance.
@@ -0,0 +1,27 @@
# #32 first diagnostic probe review
**No blocking finding for compiling/running the current finite experiment.** This is a source/evaluator-validity review, not production acceptance or a claim the candidate improves layout. Nine small deterministic FunctionalDataflow fixtures support a bounded comparison; later scope gaps should not delay this run. No compiler, GUI, helper, source edit or Gitea operation performed.
Reviewed snapshot SHA256: auto_layout_probe.rs `72d850fdd9aad65788004949380c28cba48a0c2be3dc28f3aef3900c7f8724c7`; auto_layout_probe/layered.rs `bda9a59968b4b0e284e8e1393eb86c90cdf0d856d5f9830ae3c6fc0043df839a`. The evaluator worker is separately finalizing candidate repetition reporting; later edits require a scoped diff check, not relabeling this reviewed snapshot.
## Evaluator validity
Raw strict-interior segment crossings and pairwise collinear shared length are primary outputs, with crossing coordinates deduplicated per edge pair. Endpoint sharing is not exempted wholesale: secondary figures subtract only bounded common physical tip lengths. Node/header, label/node, label/label and unrelated stroke/label measurements remain separate. Analytic primitive checks cover a proper crossing, endpoint touch, collinear overlap, disjoint parallel segments, rectangle touch and inside-length boundary. Complete before/after models and routed points are emitted, permitting independent examination beyond scalar totals. No quality threshold or automatic overall-pass field promotes a poor drawing to acceptance.
Fixture IDs are normalized before use; semantic port IDs/aliases are also stabilized. The manual reversed-child witness is separately authored and measured, not generated from candidate output. It must still be read as a witness with its reported validation/geometry, not an asserted universal optimum. Standard parse/JSON roundtrip and old-Layout history checks are meaningful within their named scope.
## Actionable limitations to retain when interpreting this run
1. **Reset control eligibility:** reset_effective_routes receives the already arranged Project, whereas layered candidate freezes its eligible set before Layout. For these all-visible, uncollapsed fixtures, Layout does not change membership/promotion, so this does not invalidate the current comparison. Freeze the original eligible IDs for future folded/filtered cases; do not generalize current equivalence.
2. **Conservation granularity:** semantic/other-view/node-metadata checks are independent and useful. target_view_nongeometry_exact restores all routes and layout before comparing; authored_routes_exact will intentionally become false. Those fields do not separately prove attachment preservation, invisible-route retention or a no-change layout field. Add targeted comparisons before making those claims on future fixtures. The current prototype source copies original route records and only clears eligible waypoints/labels, then translates retained routes, so no observed source-level violation was found here.
3. **History scope:** undo_exact/one_step_undo/redo_exact exercise existing Command::Layout through Session only. The layered candidate is a pure Project transformer, not an integrated command and has no tested operation/history lifecycle. Its repeat-same-input and second-call comparison are useful; ten-call candidate stability was pending in this snapshot. Do not describe baseline ten_layout_calls as candidate stability.
4. **Metric coverage:** strict proper crossings deliberately exclude T contacts and intersections at segment bends; pairwise shared length counts segment-pair traversal multiplicity, not geometric union length. No candidate should be selected solely by one scalar if it shifts crossings into uncounted touches. Endpoint-owner interiors are excluded from unrelated-edge penetration, and label/stroke excludes its own edge. These are declared definitions, not proof of complete route validity. Association-end labels, actual glyph ink, State multiline measured bounds and port labels are not covered by current primary label metrics. Current nine fixtures contain functional exchanges, so these omissions do not corrupt their narrow comparison.
5. **Fixture coverage:** all current fixture families are small SA FunctionalDataflow cases. Other-view exactness is vacuous where only one view exists; folding, filtering, omitted-node, State, Allocation, Scenario, undirected cycles, dense scaling, UI admission and multi-file source conservation are not established. Preserve these gaps explicitly rather than broadening the initial run unnecessarily.
## Prototype source risks the experiment should expose
The candidate preserves semantic records and route-list order, retains attachments/port anchors, rejects folded/omitted topology explicitly, and uses stable alias/ID order, deterministic cycle fallback, a graph limit and counted ordering work. Derived corners are not persisted as hard pins. No product file is replaced.
Three passes use frozen per-call minima, but those minima come from baseline Layout using the input's existing sizes; cross-call size ratcheting remains possible and should appear in repeated full-Project comparison. Global maximum label width controls every sibling gap, so one long label may balloon unrelated layout area. Incoming-neighbor ordering can improve the reversed-container fixture without proving general crossing reduction; undirected edges do not constrain ranks/order. The work budget excludes existing projection/routing work and several collections, so it bounds selected prototype loops, not total CPU complexity. With current small fixtures these are diagnostic candidate weaknesses, not reasons to prevent the run.
Interpret a failed invariant, growing repeated layout, worse collisions or huge area as evidence against this prototype. Preserve errors and unsupported statuses; do not use successful process exit as layout-quality acceptance. Decide further work from the produced geometry and independent metric vector. Production integration, complete command/source/API/history behavior, native readability and broader issue32 acceptance remain separate.
@@ -0,0 +1,75 @@
"""Render recorded probe geometry for inspection; this is not a desktop screenshot.
Usage: python render_probe.py PROBE_STDOUT_JSONL NEW_OUTPUT_DIRECTORY
Uses one scale across every result for each fixture; input geometry is unchanged.
"""
from pathlib import Path
import hashlib
import json
import sys
from PIL import Image, ImageDraw, ImageFont
def geometries(value, name=""):
if not isinstance(value, dict):
return
if "routed_edges" in value and "projected_nodes" in value:
yield name, value
return
for key, child in value.items():
yield from geometries(child, f"{name}.{key}".strip("."))
source, output = map(Path, sys.argv[1:])
output.mkdir(parents=True, exist_ok=False)
font = ImageFont.truetype("C:/Windows/Fonts/arial.ttf", 13)
files = []
for line in source.read_text(encoding="utf-8").splitlines():
item = json.loads(line)
fixture = item.get("fixture")
if not fixture:
continue
variants = list(geometries(item))
if not variants:
continue
largest_width = max(g["drawing_bounds"]["size"]["width"] for _, g in variants)
largest_height = max(g["drawing_bounds"]["size"]["height"] for _, g in variants)
scale = min(1.0, 1140 / max(1, largest_width), 690 / max(1, largest_height))
for name, geometry in variants:
canvas = Image.new("RGB", (1200, 800), "white")
draw = ImageDraw.Draw(canvas)
origin = geometry["drawing_bounds"]["position"]
def point(position):
return (30 + (position["x"] - origin["x"]) * scale,
70 + (position["y"] - origin["y"]) * scale)
def rectangle(position, size):
x, y = point(position)
return x, y, x + size["width"] * scale, y + size["height"] * scale
draw.text((20, 12), f"{fixture} / {name} | geometry only, not native UI", font=font, fill="black")
draw.text((20, 32), f"Fixed fixture scale {scale:.4f}; text below identifies bounds, not glyph measurements", font=font, fill="black")
for node in sorted(geometry["projected_nodes"], key=lambda n: (n["depth"], n["id"])):
box = rectangle(node["position"], node["size"])
draw.rectangle(box, fill="#eef2f6", outline="#687787", width=1)
draw.text((box[0] + 3, box[1] + 3), node["id"][-5:], font=font, fill="#334155")
for edge in geometry["routed_edges"]:
points = [point(p) for p in edge["points"]]
if len(points) > 1:
draw.line(points, fill="#245c88", width=2)
label = edge["label"]
if label["text"]:
box = rectangle(label["position"], label["size"])
draw.rectangle(box, outline="#ad5715", width=1)
target = output / f"{fixture}-{name.replace('.', '-')}.png"
canvas.save(target)
files.append({"name": target.name, "bytes": target.stat().st_size,
"sha256": hashlib.sha256(target.read_bytes()).hexdigest(), "scale": scale})
(output / "manifest.json").write_text(json.dumps({
"input_sha256": hashlib.sha256(source.read_bytes()).hexdigest(),
"renderer_sha256": hashlib.sha256(Path(__file__).read_bytes()).hexdigest(),
"scope": "Offline schematic of recorded core geometry; no native or font-rendering acceptance.",
"images": files}, indent=2) + "\n", encoding="utf-8")
print(f"Rendered {len(files)} geometry records without modifying the probe output.")
@@ -0,0 +1,66 @@
# #32 automatic arrangement: bounded research
Prepared 2026-09-25; local research, not an accepted design or implementation review. No compiler, GUI, helper, or remote mutation was run. Primary sources were read online. Local source spot-check: clean `.local/auto-layout-20260925`, commit `715036e132677dd2bc2fb37ed316d93dc9987aec`. Parent owns current implementation mapping and contract selection.
The user needs Arrange to generate/remove bends, reduce connection intersections, and account for connection labels, while retaining meaningful nested containment/deployment. A useful solution therefore needs coordinated node ordering, space allocation, routing, and label placement. Merely sorting boxes or clearing existing waypoints cannot establish success.
## Verified precedents and their limits
**Capella workflow.** Capella 7 release notes document Layout Children, temporary movement of pinned elements, and renaming Arrange All/Selection to Layout All/Selection. This supports making operation scope and preservation of manual organization explicit. It does **not** establish that Capella uses ELK as its default engine. [Official Capella 7 notes](https://github.com/eclipse-capella/capella/wiki/Release-Notes-7.0.0).
**Sirius workflow.** Its user guide describes avoiding overlap, fitting containers around contents, attempting fewer edge crossings, and preserving pinned locations. Layout Children operates at the immediate level. A selected container can have its contents arranged while its location remains fixed. The guide also describes simplifying an unpinned edge between pinned endpoints. However, its pinning section and later toolbar section disagree about edge pinning support; do not copy that ambiguity into ARCADIA's contract. [Sirius user guide](https://eclipse.dev/sirius/doc/user/diagrams/Diagrams.html). Sirius documents configurable default/composite and ordered-tree layouts, with ELK as an additional integration described as experimental. Availability of that integration is not evidence of a particular Capella installation's behavior. [Sirius layout configuration](https://eclipse.dev/sirius/doc/specifier/diagrams/Diagrams.html).
**ELK capabilities.** ELK Layered explicitly supports orthogonal routing, ports, labels, compound graphs, and cross-hierarchy edges with suitable configuration. [Algorithm reference](https://eclipse.dev/elk/reference/algorithms/org-eclipse-elk-layered.html). `INCLUDE_CHILDREN` allows a joint run through descendants; the root default otherwise separates children. Independent child layout is therefore not equivalent to globally considering connections across nested containers. [Hierarchy handling](https://eclipse.dev/elk/reference/options/org-eclipse-elk-hierarchyHandling.html). Port constraints distinguish free placement, fixed side/order/ratio, and fixed position. [Port constraints](https://eclipse.dev/elk/reference/options/org-eclipse-elk-portConstraints.html).
The authors describe a Sugiyama-family layered algorithm, constrained barycenter crossing minimization, size/port-aware coordinate assignment, and separate support for compound graphs and disconnected components. These are heuristics with configurable priorities, not a guarantee of globally minimal crossings. [ELK authors, GD 2024](https://drops.dagstuhl.de/storage/00lipics/lipics-vol320-gd2024/LIPIcs.GD.2024.56/LIPIcs.GD.2024.56.pdf).
**Routing is a distinct problem.** Wybrow, Marriott, and Stuckey describe orthogonal visibility graphs, A* route search, and ordering/nudging of shared segments. Their bend/length optimality statement concerns individual routes under their model, not joint optimal placement of all nodes, labels, and connectors. [Orthogonal Connector Routing, GD 2009](https://people.eng.unimelb.edu.au/pstuckey/papers/gd09.pdf). libavoid exposes penalties for crossings, segments, cluster crossings and shared paths, plus endpoint direction constraints. It supplies routing rather than a complete node arrangement solution. [libavoid API](https://www.adaptagrams.org/documentation/namespaceAvoid.html).
## Materially different approaches
| Candidate | Practical proposal for this project | Main advantage | Main risk / decisive falsifier |
| --- | --- | --- | --- |
| Native Rust layered arrangement + existing router | Build a bounded, deterministic placement pipeline over the projected hierarchy; reserve measured label space and improve routes after placement. | Best match to Rust preference, existing model/history, and deployment; reuse current geometry. | Nested cross-boundary routes and label congestion make a supposedly small implementation grow. Reject if the finite compound fixtures require ad hoc per-diagram exceptions or cannot reduce known avoidable crossings. |
| ELK Layered through elkjs | Translate one immutable projected graph to ELK JSON, run a pinned bundled JS engine off the UI thread, validate results, apply a presentation transaction. | Mature joint treatment of ports, labels, layering and hierarchy. | Adds runtime/distribution/adapter cost. Reject if exact port semantics, nested coordinates, measured label boxes, bounded cancellation, or Windows/Linux packaging cannot be demonstrated. |
| Preserve nodes; improve routes and label locations | Use existing Rust routing plus bounded rerouting/order search, or investigate libavoid through FFI. | Least disruption to the user's spatial organization; useful separate Reroute command. | Cannot untangle crossings imposed by node/port ordering. Insufficient as the sole Arrange implementation if an order swap solves a fixture it cannot improve. |
elkjs is the official JavaScript form of ELK's layout algorithms, consumes/returns graph geometry, and supports worker execution. Using the distributed JavaScript does not require a Java runtime. A Rust embedding still needs an explicitly selected JS runtime or packaged process; compatibility, footprint, cancellation and licensing must be checked in a bounded spike. It is not a Rust library or a ready Iced integration. [elkjs repository and API](https://github.com/kieler/elkjs).
Graphviz is a possible comparison baseline, not my preferred default: its current official `splines=ortho` documentation explicitly says this routing does not handle ports or, for dot, edge labels. That limitation directly intersects this task. A separate router/label solver could repair the integration, but removes much of its simplicity. [Graphviz splines](https://graphviz.org/docs/attrs/splines/). Force/stress layout is another possible overview style, but I would not spend the first experiment on it: engineering flow direction, nesting and endpoint constraints still need substantial machinery afterward. This is a fit judgment, not a claim those algorithms cannot be extended.
## Proposed minimum contract, independent of engine
1. **Operate on diagram presentation.** Preserve semantic identities, relation direction, containment/deployment, port identity and connectivity, node/edge membership, and label text. Layout cycle breaking must never reverse the actual model relation. Projected parentage, not an independently guessed semantic tree, defines nesting. Hidden/collapsed content must have a declared policy; do not silently expose it.
2. **Define scope before implementation.** Initial whole-diagram Arrange is simpler than arbitrary selection. If selection is included, decide whether a container means its immediate children or its full subtree, whether it can grow, and how boundary-crossing edges update. Outside nodes remain fixed. Do not infer support for selection from a whole-graph solver.
3. **Define replaceable geometry.** Explicit Arrange may replace the in-scope route waypoints, creating useful bends and removing redundant ones. Decide independently whether authored endpoint attachments and authored label placement are preserved or reset. Recommended initial rule: preserve semantic/fixed attachments; authorize replacement of bend geometry; treat preserved label boxes as constraints. Do not retain stale absolute label offsets accidentally after wholesale rearrangement. Parent must settle this user-visible policy.
4. **Measure before arranging.** Supply visible node dimensions, headers, port/row areas, label rectangles and clearance in world units. Use the same prepared text and measurement as painting, including multiline labels and fallback glyphs. Feed label occupancy into spacing/routing, then validate the final rendered geometry. Fitting labels only after arranging boxes can recreate collisions.
5. **Atomic application.** Return a candidate presentation delta, validate it, then create one Undo entry. Failure/cancellation leaves the model unchanged. Undo restores complete prior geometry; Redo and save/reopen reproduce the result. A stale asynchronous result must not overwrite edits made since its input snapshot.
6. **Quality, not an impossible universal promise.** Require no invalid geometry, prohibited node/header penetration, or lost constraints. Minimize avoidable crossings, label collisions and shared-path ambiguity before compactness and bend count. Preserve a satisfactory baseline when candidates are worse. Hard constraints may make a zero-collision drawing impossible; report the residual rather than claiming success from a cost scalar.
The local spot-check explains one important seam: `crates/oa-core/src/session.rs::layout` already obtains `projection::parents`, recursively sizes nested nodes and rejects Scenario geometry. `routing.rs` explicitly distinguishes persisted authored pins from bounded derived routes. Generated bends need not all become persisted manual pins: persist the chosen presentation change according to a declared regeneration policy. The #36 successor's measured State labels must be reconciled when integrating; this base predates it. No claim about uninspected callers or final API follows from this spot-check.
## Native Rust candidate: finite algorithm experiment
This is a proposal, not an implementation prescription. Build a temporary layout graph with stable IDs, measured boxes, containment, real port constraints and typed edges. Determine feedback edges for cycles without modifying semantics; assign layers; insert temporary long-edge/label placeholders; perform a fixed number of alternating barycenter/median sweeps with adjacent swaps accepted only when crossing cost improves; assign non-overlapping coordinates. Lay out children with explicit boundary connection ordering and reserve parent headers/port areas. Bottom-up subtree packing alone is a baseline, not proof of cross-hierarchy quality.
Route orthogonally around the resulting obstacles, with penalties for bends, crossings and excessive shared paths. Place measured labels in reserved corridors; if conflicts remain, permit a bounded spacing/rerouting pass. Remove duplicate and collinear intermediate vertices. Keep separate metrics for edge crossings, shared-path length, label collisions, bends, area and displacement so a shorter route cannot disguise a newly unreadable diagram. Stable tie-breaking, bounded passes and retaining the best valid candidate limit oscillation and cost. Compare this candidate against the same finite fixtures processed by elkjs before investing in a general framework.
## Falsifiers and finite milestones
**First: freeze the evaluator and geometry policy.** Six small fixtures are enough to expose foundational errors: (a) a two-layer order swap with a known crossing-free solution; (b) a diamond plus cycle/self-loop; (c) a two-level OA containment graph with internal and cross-container connections; (d) SA deployment nesting with fixed semantic ports and high-degree fan-out; (e) long/multiline labels in a tight corridor; (f) disconnected components, collapsed content and an already tidy layout. Include one route needing added bends and one needing removed bends. If scope excludes any fixture feature, state that before scoring.
Count proper interior intersections between distinct routed edges separately from collinear overlaps. Exclude common endpoint contact, but do not exempt edges merely because they share a source: an intersection elsewhere still counts. Count label-label and label-unrelated-edge/node intersections; intentional own-edge label contact and container background containment need specific exemptions. Check allowed container boundary passages rather than counting every containment-border crossing as an error. Record scores on final routes, not only straight-line layer proxies.
**Second: choose using measured results.** For the known solvable fixtures, demand the known crossing/collision target, semantic equality, correct port attachment and legal containment. Test deterministic repetition, a second Arrange reaching a stable result, and small perturbations not causing unbounded growth. Include invalid inputs/work-budget exhaustion; preserve the input on failure. Set an interactive-time budget against a declared representative model size and hardware before timing, rather than inventing a universal threshold here.
**Third: verify the real workflow.** Parent performs Arrange, inspects routes and labels at normal reading zoom, Undo, Redo, save and fresh reopen on OA and SA models. Check that connections remain traceable and nesting still conveys the intended relationship; aggregate crossing counts alone do not establish readability. No such execution or acceptance is claimed by this research.
Recommended next decision: settle reset/preservation of bends, endpoint attachments and label anchors, then compare the native layered candidate and elkjs on the same compound, port-and-label fixtures. Route-only improvement is a useful fallback capability but should not silently redefine the user's Arrange request.
## Parent's current code findings: implication for the first experiment
Parent reports that the existing router already has deterministic bounded orthogonal search, obstacle exclusion, crossing/shared-run penalties and automatic label collision sampling; current Arrange uses a four-per-row root placement after topological/alias ordering. These are parent-provided inspection findings, not independent runtime verification here. They materially favor **reuse of existing routing plus improved node arrangement** as the first candidate, rather than reimplementing routing or immediately adopting another engine. Feed its final-route metrics back into a bounded choice among placement candidates; label sampling alone still cannot create missing corridor space.
Parent's proposed policy—preserve semantic ports, authored port anchors and endpoint attachment preferences; reset only visible effective bends/label overrides during explicit full Auto-layout; retain hidden/promoted metadata; leave automatic elbows derived—is a coherent candidate. Its key falsifier is visibility scope: a folded/proxy edge must not cause deletion of dormant metadata belonging to a hidden original endpoint/route. Define “visible effective” by exact original edge ID plus unsuspended geometry, not just membership in the rendered edge list. Test folded then expanded diagrams, ancestor-promoted endpoints, partially visible authored routes and a visible label whose anchor was inherited. If retained dormant metadata becomes effective on expansion, the restored manual shape is expected under this policy and should not be advertised as newly automatically arranged. Explicit full Auto-layout can reasonably reset visible label overrides because its stated purpose authorizes rearrangement, but ordinary redraw, drag and routing must not acquire that reset behavior.
Compare reuse against elkjs only after scoring this bounded candidate. A successful existing-router experiment removes much of the justification for another runtime; persistent compound/port-order failures would supply concrete reasons to reopen engine adoption. The newly observed untracked `harness/auto-layout-investigation-20260925.md` in the worktree is parent work; this research did not modify it or product source.
@@ -0,0 +1,51 @@
# #32 residual routes after terminal-join candidate J
Read-only diagnosis of run03 `command/output.log`, `.local/auto-layout-j-comparison-20260925.json`, the geometry-only crossed-bipartite PNG, and current `routing.rs` / `projection/port_layout.rs`. No edits, builds or native GUI execution. J is an experimental cost correction, not an accepted readability improvement for every scene.
## The remaining187 units are not coincident distinct ports
The four functional exchanges deliberately reuse four semantic ports:
| Flow | Source port | Target port |
| --- | --- | --- |
| 0 (`009`) | `010` | `014` |
| 1 (`00c`) | `013` | `011` |
| 2 (`00f`) | `010` | `011` |
| 3 (`012`) | `013` | `014` |
Projected source ports are `(264,106)` and `(264,286)`; target ports `(346,106)` and `(346,286)`. Each port is a single real semantic identity serving two edges. There are no authored port anchors in this fixture. The route endpoints and ports were unchanged by J. Splitting a common port into several displayed ports or changing relation port IDs would change the contract/semantics and is not a routing remedy.
Of187 pairwise shared units,72 are four legitimate common-port18-unit terminal stubs. The remaining115 are four avoidable interior overlaps:
- Flow0/Flow2: `y=106, x=282..328`, length46.
- Flow1/Flow2: `y=106, x=305..328`, length23.
- Flow1/Flow3: `y=286, x=282..305`, length23.
- Flow0/Flow1: `y=106, x=305..328`, length23. These routes have different endpoints, so this is not a common-port exemption.
These sum115. The two diagonals now have separate vertical channels (`x=328` and `x=305`); horizontal tails still merge different routes. The geometry PNG confirms that a user sees shared horizontal lines with branch contacts, not four independent readable connections. It is not native/glyph evidence.
`port_layout::layout` chooses automatic sides from peer centers, semantic fallback and clear exits; explicit anchors override the resulting side/offset. It sorts multiple automatic ports on a side by peer-axis coordinate and ID, then allocates distinct positions around fixed anchors. Here each node has only one relevant port, so improving distinct-port separation would not address this fixture. Moving a common automatic port could alter geometry but still leaves all its incident edges sharing the same physical endpoint. Fixed anchors must remain exact.
## J trades one objective against another
J improves layered diamond overlap82→36 (nonterminal0) and layered bipartite344→187 (nonterminal115). But the original unarranged bipartite regresses730→872 shared units (nonterminal658→800), while bends5→4 and length2076→2064. The current original routes use a320-unit top horizontal trunk and a160-unit second-diagonal tail; later straight edges overlap those trunks. This is consistent with a length/bend-biased objective, not evidence that J universally improves routing.
`crossing_cost` charges only0.12 per shared unit, capped80 per prior segment; a turn costs24. J now compares more complete turn costs, so it can rationally prefer a cheaper route with more overlap. The router evaluates edges in stable-ID order and does not jointly revise earlier paths. Terminal fairness alone cannot correct an objective that undervalues ambiguous shared strokes. Public/ordinary scenes must remain in the comparison; do not judge only Arrange-generated geometry.
## Next bounded experiment: penalty versus available lanes
Recommend a tiny factorial comparison with node positions, ports, anchors and edge order frozen: J; J+P; J+L; J+P+L. Keep the original unarranged bipartite as a named regression sentinel alongside layered diamond/bipartite and the existing corpus. This separates missing candidate geometry from cost preference; no placement change initially.
**P:** use one predeclared experimental overlap coefficient, e.g.3.0 instead of0.12, retaining the80 per-segment cap and proper-crossing24 initially. This is a falsifier, not a proposed final default. A12-unit dogleg around a straight shared span can cost24 additional length plus96 for four full-path turns. The cheap existing coefficient cannot compensate for that; even3.0 with the80 cap may still reject it. Record this failure explicitly rather than repeatedly tuning. A second experiment removing/changing the cap would require separate justification; the cap and segment subdivision make the objective unlike a simple global shared-length weight.
**L:** offer a bounded number of parallel candidate coordinates at `CLEARANCE=12` on either side of occupied horizontal/vertical segments, subject to current obstacle clearance and fixed terminal stubs. These are derived path candidates, never authored waypoints. Put them in fast-path candidate space and, if needed, the grid space consistently, with a deterministic global cap. The current obstacle-boundary/midpoint coordinate set does not systematically offer these close parallel lanes. More space between boxes alone cannot supply the missing candidate coordinate or change its cost preference.
A smaller staging of this comparison is to run P first and inspect actual chosen alternatives, then add L only if the recorded candidates lack the desired lane. Do not silently combine penalty, cap, edge ordering and node spacing changes. If P merely creates crossings/long exterior detours, that disproves it as a standalone improvement. If L offers legal lanes but they are never selected, that isolates the objective. The result may justify a different explicit priority or bounded shared-segment nudge, but not before this evidence.
**Alignment control:** an additional fixed24-unit inter-layer gap or stagger is lower priority. It changes the diagram and automatic peer positions, complicates attribution, and can lengthen shared trunks. Alignment may be useful for a particular placement heuristic, but it is not a direct explanation of identical horizontal tails between the same common ports. Port-placement changes are outside the identified dependency scope.
## Acceptance constraints for the experiment
Keep exact semantic graph, port identities, attachments, authored anchors and generated-versus-authored route distinction. Report raw overlap, legitimate terminal overlap, nonterminal overlap, proper crossings AND branch/bend contacts, labels, obstacles, length, turns, warnings and workload. Current proper-crossing zero excludes contacts at bends; trading coincident strokes for distinct, obvious crossings can improve traceability even if that one metric rises. Conversely, tiny offsets that appear merged at normal zoom are not success.
Use shared-port fan-out/fan-in and distinct-port controls, fixed-anchor controls, narrow corridor, authored-pin and opposite-direction routes. Compare original, reset-only and layered scenes, plus a fixed edge-ID permutation to expose priority sensitivity. Parent should inspect actual route tracing at normal zoom before adopting any weight/lanes policy. No quality acceptance or port-placement defect is asserted here.
@@ -0,0 +1,106 @@
# First automatic-layout experiment
## Decision
Retain the hierarchy/label-spacing prototype for further experiments; do not
adopt it as production Auto-layout. It improves several named fixtures beyond
resetting old bends, but regresses shared connector length in two fixtures.
Shorter routes and zero proper crossings do not establish readable connections.
The next experiment should isolate route selection at fixed node positions,
before adding more node spacing or replacing the existing routing engine.
## Method and receipt
Tested source `e7deea7039bcf31675cffbb462cfeb19d62a7e20`, based on released
`715036e132677dd2bc2fb37ed316d93dc9987aec`. No production file changed in that
commit. Windows x86_64-pc-windows-gnullvm, Rust 1.98.1, LLVM mingw, one Cargo job,
incremental compilation disabled; Linux excluded. Command:
```text
cargo run --locked --offline -p oa-core --example auto_layout_probe
```
The recorded runner completed at 2026-09-25T00:42:06Z, exit 0, unchanged inputs,
no recorded failure. stderr contains only compile/run progress. This means the
diagnostic process completed, not that layout quality passed. The JSONL has one
metadata record and nine fixture records, with full input/result Projects and
core route geometry. Its SHA256 is
`21786ae5cb668f60e9252dc884668ee5e98289443d0b3723a9ea69bf21d47fd1`.
[Evidence archive](run-01/probe-e7deea70.zip), 900995 bytes, SHA256
`c52f82d4782cd6a7f8cbe67498c8b2371e99204a65976aa8bcbbd0a86f835069`,
contains original receipts, source inventories, output, runners and 37 offline
geometry PNGs. [Manifest](run-01/manifest.json) binds every member and the
post-run executable identity. All archive members were byte-compared to the
local originals. Original absolute paths describe this machine; reproduce using
the source commit and command above, with a suitable Windows Rust toolchain.
## Observations
Values below compare existing Layout with the layered prototype. Reset-only
matches existing Layout except for the authored-detour fixture, where it already
reduces length to 240, bends to zero and area to 90816. Shared length is pairwise
segment overlap, not the union of occupied space; area uses recorded bounds.
| Fixture | Shared length | Label/node collisions | Route length | Bends | Area |
| --- | ---: | ---: | ---: | ---: | ---: |
| Four-node chain | 0 → 0 | 0 → 0 | 720 → 246 | 0 → 0 | 213312 → 150744 |
| Seven-node chain | 0 → 0 | 0 → 0 | 2560 → 492 | 8 → 0 | 523584 → 271920 |
| Diamond | 36 → 82 | 0 → 0 | 2200 → 688 | 8 → 4 | 300576 → 260832 |
| Crossed bipartite | 276 → 344 | 0 → 0 | 3284 → 688 | 12 → 4 | 300576 → 165360 |
| Nested cross-boundary | 0 → 0 | 1 → 0 | 1076 → 364 | 6 → 2 | 284096 → 366528 |
| Authored detour/label | 0 → 0 | 0 → 0 | 2348 → 187 | 6 → 0 | 628040 → 83820 |
| Fixed ports | 0 → 0 | 0 → 0 | 916 → 863 | 4 → 4 | 125976 → 116754 |
| Reversed children in two containers | 148 → 0 | 1 → 0 | 872 → 372 | 4 → 0 | 288512 → 272800 |
| Long ordinary labels | 36 → 36 | 2 → 0 | 1340 → 1436 | 4 → 4 | 200448 → 208800 |
Proper interior crossings, unrelated node overlaps, unrelated edge/node
penetrations, label/label collisions, unrelated label/stroke intersections and
route warnings are zero in the baseline, reset and candidate outputs. This is
not proof of crossing elimination: the crossing definition excludes bend and
T contacts, and overlapping strokes can make endpoint pairing ambiguous.
The parent inspected offline geometry for the reversed-container baseline and
candidate, crossed-bipartite candidate and long-label candidate. Reordering the
right container's children separates the two routes; the bipartite result has
a visually ambiguous shared central trunk. The long-label boxes occupy the
available corridors. These images render core boxes/lines, not actual label
glyphs or the desktop application, and therefore are not native UI acceptance.
Every candidate reports exact same-input determinism, idempotence and equality
to its first result for calls 2–10. Reported semantic, node identity/metadata and
nongeometry conservation hold. JSON and canonical single-source parse/format
round trips hold for all nine before/baseline/reset/candidate Projects. The
authored-detour route changes intentionally. These assertions do not separately
establish hidden-route or attachment lifecycle policy. Other-view conservation
is vacuous in these single-view fixtures.
Existing Layout's Session operation passes one-step Undo/Redo and history
checks. The pure candidate function has no integrated command/history evidence.
The [source review](probe-review.md) defines remaining metric and fixture gaps.
Folded/filtered topology, State/Allocation diagrams, cycles/undirected graphs,
large models, multifile sources, application/API admission and native editing
remain outside this run. Issue36's multiline State candidate is not in this base.
An [independent results audit](results-audit-01.md) checked receipt bindings,
complete Projects, all 37 image hashes and eight offline images. It independently
recomputed both shared-route regressions and confirmed the scoped conclusions.
## Implications
Resetting obsolete bends is useful but insufficient. Hierarchy ordering and
label clearance have distinct value, with measurable area/length tradeoffs.
Increasing a global gap cannot be assumed to fix shared-route ambiguity.
Inspect the existing router's candidate scoring and compare complete endpoint
join costs, overlap penalties and available lane candidates using the same
geometry. Preserve the full quality vector and endpoint/obstacle invariants;
reject a change that merely converts overlap into uncounted contacts.
The [routing inspection](routing-experiment-plan.md) identified a concrete
asymmetry: a locally selected route leg scores the source join but omits the
target-stub join until after choosing that leg. Test that small correction first,
at unchanged node positions, before the auditor's proposed alignment/spacing
alternatives. This is a falsifiable source-based hypothesis, not a conclusion
that it explains all shared routes. A separate committed candidate will preserve
this first control; no overlap weight or node placement should change in that
comparison.
@@ -0,0 +1,56 @@
# #32 first experiment — independent results audit
Source e7deea7039bcf31675cffbb462cfeb19d62a7e20, production baseline715036e132677dd2bc2fb37ed316d93dc9987aec. Command: `cargo run --locked --offline -p oa-core --example auto_layout_probe`; Windows x86_64-pc-windows-gnullvm/dev. Recorded exit0, failure=null, source inputs unchanged. Independently checked receipt-bound log hashes and equal before/after source inventories. No rerun, compiler, GUI or model helper executed.
**Conclusion: useful evidence for continued experimentation, not a production winner.** All nine finite fixtures completed; candidate conservation/repeat/roundtrip checks pass. Two shared-run regressions are real, while nested/reversed-container/long-label improvements are also supported. There are no predefined quality thresholds, so exit0 means the probe ran, not that layout quality passed.
## Verified results
Exactly one metadata row plus nine fixture rows. Every candidate is supported and valid as recorded, repeats exactly from identical input, equals its second-call result, and remains equal to its first result through calls2–10. Existing Layout likewise has passing repeat/history checks. Every before/after/reset/candidate canonical JSON and single-source roundtrip is recorded exact. Candidate semantic/node-identity/visibility/port-anchor/nongeometry booleans are true on all nine. I independently compared complete output Projects after restoring only diagrams, and checked target layout fields and route attachment fields against input: no difference. Most attachment lists are empty, so this is not broad attachment coverage. Other-view checks are vacuous for single-view fixtures.
The authored_detour_and_label candidate intentionally has authored_routes_exact=false because its three pins and label override are removed. That is the experimental reset policy, not an unexplained conservation failure. Reset-only already removes the detour; layered must earn its additional complexity elsewhere. Actual Undo/Redo was executed only for existing Command::Layout. Layered is a pure transformer with no command/UI integration or history evidence.
Values below compare **baseline Layout plus reset-only control → layered candidate**, not the original hand-placed input:
| Fixture | Shared length | Label/node pairs | Bends | Route length | Drawing area |
| --- | --- | --- | --- | --- | --- |
| flat_chain_4 | 0 → 0 | 0 → 0 | 0 → 0 | 720 → 246 | 213312 → 150744 |
| chain_7_row_wrap | 0 → 0 | 0 → 0 | 8 → 0 | 2560 → 492 | 523584 → 271920 |
| diamond | 36 → 82 | 0 → 0 | 8 → 4 | 2200 → 688 | 300576 → 260832 |
| crossed_bipartite | 276 → 344 | 0 → 0 | 12 → 4 | 3284 → 688 | 300576 → 165360 |
| nested_cross_boundary | 0 → 0 | 1 → 0 | 6 → 2 | 1076 → 364 | 284096 → 366528 |
| authored_detour_and_label | 0 → 0 | 0 → 0 | 0 → 0 | 240 → 187 | 90816 → 83820 |
| fixed_ports | 0 → 0 | 0 → 0 | 4 → 4 | 916 → 863 | 125976 → 116754 |
| two_containers_reversed_children | 148 → 0 | 1 → 0 | 4 → 0 | 872 → 372 | 288512 → 272800 |
| long_ordinary_labels | 36 → 36 | 2 → 0 | 4 → 4 | 1340 → 1436 | 200448 → 208800 |
All these control/candidate outputs report zero strict proper crossings, unrelated-node collisions, unrelated edge/node penetrations, label/label pairs, unrelated label/stroke pairs and routing warnings. Therefore this run provides **no discriminating evidence of proper-crossing reduction**. Zero warnings is not proof of unambiguous independent routes.
## Independently checked regressions and improvements
- **diamond:** shared length36→82. Control has two18-unit common terminal runs. Candidate retains those and adds one46-unit horizontal shared run for edge pair0009/000c. Nonterminal shared length0→46; this is not just a terminal-exemption artifact. Bends8→4 and length2200→688 improve, but they partially come with merged routing.
- **crossed_bipartite:** shared276→344, nonterminal204→272. Control has four18-unit common-tip runs plus204 horizontal. Candidate has those same72 terminal units, two additional46 horizontal overlaps, and a180-unit vertical overlap between distinct edge pairs0009/000c. Independent axis-interval sums match the emitted values exactly. A more compact drawing and fewer turns do not erase this distinguishability regression.
- **nested_cross_boundary:** reported label/node collision1→0 and route length1076→364; area284096→366528 increases about29%. The geometry PNG shows the parent widened to place its children horizontally. This is a real tradeoff, not universal improvement.
- **two_containers_reversed_children:** shared148→0, label/node1→0, bends4→0, length872→372 and area288512→272800. The hand-authored alternate child order exists independently; the candidate's straight separated connections support a meaningful ordering improvement over reset-only.
- **long_ordinary_labels:** label/node2→0, shared36 unchanged, but length1340→1436 and area200448→208800 grow. This is supported clearance-for-space tradeoff using approximate core rectangles, not native glyph evidence.
Viewed eight offline PNGs: baseline/candidate diamond, baseline/candidate crossed_bipartite, baseline/candidate nested_cross_boundary, candidate two_containers_reversed_children and candidate long_ordinary_labels. They corroborate route sharing and spacing changes. All37 image identities and their manifest binding to the original output log were checked. Images are schematic rectangles/polylines with fixed scale per fixture, not application renders or text measurements.
## Method limits versus actual failures
Proper-crossing definition excludes T contacts and bend intersections; shared length counts pairwise traversal multiplicity. I recomputed the two reported regressions independently from axis-aligned intervals, so those particular increases are actual geometry outcomes, not metric bugs. An unconditional dominance claim fails on those results, but the experiment has not predeclared a total preference ordering across compactness and sharing. Retain the whole metric vector.
Reset eligibility is post-baseline-Layout versus pre-layout in the candidate; equivalent for current unchanged-visibility fixtures only. No folded/filter/omitted-node/State/Allocation/undirected-cycle/dense scaling/UI admission/multi-file source lifecycle acceptance follows. The source work budget is partial and runtime complexity was not established here. No native GUI, universal label-ink correctness, candidate command Undo, or issue32 completion is claimed. Base715 lacks the separate36 measured State label follow-up.
## Next falsifiable experiment
Keep these nine frozen inputs and add one explicit orthogonal proper-crossing witness plus a non-incident overlap/touch counterexample to verify the evaluator distinguishes them. Then isolate diamond/bipartite routing interaction with a tiny predeclared set of node-placement alternatives (current top alignment, centered rank, deterministic stagger, conservative wider gap), keeping the router unchanged. Record actual routed metrics for every alternative; do not silently choose whichever plot looks best or optimize only bend count.
Hypothesis: a bounded alignment/spacing change can remove the candidate's excess nonterminal sharing without surrendering reversed-container and long-label gains. Falsifier: no alternative reaches control-or-better shared length while retaining no new obstacle/label collisions and predeclared reasonable area/length bounds. Use controls' reported geometry as a fixed comparator and preserve every alternative; if it fails, investigate router channel/parallel-edge handling rather than repeatedly widening all spacing or introducing a whole external engine. This next probe remains diagnostic; attachment/hidden-route sentinels and real command/history/native validation follow before production selection.
Evidence SHA256:
- 01-probe/command/result.json: `119179803e8c26b960ca4280e0429bb537a6f9ff5666267e49bfab989f48dc73`
- 01-probe/command/output.log: `21786ae5cb668f60e9252dc884668ee5e98289443d0b3723a9ea69bf21d47fd1`
- 01-probe/command/inputs-before.json: `2e56c60055651f59c58228fb3cdfbd4bfc6dea10236f333ae1824af9a928ecfe`
- 01-probe/command/inputs-after.json: `2e56c60055651f59c58228fb3cdfbd4bfc6dea10236f333ae1824af9a928ecfe`
- geometry/manifest.json: `022fa0f307f9fb92fccbbc139637c62f40d1dcc3a4ea555a35c6b08d82bdb3b5`
@@ -0,0 +1,92 @@
# Target-join scoring experiment
**Keep experimental.** Accounting for the target turn during route-leg selection
confirms the suspected source/target scoring asymmetry, but does not establish
uniform readability improvement. The original bipartite scene becomes more
overlapped. Do not integrate this branch as a completed Arrange fix.
Production routing/test source is `497c1ad8b39788148c5f03bc8542d6d8e28dbc01`.
Successor `08656b18bf8934cf264469cf68fc6dea97c15cbb` only resolves three strict
Clippy findings in the diagnostic observer. Probe runs at both commits emitted
the same 371192 bytes, SHA256
`f9ac7c1320b1cf026259bfcaf13a39e968ae317463712d8dc793375d9a4a541f`.
The [independent source review](terminal-join-source-review.md) and
[independent results audit](terminal-join-results-audit.md) retain the scoped
claims and limitations. The latter predates the completed contact audit below.
The [evidence archive](run-j/terminal-join-08656b18.zip) contains original runs
02–06, including the failed Clippy run, source inventories, full Projects and
geometry, comparison JSON, independent contact audit/script, runners and 37
offline geometry images. It is 3178939 bytes, SHA256
`bd2146237353af05b8667dc7bd728007143a2f1b24ad9f8728fdbc50ffb3368e`.
[Manifest](run-j/manifest.json) records every member; every archived byte was
compared with its original. Paths in original receipts identify the builder's
machine, not a dependency on a peer's filesystem.
## Checks and comparison
Windows Rust 1.98.1 gnullvm, locked/offline, one job, incremental disabled;
Linux excluded. All receipts close with unchanged inputs and no runner failure.
```text
cargo test --locked --offline -p oa-core --lib --test release_routing --test routing_geometry --test routing_labels --test state_diagrams --test group_movement
cargo run --locked --offline -p oa-core --example auto_layout_probe
cargo clippy --locked --offline -p oa-core --all-targets -- -D warnings
```
Tests: 185 passed, zero failed/ignored (132 library, 7 group movement, 13 release
routing, 18 routing geometry, 2 routing labels, 13 State). Three new private tests
exercise fast/grid terminal scoring in four orientations, absent continuation,
straight/reversal and zero-length legs. Existing authored-pin/attachment/history
checks pass. Initial Clippy rejected two manual-range expressions and one nested
if in the observer; after that observer-only cleanup, strict core/all-target
Clippy passes and the rerun output is byte-identical. No whole-workspace/native
acceptance is implied.
Across all 37 before/baseline/reset/layered/manual-witness variants, complete
Projects, projected nodes, projected ports and exact route endpoints are
unchanged versus the first experiment. This isolates routing/label placement;
it is stronger than merely reusing fixture input. All nine candidate invariants,
round trips, deterministic repeats and calls 2–10 remain exact. The candidate
still has no integrated command Undo/Redo evidence.
- Layered diamond: shared length 82→36, nonterminal 46→0; all other original
metrics unchanged. Remaining 36 units are common terminal stubs.
- Layered bipartite: shared length 344→187, nonterminal 272→115; routed drawing
area 165360→175960. Labels, bends, length and other recorded metrics unchanged.
Seven other layered metric vectors are unchanged.
- **Original bipartite regression:** sharing 730→872, nonterminal 658→800;
bends 5→4 and length 2076→2064. The scoring change can favor fewer bends over
distinguishable routes. This is not a monotonic quality improvement.
- Original diamond sharing 136→36; original two-container sharing 200→54;
existing Layout/reset-only two-container sharing 148→18. The independent
audit lists the complete changed vector; 30 of 37 variants are unchanged.
The separate Python contact audit uses orthogonal interval reasoning to
reconstruct BOTH old and new contacts. Primitive witnesses pass; all 37 new
contact/proper counts match Rust, and all 37 old proper counts match v1. Layered
diamond contacts decrease 3→2. Layered bipartite contacts increase 8→9: eight bend
contacts become six bend plus three T contacts. Proper crossings remain zero.
Thus decreased coincident length partly trades into branch contacts. Details,
edge IDs, coordinates and definitions are in the archive's
`support/auto-layout-contact-audit-result-20260925.json`; script is adjacent.
There is no inferred aggregate quality pass.
The parent viewed diamond/bipartite offline images: diamond branches separate,
but bipartite horizontal tails remain visually joined despite separate vertical
channels. These are schematics of core bounds, not native application or glyph
evidence. The [residual diagnosis](residual-routing.md) confirms four genuine
shared semantic ports; distinct-port placement is not the defect. Of 187 shared
units, 72 are bounded common-port stubs and 115 are avoidable interior overlap.
## Next discriminating comparison
Retain this J result as a frozen control. Test one predeclared overlap coefficient
3.0 instead of 0.12, with the existing per-segment cap 80, crossing cost 24,
node/port geometry and edge ordering unchanged. This P variant is an isolated
experiment, not a proposed default or a release-ready source change. Preserve
the original bipartite regression as a sentinel and the entire quality vector.
If stronger penalties cannot choose useful existing alternatives, inspect
bounded parallel lane candidates separately; do not silently combine scoring,
spacing, caps and ordering changes. A candidate that shifts overlap into hard-to-
follow contacts, label collisions or large detours has not solved the user task.
@@ -0,0 +1,112 @@
# Complete scoring/lane comparison
**Stop tuning these nine fixtures; retain the variants as experimental evidence.**
Bounded parallel lane candidates have some measurable value, but do not resolve
the compact bipartite ambiguity and introduce a routing-cost concern. The
stronger overlap coefficient is no longer present at the branch tip. No routing
or Arrange replacement has been accepted for production.
PL source `d0be9ff0674373fcf9623daf62bbf50dad02c4df` combines target-join J,
coefficient 3.0/cap 80 and bounded lanes. L source
`e8ec6dbb3160f62476a5557f14605f68959f65fe` restores coefficient 0.12, retaining
J and lanes. Each leg keeps every obstacle coordinate and adds at most 64
deterministic coordinates per axis at CLEARANCE offsets from settled other-edge
segments. Fast/grid searches receive the same augmented space. Endpoint geometry,
authored pins, attachments and model metadata are not edited by this operation.
The [source review](lanes-source-review.md) records attribution limits: retained
coordinates are bounded, but collection/sorting of prior segments is not charged
to the search budget. Extra axes can hit the existing grid-cell cap and can alter
grid scores through segment subdivision. This is not a proof of globally optimal
paths or constant preparation cost. No new engine/runtime dependency was added.
## Results
Every one of the 37 complete Projects, node sets, port sets and exact endpoints
matches its control in both comparisons. All nine candidate invariants,
roundtrips and repeated-call checks hold. Values below are raw pairwise shared
length for the crossed-bipartite fixture; complete vectors remain in the archive.
| Router | Original scene | Existing Layout/reset | Layered candidate |
| --- | ---: | ---: | ---: |
| Released router | 730 | 276 | 344 |
| J | 872 | 276 | 187 |
| J + stronger penalty (P) | 730 | 276 | 187 |
| J + stronger penalty + lanes (PL) | 724 | 72 | 176 |
| J + lanes, original penalty (L) | 724 | 276 | 176 |
PL's existing-Layout/reset result eliminates 204 nonterminal shared units but
adds one proper crossing, 24 route-length units and 19392 area units. Contacts
decrease 6→5; these tradeoffs stay explicit. L with the original coefficient
does not achieve that baseline improvement. Both lane variants leave 104
nonterminal units in the compact layered case, with nine point contacts. Its
drawing area returns from J's 175960 to 165360. Eight other layered metric
vectors are unchanged. The parent viewed the PL bipartite schematic: vertical
channels are separated, but horizontal tails still join at ambiguous branches.
Compared with J, L reduces original bipartite sharing 872→724 and original
two-container sharing 54→12 without changing the other original quality fields;
two-container baseline/reset sharing reduces 18→12. PL compared with P includes
a two-container original sharing regression 6→12 while reducing one bend, 12
length units and one contact. The [PL audit](penalty-lanes-audit.md) documents all
seven changed rows; no scalar is treated as a universal quality score. Labels,
obstacles and warnings remain separately checked. Core schematics and approximate
label boxes do not establish actual glyph layout or native route traceability.
## Verification and evidence
Same frozen Windows Rust1.98.1 gnullvm environment, one Cargo job, locked/offline,
incremental disabled. Linux excluded. All runs close with exit0, unchanged
inputs and no runner failure:
```text
# PL: five matched tests, including all three new lane tests
cargo test --locked --offline -p oa-core --lib lane
# L: 188 tests passed, zero failed/ignored
cargo test --locked --offline -p oa-core --lib --test release_routing --test routing_geometry --test routing_labels --test state_diagrams --test group_movement
# Both variants: complete nine-fixture diagnostic
cargo run --locked --offline -p oa-core --example auto_layout_probe
# L: strict core/all-target Clippy passed
cargo clippy --locked --offline -p oa-core --all-targets -- -D warnings
```
The full 188 check is for L, not PL: an existing private J test intentionally
asserts the original 0.12 cost. PL's filtered run executes three new lane tests
and two existing Scenario tests matched by `lane`; 130 other library tests are
filtered. Lane witnesses cover base-coordinate preservation, 64-extra bounds,
deduplication/order independence, relevance ranking, nonfinite rejection and
fast/grid selection of a feasible parallel path under the experimental cost.
**Cost signal:** routing_geometry's 18-test runtime is 16.43s under L versus
4.30s under J on the same machine/environment. This excludes compilation, but
is one suite observation per variant, not an isolated route benchmark or a
performance bound. It warrants profiling before adopting per-leg lane scans.
Whole cargo/probe elapsed times include compilation and are unsuitable for that
comparison.
The [independent final L/four-way audit](lanes-results-audit.md) confirms all 37
fixed bindings and the 188-test result, checks the recorded geometry/contact
evidence, and likewise recommends withholding L from production pending the
independent corpus and route-only timing.
[Evidence archive](run-lanes/lanes-e8ec6db.zip), 3518507 bytes, SHA256
`5512b4cbd99e3bf03eb975e25fb980f7b1a95a5cb19f3687fb3d6d83d1839e8f`,
contains original runs08–12, 74 offline images, complete comparisons, independent
axis-based contact audits, binary identities and runners. [Manifest](
run-lanes/manifest.json) binds every member, all byte-compared to originals.
PL output SHA256 `04bb9ba4ab551a965cea130510cca6b96d9c1012911f191b14a7aa7efbf563dd`;
L output SHA256 `64232f580600b7a874a6f4ceb128a6188fd5ff72655bc6b93d824ebed27ffb7d`.
The earlier P result and original archive remain linked in [results-p.md](results-p.md).
## Decision and next evidence
Do not choose a global router policy from these nine scenes. Freeze a small
independent corpus before the next run: realistic authored nested/folded content,
mixed-direction shared/distinct-port fan-in/out with a fixed edge-ID permutation,
and a modest sparse diagram for isolated routing-time/budget observations. Compare
J and L with an explicit route-tracing rubric and full quality vector. Preserve
the original Scene/Arrange distinction and source/history lifecycle constraints.
This should decide whether the lane cost is justified or the simpler router
should be retained before broadening the Layout prototype. Fold/filter rules,
one-command Undo, API/source parity and real desktop acceptance remain required;
the separately pending State-label issue36 is not silently included here.
@@ -0,0 +1,50 @@
# Capped overlap-penalty experiment
**P alone does not solve the arranged bipartite overlap.** Source
`3d7adff923dee5bed54dfdccc119dbee0c7c2773` changes only the overlap coefficient
from 0.12 to 3.0 (plus an experiment comment), retaining the per-segment cap 80.
Node placement, ports, target-join J logic, edge order and crossing cost remain
unchanged. This is a transient diagnostic source variant, not a proposed default.
Windows command `cargo run --locked --offline -p oa-core --example
auto_layout_probe` completed exit 0 with unchanged inputs at
2026-09-25T01:04:57Z. Same frozen Rust1.98.1 gnullvm environment as J; Linux
excluded. Output SHA256
`13111fe89e4e637d1af572a8a45b8bdd763e94b58a2ec5ec6ca83e40ffa209c7`.
J's 185 passing tests and strict Clippy are not claimed for P; this run executes
the diagnostic observer and its analytic primitives only.
[Original evidence archive](run-p/penalty-3d7adff.zip), 916568 bytes, SHA256
`6b819b01c8012aa72d423443f4dc04ae261f1ee9dc6907f0063c219ff173ad36`,
contains receipts, complete Projects/geometry, independent contact audit,
comparison, renderer output and runner. [Manifest](run-p/manifest.json) binds
original bytes and post-run probe executable; every member was checked against
its local original.
Complete Projects, nodes, ports and route endpoints are exact versus J in all
37 variants. All nine layered candidates retain the previous quality vector;
in particular bipartite sharing remains 187 (115 nonterminal), with nine contacts
and zero proper crossings. Two original-scene vectors change; the other 35 do
not:
| Original scene | Shared length J → P | Bends | Route length | Point contacts |
| --- | ---: | ---: | ---: | ---: |
| Crossed bipartite | 872 → 730 | 4 → 5 | 2064 → 2076 | 9 → 10 |
| Reversed children in two containers | 54 → 6 | 4 → 5 | 1512 → 1524 | 2 → 3 |
The bipartite result restores the first experiment's original-scene vector;
the extra bend/length tradeoff was predicted by the low overlap cost diagnosis.
Both contact increases remain visible rather than being counted as a clean
readability win. These results concern core geometry, not native route tracing.
The [independent P audit](penalty-results-audit.md) confirms these results and
all nine recorded conservation/roundtrip/repetition checks. Both additional
point contacts are T contacts; no proper crossing change is hidden by the table.
The next L comparison adds a bounded number of deterministic parallel lane
coordinates while preserving all existing obstacle coordinates and fixed
endpoints. Use the same augmented geometry for fast/grid search. P+L and J+L
will distinguish available alternatives from preference. The cap 80 may still
reject a four-turn detour whose extra length/turn cost exceeds it; retain that
outcome rather than retuning cap, ordering and spacing together. No weight or
lane policy is accepted for production by this diagnostic run.
@@ -0,0 +1,53 @@
# #32 next routing experiment: distinguish shared terminals from ambiguous trunks
Local source/receipt diagnosis only; no builds, formatters, GUI or product edits. Input: completed `e7deea70` probe `.local/auto-layout-checks-20260925/01-probe/command/output.log`; implementation read from `.local/auto-layout-20260925/crates/oa-core/src/routing.rs`. The probe uses **core estimated label bounds**, not renderer glyph measurement. No new candidate module is justified until the required private routing hook is agreed.
## Exact observed geometry
The layered diamond has 82 units of pairwise shared length: 36 classified as common terminal stubs, 46 nonterminal. Edges ending `009` and `00c` share the physical output `(264,106)`, its 18-unit stub to `(282,106)`, then an avoidable extra horizontal run to `(328,106)`. Edge `00c` then turns down to `(328,286)` and enters `(346,286)`. A candidate branching immediately at `(282,106)`, going to `(282,286)`, then right to the target would separate that extra 46-unit trunk while preserving the port and stub. This is a geometric proposal, not a verified collision-free final result; the same obstacle and label checks must evaluate it.
The layered crossed-bipartite case has 344 shared units: 72 common-terminal, 272 nonterminal. Its opposite diagonal routes both use `x=328, y=106..286`, an exact 180-unit coincident vertical run. Top and bottom horizontal portions add 92 nonterminal shared units. The source stubs are at `x=282`; target stubs at `x=328`. Proper crossings are reported as zero, but these coincident routes do not provide unambiguous independent traces. Do not treat the zero as a solved crossing problem.
## Source evidence: a local scoring asymmetry precedes penalty tuning
`crossing_cost` charges `min(shared_length * 0.12, 80)` per previously occupied segment, multiplied by segment count. A proper interior crossing costs24. `BEND_COST` is24. `RoutingSpace::new` uses obstacle boundary coordinates; fast-path candidates add the start/end midpoint, and grid coordinates add start/end. Neither systematically adds lanes offset from previously occupied trunks.
More importantly, `fast_path(start, end, space, previous, incoming)` includes the turn from the preceding source-side segment, but has no outgoing direction parameter. `route_edge` chooses this local path and only later appends the final target attachment point `b.point`; complete `path_cost` is then compared across endpoint choices, not across the discarded alternative paths for that same endpoint pair. With fixed semantic ports there may be no alternate endpoint choice to recover the discarded route. `grid_path` is only a fallback when `fast_path` returns None, not a better-cost search after a valid fast path.
For the diamond, horizontal-then-vertical has one internal bend and no incoming bend; its 46-unit shared portion costs5.52. Vertical-then-horizontal has one internal bend plus an incoming bend, costing24 extra locally. But appending the target segment adds the missing final bend to horizontal-then-vertical. Complete paths have equal bend counts; the local comparison prefers the shared route because it omitted one terminal join. The 180-unit bipartite overlap costs21.6, also below one turn. This explains a plausible bias, not proof that correcting it resolves every route.
## Minimal discriminating comparison (proposal to parent)
Freeze layered node positions, labels' text, attachments, port identities/anchors, edge order and work budgets. Compare a small fixed sequence, not an open parameter search:
1. **J: symmetric terminal-join cost.** Supply an optional outgoing point/direction to fast-path scoring. For the final segment to `b.stub`, use `b.point`; include `turn_cost(penultimate, end, outgoing)` both in actual candidate score and admissible pruning estimates where applicable. Intermediate authored pins have no predetermined outgoing direction; do not invent one. If grid routing supports this experiment too, target state selection must account for the terminal turn before returning the best result. Preserve baseline behavior for absent outgoing context. This isolates the suspected bias without stronger penalties or enlarged diagrams.
2. **JP: J plus stronger shared-run penalty.** One declared test coefficient, e.g.1.0 per unit with the existing80 cap, compared with0.12. Keep the proper crossing cost24 unchanged so the comparison exposes whether penalizing overlap merely substitutes crossings. Do not choose this variant solely by shared length. Preserve all hard endpoint constraints.
3. **JPL: JP plus bounded lane candidates.** For occupied horizontal/vertical trunks, offer parallel offsets of `CLEARANCE` on either side where applicable; validate obstacle clearance and fixed terminal stubs. Use a strict deterministic global cap on added coordinates/candidates and reuse existing work exhaustion handling. This tests whether absent geometric alternatives, rather than insufficient penalty, is the blocker. No persisted waypoint authoring and no unconstrained port movement.
4. **Spacing control.** Only if J/JP fail, compare the unchanged router with one fixed additional inter-layer gap (e.g.24 world units). This is a control, not the assumed remedy: larger gaps may lengthen shared trunks and enlarge diagrams while retaining identical topological ambiguity. Do not mix spacing and router changes in the first comparison.
The first J variant is the smallest informative next experiment. If it removes diamond's extra trunk while retaining invariants, stop and inspect results before implementing JP/JPL. A clean shared terminal fan-out need not eliminate its unavoidable initial18-unit stub. Conversely, sharing a node identity does not exempt arbitrary later overlap, and different physical ports on the same node must not be collapsed into one endpoint exemption.
## Required hook and ownership boundary
The candidate module can call public `route_diagram`, but cannot change private `crossing_cost`, `fast_path`, `grid_path` or candidate generation. Returning a changed Project with temporary authored waypoints would test a different policy and would violate the generated-elbows-remain-derived experiment. Copying the entire router into an example introduces unnecessary divergence.
Therefore propose a parent-owned isolated routing experiment hook, with no default production behavior change, or a separate disposable source variant controlled by the existing runner. Evaluator needs an optional routing provider returning `RoutedView` for a fixed Project, rather than only `candidate -> Project`. It must reuse that provider for every metric, capture, and repeat check and label the variant explicitly. Until parent approves that hook, no module/interface changes were made. The current original/reset/layered comparisons remain reproducible.
## Falsifiers and risks
Evaluate both raw and terminal-excluded shared length, proper crossings, T/bend contacts, route lengths/turns, unrelated-node/header penetration, label collisions, warnings and runtime/work counts. A positive overlap moved onto a bend is not a successful reduction in intersections. The crossed K2,2 fixture with fixed port ordering may require a genuine visible crossing or an outer detour; do not demand zero overlap AND zero crossings without a fixed-attachment feasible witness. Separate forced terminal contacts from independently traceable route interiors.
Keep diamond, crossed-bipartite, chain and long-label fixtures; add a fixed shared-output fan-out, two distinct ports on one node, opposite-direction connections, and a narrow obstacle corridor. Verify exact endpoint coordinates and metadata; preserve ordinary authored pins and preview/settled agreement if the routing change advances beyond this isolated experiment. Stable-ID order can privilege earlier routes, so rerun the same graph under a fixed edge-ID permutation and disclose sensitivity instead of relabeling edges to improve the score.
Labels are placed after paths settle; lane changes can worsen label collisions even when node positions stay fixed. Parent's eventual GUI inspection should trace each diagonal from its source label/port through the corridor to its intended target at normal zoom. No geometry metric alone establishes that user outcome. This note selects a falsifiable experiment, not an accepted product fix.
## Exact J patch plan, held for parent's separate candidate pin
No edits to `routing.rs` are authorized yet. After parent publishes the first experiment evidence, the narrowly proposed change is:
- Add `outgoing: Option<Position>` beside `incoming` in private `fast_path` and `grid_path` only. At `route_edge`'s loop over authored pins followed by `b.stub`, pass `Some(b.point)` only when `index == pins.len()`; pass `None` for every intermediate waypoint. The preceding settled path still supplies `incoming` exactly as now. No public API or metadata change.
- In `fast_path`, extend its shared `join(path)` closure with the terminal turn from `path[path.len()-2]` through `end` to `outgoing`, when two path points exist. Both `estimate(path)` and actual scoring already call `join`; change both consistently through that closure. Keep the two-bend lower-bound early exit: Manhattan distance plus two bend costs remains a lower bound for two-bend alternatives because terminal penalties are nonnegative. Keep final whole-path `path_cost` unchanged; local scoring does not persist a cost, so it does not double-charge the returned path.
- In `grid_path`, add the terminal turn **during relaxation when `next == last`**, using `turn_cost(a, b, outgoing)`. Include it in `costs[state]` and the queue priority before the target state can be popped. Do not merely add a penalty after returning the first reached target: that would fail to compare alternative arrival directions. The existing Manhattan heuristic remains admissible because the new terminal penalty is nonnegative. The initial incoming-turn logic remains unchanged.
- `start == end` has no locally selectable segment. Keep the degenerate result, and rely on whole-path scoring for the junction between preceding/following points. Do not fabricate a direction from an absent penultimate point. Waypoint positions/order remain exact; this patch does not claim globally optimal look-ahead across several hard-pin legs.
Focused tests before comparing full fixtures: (1) fast-path diamond geometry with one occupied horizontal trunk, fixed start/target stubs, asserting the equal-complete-bend alternative branches earlier; (2) directly exercise grid routing with two clear arrivals where adding the target join changes the preferred total, compare complete cost to an enumerated feasible alternative; (3) mirrored/rotated terminal geometry, including straight continuation and reversal costs; (4) outgoing=None preserves established intermediate-waypoint behavior; (5) full route with one/multiple authored pins retains exact pins and terminal attachments, plus start=end leg; (6) preview versus settled geometry remains consistent under the same baseline/budget. Use private unit tests for fast/grid so a passing public route cannot accidentally avoid the grid test. Then rerun the unchanged probe corpus and report the complete quality vector, especially any newly visible crossings. No expected overall improvement is asserted before execution.
@@ -0,0 +1 @@
* -text whitespace=blank-at-eol,blank-at-eof,space-before-tab,cr-at-eol
@@ -0,0 +1,217 @@
{
"source_commit": "e7deea7039bcf31675cffbb462cfeb19d62a7e20",
"baseline_commit": "715036e132677dd2bc2fb37ed316d93dc9987aec",
"scope": "Nine small SA FunctionalDataflow diagnostic fixtures; Windows core geometry, not native UI or production acceptance.",
"binary_after_completed_run": {
"path": "target/debug/examples/auto_layout_probe.exe",
"bytes": 24039424,
"sha256": "a95736170f58d25c60a16f2683b961aad8b31f97e4357c3e0d2f7e81d21176bd"
},
"members": {
"run/command/changes.patch": {
"bytes": 0,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
"run/command/context.json": {
"bytes": 3488,
"sha256": "4dd4e602c2b33fd229a7241e1a4d6a2de399df197b5613af4b3126bfd357c476"
},
"run/command/inputs-after.json": {
"bytes": 1722092,
"sha256": "2e56c60055651f59c58228fb3cdfbd4bfc6dea10236f333ae1824af9a928ecfe"
},
"run/command/inputs-before.json": {
"bytes": 1722092,
"sha256": "2e56c60055651f59c58228fb3cdfbd4bfc6dea10236f333ae1824af9a928ecfe"
},
"run/command/output.log": {
"bytes": 331975,
"sha256": "21786ae5cb668f60e9252dc884668ee5e98289443d0b3723a9ea69bf21d47fd1"
},
"run/command/result.json": {
"bytes": 4124,
"sha256": "119179803e8c26b960ca4280e0429bb537a6f9ff5666267e49bfab989f48dc73"
},
"run/command/stderr.log": {
"bytes": 279,
"sha256": "492e4346b5daf9d9eed2aa96c89c5104b9d8d7f830074b1346c9f5aaf053d662"
},
"run/environment.json": {
"bytes": 9463,
"sha256": "14e53dd6359601bdd9be3ad24c527fca96310d9eb4ffa91965f71841f8df2f82"
},
"run/orchestration.log": {
"bytes": 165,
"sha256": "e4bf56f792c8e8fc9288fc08ef5f0159941b8026e435a427722b59ccc4e34831"
},
"geometry/authored_detour_and_label-after.png": {
"bytes": 12492,
"sha256": "44e30b94ef11b176155ab3df90fe5b52788c07442a42a355d3458fa904c147bd"
},
"geometry/authored_detour_and_label-before.png": {
"bytes": 12468,
"sha256": "097588fae4fd6253c25ab4b60be1ad685ead3a3012b206f382cec0be1341e700"
},
"geometry/authored_detour_and_label-experimental_candidate-metrics.png": {
"bytes": 13162,
"sha256": "82a07de168cda7dacdad2bd532dae408172466b2776acf8cfaabef6711c0539b"
},
"geometry/authored_detour_and_label-reset_only_control-metrics.png": {
"bytes": 12777,
"sha256": "e676edc49ec6ebb2fe29e976c36cba589f76330e7f9eb27b8835415a2ed5d225"
},
"geometry/chain_7_row_wrap-after.png": {
"bytes": 14271,
"sha256": "a020c1535e3d6e48cb5e6d7ae8403a256767c68ce26876fd773f3b626c61e365"
},
"geometry/chain_7_row_wrap-before.png": {
"bytes": 13771,
"sha256": "0b282cbd29eb3e29fa7d3bb9c3c9746caa14c77372b3191d78fa1a60743d0b11"
},
"geometry/chain_7_row_wrap-experimental_candidate-metrics.png": {
"bytes": 14396,
"sha256": "3ca042155d918083c5f4135ae059205b29b8ee5944819a6040a22eeb572f5f04"
},
"geometry/chain_7_row_wrap-reset_only_control-metrics.png": {
"bytes": 14549,
"sha256": "8828f289159deb0c2bedd130495197639775cd8edeb561f0de5501e2980d1e1d"
},
"geometry/crossed_bipartite-after.png": {
"bytes": 13144,
"sha256": "54a1b5e983bc9b6f619e67c13173d0ff5000e30f080aaf6c0a9064f59036f4b7"
},
"geometry/crossed_bipartite-before.png": {
"bytes": 13413,
"sha256": "8622726dfdfc01e93fcd2d9d22c6d082e5a169b9dcd59f788e876954431214fd"
},
"geometry/crossed_bipartite-experimental_candidate-metrics.png": {
"bytes": 13848,
"sha256": "de238d7e904a050b93cc63926f9b4b4723d4b98e6741971fc98f81d771a8a52e"
},
"geometry/crossed_bipartite-reset_only_control-metrics.png": {
"bytes": 13349,
"sha256": "0b2a8c49368258b23c15a8e06bf9b881f0e53fed8b5fd7d6f216a09837634abc"
},
"geometry/diamond-after.png": {
"bytes": 12556,
"sha256": "6afcff1e5c10758e5eab3b803ec858d0af3da953be4af93df75257544a7252b2"
},
"geometry/diamond-before.png": {
"bytes": 13255,
"sha256": "30d514155ec0e88a68e62089cd50b7747924a81095c957aa67b1bf5a70ac18f8"
},
"geometry/diamond-experimental_candidate-metrics.png": {
"bytes": 13581,
"sha256": "14dc1304822e74f8c7e0146a02af69ea39dc90d0d655a75b97383644c04b8900"
},
"geometry/diamond-reset_only_control-metrics.png": {
"bytes": 12982,
"sha256": "48414d7a859c774f2cef51d0fc1512b1c9fe1540c540f96fac7f9531583d8731"
},
"geometry/fixed_ports-after.png": {
"bytes": 11889,
"sha256": "574cb0249c3bcc3140b9d61906332295a8462e32fbcd6b9526d8b05e202e38a0"
},
"geometry/fixed_ports-before.png": {
"bytes": 12013,
"sha256": "d07ef2105aa63f3aaacb19e9a7ca4960239b01f05f734baa6270657a465b4ab0"
},
"geometry/fixed_ports-experimental_candidate-metrics.png": {
"bytes": 12394,
"sha256": "d7fdcd7d4a1a014dd34243400dcd74a7596388fc5747853e5a6629535ca3083e"
},
"geometry/fixed_ports-reset_only_control-metrics.png": {
"bytes": 12231,
"sha256": "237e181db92baf49c24554ec4325137b014413603b62cb08dee63e6e68fa3e8d"
},
"geometry/flat_chain_4-after.png": {
"bytes": 12582,
"sha256": "7326cd482a61bea4cb787770449253a55f41e102794c8fc6a8500d9244c20cc7"
},
"geometry/flat_chain_4-before.png": {
"bytes": 12723,
"sha256": "b086e2b23d4b68cb43366725a26fbb39fdc2e87d18cd7109c9b3ec950ab665ba"
},
"geometry/flat_chain_4-experimental_candidate-metrics.png": {
"bytes": 13405,
"sha256": "3bb352dd5066a3b2cc5bb298c9f47a02416186421df4841b96c944e523d11104"
},
"geometry/flat_chain_4-reset_only_control-metrics.png": {
"bytes": 12918,
"sha256": "eba3eede6771a1ebfc7b773b48b599f5ca49613ad7444ab26acfea7d20dde515"
},
"geometry/long_ordinary_labels-after.png": {
"bytes": 12643,
"sha256": "f4f0c360e9ce44d631dd1d9f7c202954be943fdbede6288edbaddf9b890fcaec"
},
"geometry/long_ordinary_labels-before.png": {
"bytes": 12661,
"sha256": "3c17c1195337cda7cd4141a15428b66cac1475421600b6ecffc3db57dbfd9f94"
},
"geometry/long_ordinary_labels-experimental_candidate-metrics.png": {
"bytes": 13235,
"sha256": "d2931b576901fec68b23afd1a54d4c301eda4a75ba30184344749e0add277bc0"
},
"geometry/long_ordinary_labels-reset_only_control-metrics.png": {
"bytes": 12969,
"sha256": "68aacc7196a4995ff8a884a15ec91a31bd386bdd2d232fd2c27696a7f83cf791"
},
"geometry/manifest.json": {
"bytes": 8099,
"sha256": "022fa0f307f9fb92fccbbc139637c62f40d1dcc3a4ea555a35c6b08d82bdb3b5"
},
"geometry/nested_cross_boundary-after.png": {
"bytes": 13344,
"sha256": "51db6785ab89f5d92c61288604ae3c76aee467e291738b54466db5704c0eec8b"
},
"geometry/nested_cross_boundary-before.png": {
"bytes": 13547,
"sha256": "3b3963f5ef5a42106a006f1825ae5d85b722aa3bf163452ba986ef3369b57cc2"
},
"geometry/nested_cross_boundary-experimental_candidate-metrics.png": {
"bytes": 13535,
"sha256": "0ba365785753e748dc7c86c3f1eb5767f9ccb87c0ae0e18085e6a9d32b79f60d"
},
"geometry/nested_cross_boundary-reset_only_control-metrics.png": {
"bytes": 13549,
"sha256": "fbe21b57616459ecdbbd083dccd73bd120f5b7ad59a8ad80ea38cbbf9c3f49c1"
},
"geometry/two_containers_reversed_children-after.png": {
"bytes": 13950,
"sha256": "1536fa9da0fa89a64b12e7faf6d0dab1c3fa0177517e39fd1adaeaee3f2214fe"
},
"geometry/two_containers_reversed_children-before.png": {
"bytes": 14076,
"sha256": "2f3a2d55d30ff206d9daf9b8f84ba0e960332a02275289c66ab2e104da2e890e"
},
"geometry/two_containers_reversed_children-experimental_candidate-metrics.png": {
"bytes": 14500,
"sha256": "8a0744cf04d305a0431468395b3676604e03991e577f9f9d2f45e8497b1ef9ca"
},
"geometry/two_containers_reversed_children-manual_planar_order_witness-metrics.png": {
"bytes": 14531,
"sha256": "60623d358d9cb930c2c2c4995c9286c653e597c411b8879b77ac099fe9b0406b"
},
"geometry/two_containers_reversed_children-reset_only_control-metrics.png": {
"bytes": 14126,
"sha256": "2483bf404953ce9b3da38f2adca10c4e02e34638e2999acae7beecb5fe936a83"
},
"tooling/run-auto-layout-check-20260925.py": {
"bytes": 2771,
"sha256": "d652c4df47f13aefc723bc54362d7b61308b8983c8d39ae7721c012519358ea2"
},
"tooling/run.py": {
"bytes": 13814,
"sha256": "698ed7182726c77fa4a1c85fc6dd08187ed20689941f57e749e3f0bc845f3252"
},
"tooling/auto-layout-format-precommit-20260925.json": {
"bytes": 364,
"sha256": "29c93825b415e04204d3fe23a85eef0798560bbb5a1a169689326602f65a9f35"
}
},
"archive": {
"name": "probe-e7deea70.zip",
"bytes": 900995,
"sha256": "c52f82d4782cd6a7f8cbe67498c8b2371e99204a65976aa8bcbbd0a86f835069"
}
}
@@ -0,0 +1 @@
* -text whitespace=blank-at-eol,blank-at-eof,space-before-tab,cr-at-eol
@@ -0,0 +1,398 @@
{
"scope": "Target-join candidate experiment, not production or native acceptance",
"runs": [
{
"run": "02-terminal-join-tests",
"source_commit": "497c1ad8b39788148c5f03bc8542d6d8e28dbc01",
"exit_code": 0
},
{
"run": "03-terminal-join-probe",
"source_commit": "497c1ad8b39788148c5f03bc8542d6d8e28dbc01",
"exit_code": 0
},
{
"run": "04-terminal-join-clippy",
"source_commit": "497c1ad8b39788148c5f03bc8542d6d8e28dbc01",
"exit_code": 101
},
{
"run": "05-terminal-join-clippy",
"source_commit": "08656b18bf8934cf264469cf68fc6dea97c15cbb",
"exit_code": 0
},
{
"run": "06-terminal-join-probe",
"source_commit": "08656b18bf8934cf264469cf68fc6dea97c15cbb",
"exit_code": 0
}
],
"observer_lint_cleanup_output_byte_identical": true,
"post_run_probe_binary": {
"bytes": 24156160,
"sha256": "3541618adc25d12ca136941e973f506cc11bc556840193023d4295c27be758a5"
},
"members": {
"02-terminal-join-tests/command/changes.patch": {
"bytes": 0,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
"02-terminal-join-tests/command/context.json": {
"bytes": 3658,
"sha256": "6d8d90920415df092df40afb4031fdbd12d2bc87a0daebea692b9f9288804783"
},
"02-terminal-join-tests/command/inputs-after.json": {
"bytes": 1725008,
"sha256": "bca5713f2201da1c52334e2bd941f76ea59efda5a3036bbb9b9ddd6ec5667606"
},
"02-terminal-join-tests/command/inputs-before.json": {
"bytes": 1725008,
"sha256": "bca5713f2201da1c52334e2bd941f76ea59efda5a3036bbb9b9ddd6ec5667606"
},
"02-terminal-join-tests/command/output.log": {
"bytes": 18709,
"sha256": "f024aaba0fcb680d02d49b5afde398453cf5183a08b561ff96e4c9a42cf70a17"
},
"02-terminal-join-tests/command/result.json": {
"bytes": 4321,
"sha256": "4b56d6ac30cc87ba7c7757906a3d0258e056178528b56c77312bd9675ce579f3"
},
"02-terminal-join-tests/command/stderr.log": {
"bytes": 990,
"sha256": "73f8f63d3339affe4bf6b47408a2ed020aca62606dc14faa03be3f094550eccf"
},
"02-terminal-join-tests/environment.json": {
"bytes": 9463,
"sha256": "14e53dd6359601bdd9be3ad24c527fca96310d9eb4ffa91965f71841f8df2f82"
},
"02-terminal-join-tests/orchestration.log": {
"bytes": 179,
"sha256": "3c673d3e29089ce517cd6026946443f9ae8fcba2fccb384835fffe2e8e19b58d"
},
"03-terminal-join-probe/command/changes.patch": {
"bytes": 0,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
"03-terminal-join-probe/command/context.json": {
"bytes": 3502,
"sha256": "380e3e8fae97e1ee75417c1efc3ee64bdca1958aa61caa1766fba86a6c7e52d3"
},
"03-terminal-join-probe/command/inputs-after.json": {
"bytes": 1725008,
"sha256": "bca5713f2201da1c52334e2bd941f76ea59efda5a3036bbb9b9ddd6ec5667606"
},
"03-terminal-join-probe/command/inputs-before.json": {
"bytes": 1725008,
"sha256": "bca5713f2201da1c52334e2bd941f76ea59efda5a3036bbb9b9ddd6ec5667606"
},
"03-terminal-join-probe/command/output.log": {
"bytes": 371192,
"sha256": "f9ac7c1320b1cf026259bfcaf13a39e968ae317463712d8dc793375d9a4a541f"
},
"03-terminal-join-probe/command/result.json": {
"bytes": 4166,
"sha256": "57a1f866b2cb67880decd26becf3ac2e7715e38bbd26d3089c47dd62294afa4f"
},
"03-terminal-join-probe/command/stderr.log": {
"bytes": 278,
"sha256": "686a3d705aa21b73652e7589b1fa44094bbbc05f3118b2886161e0f815c8a334"
},
"03-terminal-join-probe/environment.json": {
"bytes": 9463,
"sha256": "14e53dd6359601bdd9be3ad24c527fca96310d9eb4ffa91965f71841f8df2f82"
},
"03-terminal-join-probe/orchestration.log": {
"bytes": 179,
"sha256": "bf0516c97a7f3003b29a549f463458f3dc5516b1f41af024e9cf7f6062b9280b"
},
"04-terminal-join-clippy/command/changes.patch": {
"bytes": 0,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
"04-terminal-join-clippy/command/context.json": {
"bytes": 3521,
"sha256": "9f20479acb398ab0dc3c35708ddaef39e4457fcc22f910e13342163718d61fc0"
},
"04-terminal-join-clippy/command/inputs-after.json": {
"bytes": 1725008,
"sha256": "bca5713f2201da1c52334e2bd941f76ea59efda5a3036bbb9b9ddd6ec5667606"
},
"04-terminal-join-clippy/command/inputs-before.json": {
"bytes": 1725008,
"sha256": "bca5713f2201da1c52334e2bd941f76ea59efda5a3036bbb9b9ddd6ec5667606"
},
"04-terminal-join-clippy/command/output.log": {
"bytes": 0,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
"04-terminal-join-clippy/command/result.json": {
"bytes": 4185,
"sha256": "fe9e21a9e203fa0ecfeed957041faa22180287a2ecd158f8a7e77b881ccffc1b"
},
"04-terminal-join-clippy/command/stderr.log": {
"bytes": 2728,
"sha256": "e375843af8e5bb8e921e78b7e7fca844787d6785710506f5e2c5c8b6788beb59"
},
"04-terminal-join-clippy/environment.json": {
"bytes": 9463,
"sha256": "14e53dd6359601bdd9be3ad24c527fca96310d9eb4ffa91965f71841f8df2f82"
},
"04-terminal-join-clippy/orchestration.log": {
"bytes": 182,
"sha256": "949915299dd53b9c59b5116da6f163973cc339d5ae3eb3b1a73151065f68bd6e"
},
"05-terminal-join-clippy/command/changes.patch": {
"bytes": 0,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
"05-terminal-join-clippy/command/context.json": {
"bytes": 3521,
"sha256": "15bd08c0a37dc3e01a0d046b6cd1341aa11bfc858b8da92ad20512e8401e8815"
},
"05-terminal-join-clippy/command/inputs-after.json": {
"bytes": 1725008,
"sha256": "721dfe221e70bc2fee3ffd53061d6ad154c01e843c0c8e325d5c70c756aa809c"
},
"05-terminal-join-clippy/command/inputs-before.json": {
"bytes": 1725008,
"sha256": "721dfe221e70bc2fee3ffd53061d6ad154c01e843c0c8e325d5c70c756aa809c"
},
"05-terminal-join-clippy/command/output.log": {
"bytes": 0,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
"05-terminal-join-clippy/command/result.json": {
"bytes": 4182,
"sha256": "6970eff6a4d9741e27acdb2ef2bfceefcd122c1590741edeb7a112f4f5c357d4"
},
"05-terminal-join-clippy/command/stderr.log": {
"bytes": 177,
"sha256": "5be2ed4d5b119d9b42e2b1e874e3c9e2d6b27cc0610c7d2300760f51a3dccdbe"
},
"05-terminal-join-clippy/environment.json": {
"bytes": 9463,
"sha256": "14e53dd6359601bdd9be3ad24c527fca96310d9eb4ffa91965f71841f8df2f82"
},
"05-terminal-join-clippy/orchestration.log": {
"bytes": 180,
"sha256": "dc27f63c6f1e1ea3c1e6ff6dac85cf49d5927f6784ee768c3c12b1e8ec62848f"
},
"06-terminal-join-probe/command/changes.patch": {
"bytes": 0,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
"06-terminal-join-probe/command/context.json": {
"bytes": 3502,
"sha256": "5500c31ead4f64132a628646a9863f587e59ef8281a74a70fcff9a038c283200"
},
"06-terminal-join-probe/command/inputs-after.json": {
"bytes": 1725008,
"sha256": "721dfe221e70bc2fee3ffd53061d6ad154c01e843c0c8e325d5c70c756aa809c"
},
"06-terminal-join-probe/command/inputs-before.json": {
"bytes": 1725008,
"sha256": "721dfe221e70bc2fee3ffd53061d6ad154c01e843c0c8e325d5c70c756aa809c"
},
"06-terminal-join-probe/command/output.log": {
"bytes": 371192,
"sha256": "f9ac7c1320b1cf026259bfcaf13a39e968ae317463712d8dc793375d9a4a541f"
},
"06-terminal-join-probe/command/result.json": {
"bytes": 4166,
"sha256": "bd3b20390a03ed7c637586119e12e0e55949303f9b17f6e018f231b1a9095833"
},
"06-terminal-join-probe/command/stderr.log": {
"bytes": 278,
"sha256": "8a49d76b8cf56f31f6877cb6baf9d9f82d5abfbc1bfcc2ae73b8d3ff209c482c"
},
"06-terminal-join-probe/environment.json": {
"bytes": 9463,
"sha256": "14e53dd6359601bdd9be3ad24c527fca96310d9eb4ffa91965f71841f8df2f82"
},
"06-terminal-join-probe/orchestration.log": {
"bytes": 179,
"sha256": "e85db49cd4d73dbff0b45f92cfb96016f743db3e64878ceeee078515e7fe324d"
},
"geometry/authored_detour_and_label-after.png": {
"bytes": 12492,
"sha256": "44e30b94ef11b176155ab3df90fe5b52788c07442a42a355d3458fa904c147bd"
},
"geometry/authored_detour_and_label-before.png": {
"bytes": 12468,
"sha256": "097588fae4fd6253c25ab4b60be1ad685ead3a3012b206f382cec0be1341e700"
},
"geometry/authored_detour_and_label-experimental_candidate-metrics.png": {
"bytes": 13162,
"sha256": "82a07de168cda7dacdad2bd532dae408172466b2776acf8cfaabef6711c0539b"
},
"geometry/authored_detour_and_label-reset_only_control-metrics.png": {
"bytes": 12777,
"sha256": "e676edc49ec6ebb2fe29e976c36cba589f76330e7f9eb27b8835415a2ed5d225"
},
"geometry/chain_7_row_wrap-after.png": {
"bytes": 14271,
"sha256": "a020c1535e3d6e48cb5e6d7ae8403a256767c68ce26876fd773f3b626c61e365"
},
"geometry/chain_7_row_wrap-before.png": {
"bytes": 13771,
"sha256": "0b282cbd29eb3e29fa7d3bb9c3c9746caa14c77372b3191d78fa1a60743d0b11"
},
"geometry/chain_7_row_wrap-experimental_candidate-metrics.png": {
"bytes": 14396,
"sha256": "3ca042155d918083c5f4135ae059205b29b8ee5944819a6040a22eeb572f5f04"
},
"geometry/chain_7_row_wrap-reset_only_control-metrics.png": {
"bytes": 14549,
"sha256": "8828f289159deb0c2bedd130495197639775cd8edeb561f0de5501e2980d1e1d"
},
"geometry/crossed_bipartite-after.png": {
"bytes": 13144,
"sha256": "54a1b5e983bc9b6f619e67c13173d0ff5000e30f080aaf6c0a9064f59036f4b7"
},
"geometry/crossed_bipartite-before.png": {
"bytes": 13406,
"sha256": "aa3ba4f8ab67f6cd63d1c31fe309786e139db78ec6a36a0b00e8a854b590fe0c"
},
"geometry/crossed_bipartite-experimental_candidate-metrics.png": {
"bytes": 13855,
"sha256": "e3cc0a326a2c8b577e40101c5f67ea9c3182bca6d3f0498cdc1f25a64df57b90"
},
"geometry/crossed_bipartite-reset_only_control-metrics.png": {
"bytes": 13349,
"sha256": "0b2a8c49368258b23c15a8e06bf9b881f0e53fed8b5fd7d6f216a09837634abc"
},
"geometry/diamond-after.png": {
"bytes": 12556,
"sha256": "6afcff1e5c10758e5eab3b803ec858d0af3da953be4af93df75257544a7252b2"
},
"geometry/diamond-before.png": {
"bytes": 13228,
"sha256": "38e341a1ed51d4b4a94513f34c3d48b05c72177dcc3792dc660cb780fd7f98c9"
},
"geometry/diamond-experimental_candidate-metrics.png": {
"bytes": 13555,
"sha256": "a61d178e2cd3fcff8fc4ed3c42e5ab71b042d26d776f59fe5eaadb3ef8481e5e"
},
"geometry/diamond-reset_only_control-metrics.png": {
"bytes": 12982,
"sha256": "48414d7a859c774f2cef51d0fc1512b1c9fe1540c540f96fac7f9531583d8731"
},
"geometry/fixed_ports-after.png": {
"bytes": 11889,
"sha256": "574cb0249c3bcc3140b9d61906332295a8462e32fbcd6b9526d8b05e202e38a0"
},
"geometry/fixed_ports-before.png": {
"bytes": 12013,
"sha256": "d07ef2105aa63f3aaacb19e9a7ca4960239b01f05f734baa6270657a465b4ab0"
},
"geometry/fixed_ports-experimental_candidate-metrics.png": {
"bytes": 12394,
"sha256": "d7fdcd7d4a1a014dd34243400dcd74a7596388fc5747853e5a6629535ca3083e"
},
"geometry/fixed_ports-reset_only_control-metrics.png": {
"bytes": 12231,
"sha256": "237e181db92baf49c24554ec4325137b014413603b62cb08dee63e6e68fa3e8d"
},
"geometry/flat_chain_4-after.png": {
"bytes": 12582,
"sha256": "7326cd482a61bea4cb787770449253a55f41e102794c8fc6a8500d9244c20cc7"
},
"geometry/flat_chain_4-before.png": {
"bytes": 12723,
"sha256": "b086e2b23d4b68cb43366725a26fbb39fdc2e87d18cd7109c9b3ec950ab665ba"
},
"geometry/flat_chain_4-experimental_candidate-metrics.png": {
"bytes": 13405,
"sha256": "3bb352dd5066a3b2cc5bb298c9f47a02416186421df4841b96c944e523d11104"
},
"geometry/flat_chain_4-reset_only_control-metrics.png": {
"bytes": 12918,
"sha256": "eba3eede6771a1ebfc7b773b48b599f5ca49613ad7444ab26acfea7d20dde515"
},
"geometry/long_ordinary_labels-after.png": {
"bytes": 12643,
"sha256": "f4f0c360e9ce44d631dd1d9f7c202954be943fdbede6288edbaddf9b890fcaec"
},
"geometry/long_ordinary_labels-before.png": {
"bytes": 12661,
"sha256": "3c17c1195337cda7cd4141a15428b66cac1475421600b6ecffc3db57dbfd9f94"
},
"geometry/long_ordinary_labels-experimental_candidate-metrics.png": {
"bytes": 13235,
"sha256": "d2931b576901fec68b23afd1a54d4c301eda4a75ba30184344749e0add277bc0"
},
"geometry/long_ordinary_labels-reset_only_control-metrics.png": {
"bytes": 12969,
"sha256": "68aacc7196a4995ff8a884a15ec91a31bd386bdd2d232fd2c27696a7f83cf791"
},
"geometry/manifest.json": {
"bytes": 8099,
"sha256": "0102449f730651a92233c4ae959e95e0a58036fe2b1c309224558c496241c741"
},
"geometry/nested_cross_boundary-after.png": {
"bytes": 13344,
"sha256": "51db6785ab89f5d92c61288604ae3c76aee467e291738b54466db5704c0eec8b"
},
"geometry/nested_cross_boundary-before.png": {
"bytes": 13547,
"sha256": "3b3963f5ef5a42106a006f1825ae5d85b722aa3bf163452ba986ef3369b57cc2"
},
"geometry/nested_cross_boundary-experimental_candidate-metrics.png": {
"bytes": 13535,
"sha256": "0ba365785753e748dc7c86c3f1eb5767f9ccb87c0ae0e18085e6a9d32b79f60d"
},
"geometry/nested_cross_boundary-reset_only_control-metrics.png": {
"bytes": 13549,
"sha256": "fbe21b57616459ecdbbd083dccd73bd120f5b7ad59a8ad80ea38cbbf9c3f49c1"
},
"geometry/two_containers_reversed_children-after.png": {
"bytes": 13955,
"sha256": "1722ff21eaadd831e00b2176d61f8fa65b083d2fd5b633a196a3335d48ca125e"
},
"geometry/two_containers_reversed_children-before.png": {
"bytes": 14080,
"sha256": "78e765c579dd317e26177d0cd05cccc61519638e9198fdd0d344d9b54a95c1ba"
},
"geometry/two_containers_reversed_children-experimental_candidate-metrics.png": {
"bytes": 14500,
"sha256": "8a0744cf04d305a0431468395b3676604e03991e577f9f9d2f45e8497b1ef9ca"
},
"geometry/two_containers_reversed_children-manual_planar_order_witness-metrics.png": {
"bytes": 14531,
"sha256": "60623d358d9cb930c2c2c4995c9286c653e597c411b8879b77ac099fe9b0406b"
},
"geometry/two_containers_reversed_children-reset_only_control-metrics.png": {
"bytes": 14134,
"sha256": "ce6bc93dc38fe5b25552c8ebc5e778da175df53992e6a70ded8377fd99372587"
},
"support/auto-layout-j-comparison-20260925.json": {
"bytes": 76669,
"sha256": "3d5435686c2d23e97f08340693015ef66d1127726c9b5b5212678f43b3ad6b76"
},
"support/auto-layout-contact-audit-result-20260925.json": {
"bytes": 104481,
"sha256": "ec94203f42782d87c0459c251ab226567818e2715bc46a3e9ee0c25a6c15d620"
},
"support/auto-layout-contact-audit-20260925.py": {
"bytes": 9838,
"sha256": "398b9677d2ef2333514dd9c1ba5bc2f020c9927f395ed33bf11ac59b68139f46"
},
"support/auto-layout-format-j-20260925.json": {
"bytes": 203,
"sha256": "5bd0e4b979aedf21bd38e4c41a1521dafffb78c3cccb61cd12dad56d4f4699e9"
},
"support/run-auto-layout-check-20260925.py": {
"bytes": 2771,
"sha256": "d652c4df47f13aefc723bc54362d7b61308b8983c8d39ae7721c012519358ea2"
},
"support/run.py": {
"bytes": 13814,
"sha256": "698ed7182726c77fa4a1c85fc6dd08187ed20689941f57e749e3f0bc845f3252"
}
},
"archive": {
"name": "terminal-join-08656b18.zip",
"bytes": 3178939,
"sha256": "bd2146237353af05b8667dc7bd728007143a2f1b24ad9f8728fdbc50ffb3368e"
}
}
@@ -0,0 +1 @@
* -text whitespace=blank-at-eol,blank-at-eof,space-before-tab,cr-at-eol
@@ -0,0 +1,565 @@
{
"scope": "Finite PL/L experiments; no production/native/performance acceptance",
"runs": [
{
"name": "08-lane-tests",
"source_commit": "d0be9ff0674373fcf9623daf62bbf50dad02c4df",
"exit_code": 0
},
{
"name": "09-penalty-lanes-probe",
"source_commit": "d0be9ff0674373fcf9623daf62bbf50dad02c4df",
"exit_code": 0
},
{
"name": "10-lanes-core-tests",
"source_commit": "e8ec6dbb3160f62476a5557f14605f68959f65fe",
"exit_code": 0
},
{
"name": "11-lanes-probe",
"source_commit": "e8ec6dbb3160f62476a5557f14605f68959f65fe",
"exit_code": 0
},
{
"name": "12-lanes-clippy",
"source_commit": "e8ec6dbb3160f62476a5557f14605f68959f65fe",
"exit_code": 0
}
],
"post_run_l_probe_binary": {
"bytes": 24202240,
"sha256": "3c6a6fd09bc86e7e6b3ff1ace05540c103393d66bbb02c46bf0ebd6214fbabb1"
},
"members": {
"08-lane-tests/command/changes.patch": {
"bytes": 0,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
"08-lane-tests/command/context.json": {
"bytes": 3478,
"sha256": "7f30e52a87c964d86e86229061be72372a9cfc1a3fad9a37c326370c38aec4ad"
},
"08-lane-tests/command/inputs-after.json": {
"bytes": 1730052,
"sha256": "2173ea00107237f320ab249a6fcde0ec616421aa09d6c274ef563e9974b47e6e"
},
"08-lane-tests/command/inputs-before.json": {
"bytes": 1730052,
"sha256": "2173ea00107237f320ab249a6fcde0ec616421aa09d6c274ef563e9974b47e6e"
},
"08-lane-tests/command/output.log": {
"bytes": 670,
"sha256": "1b3cc48bee9bb4d564f0479a692b7b20fa6df5da3b8dccfda5d1487b6b93f53e"
},
"08-lane-tests/command/result.json": {
"bytes": 4121,
"sha256": "9a48e66ce638944e885e708b3f361ce2d099352e154536a46fafa6bca49af12f"
},
"08-lane-tests/command/stderr.log": {
"bytes": 304,
"sha256": "c534ac6e8df5add7c08f663cd516b9a0da947a93127ebda6c378ede9bd42f575"
},
"08-lane-tests/environment.json": {
"bytes": 9463,
"sha256": "14e53dd6359601bdd9be3ad24c527fca96310d9eb4ffa91965f71841f8df2f82"
},
"08-lane-tests/orchestration.log": {
"bytes": 170,
"sha256": "bf1ebdbbdd1955bd7b874592340e3d79a48a145a9b39faec898aaa807133f81a"
},
"09-penalty-lanes-probe/command/changes.patch": {
"bytes": 0,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
"09-penalty-lanes-probe/command/context.json": {
"bytes": 3502,
"sha256": "4c95894559f60d51fda0e2af09b2ec66abf9dc687d623c1e67c9e042747d0084"
},
"09-penalty-lanes-probe/command/inputs-after.json": {
"bytes": 1730052,
"sha256": "2173ea00107237f320ab249a6fcde0ec616421aa09d6c274ef563e9974b47e6e"
},
"09-penalty-lanes-probe/command/inputs-before.json": {
"bytes": 1730052,
"sha256": "2173ea00107237f320ab249a6fcde0ec616421aa09d6c274ef563e9974b47e6e"
},
"09-penalty-lanes-probe/command/output.log": {
"bytes": 371062,
"sha256": "04bb9ba4ab551a965cea130510cca6b96d9c1012911f191b14a7aa7efbf563dd"
},
"09-penalty-lanes-probe/command/result.json": {
"bytes": 4166,
"sha256": "538aa79bcedd4cf646c63d5309ec738be4676e2045715db1035a4f334a536117"
},
"09-penalty-lanes-probe/command/stderr.log": {
"bytes": 279,
"sha256": "126be48ed1535d641835bd72b6fc5aa05a9a2120dd2012daa3e30108eb1703e2"
},
"09-penalty-lanes-probe/environment.json": {
"bytes": 9463,
"sha256": "14e53dd6359601bdd9be3ad24c527fca96310d9eb4ffa91965f71841f8df2f82"
},
"09-penalty-lanes-probe/orchestration.log": {
"bytes": 179,
"sha256": "4e398b4fed1ac76d6bd61cb18fe2c50cae2764f040d878006e464c5728428cd3"
},
"10-lanes-core-tests/command/changes.patch": {
"bytes": 0,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
"10-lanes-core-tests/command/context.json": {
"bytes": 3655,
"sha256": "2ee756c13c33bccf0cb7c87492bb62be5d5203044a066af0af36389059b2181c"
},
"10-lanes-core-tests/command/inputs-after.json": {
"bytes": 1730052,
"sha256": "27af33c77ce384358dfbd8a6730d2eff7399575fb1cacbb2c338eaa441e5db91"
},
"10-lanes-core-tests/command/inputs-before.json": {
"bytes": 1730052,
"sha256": "27af33c77ce384358dfbd8a6730d2eff7399575fb1cacbb2c338eaa441e5db91"
},
"10-lanes-core-tests/command/output.log": {
"bytes": 19032,
"sha256": "bde8202db1c2f3f407a7dcbf6a1184830d0914e2bbba0e5f7cbd2c55d72d56b4"
},
"10-lanes-core-tests/command/result.json": {
"bytes": 4312,
"sha256": "828871867eb7ff0ccfd5953a694ae594ae145e89bf2eca831368297d1ea9b874"
},
"10-lanes-core-tests/command/stderr.log": {
"bytes": 990,
"sha256": "df86da47eb1884669f1d0ce14107713ea9afd9e3cbb68becf451a018a1bf7303"
},
"10-lanes-core-tests/environment.json": {
"bytes": 9463,
"sha256": "14e53dd6359601bdd9be3ad24c527fca96310d9eb4ffa91965f71841f8df2f82"
},
"10-lanes-core-tests/orchestration.log": {
"bytes": 176,
"sha256": "6a2f55638fd26b60333bcd61cc1faaa29baaf9041962cbc1cfacd762bfc933e5"
},
"11-lanes-probe/command/changes.patch": {
"bytes": 0,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
"11-lanes-probe/command/context.json": {
"bytes": 3494,
"sha256": "d8ec80836b09c1859b7d482dbd2ebb0243495be3e022ce2cb5dcc5c47e9464bf"
},
"11-lanes-probe/command/inputs-after.json": {
"bytes": 1730052,
"sha256": "27af33c77ce384358dfbd8a6730d2eff7399575fb1cacbb2c338eaa441e5db91"
},
"11-lanes-probe/command/inputs-before.json": {
"bytes": 1730052,
"sha256": "27af33c77ce384358dfbd8a6730d2eff7399575fb1cacbb2c338eaa441e5db91"
},
"11-lanes-probe/command/output.log": {
"bytes": 371182,
"sha256": "64232f580600b7a874a6f4ceb128a6188fd5ff72655bc6b93d824ebed27ffb7d"
},
"11-lanes-probe/command/result.json": {
"bytes": 4142,
"sha256": "feee84c2fde115c22ea865033ff2d136da2aace1f1c8a7423c8add11358366a8"
},
"11-lanes-probe/command/stderr.log": {
"bytes": 278,
"sha256": "2e73f2722528e8f80b9e64f722643769e279d822835e9ea47e96cb07eaf51162"
},
"11-lanes-probe/environment.json": {
"bytes": 9463,
"sha256": "14e53dd6359601bdd9be3ad24c527fca96310d9eb4ffa91965f71841f8df2f82"
},
"11-lanes-probe/orchestration.log": {
"bytes": 171,
"sha256": "93c3867e1dc8b07314a7ac03db0d76b641eda2eaad1ad58a61914f1d6ec64794"
},
"12-lanes-clippy/command/changes.patch": {
"bytes": 0,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
"12-lanes-clippy/command/context.json": {
"bytes": 3513,
"sha256": "eb644e36413cc55e61b7f6aa6162f7f17f21f67911123f951f1ed96c91b30430"
},
"12-lanes-clippy/command/inputs-after.json": {
"bytes": 1730052,
"sha256": "27af33c77ce384358dfbd8a6730d2eff7399575fb1cacbb2c338eaa441e5db91"
},
"12-lanes-clippy/command/inputs-before.json": {
"bytes": 1730052,
"sha256": "27af33c77ce384358dfbd8a6730d2eff7399575fb1cacbb2c338eaa441e5db91"
},
"12-lanes-clippy/command/output.log": {
"bytes": 0,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
"12-lanes-clippy/command/result.json": {
"bytes": 4158,
"sha256": "ea20c2e64c26667f575b70b598a790aceb1804070c208e5f8f1dd7839e8b0a8f"
},
"12-lanes-clippy/command/stderr.log": {
"bytes": 177,
"sha256": "5e1eff044f676ea902db61f76e53375f034cc2cd33c8671a56d11dd4a2aa2936"
},
"12-lanes-clippy/environment.json": {
"bytes": 9463,
"sha256": "14e53dd6359601bdd9be3ad24c527fca96310d9eb4ffa91965f71841f8df2f82"
},
"12-lanes-clippy/orchestration.log": {
"bytes": 172,
"sha256": "44d905169275ce11c8f66e16ce232d750f6b2701e6fb085880e9ad013219d18b"
},
"pl-geometry/authored_detour_and_label-after.png": {
"bytes": 12492,
"sha256": "44e30b94ef11b176155ab3df90fe5b52788c07442a42a355d3458fa904c147bd"
},
"pl-geometry/authored_detour_and_label-before.png": {
"bytes": 12468,
"sha256": "097588fae4fd6253c25ab4b60be1ad685ead3a3012b206f382cec0be1341e700"
},
"pl-geometry/authored_detour_and_label-experimental_candidate-metrics.png": {
"bytes": 13162,
"sha256": "82a07de168cda7dacdad2bd532dae408172466b2776acf8cfaabef6711c0539b"
},
"pl-geometry/authored_detour_and_label-reset_only_control-metrics.png": {
"bytes": 12777,
"sha256": "e676edc49ec6ebb2fe29e976c36cba589f76330e7f9eb27b8835415a2ed5d225"
},
"pl-geometry/chain_7_row_wrap-after.png": {
"bytes": 14271,
"sha256": "a020c1535e3d6e48cb5e6d7ae8403a256767c68ce26876fd773f3b626c61e365"
},
"pl-geometry/chain_7_row_wrap-before.png": {
"bytes": 13771,
"sha256": "0b282cbd29eb3e29fa7d3bb9c3c9746caa14c77372b3191d78fa1a60743d0b11"
},
"pl-geometry/chain_7_row_wrap-experimental_candidate-metrics.png": {
"bytes": 14396,
"sha256": "3ca042155d918083c5f4135ae059205b29b8ee5944819a6040a22eeb572f5f04"
},
"pl-geometry/chain_7_row_wrap-reset_only_control-metrics.png": {
"bytes": 14549,
"sha256": "8828f289159deb0c2bedd130495197639775cd8edeb561f0de5501e2980d1e1d"
},
"pl-geometry/crossed_bipartite-after.png": {
"bytes": 13163,
"sha256": "bb809e93b44e802d05430903cf397618fb901efdb6c5ab272574064e20641af6"
},
"pl-geometry/crossed_bipartite-before.png": {
"bytes": 13399,
"sha256": "9b7af6a43ec655cdcb05da49b5fffea661d4f5c9a5ae70a0c74c6e7696b066d3"
},
"pl-geometry/crossed_bipartite-experimental_candidate-metrics.png": {
"bytes": 13856,
"sha256": "c386b537f077f014e73e99831fdbb57e64ae9709a30afd2909dd50d7443c1899"
},
"pl-geometry/crossed_bipartite-reset_only_control-metrics.png": {
"bytes": 13368,
"sha256": "bdcf079498dc2dce5245e47eac8038d736cea7af6176ada13bf682671c70b4b4"
},
"pl-geometry/diamond-after.png": {
"bytes": 12556,
"sha256": "6afcff1e5c10758e5eab3b803ec858d0af3da953be4af93df75257544a7252b2"
},
"pl-geometry/diamond-before.png": {
"bytes": 13228,
"sha256": "38e341a1ed51d4b4a94513f34c3d48b05c72177dcc3792dc660cb780fd7f98c9"
},
"pl-geometry/diamond-experimental_candidate-metrics.png": {
"bytes": 13555,
"sha256": "a61d178e2cd3fcff8fc4ed3c42e5ab71b042d26d776f59fe5eaadb3ef8481e5e"
},
"pl-geometry/diamond-reset_only_control-metrics.png": {
"bytes": 12982,
"sha256": "48414d7a859c774f2cef51d0fc1512b1c9fe1540c540f96fac7f9531583d8731"
},
"pl-geometry/fixed_ports-after.png": {
"bytes": 11889,
"sha256": "574cb0249c3bcc3140b9d61906332295a8462e32fbcd6b9526d8b05e202e38a0"
},
"pl-geometry/fixed_ports-before.png": {
"bytes": 12013,
"sha256": "d07ef2105aa63f3aaacb19e9a7ca4960239b01f05f734baa6270657a465b4ab0"
},
"pl-geometry/fixed_ports-experimental_candidate-metrics.png": {
"bytes": 12394,
"sha256": "d7fdcd7d4a1a014dd34243400dcd74a7596388fc5747853e5a6629535ca3083e"
},
"pl-geometry/fixed_ports-reset_only_control-metrics.png": {
"bytes": 12231,
"sha256": "237e181db92baf49c24554ec4325137b014413603b62cb08dee63e6e68fa3e8d"
},
"pl-geometry/flat_chain_4-after.png": {
"bytes": 12582,
"sha256": "7326cd482a61bea4cb787770449253a55f41e102794c8fc6a8500d9244c20cc7"
},
"pl-geometry/flat_chain_4-before.png": {
"bytes": 12723,
"sha256": "b086e2b23d4b68cb43366725a26fbb39fdc2e87d18cd7109c9b3ec950ab665ba"
},
"pl-geometry/flat_chain_4-experimental_candidate-metrics.png": {
"bytes": 13405,
"sha256": "3bb352dd5066a3b2cc5bb298c9f47a02416186421df4841b96c944e523d11104"
},
"pl-geometry/flat_chain_4-reset_only_control-metrics.png": {
"bytes": 12918,
"sha256": "eba3eede6771a1ebfc7b773b48b599f5ca49613ad7444ab26acfea7d20dde515"
},
"pl-geometry/long_ordinary_labels-after.png": {
"bytes": 12643,
"sha256": "f4f0c360e9ce44d631dd1d9f7c202954be943fdbede6288edbaddf9b890fcaec"
},
"pl-geometry/long_ordinary_labels-before.png": {
"bytes": 12661,
"sha256": "3c17c1195337cda7cd4141a15428b66cac1475421600b6ecffc3db57dbfd9f94"
},
"pl-geometry/long_ordinary_labels-experimental_candidate-metrics.png": {
"bytes": 13235,
"sha256": "d2931b576901fec68b23afd1a54d4c301eda4a75ba30184344749e0add277bc0"
},
"pl-geometry/long_ordinary_labels-reset_only_control-metrics.png": {
"bytes": 12969,
"sha256": "68aacc7196a4995ff8a884a15ec91a31bd386bdd2d232fd2c27696a7f83cf791"
},
"pl-geometry/manifest.json": {
"bytes": 8099,
"sha256": "dc9c09882d841e6ba050caabf411de8dd0e868879fe8f0b7a1b44cece9ffde8b"
},
"pl-geometry/nested_cross_boundary-after.png": {
"bytes": 13344,
"sha256": "51db6785ab89f5d92c61288604ae3c76aee467e291738b54466db5704c0eec8b"
},
"pl-geometry/nested_cross_boundary-before.png": {
"bytes": 13547,
"sha256": "3b3963f5ef5a42106a006f1825ae5d85b722aa3bf163452ba986ef3369b57cc2"
},
"pl-geometry/nested_cross_boundary-experimental_candidate-metrics.png": {
"bytes": 13535,
"sha256": "0ba365785753e748dc7c86c3f1eb5767f9ccb87c0ae0e18085e6a9d32b79f60d"
},
"pl-geometry/nested_cross_boundary-reset_only_control-metrics.png": {
"bytes": 13549,
"sha256": "fbe21b57616459ecdbbd083dccd73bd120f5b7ad59a8ad80ea38cbbf9c3f49c1"
},
"pl-geometry/two_containers_reversed_children-after.png": {
"bytes": 13957,
"sha256": "b0911f8afb39313e6ce43661396d6553276d5db53c659937acf87252ed502c6e"
},
"pl-geometry/two_containers_reversed_children-before.png": {
"bytes": 14083,
"sha256": "2d42d34af4674bbf1739e85967d743e6fc78c117ef1e7645724c504b006ee881"
},
"pl-geometry/two_containers_reversed_children-experimental_candidate-metrics.png": {
"bytes": 14500,
"sha256": "8a0744cf04d305a0431468395b3676604e03991e577f9f9d2f45e8497b1ef9ca"
},
"pl-geometry/two_containers_reversed_children-manual_planar_order_witness-metrics.png": {
"bytes": 14531,
"sha256": "60623d358d9cb930c2c2c4995c9286c653e597c411b8879b77ac099fe9b0406b"
},
"pl-geometry/two_containers_reversed_children-reset_only_control-metrics.png": {
"bytes": 14136,
"sha256": "f690678d5697cbec20e64bf92a128a8fd3a1dffe3948fcbe75ad99a4f33de96d"
},
"support/auto-layout-pl-comparison-20260925.json": {
"bytes": 75815,
"sha256": "81f57e651a8840aaaeacc085df2eb90174b2e494182f6cd40ccd6d50e7bb531e"
},
"support/auto-layout-pl-contact-audit-20260925.json": {
"bytes": 103918,
"sha256": "60ce7e9735ceef50a92d29a0414aafe3d8fb6174dab99eff6397e2abc88e0afa"
},
"support/auto-layout-pl-binary-20260925.json": {
"bytes": 220,
"sha256": "00f0ceb0e92a156de2d870e32b1d57d88465e5acedd9c5f5e733f183a750c46e"
},
"l-geometry/authored_detour_and_label-after.png": {
"bytes": 12492,
"sha256": "44e30b94ef11b176155ab3df90fe5b52788c07442a42a355d3458fa904c147bd"
},
"l-geometry/authored_detour_and_label-before.png": {
"bytes": 12468,
"sha256": "097588fae4fd6253c25ab4b60be1ad685ead3a3012b206f382cec0be1341e700"
},
"l-geometry/authored_detour_and_label-experimental_candidate-metrics.png": {
"bytes": 13162,
"sha256": "82a07de168cda7dacdad2bd532dae408172466b2776acf8cfaabef6711c0539b"
},
"l-geometry/authored_detour_and_label-reset_only_control-metrics.png": {
"bytes": 12777,
"sha256": "e676edc49ec6ebb2fe29e976c36cba589f76330e7f9eb27b8835415a2ed5d225"
},
"l-geometry/chain_7_row_wrap-after.png": {
"bytes": 14271,
"sha256": "a020c1535e3d6e48cb5e6d7ae8403a256767c68ce26876fd773f3b626c61e365"
},
"l-geometry/chain_7_row_wrap-before.png": {
"bytes": 13771,
"sha256": "0b282cbd29eb3e29fa7d3bb9c3c9746caa14c77372b3191d78fa1a60743d0b11"
},
"l-geometry/chain_7_row_wrap-experimental_candidate-metrics.png": {
"bytes": 14396,
"sha256": "3ca042155d918083c5f4135ae059205b29b8ee5944819a6040a22eeb572f5f04"
},
"l-geometry/chain_7_row_wrap-reset_only_control-metrics.png": {
"bytes": 14549,
"sha256": "8828f289159deb0c2bedd130495197639775cd8edeb561f0de5501e2980d1e1d"
},
"l-geometry/crossed_bipartite-after.png": {
"bytes": 13144,
"sha256": "54a1b5e983bc9b6f619e67c13173d0ff5000e30f080aaf6c0a9064f59036f4b7"
},
"l-geometry/crossed_bipartite-before.png": {
"bytes": 13399,
"sha256": "9b7af6a43ec655cdcb05da49b5fffea661d4f5c9a5ae70a0c74c6e7696b066d3"
},
"l-geometry/crossed_bipartite-experimental_candidate-metrics.png": {
"bytes": 13856,
"sha256": "c386b537f077f014e73e99831fdbb57e64ae9709a30afd2909dd50d7443c1899"
},
"l-geometry/crossed_bipartite-reset_only_control-metrics.png": {
"bytes": 13349,
"sha256": "0b2a8c49368258b23c15a8e06bf9b881f0e53fed8b5fd7d6f216a09837634abc"
},
"l-geometry/diamond-after.png": {
"bytes": 12556,
"sha256": "6afcff1e5c10758e5eab3b803ec858d0af3da953be4af93df75257544a7252b2"
},
"l-geometry/diamond-before.png": {
"bytes": 13228,
"sha256": "38e341a1ed51d4b4a94513f34c3d48b05c72177dcc3792dc660cb780fd7f98c9"
},
"l-geometry/diamond-experimental_candidate-metrics.png": {
"bytes": 13555,
"sha256": "a61d178e2cd3fcff8fc4ed3c42e5ab71b042d26d776f59fe5eaadb3ef8481e5e"
},
"l-geometry/diamond-reset_only_control-metrics.png": {
"bytes": 12982,
"sha256": "48414d7a859c774f2cef51d0fc1512b1c9fe1540c540f96fac7f9531583d8731"
},
"l-geometry/fixed_ports-after.png": {
"bytes": 11889,
"sha256": "574cb0249c3bcc3140b9d61906332295a8462e32fbcd6b9526d8b05e202e38a0"
},
"l-geometry/fixed_ports-before.png": {
"bytes": 12013,
"sha256": "d07ef2105aa63f3aaacb19e9a7ca4960239b01f05f734baa6270657a465b4ab0"
},
"l-geometry/fixed_ports-experimental_candidate-metrics.png": {
"bytes": 12394,
"sha256": "d7fdcd7d4a1a014dd34243400dcd74a7596388fc5747853e5a6629535ca3083e"
},
"l-geometry/fixed_ports-reset_only_control-metrics.png": {
"bytes": 12231,
"sha256": "237e181db92baf49c24554ec4325137b014413603b62cb08dee63e6e68fa3e8d"
},
"l-geometry/flat_chain_4-after.png": {
"bytes": 12582,
"sha256": "7326cd482a61bea4cb787770449253a55f41e102794c8fc6a8500d9244c20cc7"
},
"l-geometry/flat_chain_4-before.png": {
"bytes": 12723,
"sha256": "b086e2b23d4b68cb43366725a26fbb39fdc2e87d18cd7109c9b3ec950ab665ba"
},
"l-geometry/flat_chain_4-experimental_candidate-metrics.png": {
"bytes": 13405,
"sha256": "3bb352dd5066a3b2cc5bb298c9f47a02416186421df4841b96c944e523d11104"
},
"l-geometry/flat_chain_4-reset_only_control-metrics.png": {
"bytes": 12918,
"sha256": "eba3eede6771a1ebfc7b773b48b599f5ca49613ad7444ab26acfea7d20dde515"
},
"l-geometry/long_ordinary_labels-after.png": {
"bytes": 12643,
"sha256": "f4f0c360e9ce44d631dd1d9f7c202954be943fdbede6288edbaddf9b890fcaec"
},
"l-geometry/long_ordinary_labels-before.png": {
"bytes": 12661,
"sha256": "3c17c1195337cda7cd4141a15428b66cac1475421600b6ecffc3db57dbfd9f94"
},
"l-geometry/long_ordinary_labels-experimental_candidate-metrics.png": {
"bytes": 13235,
"sha256": "d2931b576901fec68b23afd1a54d4c301eda4a75ba30184344749e0add277bc0"
},
"l-geometry/long_ordinary_labels-reset_only_control-metrics.png": {
"bytes": 12969,
"sha256": "68aacc7196a4995ff8a884a15ec91a31bd386bdd2d232fd2c27696a7f83cf791"
},
"l-geometry/manifest.json": {
"bytes": 8099,
"sha256": "897b9d42b494bac1e5263a8bab42756c3038403948e6ac76028d86fd6e56ae20"
},
"l-geometry/nested_cross_boundary-after.png": {
"bytes": 13344,
"sha256": "51db6785ab89f5d92c61288604ae3c76aee467e291738b54466db5704c0eec8b"
},
"l-geometry/nested_cross_boundary-before.png": {
"bytes": 13547,
"sha256": "3b3963f5ef5a42106a006f1825ae5d85b722aa3bf163452ba986ef3369b57cc2"
},
"l-geometry/nested_cross_boundary-experimental_candidate-metrics.png": {
"bytes": 13535,
"sha256": "0ba365785753e748dc7c86c3f1eb5767f9ccb87c0ae0e18085e6a9d32b79f60d"
},
"l-geometry/nested_cross_boundary-reset_only_control-metrics.png": {
"bytes": 13549,
"sha256": "fbe21b57616459ecdbbd083dccd73bd120f5b7ad59a8ad80ea38cbbf9c3f49c1"
},
"l-geometry/two_containers_reversed_children-after.png": {
"bytes": 13957,
"sha256": "b0911f8afb39313e6ce43661396d6553276d5db53c659937acf87252ed502c6e"
},
"l-geometry/two_containers_reversed_children-before.png": {
"bytes": 14083,
"sha256": "2d42d34af4674bbf1739e85967d743e6fc78c117ef1e7645724c504b006ee881"
},
"l-geometry/two_containers_reversed_children-experimental_candidate-metrics.png": {
"bytes": 14500,
"sha256": "8a0744cf04d305a0431468395b3676604e03991e577f9f9d2f45e8497b1ef9ca"
},
"l-geometry/two_containers_reversed_children-manual_planar_order_witness-metrics.png": {
"bytes": 14531,
"sha256": "60623d358d9cb930c2c2c4995c9286c653e597c411b8879b77ac099fe9b0406b"
},
"l-geometry/two_containers_reversed_children-reset_only_control-metrics.png": {
"bytes": 14136,
"sha256": "f690678d5697cbec20e64bf92a128a8fd3a1dffe3948fcbe75ad99a4f33de96d"
},
"support/auto-layout-l-comparison-20260925.json": {
"bytes": 76659,
"sha256": "e1e23e6c2a50cd384457bb061d5b70a740f586f42767164fad9703ecbb4096a8"
},
"support/auto-layout-l-contact-audit-20260925.json": {
"bytes": 103893,
"sha256": "6f6cc674c27d3f43001fe1751ef3b09332caa9ddca396759f5525f811cbe1aaf"
},
"support/auto-layout-l-binary-20260925.json": {
"bytes": 220,
"sha256": "9c6d6e20c86237e5d1b917a981f2a5a8612d1c58b6e0706d4701e6c7cc59e06f"
},
"support/auto-layout-contact-audit-20260925.py": {
"bytes": 9838,
"sha256": "398b9677d2ef2333514dd9c1ba5bc2f020c9927f395ed33bf11ac59b68139f46"
},
"support/run-auto-layout-check-20260925.py": {
"bytes": 2771,
"sha256": "d652c4df47f13aefc723bc54362d7b61308b8983c8d39ae7721c012519358ea2"
},
"support/auto-layout-format-j-20260925.json": {
"bytes": 203,
"sha256": "5bd0e4b979aedf21bd38e4c41a1521dafffb78c3cccb61cd12dad56d4f4699e9"
},
"support/run.py": {
"bytes": 13814,
"sha256": "698ed7182726c77fa4a1c85fc6dd08187ed20689941f57e749e3f0bc845f3252"
}
},
"archive": {
"name": "lanes-e8ec6db.zip",
"bytes": 3518507,
"sha256": "5512b4cbd99e3bf03eb975e25fb980f7b1a95a5cb19f3687fb3d6d83d1839e8f"
}
}
@@ -0,0 +1 @@
* -text whitespace=blank-at-eol,blank-at-eof,space-before-tab,cr-at-eol
@@ -0,0 +1,223 @@
{
"source_commit": "3d7adff923dee5bed54dfdccc119dbee0c7c2773",
"scope": "Transient P coefficient3.0/cap80 experiment, not production acceptance or claim that J tests pass with P.",
"post_run_probe_binary": {
"bytes": 24156160,
"sha256": "8f3ac5aa1fa01be6a8ab664e8ae269f6527b8ffb7e326c2926c8fae7c3d7e366"
},
"members": {
"run/command/changes.patch": {
"bytes": 0,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
"run/command/context.json": {
"bytes": 3504,
"sha256": "6f6019b2be69e12e3270d8570e9e4950c93e42e8b3093a0e0324a30f7e5eb0ff"
},
"run/command/inputs-after.json": {
"bytes": 1727969,
"sha256": "607febcd0d74d11deaa07a44b8b9cf6c382fd65d55deadb2d8ac7e889797dcf1"
},
"run/command/inputs-before.json": {
"bytes": 1727969,
"sha256": "607febcd0d74d11deaa07a44b8b9cf6c382fd65d55deadb2d8ac7e889797dcf1"
},
"run/command/output.log": {
"bytes": 371606,
"sha256": "13111fe89e4e637d1af572a8a45b8bdd763e94b58a2ec5ec6ca83e40ffa209c7"
},
"run/command/result.json": {
"bytes": 4172,
"sha256": "6149122d05d77e02b463edb3dd359b06786bc4468c27d8cd625cbc1bc80c9894"
},
"run/command/stderr.log": {
"bytes": 279,
"sha256": "fe5d8d303700ef5b92bfaa95ac1f99a2214e1fc5f6c5ac8d83f6303aa258965b"
},
"run/environment.json": {
"bytes": 9463,
"sha256": "14e53dd6359601bdd9be3ad24c527fca96310d9eb4ffa91965f71841f8df2f82"
},
"run/orchestration.log": {
"bytes": 181,
"sha256": "db5af22d893966919b9342fd06b99e7e1c82c5c87593544b7762cc85f9a7e45a"
},
"geometry/authored_detour_and_label-after.png": {
"bytes": 12492,
"sha256": "44e30b94ef11b176155ab3df90fe5b52788c07442a42a355d3458fa904c147bd"
},
"geometry/authored_detour_and_label-before.png": {
"bytes": 12468,
"sha256": "097588fae4fd6253c25ab4b60be1ad685ead3a3012b206f382cec0be1341e700"
},
"geometry/authored_detour_and_label-experimental_candidate-metrics.png": {
"bytes": 13162,
"sha256": "82a07de168cda7dacdad2bd532dae408172466b2776acf8cfaabef6711c0539b"
},
"geometry/authored_detour_and_label-reset_only_control-metrics.png": {
"bytes": 12777,
"sha256": "e676edc49ec6ebb2fe29e976c36cba589f76330e7f9eb27b8835415a2ed5d225"
},
"geometry/chain_7_row_wrap-after.png": {
"bytes": 14271,
"sha256": "a020c1535e3d6e48cb5e6d7ae8403a256767c68ce26876fd773f3b626c61e365"
},
"geometry/chain_7_row_wrap-before.png": {
"bytes": 13771,
"sha256": "0b282cbd29eb3e29fa7d3bb9c3c9746caa14c77372b3191d78fa1a60743d0b11"
},
"geometry/chain_7_row_wrap-experimental_candidate-metrics.png": {
"bytes": 14396,
"sha256": "3ca042155d918083c5f4135ae059205b29b8ee5944819a6040a22eeb572f5f04"
},
"geometry/chain_7_row_wrap-reset_only_control-metrics.png": {
"bytes": 14549,
"sha256": "8828f289159deb0c2bedd130495197639775cd8edeb561f0de5501e2980d1e1d"
},
"geometry/crossed_bipartite-after.png": {
"bytes": 13144,
"sha256": "54a1b5e983bc9b6f619e67c13173d0ff5000e30f080aaf6c0a9064f59036f4b7"
},
"geometry/crossed_bipartite-before.png": {
"bytes": 13413,
"sha256": "8622726dfdfc01e93fcd2d9d22c6d082e5a169b9dcd59f788e876954431214fd"
},
"geometry/crossed_bipartite-experimental_candidate-metrics.png": {
"bytes": 13855,
"sha256": "e3cc0a326a2c8b577e40101c5f67ea9c3182bca6d3f0498cdc1f25a64df57b90"
},
"geometry/crossed_bipartite-reset_only_control-metrics.png": {
"bytes": 13349,
"sha256": "0b2a8c49368258b23c15a8e06bf9b881f0e53fed8b5fd7d6f216a09837634abc"
},
"geometry/diamond-after.png": {
"bytes": 12556,
"sha256": "6afcff1e5c10758e5eab3b803ec858d0af3da953be4af93df75257544a7252b2"
},
"geometry/diamond-before.png": {
"bytes": 13228,
"sha256": "38e341a1ed51d4b4a94513f34c3d48b05c72177dcc3792dc660cb780fd7f98c9"
},
"geometry/diamond-experimental_candidate-metrics.png": {
"bytes": 13555,
"sha256": "a61d178e2cd3fcff8fc4ed3c42e5ab71b042d26d776f59fe5eaadb3ef8481e5e"
},
"geometry/diamond-reset_only_control-metrics.png": {
"bytes": 12982,
"sha256": "48414d7a859c774f2cef51d0fc1512b1c9fe1540c540f96fac7f9531583d8731"
},
"geometry/fixed_ports-after.png": {
"bytes": 11889,
"sha256": "574cb0249c3bcc3140b9d61906332295a8462e32fbcd6b9526d8b05e202e38a0"
},
"geometry/fixed_ports-before.png": {
"bytes": 12013,
"sha256": "d07ef2105aa63f3aaacb19e9a7ca4960239b01f05f734baa6270657a465b4ab0"
},
"geometry/fixed_ports-experimental_candidate-metrics.png": {
"bytes": 12394,
"sha256": "d7fdcd7d4a1a014dd34243400dcd74a7596388fc5747853e5a6629535ca3083e"
},
"geometry/fixed_ports-reset_only_control-metrics.png": {
"bytes": 12231,
"sha256": "237e181db92baf49c24554ec4325137b014413603b62cb08dee63e6e68fa3e8d"
},
"geometry/flat_chain_4-after.png": {
"bytes": 12582,
"sha256": "7326cd482a61bea4cb787770449253a55f41e102794c8fc6a8500d9244c20cc7"
},
"geometry/flat_chain_4-before.png": {
"bytes": 12723,
"sha256": "b086e2b23d4b68cb43366725a26fbb39fdc2e87d18cd7109c9b3ec950ab665ba"
},
"geometry/flat_chain_4-experimental_candidate-metrics.png": {
"bytes": 13405,
"sha256": "3bb352dd5066a3b2cc5bb298c9f47a02416186421df4841b96c944e523d11104"
},
"geometry/flat_chain_4-reset_only_control-metrics.png": {
"bytes": 12918,
"sha256": "eba3eede6771a1ebfc7b773b48b599f5ca49613ad7444ab26acfea7d20dde515"
},
"geometry/long_ordinary_labels-after.png": {
"bytes": 12643,
"sha256": "f4f0c360e9ce44d631dd1d9f7c202954be943fdbede6288edbaddf9b890fcaec"
},
"geometry/long_ordinary_labels-before.png": {
"bytes": 12661,
"sha256": "3c17c1195337cda7cd4141a15428b66cac1475421600b6ecffc3db57dbfd9f94"
},
"geometry/long_ordinary_labels-experimental_candidate-metrics.png": {
"bytes": 13235,
"sha256": "d2931b576901fec68b23afd1a54d4c301eda4a75ba30184344749e0add277bc0"
},
"geometry/long_ordinary_labels-reset_only_control-metrics.png": {
"bytes": 12969,
"sha256": "68aacc7196a4995ff8a884a15ec91a31bd386bdd2d232fd2c27696a7f83cf791"
},
"geometry/manifest.json": {
"bytes": 8099,
"sha256": "c1b59384878e519efec99757efee571635f30d814247db2f183eebbf9c467a0f"
},
"geometry/nested_cross_boundary-after.png": {
"bytes": 13344,
"sha256": "51db6785ab89f5d92c61288604ae3c76aee467e291738b54466db5704c0eec8b"
},
"geometry/nested_cross_boundary-before.png": {
"bytes": 13547,
"sha256": "3b3963f5ef5a42106a006f1825ae5d85b722aa3bf163452ba986ef3369b57cc2"
},
"geometry/nested_cross_boundary-experimental_candidate-metrics.png": {
"bytes": 13535,
"sha256": "0ba365785753e748dc7c86c3f1eb5767f9ccb87c0ae0e18085e6a9d32b79f60d"
},
"geometry/nested_cross_boundary-reset_only_control-metrics.png": {
"bytes": 13549,
"sha256": "fbe21b57616459ecdbbd083dccd73bd120f5b7ad59a8ad80ea38cbbf9c3f49c1"
},
"geometry/two_containers_reversed_children-after.png": {
"bytes": 13955,
"sha256": "1722ff21eaadd831e00b2176d61f8fa65b083d2fd5b633a196a3335d48ca125e"
},
"geometry/two_containers_reversed_children-before.png": {
"bytes": 14092,
"sha256": "dcbc521b8e5efd6a201da8a724ed218a9f1331a8abb2eda299a5807828037cb9"
},
"geometry/two_containers_reversed_children-experimental_candidate-metrics.png": {
"bytes": 14500,
"sha256": "8a0744cf04d305a0431468395b3676604e03991e577f9f9d2f45e8497b1ef9ca"
},
"geometry/two_containers_reversed_children-manual_planar_order_witness-metrics.png": {
"bytes": 14531,
"sha256": "60623d358d9cb930c2c2c4995c9286c653e597c411b8879b77ac099fe9b0406b"
},
"geometry/two_containers_reversed_children-reset_only_control-metrics.png": {
"bytes": 14134,
"sha256": "ce6bc93dc38fe5b25552c8ebc5e778da175df53992e6a70ded8377fd99372587"
},
"support/auto-layout-p-comparison-20260925.json": {
"bytes": 77494,
"sha256": "9f228fbe78e1888c5d93e87dddd8d2effc01bab723929a87423c61e23f5cad66"
},
"support/auto-layout-p-contact-audit-20260925.json": {
"bytes": 104912,
"sha256": "6f4add5c81f6d16c45efabbefd9e36c9beb637b6823993f0d51adc3e20485ad9"
},
"support/auto-layout-contact-audit-20260925.py": {
"bytes": 9838,
"sha256": "398b9677d2ef2333514dd9c1ba5bc2f020c9927f395ed33bf11ac59b68139f46"
},
"support/run-auto-layout-check-20260925.py": {
"bytes": 2771,
"sha256": "d652c4df47f13aefc723bc54362d7b61308b8983c8d39ae7721c012519358ea2"
},
"support/run.py": {
"bytes": 13814,
"sha256": "698ed7182726c77fa4a1c85fc6dd08187ed20689941f57e749e3f0bc845f3252"
}
},
"archive": {
"name": "penalty-3d7adff.zip",
"bytes": 916568,
"sha256": "6b819b01c8012aa72d423443f4dc04ae261f1ee9dc6907f0063c219ff173ad36"
}
}
@@ -0,0 +1,61 @@
# #32 Auto-layout source and evaluator map
Read-only inspection of `.local/auto-layout-20260925`, base715036e132677dd2bc2fb37ed316d93dc9987aec, on2026-09-25. Issue32 explicitly asks automatic creation/removal of bends, attention to connection intersections and labels. No source changes, compiler, GUI, model helper or Gitea writes were performed. No geometric outcomes below are claimed as measured results.
## Actual surfaces and shared execution
- `crates/oa-desktop/src/views.rs:443`: saved Architecture diagram toolbar **Auto-layout** sends `Message::Command(Command::Layout(id))`. `model_menu.rs:417`: background **Arrange elements** sends the same command. The generic chooser excludes Scenario, not State, but normal State navigation routes to its dedicated workspace. `behavior/state_menu.rs` returns early for the dedicated State menu, and `state_view.rs` has no equivalent Auto-layout button. State view creation still calls shared Layout (`behavior/state_edit.rs:361–410`); behavior creation batches Layout too (`behavior/creation.rs:477–504`). Distinguish exposed UI from callable core behavior.
- `crates/oa-core/src/model.rs:1431`: saved projection kinds are Overview, Decomposition, Allocation, Traceability, FunctionalDataflow, Capabilities, StateMachine and Scenario. `session.rs:947` accepts the first seven; Scenario explicitly refuses and requires its separate geometry operation. Process topology and Scenario lane/message arrangement are separate behavior canvases, not this generic command.
- Allocation **Arrange next drag / Alt+A** is a different manual translation workflow (`allocation_drag/arrange.rs`, `app/allocation_drag.rs`, `diagram/work_drag.rs`). Do not silently turn it into automatic graph layout or change its allocation/group/refusal/Undo contracts merely because its label contains Arrange.
- `app.rs:3559` applies pending property edits and remaps a changed diagram identity before executing Layout; preserve that admission/identity behavior (`tests.rs::placement_identity_edit_then_drag_and_diagram_identity_then_arrange`). `document.rs:322` prepares/validates a candidate, updates the source bundle with identity provenance, then commits one model/source history entry. Core Session execute at`session.rs:275` likewise records one changed candidate; Undo/Redo swap exact Projects and no-op candidates create no history.
- REST transactions at`crates/oa-server/src/routes.rs:1011–1041` accept serialized Command batches including Layout under expected-revision admission, with dry-run and atomic PreparedProject/store commit. Core behavior therefore cannot rely on desktop-only fonts/state. `docs/v2-interface.md` lists Layout convenience commands. CLI exposes check/lint/stabilize/rename-alias, not a direct layout verb. Text/source may store explicit node/route records; parsing/loading them must not implicitly auto-arrange.
## Current algorithm and why the issue is real
`session.rs::layout` measures a projection-parent forest, vertically stacks children below headers, and puts roots into rows of four. Roots use an exchange-aware topological-like ordering with alias/ID tie breaks and deterministic cycle fallback. Child order is placement-ID order. It changes node positions/sizes, computes per-node deltas and calls `projection::translated_routes`; it neither scores routed geometry/labels nor generates or removes stored waypoints.
`projection.rs:81` supplies actual visual parents: nearest placed semantic ancestor; Allocation derives performer grouping from allocation summaries; Traceability is flat; State filters the machine. Ports have owner placements, not independent node placements (`edge_endpoint_placements`). Measurement reserves minimum sizes, State action/internal rows and port footer (`session.rs:967–1006`). Folded descendants still belong to the authored forest; projected visibility and stored layout are distinct.
The router is **not unaware of crossings or labels**: `routing.rs:457` routes stable edge IDs with a bounded search and previous-edge costs; `crossing_cost:990` penalizes proper crossings and positive collinear overlap separately. All paths settle before automatic label placement; `label_geometry`/`label_collisions:1370–1552` consider node obstacles, earlier labels and all visible strokes. But routing remains greedy, has finite search budgets, and authored label placement bypasses avoidance. Auto-layout never feeds these costs back into node order/spacing. Existing pins constrain paths even after node arrangement, except folded/promoted routes temporarily suspend their custom geometry. Thus a node-only Layout plus pinned greedy routing cannot promise the joint improvement requested by32.
Base715 uses scalar-estimated label rectangles. The separate #36 measured State multiline layout is not present in this base; do not assume its geometry or acceptance has already integrated. Ordinary labels, association-end labels/stereotypes and State labels also differ in presentation. Parent should coordinate routing-file overlap with #36 before integration, not silently reimplement its measurement policy.
## Contracts requiring an intentional change
1. `harness/release-routing-contract.md:143–146` explicitly says Layout preserves pins, translating only equally moved endpoint owners, with Reset routing separate. `crates/oa-desktop/src/guide.rs:360` repeats **Auto-layout preserves pins**. These are directly incompatible with the user's requested automatic bend removal/generation and must be revised for the explicit whole-view operation, with Undo restoring prior authorship.
2. `crates/oa-core/tests/release_routing.rs::layout_preserves_authored_route_count_order_labels_and_undo` fixes route list count/order, two pins on each route and exact labels. Replace only the superseded Layout assertions with the new declared output contract plus stronger geometry checks; retain unrelated-diagram exactness and exact Undo. Do not remove all route-preservation tests.
3. `state_diagrams.rs::layout_reserves_internal_rows_and_state_routes_retain_hard_pins` combines two things: the **pre-Layout** router must traverse authored pins without mutation; the post-Layout assertions reserve State rows and source roundtrip. Preserve those valid assertions, and rename/split the test if Layout no longer retains pins. Its current body does not actually assert post-Layout pin preservation.
4. Decide explicitly whether generated bends remain derived route corners or become persisted editable waypoints. The user asks visible automatic routing improvement, not necessarily a new semantic field. `routing.rs` documents only authored pins in the model; blindly storing every solver corner would turn generated geometry into hard constraints and can cause second-run drift. Whichever representation is chosen needs first/repeat Arrange and manual-after-Arrange tests.
5. Define scope for invisible/suspended routes, authored label offsets and explicit attachments. User32 authorizes changing bends; it does not automatically authorize deleting manual semantic-port metadata, every hidden route, or authored label placement. A narrow baseline is reset/rebuild bends for eligible visible edges while retaining attachments, labels and unrelated/hidden records; measure whether that suffices. If an authored label constraint defeats label clearance, report it or make its intentional reset policy explicit instead of pretending the layout is collision-free.
## Invariants to retain
Complete elements/relations/traces/types/names/text/IDs/containment/allocations/occurrences remain exact; only the selected diagram's declared presentation fields may differ. Other diagrams, node identities, collapsed flags, view options, unplaced elements and manual port side/fraction metadata remain exact. Automatic port geometry may change with node geometry, but semantic port identity/contract and explicit anchors do not. Existing tests `port_anchors::separate_views_resize_layout_and_hidden_owners_keep_manual_metadata`, `workflows` nested-parent containment, and desktop port/identity tests are relevant sentinels.
Keep move-one-end pins-fixed and move-both-owners equal-delta translation in `projection::translated_routes`, normal drag/reflow/group previews, reconnect, folding suspension/resumption, Hide/show and Reset routing unchanged unless separately justified. Keep finite geometry and existing point-count/attachment/fraction validation (`validation.rs:650–720`), deterministic bounded failure/fallback, source/JSON roundtrips, dry-run nonmutation, rejected-operation no-history, and exact one-step Undo/Redo. A Layout improvement must not trigger new allocation/reconnect semantics.
## Independent fixtures and falsifiers to freeze before candidate comparison
Use hand-declared topology, IDs and geometry; record the old candidate's metrics once, then compare materially different candidates against the same evaluator. Do not use a candidate's own cost function as the acceptance oracle.
| Fixture | Independent observable requirement |
| --- | --- |
| Two connected OA activities / SA functions with redundant dogleg and one blocking third card | Explicit Arrange removes obsolete turns; unobstructed edge straightens, blocked edge gains necessary orthogonal turns; semantic endpoints exact, no nonincident-node penetration. Include authored source/target attachments. |
| Two containers, each with two children, reversed child-order connections | A planar hand-constructed witness has zero proper crossings; require zero on this bounded case, child containment and preserved port contract. Detect current ID-ordered child stacking weakness. |
| Four-node diamond plus return edge, self-loop, parallel and reverse-direction edges | No false cycle/deadlock; meaningful separation of parallel paths; self-loop remains visible; distinguish shared endpoint fans from mid-path overlap. Test swapped UUIDs and stable semantic aliases. |
| Five-plus roots and disconnected component | Exposes current every-four-roots wrap and ordering discontinuity. No overlapping cards, reasonable route/area bounds and deterministic repeated result. |
| OA/SA nested Allocation with ports, uniform subtree, folded ancestor and hidden route | Child interiors below reserved headers/footer, anchors on owner boundary, no fabricated ports, no semantic reassignment; hidden-route policy explicit; other view exact. |
| Traceability mixed OA/SA with duplicate placement sentinels elsewhere | Flat visual graph stays flat, valid edge identities and other diagram exact. |
| State machine with composite State, region, entry/exit boundary, internal/action rows and self-transition | State-specific legal boundary crossings, row reservations and no overwritten semantic state; shared Layout initialization remains valid even without a State Auto-layout button. |
| Long ordinary/association labels, adjacent parallel labels, and #36 B/W/U State cases once integrated | Independently measure label/node, label/label and unrelated stroke/label intersections using actual displayed geometry; native ink/readability check separate from advance-box arithmetic. |
| Dense/nonplanar or contradictory-anchor stress case | Finite time/work and explicit residual warnings/metrics; no claim all crossings can disappear. Preserve exact Undo and input on refusal. |
## Metrics and acceptance discipline
Report a vector, not a single opaque beauty score: (1) hard invalid geometry/illegal obstacle penetration and sibling overlap area; (2) label-node/label-label intersection area and other-edge strokes entering labels; (3) distinct proper edge-pair crossings; (4) positive-length collinear shared segments; (5) bend count after duplicate/collinear simplification, path length/stretch and total view bounds. Define endpoint/ancestor exemptions before running: parent-child containment is intentional, a shared semantic endpoint is not a crossing, owner leads and legal State boundary crossings are not nonincident obstacle violations. Deduplicate segment-pair crossing events at the same physical point; count edge pairs separately. Count near-parallel clearance as a separate readability observation, not zero-width intersection.
Use independent rectangle/segment math and full projected/routed outputs; export SVG and canvas must agree with those outputs. Include authored and derived labels, including association ends. A zero-crossing result obtained by coincident overlapping paths, gigantic detours, clipping labels or hiding edges fails the other metrics. Set fixture-specific route/area ceilings from a feasible manually declared witness rather than arbitrary post-hoc thresholds. Compare a second Arrange for idempotence, input-order permutation for determinism, pure camera changes for model invariance, then source Save/reopen and exact Undo/Redo.
Unavoidable cases: nonplanar graphs cannot all be planar with distinct nonoverlapping edges; fixed attachment orders, nested boundaries, coincident semantic endpoints and required self-loops can constrain clearance. Existing authored labels can intentionally occlude nodes. Report constrained residuals honestly and avoid turning ordinary automatic layout into deletion of semantic edges or impossible universal no-crossing guarantees.
The first useful milestone is a finite fixture/evaluator plus an explicit bend/label/hidden-route policy, then a prototype compared against current Layout. No need to expand into unrelated Process/Scenario geometry or redesign manual Arrange drag for this issue.
@@ -0,0 +1,39 @@
# J terminal-join actual-results independent audit
Result: the finite experiment supports the terminal-cost asymmetry diagnosis, with a real ordinary-scene overlap regression retained below. It does not establish universally improved routing, complete readability, or #32 acceptance. Static audit only: no compilation, probe rerun, GUI, source edit or publication by reviewer.
## Evidence and checks
Tested pin `497c1ad8b39788148c5f03bc8542d6d8e28dbc01`, compared against first probe `e7deea7039bcf31675cffbb462cfeb19d62a7e20`. Run03 command `cargo run --locked --offline -p oa-core --example auto_layout_probe`, Windows x86_64-pc-windows-gnullvm/dev: receipt exit0, failure null, inputs unchanged. Independently verified receipt log hashes. Test02 at the same pin also exit0/unchanged: six summaries132+7+13+18+2+13 =185 passed, zero failed/ignored.
Independently parsed both raw JSONL outputs, rather than relying only on comparison-helper booleans: all37 variant Projects, complete projected nodes/ports, and per-edge first/last coordinates match exactly. Thus changed area is routed/label bounds, not changed node placement. Independently recomputed every raw pairwise axis-aligned collinear interval total in both runs (74 variant observations); all agree with emitted totals.
All nine candidate rows report supported, identical repeated input, idempotence, passes2–10 equal first and previous, exact JSON/canonical-single-source roundtrips, no associated errors. Conservation remains true except the intentional authored-detour route reset (`authored_routes_exact=false`). Exact old/new Projects additionally confirm J changed none of these model records. Existing Command::Layout history/Undo/Redo checks pass in all nine rows; the layered candidate remains a pure Project transformation with no candidate-command history evidence. No native GUI evidence.
## Complete changed quality-vector rows
| Fixture / variant | Raw sharing old→J | Nonterminal sharing old→J | Other changed fields |
|---|---:|---:|---|
| diamond / before |136→36|100→0|none|
| diamond / layered candidate |82→36|46→0|none|
| crossed_bipartite / before |730→872|658→800|bends5→4; length2076→2064|
| crossed_bipartite / layered candidate |344→187|272→115|drawing area165360→175960|
| two_containers_reversed_children / before |200→54|200→54|none|
| two_containers_reversed_children / baseline |148→18|148→18|none|
| two_containers_reversed_children / reset control |148→18|148→18|none|
These are all changes in the comparison's pre-existing12-field quality vector; the remaining30 rows are unchanged. All baseline/reset rows outside the two-container case are unchanged. Seven of nine layered candidate vectors remain unchanged. Raw strict proper crossings remain zero throughout these comparisons; no demonstrated proper-crossing reduction follows.
The crossed-bipartite BEFORE regression is substantive, not a metric artifact: independent interval sums confirm142 additional shared units. Fewer bends and12 shorter length do not erase that readability tradeoff. Layered bipartite improves157 shared units but retains115 nonterminal units and grows routed drawing area10600 (~6.41%). Viewed its actual offline PNG: distinct vertical channels remain joined to shared horizontal routes. This is not an unambiguous-trace acceptance result. Diamond's remaining36 units are the separately classified common terminal stubs.
The new point-contact metric has no v1 field to compare directly: do not interpret its mere introduction as newly created contacts. Current layered diamond records2 contacts and bipartite9; the independent contact worker's retrospective audit is pending at this review. Counts classify contacts and do not alone establish readability. Approximate core label bounds, missing renderer/port-label measurements, finite fixture families, axis-only grid state, sequential edge ordering and no global hard-pin optimization remain limits.
## Binding and recommendation
- Old raw output SHA256 `21786ae5cb668f60e9252dc884668ee5e98289443d0b3723a9ea69bf21d47fd1`.
- J raw output SHA256 `f9ac7c1320b1cf026259bfcaf13a39e968ae317463712d8dc793375d9a4a541f`.
- Run03 result.json SHA256 `57a1f866b2cb67880decd26becf3ac2e7715e38bbd26d3089c47dd62294afa4f`.
- Comparison JSON SHA256 `3d5435686c2d23e97f08340693015ef66d1127726c9b5b5212678f43b3ad6b76`.
- Geometry manifest SHA256 `0102449f730651a92233c4ae959e95e0a58036fe2b1c309224558c496241c741`; all37 image byte lengths/SHA256 verified. These are offline geometry schematics, not native captures.
Recommendation: retain J as a successfully isolated scoring correction candidate, with explicit non-monotonic quality results. Before further penalty tuning, use the independently reconstructed contacts and the crossed-bipartite BEFORE regression as falsifiers for any stronger penalty/lane experiment. Keep exact Projects/nodes/ports/endpoints fixed and retain the entire vector, including labels and area. Do not advance directly to broad production quality acceptance based on the layered diamond improvement.
@@ -0,0 +1,25 @@
# Terminal-join experiment independent source review
Read-only review of the local candidate based on e7deea7039bcf31675cffbb462cfeb19d62a7e20; no compiler, probe, GUI or helper executed by reviewer. Reviewed routing.rs SHA256 43c2e983a132fd1c687706f5dc6b27867403aae2aa253cdc25195141d24759a5 and the accompanying v2 probe diff. Parent owns formatting, final source pin and execution receipts.
Recommendation: proceed with the bounded J experiment. No blocking defect found in the changed scoring or contact observer. This is source review, not test success or production acceptance.
- route_edge supplies the known target continuation only to the final leg, preserving intermediate hard-pin context. The final whole-path score remains unchanged; local selection does not persist or double-charge its score.
- fast_path applies the same incoming/outgoing join to both candidate estimates and actual costs. Its Manhattan + two-turn lower bound remains safe for genuine two-bend alternatives because joins/occupied-segment costs are nonnegative. Compacted zero/one-bend alternatives are already represented by the initial L candidates.
- grid_path charges the terminal turn before inserting target states into the queue. Manhattan remains an admissible and consistent lower bound for these nonnegative transition additions. Adding a charge after returning the first target would have been incorrect; this implementation avoids that trap.
- Zero-length spans retain the existing result and do not invent an arrival vector. turn_cost explicitly returns zero for a zero-length adjacent leg. Whole-route behavior around repeated pins remains covered by existing integration checks rather than a new optimization claim.
- New private tests exercise both fast and grid directly, four reflected/rotated diamond witnesses with independently stated complete cost310, absent outgoing context, straight continuation/reversal and degenerate spans. Full route pins, attachment metadata and preview/settled agreement still depend on the parent-planned existing suites; no passing result is inferred from their presence.
Important limits: grid states remain two axes, not four signed arrival directions, although turn_cost distinguishes reversals. This pre-existing state abstraction is not proof of globally optimal reversal-aware A*. The patch correctly incorporates target cost before arrival-state merging, but does not fix all possible intermediate state dominance issues. Fast routing is still preferred over grid whenever feasible; authored pins are solved leg by leg without global lookahead. Budget/obstacle fallback behavior is unchanged. These are not blockers to this finite asymmetry experiment, but must remain outside its claimed guarantee.
The probe contact metric is additive: closed point intersections deduplicate by edge pair and EPS-quantized coordinate, OR proper-crossing witnesses, distinguish endpoint/vertex/interior roles, and classify common terminals by both owner and physical coordinate without subtracting them. Positive collinear intervals remain in the separate shared-length metric; a noncollinear witness at their boundary can still count as a point contact. The analytic witnesses include proper, T, bend, endpoint, collinear-touch and overlap cases. These definitions are suitable for the finite orthogonal fixtures, not an exact general floating-point geometry theorem.
Execution interpretation: compare projected_nodes, projected_ports and exact emitted endpoints against the frozen first run before calling this a fixed-node experiment. The layered prototype itself reroutes while deriving spacing, so identical fixture input alone does not establish identical output placement. Preserve the full metric vector: replacing coincidence with point contacts/crossings or worsening labels must stay visible. No generic node/label layout or native traceability acceptance follows from this experiment.
## Formatting recheck and source identities
Original reviewed routing SHA256: `43c2e983a132fd1c687706f5dc6b27867403aae2aa253cdc25195141d24759a5` (measured by reviewer). Original evaluator SHA256: `b7e10e6d346bac93486b075229d8b5b4b74acba05b4bc5778cc60b89a7a6f4d3` (worker/parent-provided identity; reviewer inspected its actual diff).
Current formatted routing SHA256: `f482f6ef79ee0ef2c615a0ef7fff0d5ad0c56dd080efa8e374b7c29ab9961507`. Current formatted evaluator SHA256: `2cc8d3cb04f8a48bef02008e7b9a0d78aa254009ee85ef7d546df1d04f97ebca`. Both measured by reviewer. Reread full scoped `git diff --ignore-all-space` after formatting: scoring expressions, conditionals, witness coordinates/assertions and observer definitions agree with the pre-format reviewed output. Observed differences are wrapping/indentation and optional trailing commas in routing tests. There is no retained original-byte snapshot for a mechanical whitespace-only comparison; this is an independent source re-review, not a claimed byte-level proof.
Exact fixed-geometry comparators: per fixture and variant compare full `projected_nodes`, full `projected_ports`, and per-edge first/last coordinates in `routed_edges` with the frozen prior output. Compare endpoint owners to projected edge definitions, and preserve source/target attachment and semantic port identity/anchor metadata in the full Projects. New `route_endpoints` makes the current identity/coordinate association explicit; derive the prior coordinates from prior `routed_edges`, since v1 did not emit that new field. Inspect route/label changes only after those placement/attachment checks, and retain any mismatches as results rather than silently excluding fixtures.
@@ -0,0 +1,19 @@
# Boundary-order conservation review
Read-only audit of `.local/boundary-order-20260925/run01`, source `3e21c1bca245944ae0ec7bb02aaa920f45f2cc30`. No compiler, router, engine or native execution. Twenty boundary candidates are supported; zero unsupported cases. Fourteen current SCC controls and six paired extra-fixture baselines are accounted for separately, not counted as additional boundary cases.
Independent complete-Project reconstruction passes for all20 boundary candidates,14 controls and6 paired baselines. Reconstruction starts from raw original Project, resolves actual endpoint owners and preoperation reset eligibility, retains entire semantic/nongeometry structures, applies only permitted visible geometry/minimum restoration and hidden-owner translation, and compares the entire expected result. No conservation boolean substitutes for this comparison. All66 receipt hash/byte-size bindings read back exactly.
All14 current SCC-control Projects equal their prior SCCrun02 Projects exactly. All20 successful boundary/baseline pairs retain exact effective node sizes, per-group SCC membership/rank/canonical walk/grid/gaps, and rigid internal SCC geometry within0.001units. Eleven boundary Projects equal baseline completely. Nine differ: all six extra fixtures, two_containers_reversed_children, workshop original and workshop folded. Node positions are the intended variable; no semantic reparenting or new authored elbows are persisted.
Every authored port-anchor record, other diagram, route entry identity/order and nongeometry field remains exact. The only authored-route changes are the same three eligible waypoint/label resets (mixed, reversed mixed, authored_detour_and_label). No retained dormant/promoted authored-route translation or nonempty authored attachment coverage occurs; do not infer it from the checker implementing that policy. Folded workshop's three hidden descendants still independently follow Allocation/semantic ancestry under collapsed owner `75920e7a-4586-58b7-9eb2-7e0a3d8a1825` by(-680,0), preserving latent sizes/metadata. All checked non-State container enclosures/minima pass.
Automatic port changes are preserved, not normalized away: boundary_two_local_sccs has two side changes, owner`…000004`/port`…000026` Bottom→Right and owner`…00000a`/port`…00002a` Bottom→Left. Neither is authored. Every pair has identical projected port inventory; no other side changes were observed. These are actual projection results, not evidence that source anchor records changed.
All34 primary successful records report exact same-input output, all nine repeated calls2–10 equal first/previous, and exact JSON/single-source round trips. These remain recorded Rust checks: repeat Projects/source strings are not serialized for independent rerun. Exact intended node plans are absent; final-plan equality remains the source import gate. The audit independently checks final emitted projection, permissible full-Project deltas, component invariants and enclosures, not a second layout implementation. It does not validate renderer text, estimated label collision accuracy, trace readability, general native workflow or adoption.
Auditor: `.local/audit-boundary-conservation-20260925.py`, SHA256 `f14d96f7ee1487d245bd30436dea356df7524006c51f2fbac929b067b881d893`. Final result: `.local/boundary-conservation-audit-final-20260925.json`, SHA256 `f6a3fa93c03174e0eae6f816975f06a10a696659bd3fd58291ac805a69d5edee`. Raw paired rows are read directly and bound to original paths/hashes, with baseline selector attribution; no normalized copy is represented as runtime output.
The first audit output is preserved: it successfully checked conservation but grouped14 SCC controls with20 candidates and used prior baselines directly. Final checker explicitly labels those roles, compares controls to prior results and boundary cases to the current controls, while retaining six in-row baseline/boundary pairs. This is inventory/attribution refinement, not a suppressed model failure. Original SCC auditors/reviews were untouched.
Reproduce: `python audit-boundary-conservation-20260925.py RUN_DIRECTORY NEW_OUTPUT_JSON 3e21c1bca245944ae0ec7bb02aaa920f45f2cc30 PRIOR_SCC_RUN02_DIRECTORY`. UTF-8 is explicit; output refuses overwrite; only the Python checker executes.
@@ -0,0 +1,81 @@
# Boundary-order design critique
Local-only independent design review, 2026-09-25. Read source at `f4fa0c11f2271a93e324541e570ba391dce8122f` in `.local/auto-layout-20260925` and the SCC experiment's results, run02 quality review and transfer diagnosis. No implementation, build, router execution, GUI exercise or Gitea publication. The working tree was clean when inspected. This is a proposal and source-derived reasoning, not new empirical quality evidence.
## Recommendation
Use one deterministic sweep over the SCC condensation of **ordering dependencies between sibling groups**, starting from the complete canonical SCC plan. A dependency must represent every mutable ancestor ordering that can move an external source endpoint. Do not substitute source-parent dependencies, parent ID order, absolute-X order, or a fixed count of snapshot passes. Freeze the internal decision state of a cyclic dependency SCC: compute its members' proposed orders from one pre-SCC snapshot and commit all together. Explicitly document that cyclic dependencies can retain or exchange crossings; this is a bounded heuristic, not a cycle solver.
Preserve the existing semantic SCCs, ranks, member walks, perimeter offsets, block dimensions, gaps and root ordering. Repack the changed ordinal orders from the same block dimensions. Group envelopes are invariant under this operation; assert that fact instead of introducing a new enclosure growth pass. Import only the final plan using the existing exact projection and mandatory-lead guards. This adds precisely the information omitted by the current child-group planner while retaining the sparse structural witness.
The selected policy preserves same-rank undirected-connected bundles as indivisible ordering units with their canonical BFS internal order. A new external-order pass must not silently undo that proximity policy. Use visible endpoint centers as a declared heuristic, and retain final port/route checks; do not pretend center ordering proves attachment alignment or readability.
## Why the alternatives differ
| Candidate | Main benefit | Concrete weakness | Verdict |
| --- | --- | --- | --- |
| One canonical snapshot, incoming only | Smallest change; no mutation-order dependence; handles the two-container witness | A→B→C chain: B changes order to A, but C sorts against B's stale canonical order | Useful control, too weak as the preferred next candidate |
| Fixed number of snapshot passes | Carries influence farther without traversal code | Any fixed count has a longer-chain counterexample; cyclic groups may alternate; the pass count becomes an arbitrary hidden bound on quality | Reject as a generalization of old layered's three passes |
| Parent-ID or X/topological order using only parent relations | Cheap sequential propagation | Source descendants can later move under an ancestor ordering; ancestor-level SCC perimeter X does not express causality; unrelated ID changes select winners | Reject as the dependency definition |
| Complete ordering-dependency SCC sweep | Acyclic influence propagates once; explicit handling of genuine mutual dependence; bounded work | More bookkeeping; cyclic-SCC snapshot retains a declared limitation | Preferred bounded experiment |
| Joint crossing minimization or repeated barycenter optimization | Could optimize opposing constraints and port attachment geometry | Introduces a substantially different optimization problem, objectives and stopping policy before the simple witness is resolved | Defer until evidence defeats the bounded structural candidate |
Incoming-only treatment is meaningful because it anchors downstream order to upstream arrangement. It does not by itself remove cycles: two containers can each have an incoming edge from the other even when their internal child graphs are edgeless. The SCC used for ordering dependencies is distinct from the sibling-subtree semantic SCC already used for rank and perimeter placement. Never merge these concepts or feed ordering dependencies into semantic ranks.
## Precise candidate contract
1. Run the existing canonical planner to an in-memory plan. Keep a record for each sibling group of its SCC blocks, ranks, canonical ordinal order, dimensions, cut gaps and member offsets. Keep the root order fixed. Record a complete canonical absolute-position snapshot without importing an intermediate Project.
2. A target ordering group G is the children of a visible, noncollapsed parent P. An eligible boundary signal is an oriented **projected** edge for which the target lifts to a child of P and the source does not lift to any child of P. Internal-group incoming links are not boundary signals; outgoing and undirected boundary links supply no ordering signal in this candidate. An edge to a hidden endpoint uses its actual projected promoted endpoint, never the latent hidden placement.
3. The unit that moves is an SCC block, or an explicitly defined contiguous undirected-connected bundle of same-rank blocks. If bundle support is deferred, freeze layers with any such cross-block undirected link and report the reason. Do not break an SCC or reorder its perimeter members. A unit is anchored when at least one eligible incoming boundary signal targets one of its members or visible descendants.
4. For each same-rank layer, preserve the canonical ordinal indices occupied by unanchored units. Sort anchored units only into the remaining indices, by the arithmetic mean of their external source endpoint centers' absolute Y coordinates, followed by the existing canonical unit key. Deduplicate external visible source placement IDs per unit; several ports or parallel relations from one source owner contribute once. Sort those placement IDs independently of relation IDs, accumulate in f64, and reject nonfinite values. This is a declared source-owner weighting policy: relation multiplicity does not increase influence. This score is a center-based ordering heuristic, not a crossing objective.
5. Build a dependency graph whose vertices are the nonroot ordering groups that can alter an order. For every eligible signal consumed by G from visible source S, add H→G for **every** mutable group H whose child subtree contains S. In other words, include all ancestor ordering transforms that can change S's absolute position, not merely S's immediate sibling group. A collapsed visible source has no visible interior ordering group; its visible ancestors still matter. Conservative extra dependencies are permissible if reported, but may enlarge SCCs and freeze useful propagation.
6. Condense that graph. Process dependency SCCs in deterministic topological order. Before each SCC, reconstruct a complete absolute-position snapshot from the current relative offsets. All groups inside that SCC read this same snapshot, calculate proposed orders, then commit their orders together. Acyclic singleton SCCs therefore observe completed upstream changes; cyclic SCCs take one simultaneous bounded step. Independent ready SCCs cannot influence each other's consumed sources by construction, but use the canonical key to make diagnostics reproducible.
7. After committing an order, repack by prefix sums of the unchanged block heights and unchanged gaps. Preserve rank X origins and internal block/member offsets. Recompose absolute descendant positions from all relevant relative transforms. Do not move only the source node while leaving its descendants behind.
8. Assert every group's planned envelope and every block size equal their canonical values. Assemble one final complete plan and call the existing importer once. Keep original-to-final route translation, eligible reset, dormant-route preservation, authored anchors, hidden translations, semantic conservation and exact second-projection rules unchanged. A final projection mismatch or incompatible mandatory lead remains a refusal, not permission to grow sizes or iterate.
Two useful simplifications: source-center ordering does not require target-ancestor-before-descendant execution, since a target ancestor's translation does not change the order of consumed external source values. Explicit source-influence dependencies plus absolute coordinate recomposition are sufficient. Also, arbitrary intermediate geometry within a dependency SCC need never be externally visible: compute its decisions before committing any of them.
## Important limits and traps
**Ordinal slots are not fixed Y coordinates.** If an 80-high and 200-high block trade old Y coordinates, they can overlap. Preserve ordinal indices, then recalculate packed Y values using the unchanged heights. The layer envelope is nevertheless unchanged: height is the same sum of heights plus gaps, and width is the same maximum. Every ancestor enclosure is consequently unchanged by induction, even for nested containers. Nested size feedback is not inherent in this permutation stage.
**Automatic port projection remains geometry dependent.** Rearrangement may select different automatic sides or put more ports onto a side, increasing projected size beyond the frozen plan. Derived enclosure invariance does not exclude that. The exact final projection and lead checks must catch it. Preserve explicit port-side and fraction metadata. A center-based score may worsen actual fixed-port endpoint order; inspect routed outcomes and include that counterexample in evaluation.
**Determinism differs from idempotence.** Complete canonical reconstruction plus deterministic dependency processing gives same-input determinism. It does not prove repeated-call equality because the next input's effective minima may include stored enclosure repairs or new side-capacity minima. Existing repeated-call checks must remain, including nested automatic-port cases, and must compare complete Projects. Do not justify a failed repeat by saying that the ordering graph is deterministic.
**Canonical choice is a policy, not evidence of quality.** Alias and placement-ID keys are defensible tie breakers already present in this experiment. They imply sensitivity to alias changes and duplicate-alias placement IDs. Relation-ID permutation should not change geometry or scores. Full graph-isomorphism invariance is neither supplied by the current planner nor established by this proposal. Diagnostics should distinguish a canonical tie from a strict source-derived ordering decision.
**Cyclic dependency SCCs have no guaranteed winner.** Simultaneous calculation is independent of group traversal order, but opposing permutations may both reverse and remain crossed. Freezing canonical order for cyclic SCCs is a still more conservative alternative if the corpus shows worsening; predeclare which policy is being tested. Do not quietly select between the two after routing each fixture. Sequential execution within a cyclic SCC merely chooses an arbitrary winner and is not a resolution of the constraints.
**Non-singleton target blocks weaken the center proxy.** Two SCCs can receive edges into different perimeter rows. Mean external-source center ignores target member offsets, so it can place their block centers in an order that crosses the actual connections. An offset-adjusted score would be a different candidate and would introduce dependencies on target descendants as well. Keep this limitation explicit and use a cyclic-block target fixture; do not add that refinement without a separate reason and specification.
**External order can conflict with internal rank order.** The current canonical pass already uses preceding-rank proximity. Overriding its same-rank order from boundary neighbors can increase internal crossings. A fixture must include this conflict, and evaluation must retain all internal and external routes. Success on one reversed-child witness is insufficient.
## Falsifying fixtures and acceptance evidence
- Preserve the frozen 14 cases and exact complete sparse candidate geometry/metrics. The targeted two-container witness should reach zero shared length and zero label/node collisions like old layered, without semantic, port, fold or import regression; report the whole quality vector rather than only that target.
- Three containers A→B→C, two matched channels, chosen aliases giving differing canonical child orders. A second version with a longer chain distinguishes an actual dependency sweep from a fixed snapshot-pass count. Verify final channel alignment and dependency order from emitted plans, not just zero process exit.
- Ancestor influence: an ordering group moves a container holding an external source whose own immediate child group does not reorder; that source feeds a different target group. The target must consume the source's updated absolute Y. Reverse container IDs to expose accidental iteration dependencies.
- Opposing incoming constraints between two containers; a three-container directed cycle; and an acyclic semantic graph whose container grouping induces a dependency cycle. Confirm the declared cyclic policy and same-input determinism, and report unresolved crossings rather than claiming all cycles solved.
- Heterogeneous block heights with an unanchored block between two anchored blocks. Require unanchored ordinal-slot preservation, no overlap, exact unchanged total envelope and deterministic packing. Repeat with nested containers and a multirow SCC.
- A same-rank chain of undirected-connected blocks plus externally anchored endpoints, and a purely undirected cross-container fixture. Require the declared bundle/freeze policy, no invented directed arcs/ranks and no silent loss of existing contiguity.
- An internal diamond or fan-out whose canonical target order is good but boundary signals prefer its reverse. Detect improvement to the boundary at the cost of internal tracing.
- Fixed ports with source-center order opposite to port-attachment Y order; many automatic ports near a side-choice/capacity threshold. Require exact model-anchor preservation and final projection acceptance or clean refusal, never size chasing.
- A collapsed source container promoting hidden edges; nested collapsed descendants; and a surviving dormant authored route. Require only visible geometry to influence ordering, hidden translation with owner, and the existing route/reset conservation contract.
- Parallel boundary relations and a relation-ID permutation with identical endpoints; equal mean scores; repeated invocation; JSON/text round trips. Require exact geometry determinism under relation-ID changes and exact repeated Projects. Route relation-ID sensitivity remains a separate existing router limitation.
The existing full corpus remains the primary comparative experiment. These small synthetic fixtures test the candidate's mechanism and claimed invariants. No compilation result, small-corpus metric or this design review establishes native usability, production Arrange integration, or release acceptance.
## Final selected-policy review
The parent's final proposal adopts indivisible undirected-connected bundles, deduplicated visible source owners, active groups only when some band has at least two anchored units, source-ancestor dependencies only, and one simultaneous pre-SCC snapshot for each cyclic dependency SCC. I find no material correctness flaw in that bounded contract, provided the following definitions are implemented literally:
- A source owner is the projected edge's visible source **placement ID**, not a semantic element ID or unprojected port owner. Distinct visible placements remain distinct observations; promoted endpoints contribute their visible owner.
- The source influence chain includes the ordering group immediately containing the source placement, and every higher active ordering group that can move it. It must not start one level too high.
- Bundle order is preserved by ordinal slot, then prefix-height packing; old absolute Y coordinates are not reused for differently sized blocks.
- Before an SCC computes scores, all source absolute coordinates are recomposed from the current full offset map. All its proposals are calculated before any are committed.
- Work charging and diagnostic bounds include ancestor-chain discovery, dependency SCC processing, source-set accumulation and absolute-coordinate recomposition.
Active-group restriction is sound for this contract: fewer than two anchored units cannot change order because unanchored unit slots are preserved. Unchanged active groups can conservatively remain dependencies. No extra hierarchy edges are necessary.
The important remaining risks are empirical: cyclic dependencies may preserve or exchange crossings, source-center order can disagree with fixed-port/perimeter attachment order, deduplicated owners intentionally ignore connection multiplicity, and boundary preference can worsen internal rank tracing. Keep those limitations and the chain/ancestor influence fixtures above. Exact repeated-call stability and final automatic-port projection remain acceptance checks, not consequences of the ordering proof.
@@ -0,0 +1,46 @@
# Independent boundary-order evaluator contract
Source inspection at research f4fa0c11; controls are frozen SCC run02 and prior old-layered results. No implementation, compiler, router, UI or Gitea action. Evaluate one predeclared boundary-order candidate, not a family selected after viewing scores.
## Exact missing information
`layered.rs` incoming-neighbor pass (approximately137–167) considers an edge whose target lifts to the current child and whose source does not lift to that same child. An external source need not be a sibling. Its world-space center Y influences downstream child order. Three passes carry updated positions between groups; outgoing neighbors are intentionally not chased simultaneously, avoiding a swap of both groups that preserves reversal. This is a useful mechanism, not a proof that its three-pass update converges for arbitrary parent cycles.
`condensed.rs:224–241` requires BOTH endpoints to lift into the same sibling group before retaining a link. Thus an external source contributes no local child-order information. Lines365–390 only order SCC blocks from lower-ranked neighbors already in that local links list, followed by same-rank proximity. Correct root condensation knows Left→Right, but the singleton children inside Right have neither local arcs nor external order hints. Their component/canonical order survives. This omission is separate from the successfully fixed SCC rank problem.
## Existing two-container witness: fixed expected order
Fixture definitions are `auto_layout_probe.rs:580–603`. Placement suffixes uniquely identify this fixture:
- Left parent002: Left A006 then Left B008.
- Right parent004: Right A00a then Right B00c.
- Directed edge00d: Left A006→Right B00c.
- Directed edge010: Left B008→Right A00a.
In frozen SCC run02 both groups retain A above B: child Y136 and300. The concrete downstream order witness is **Left A < Left B and Right B < Right A** (compare projected centerY; names are labels, IDs are identities). Preserve Left's order as the upstream reference; swapping both groups is not success. The mirrored physical arrangement can be equivalent globally, but it is not the predeclared one-sided witness. Every child remains under its original parent and each parent's local SCC membership/rank remains unchanged.
Old layered establishes a feasible target with unchanged semantic endpoints: zero shared length/contacts/label collisions versus SCC164 sharing,2contacts,1label/node; route length372 versus788, area272800 versus339852. Do not assert the new implementation must reproduce every coordinate. Require the ordered pairs above and compare actual final released routes to that zero-sharing witness. Smaller connector inversions alone do not pass if routing still merges the traces or labels become obstructed.
## Three additional topology falsifiers, fixed before implementation
Use equal-size ordinary SA function children, no authored ports initially; add explicit anchors only as the stated separate invariant variant. Stable aliases A/B determine the unconstrained upstream order. Use the same renderer-independent node sizes/gaps as existing fixtures, not dimensions chosen after candidate output.
1. **Opposing cross-parent evidence.** Two parents L/R, children A/B each; arcs L.A→R.B, L.B→R.A, R.A→L.A, R.B→L.B. The first pair asks for opposite L/R order; the second pair asks for matching order. These demands are incompatible. Expected: semantic direction and SCC condensation remain exact, deterministic bounded resolution with explicit ties/diagnostics, no endless joint swapping or silently dropped arcs. Do NOT require zero crossing or a particular arbitrary winner. Repeated full candidate calls must stabilize; route/label costs and any introduced hard violation remain visible.
2. **Three-parent propagation.** Parents L/M/R with A/B children. Arcs L.A→M.B, L.B→M.A, M.B→R.A, M.A→R.B. With upstream L.A<L.B fixed, expected M.B<M.A and R.A<R.B. This catches a one-step only pass and averaging stale absolute coordinates from differently positioned parents. Add no reverse arcs. Verify final relative child orders and actual connection quality; parent ranks must remain L<M<R. Run a storage/relation-ID permutation without changing semantic topology and report any different order explicitly.
3. **Multiple SCCs and local ranks.** Within target parent T, two two-member directed SCCs X=(X1↔X2) and Y=(Y1↔Y2), each feeds singleton Z via X2→Z and Y2→Z. External parent S has upperA/lowerB, with S.A→Y1 and S.B→X1. Expected same-rank block Y above X, both before Z's rank; preserve exact SCC membership and internal perimeter order. This catches implementations that sort all children globally by external neighbor Y, split cyclic blocks, or move Z into the same rank. Boundary ordering must act on SCC blocks within an existing rank, not override rank constraints. If cross-boundary preference is unsupported for multi-member blocks, return/report that scope explicitly rather than falsely claiming this fixture passes.
These are structural tests, not new gap or penalty parameters. One variant of the main witness should retain authored port-side/offset anchors to ensure order is achieved by permissible geometry, not anchor mutation. Undirected edges supply no invented rank direction; reversing their stored endpoint order must not change their meaning or create hidden directed precedence. Preserve the existing compound/folded corpus instead of constructing a grand taxonomy of synthetic cases.
## Acceptance checks for the single candidate
Retain all14 existing cases with full failures, reset/old-layered/SCC controls and raw before/after Projects. Require exact source/model conservation under declared node geometry plus existing route reset/translation; original semantic orientation, containment, port identities, attachments and route-entry ordering remain exact. Parent owns the complete Project oracle. Folded nodes remain opaque; stored expanded sizes and latent descendant offsets survive fold→layout→expand, and out-of-projection placements are untouched according to the existing contract.
Independently reconstruct quotient SCC membership, condensation ranks and rank membership before comparing boundary orders. Record hints with edge identity, actual external owner and destination block, using final projected coordinates or a deterministic canonical order as explicitly specified; avoid alias-only success claims. If ordering is ordinal, compare like-for-like coordinates within a reference parent rather than treating arbitrary parent Y translation as semantic priority. Parallel edges must not be silently deduplicated or weighted accidentally by whichever container pass saw them first.
Final actual projection is authoritative for automatic port sides/positions, node capacity and enclosure. Require exact authored anchors, no hard lead incompatibility, no new unrelated node/header penetration or overlap, and preserved pin/metadata policy. Report final route crossings, proper/T/bend contacts, raw/edge-deduplicated sharing with identified pairs, labels, area and length. Keep folded retrace visible. A better topological order that worsens actual traceability is not accepted automatically.
**Sparse40 exactness is a predeclared preservation target**: it is flat and lacks cross-parent boundary relations, so its whole candidate Project, plan geometry and metrics should remain exact to run02 (except new diagnostic fields). Prefer the same check for every case without applicable cross-boundary hints. If it changes, demand a causal explanation and classify it as scope leakage rather than accepting an accidental improvement. Mixed and reversed-ID cases are also flat: preserve their existing outputs, including known differences, rather than promising to remove router ID sensitivity in this change.
Require same-input determinism and ten full repeated candidate calls, including opposing parents, multilevel propagation and the real folded/compound views. Success cannot depend on starting from the special fixture's authored Y values alone; compare a harmless uniform translation of an entire parent subtree while preserving local child order. With canonical ordering policy, this should not reverse the result merely by changing the parent's global position. An explicitly geometry-sensitive policy must state that tradeoff before testing.
Stop after one implementation and one frozen comparison. A viable result must solve the exact existing two-container order/tracing witness, preserve sparse/mixed flat outputs, survive the contradictory/chained/multi-SCC tests without topology corruption or instability, and retain all real-case regressions transparently. Production command/history integration and native tracing remain separate acceptance work; passing these geometry checks alone is not closure of #32.
@@ -0,0 +1,791 @@
{
"schema": 1,
"source_commit": "3e21c1bca245944ae0ec7bb02aaa920f45f2cc30",
"archive": {
"file": "boundary-order-3e21c1bc.zip",
"bytes": 4310136,
"sha256": "bb6b896ff2401290869a904f7768ebb52b6f0ab36fada06badbe9abb301c37b8"
},
"members": [
{
"member": "collector/archive-boundary-order-20260925.py",
"bytes": 3843,
"sha256": "81adb0add2c57a4c1109efc8b74ba0f6ffb37250abc8edcd95b30853a93eebab"
},
{
"member": "collector/audit-boundary-conservation-20260925.py",
"bytes": 17232,
"sha256": "f14d96f7ee1487d245bd30436dea356df7524006c51f2fbac929b067b881d893"
},
{
"member": "collector/audit-boundary-order-20260925.py",
"bytes": 5352,
"sha256": "96bb938961255b59add3922f2cc8b30ddc7ec127d77ab1d7b61974e116930432"
},
{
"member": "collector/audit-scc-placement-v3-20260925.py",
"bytes": 8729,
"sha256": "8b238f010eeb81720a59a0ad290544f87934acb8070e58a3fd25f9eda9f47a0d"
},
{
"member": "collector/launch-boundary-native-20260925.py",
"bytes": 13468,
"sha256": "4d75605389ee53a6a2d2b73b7fd9c28400db89efce1097c7fad5656e7bf4d663"
},
{
"member": "collector/run-boundary-order-20260925.py",
"bytes": 4575,
"sha256": "01c6ddd4953ee485123cbed5d99aa4f1b485b21be69083828959cf50c938a9b1"
},
{
"member": "collector/run-routing-corpus-check-20260925.py",
"bytes": 2793,
"sha256": "2726a397bd4ecb4dcfff6fc170a66ce2655229079f42335faa2a4bfe4a202f6d"
},
{
"member": "gates/27-boundary-example-tests/command/changes.patch",
"bytes": 0,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
{
"member": "gates/27-boundary-example-tests/command/context.json",
"bytes": 3510,
"sha256": "13b6e4d82cd228eabe26a45c3294fef319eccd8edca784f5b41d3de7003b65ec"
},
{
"member": "gates/27-boundary-example-tests/command/inputs-after.json",
"bytes": 1762589,
"sha256": "800940ce4e0bd2b3bd56b87f65308808dc99e5f897d643bffa04405de211bfbd"
},
{
"member": "gates/27-boundary-example-tests/command/inputs-before.json",
"bytes": 1762589,
"sha256": "800940ce4e0bd2b3bd56b87f65308808dc99e5f897d643bffa04405de211bfbd"
},
{
"member": "gates/27-boundary-example-tests/command/output.log",
"bytes": 1385,
"sha256": "a2e29cb282440949664ca33aafc6c0174c6394a2327e76ad16994aa3ee21b3b7"
},
{
"member": "gates/27-boundary-example-tests/command/result.json",
"bytes": 4184,
"sha256": "ccc581072ecdd59c8b9881571aae6aaf32c4eb6ccbbcc7d712e9ef2cec9655ac"
},
{
"member": "gates/27-boundary-example-tests/command/stderr.log",
"bytes": 336,
"sha256": "b3f2054556656f6a805f054ffb8d5c780be84b69c65135cc6c0d28f7d1a7d6f8"
},
{
"member": "gates/27-boundary-example-tests/environment.json",
"bytes": 9466,
"sha256": "d9d984528a82c0db3c2e009db91104d7b7c1233921793802bbeb87677f35daf8"
},
{
"member": "gates/27-boundary-example-tests/orchestration.log",
"bytes": 185,
"sha256": "6f08f77e5593cbd746ee6ab7b6f16641bee0a7058f03b9e2fb04b7cb747c8181"
},
{
"member": "gates/28-boundary-clippy/command/changes.patch",
"bytes": 0,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
{
"member": "gates/28-boundary-clippy/command/context.json",
"bytes": 3540,
"sha256": "b1686a8cfac3bfec3627da11f0950826c2a06dcb115851723ac1987b8ca91c3d"
},
{
"member": "gates/28-boundary-clippy/command/inputs-after.json",
"bytes": 1762589,
"sha256": "800940ce4e0bd2b3bd56b87f65308808dc99e5f897d643bffa04405de211bfbd"
},
{
"member": "gates/28-boundary-clippy/command/inputs-before.json",
"bytes": 1762589,
"sha256": "800940ce4e0bd2b3bd56b87f65308808dc99e5f897d643bffa04405de211bfbd"
},
{
"member": "gates/28-boundary-clippy/command/output.log",
"bytes": 0,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
{
"member": "gates/28-boundary-clippy/command/result.json",
"bytes": 4197,
"sha256": "2fbd9f68cc45957b62122fda4b022a9e6eb3661a871132f06444f87ef862c9bf"
},
{
"member": "gates/28-boundary-clippy/command/stderr.log",
"bytes": 176,
"sha256": "7be90bc986d32ae1b1f4bf7e877bbcb73389e60288af76e6af2b6626e8cd2626"
},
{
"member": "gates/28-boundary-clippy/environment.json",
"bytes": 9466,
"sha256": "d9d984528a82c0db3c2e009db91104d7b7c1233921793802bbeb87677f35daf8"
},
{
"member": "gates/28-boundary-clippy/orchestration.log",
"bytes": 178,
"sha256": "ce952a08d4fe80b4e7091db3ec33ab562136997a9c6900c3efde32bbf027793f"
},
{
"member": "gates/29-boundary-precision-tests/command/changes.patch",
"bytes": 0,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
{
"member": "gates/29-boundary-precision-tests/command/context.json",
"bytes": 3512,
"sha256": "df7d084938c7f45b6b4d7229124ad92eeb4dcbde80d77cfac6a1902754d72f9a"
},
{
"member": "gates/29-boundary-precision-tests/command/inputs-after.json",
"bytes": 1762589,
"sha256": "8c1c5b141d9322956c9914284387fd1471879a0cfadd4eb720e1be3cdbcd524e"
},
{
"member": "gates/29-boundary-precision-tests/command/inputs-before.json",
"bytes": 1762589,
"sha256": "8c1c5b141d9322956c9914284387fd1471879a0cfadd4eb720e1be3cdbcd524e"
},
{
"member": "gates/29-boundary-precision-tests/command/output.log",
"bytes": 1473,
"sha256": "bf6344286c5fb4531117b25deef585502167c9ffaf7f11416bd7b9af9fde2659"
},
{
"member": "gates/29-boundary-precision-tests/command/result.json",
"bytes": 4190,
"sha256": "5a412dc08cb74c2bb2206b901e97878b29a1a87f7c26891981b08db374a20fe2"
},
{
"member": "gates/29-boundary-precision-tests/command/stderr.log",
"bytes": 336,
"sha256": "e4f89d4438ee4f29fdd9c827af6c8b9c6be621182e734b094ffedbcd3d03a467"
},
{
"member": "gates/29-boundary-precision-tests/environment.json",
"bytes": 9466,
"sha256": "d9d984528a82c0db3c2e009db91104d7b7c1233921793802bbeb87677f35daf8"
},
{
"member": "gates/29-boundary-precision-tests/orchestration.log",
"bytes": 187,
"sha256": "205746a7fd277265ba7f1a3c52073fdc9618909dfab31b9f15ff210abf4abbed"
},
{
"member": "gates/30-boundary-precision-clippy/command/changes.patch",
"bytes": 0,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
{
"member": "gates/30-boundary-precision-clippy/command/context.json",
"bytes": 3550,
"sha256": "fe1ce5ca19ef4c19b1c4627c73ef3d42c4be20da52952de5fe0ebf606e549d6c"
},
{
"member": "gates/30-boundary-precision-clippy/command/inputs-after.json",
"bytes": 1762589,
"sha256": "8c1c5b141d9322956c9914284387fd1471879a0cfadd4eb720e1be3cdbcd524e"
},
{
"member": "gates/30-boundary-precision-clippy/command/inputs-before.json",
"bytes": 1762589,
"sha256": "8c1c5b141d9322956c9914284387fd1471879a0cfadd4eb720e1be3cdbcd524e"
},
{
"member": "gates/30-boundary-precision-clippy/command/output.log",
"bytes": 0,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
{
"member": "gates/30-boundary-precision-clippy/command/result.json",
"bytes": 4227,
"sha256": "df153dbc6cfa994be50f3ade29023885397131d4b66727e5464781acd473b339"
},
{
"member": "gates/30-boundary-precision-clippy/command/stderr.log",
"bytes": 176,
"sha256": "971c0f30a7e4473172912bf8e3d34edd27c36a6df689613be3bac74262dc89b7"
},
{
"member": "gates/30-boundary-precision-clippy/environment.json",
"bytes": 9466,
"sha256": "d9d984528a82c0db3c2e009db91104d7b7c1233921793802bbeb87677f35daf8"
},
{
"member": "gates/30-boundary-precision-clippy/orchestration.log",
"bytes": 188,
"sha256": "2aa50a7d9dfa26b6281a5492584836edf59ad7fab5ece9e1ade8df5c618690b0"
},
{
"member": "gates/31-boundary-release/command/changes.patch",
"bytes": 0,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
{
"member": "gates/31-boundary-release/command/context.json",
"bytes": 3529,
"sha256": "8d95ec8851bdb5298456db6779c3935c992ac94fb01006b5bda77733f035643d"
},
{
"member": "gates/31-boundary-release/command/inputs-after.json",
"bytes": 1762589,
"sha256": "8c1c5b141d9322956c9914284387fd1471879a0cfadd4eb720e1be3cdbcd524e"
},
{
"member": "gates/31-boundary-release/command/inputs-before.json",
"bytes": 1762589,
"sha256": "8c1c5b141d9322956c9914284387fd1471879a0cfadd4eb720e1be3cdbcd524e"
},
{
"member": "gates/31-boundary-release/command/output.log",
"bytes": 0,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
{
"member": "gates/31-boundary-release/command/result.json",
"bytes": 4188,
"sha256": "c9eb4f6148e4dc81bf03225600b67608044ebd9c2a2e5fff8cfa1a6fcedad526"
},
{
"member": "gates/31-boundary-release/command/stderr.log",
"bytes": 179,
"sha256": "cb88f4b7def4df8a09d67f7fdff2046fb076a0ea293f525cae46a59f801bf019"
},
{
"member": "gates/31-boundary-release/environment.json",
"bytes": 9470,
"sha256": "1cda80470e5893747b1aad7eda5ef51d57069441e810698cd7f94f7a7521bfe5"
},
{
"member": "gates/31-boundary-release/orchestration.log",
"bytes": 179,
"sha256": "577b9b108998d0e065a53dc08f658cb42026a539fe4b692bc5d50a44da49eca1"
},
{
"member": "inputs/corpus/manifest.json",
"bytes": 10593,
"sha256": "a58a6956e26b414e88fafd4fdb92e006050653a4149178f08df84128e3917717"
},
{
"member": "inputs/corpus/mixed_dispatch.json",
"bytes": 32787,
"sha256": "81d25af021cbad18af4262a220ba60372e51ca19083b7e80f2b19196baf05990"
},
{
"member": "inputs/corpus/mixed_dispatch_edge_ids_reversed.json",
"bytes": 32787,
"sha256": "25c95be6b6e7ad9ebcdc83aacc13fbbaa5a56651ba2843f8d1658c075ed789f8"
},
{
"member": "inputs/corpus/subsystems_40_sparse.json",
"bytes": 124043,
"sha256": "ad0ee7a0ab9c9f3c5f26a6d839f6627a82aa38858a3d5cc1ca87f5a8d795533e"
},
{
"member": "inputs/corpus/workshop_sa_folded.json",
"bytes": 102922,
"sha256": "ea5fd285d11795e5e543e4144349c32c92a5b328b8c5e2d1be8b75b70703dc97"
},
{
"member": "inputs/corpus/workshop_sa_original.json",
"bytes": 102923,
"sha256": "cc9b26390594513c11b3d85b768d244b2df8e7bed8cb76c4bdf0dc24db1d2ef6"
},
{
"member": "inputs/prior-arrange-controls.jsonl",
"bytes": 1862455,
"sha256": "f924af4038ade0de2e0f44988fa28d2566019d8c4bc2bde70e61fe4813364d40"
},
{
"member": "inputs/released-route-control.jsonl",
"bytes": 470144,
"sha256": "9a31a676f23dea2f41daec39497f569d68231fa0fd66e5a1a476be97d5d61ac2"
},
{
"member": "inputs/scc-run02/mixed_dispatch/stdout.jsonl",
"bytes": 93898,
"sha256": "28a7f598746f4c0c58afd0321861e417b2715976cde52bf55361dce8c35b4d74"
},
{
"member": "inputs/scc-run02/mixed_dispatch_edge_ids_reversed/stdout.jsonl",
"bytes": 93308,
"sha256": "6a723a4899026c2df80327fb76c5dda8e5dc5e4e15e9e066991413c2246bade5"
},
{
"member": "inputs/scc-run02/route-control/stdout.jsonl",
"bytes": 470144,
"sha256": "9a31a676f23dea2f41daec39497f569d68231fa0fd66e5a1a476be97d5d61ac2"
},
{
"member": "inputs/scc-run02/small-controls/stdout.jsonl",
"bytes": 425661,
"sha256": "0ec2bc1f51542b41b5df0b89d2a87306716a49a22951c4dfeb128dd75247d074"
},
{
"member": "inputs/scc-run02/small-scc/stdout.jsonl",
"bytes": 220188,
"sha256": "0c8b9ef7888cd5ba99733f15d22478cdb2878ef02e22d297d02c8b3eea2e9e15"
},
{
"member": "inputs/scc-run02/subsystems_40_sparse/stdout.jsonl",
"bytes": 361147,
"sha256": "452690f23dda57f62d115b6d88e42d7f0283b4c86081d5149d8738ba6052e53b"
},
{
"member": "inputs/scc-run02/workshop_sa_folded/stdout.jsonl",
"bytes": 194613,
"sha256": "ddf05a2ca36950305090bd5d91ec2a6c617d2eae373c33ad84e9ff4157581c0c"
},
{
"member": "inputs/scc-run02/workshop_sa_original/stdout.jsonl",
"bytes": 198272,
"sha256": "e30a2a6a30d1522cb2f8e9377e7eb8f0a81d1fe8b5aa18550f78c4829b8cc408"
},
{
"member": "native/inputs/boundary_two_containers_anchored__boundary.json",
"bytes": 8997,
"sha256": "f84fafbc3a80aee5caebf5b1cd6d41f340d954342664c64e7b627ba4fcefbe39"
},
{
"member": "native/inputs/boundary_two_containers_anchored__scc.json",
"bytes": 8997,
"sha256": "1da4878fa52fe665e4639eb1a9cc99b5b0e85516dd51f898270ed619c4eafb06"
},
{
"member": "native/inputs/boundary_two_local_sccs__boundary.json",
"bytes": 16991,
"sha256": "c408c437d60a8f62952151d1880ea50bc10985d5ffadec1d91b25361c1862737"
},
{
"member": "native/inputs/boundary_two_local_sccs__scc.json",
"bytes": 16991,
"sha256": "a6e83c92dd4b9a4cb59054730b81a995a4ed2351d9a865e0c7a1572a80542dc0"
},
{
"member": "native/inputs/two_containers_reversed_children__boundary.json",
"bytes": 8179,
"sha256": "6ed26cc72a540207f31c04bd989dfa718b1c573be1d15fcffebc8046c32fb84f"
},
{
"member": "native/inputs/two_containers_reversed_children__scc.json",
"bytes": 8179,
"sha256": "56e980ef38cdc24e1a6b710557736c84026bb052da7e88726d55fd61e5d0c375"
},
{
"member": "native/manifest.json",
"bytes": 7509,
"sha256": "940a7605b5a83308261e1fde0ecd7291cc855614a515e87e084823c8fc4e9016"
},
{
"member": "native/observations/01-two-container-boundary.json",
"bytes": 294,
"sha256": "aa930a00b846e037130a219043d0e71892be95ce3c3739ccb2b88956d4d204af"
},
{
"member": "native/observations/01-two-container-boundary.png",
"bytes": 122265,
"sha256": "bc4e28a0fcc37cdc4c904993b67e5b018c66a3a0a2ff7c2511fec324e03d5cd7"
},
{
"member": "native/observations/02-two-container-scc.json",
"bytes": 310,
"sha256": "741b04f292bdfea132a8131525be0f6215fa4d557100dd8114e41cb2e3088dbf"
},
{
"member": "native/observations/02-two-container-scc.png",
"bytes": 122550,
"sha256": "e1c68d66a4eae12c2e2c056858a1d4fc6b5eec8c387ee39b19337abbce7f49cf"
},
{
"member": "native/observations/03-anchored-boundary.json",
"bytes": 310,
"sha256": "e175e1ed5c9ae6a551bad7f6fd16badabeff245e0fe5c37876421467ce4b70eb"
},
{
"member": "native/observations/03-anchored-boundary.png",
"bytes": 122077,
"sha256": "8038656c660d826426c36e059997eb955ef8e84db4bdc2d23bcc214f64580c1d"
},
{
"member": "native/observations/04-local-scc-boundary.json",
"bytes": 310,
"sha256": "81f0bb6e233b37c5c119c6b0c158abb18bb2632af172136cc7411289a14f4865"
},
{
"member": "native/observations/04-local-scc-boundary.png",
"bytes": 120833,
"sha256": "677057c6870103daeeb31d48a11a5f9b8b8ebb604a7864e71a541a28d6024342"
},
{
"member": "native/observations/05-local-scc-selected-lower-boundary.json",
"bytes": 301,
"sha256": "e2ba43b96ffef6f6c6b4a349456d428a1f6b65d329cae7a0cf8fee6e0cec4d26"
},
{
"member": "native/observations/05-local-scc-selected-lower-boundary.png",
"bytes": 132475,
"sha256": "732cc188a8e8fbe3632d3bd824f218560c5a40afac053d5f8936e62be2699861"
},
{
"member": "native/run01/close.json",
"bytes": 3845,
"sha256": "a89d86f5c321299b034d1b5b28a635ee0574736d9ef715ba1c85fd26fc0fde1c"
},
{
"member": "native/run01/preflight.json",
"bytes": 3503,
"sha256": "1aa792b64b96fee428594d76f4c838256db7e4d86ef12f12cfc59e197ff96178"
},
{
"member": "native/run01/profile/ArcadiaStudio/recent-models.json",
"bytes": 946,
"sha256": "cfa44a8c46c193ba27b41ad84ba437a588250b4bed27437dfb4c2a1cb08b8f8b"
},
{
"member": "native/run01/start.json",
"bytes": 453,
"sha256": "5e403aff77fc582f961fad424bbd863ebcd2c069d939963ea2723ccee6f63dd1"
},
{
"member": "native/run01/stderr.log",
"bytes": 0,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
{
"member": "native/run01/stdout.log",
"bytes": 0,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
{
"member": "native/run01/working/boundary_two_containers_anchored__boundary.json",
"bytes": 8997,
"sha256": "f84fafbc3a80aee5caebf5b1cd6d41f340d954342664c64e7b627ba4fcefbe39"
},
{
"member": "native/run01/working/boundary_two_containers_anchored__scc.json",
"bytes": 8997,
"sha256": "1da4878fa52fe665e4639eb1a9cc99b5b0e85516dd51f898270ed619c4eafb06"
},
{
"member": "native/run01/working/boundary_two_local_sccs__boundary.json",
"bytes": 16991,
"sha256": "c408c437d60a8f62952151d1880ea50bc10985d5ffadec1d91b25361c1862737"
},
{
"member": "native/run01/working/boundary_two_local_sccs__scc.json",
"bytes": 16991,
"sha256": "a6e83c92dd4b9a4cb59054730b81a995a4ed2351d9a865e0c7a1572a80542dc0"
},
{
"member": "native/run01/working/two_containers_reversed_children__boundary.json",
"bytes": 8179,
"sha256": "6ed26cc72a540207f31c04bd989dfa718b1c573be1d15fcffebc8046c32fb84f"
},
{
"member": "native/run01/working/two_containers_reversed_children__scc.json",
"bytes": 8179,
"sha256": "56e980ef38cdc24e1a6b710557736c84026bb052da7e88726d55fd61e5d0c375"
},
{
"member": "native/staging.json",
"bytes": 331,
"sha256": "df221a4389c519db81276595d8154661da78812798af64b49e81c382ecaae348"
},
{
"member": "reviews/boundary-conservation-audit-20260925.json",
"bytes": 221331,
"sha256": "a59a3dcdd72eeab1502570d5189a31b2560bed57ed0d4f072a41e6de1534ac0e"
},
{
"member": "reviews/boundary-conservation-audit-final-20260925.json",
"bytes": 235597,
"sha256": "f6a3fa93c03174e0eae6f816975f06a10a696659bd3fd58291ac805a69d5edee"
},
{
"member": "reviews/boundary-conservation-review-20260925.md",
"bytes": 4315,
"sha256": "2612286fb2696f0276615427b303f3fd348098e59a00ae4f4683b04e812fd7d1"
},
{
"member": "reviews/boundary-native-observation-20260925.md",
"bytes": 2698,
"sha256": "fccd79467f48a0593256c9858ac14609a9291f3271c491f8149739314667cd48"
},
{
"member": "reviews/boundary-native-review-20260925.md",
"bytes": 6850,
"sha256": "e791925f8794e10c966f4e45d5bfb560395873d2dd2b0258340087ef20ba67a2"
},
{
"member": "reviews/boundary-order-design-critique-20260925.md",
"bytes": 17016,
"sha256": "100147aa35aff59840563e8c5c37a7c56298671910e805f55c21cac0373f435f"
},
{
"member": "reviews/boundary-order-evaluation-20260925.md",
"bytes": 8964,
"sha256": "bfdb122e9fdb5532137e5760381d7a61ef094d203ab2e3b157f90c0674894e0b"
},
{
"member": "reviews/boundary-order-implementation-20260925.md",
"bytes": 4464,
"sha256": "31e1657cc05aff9b0a802b84786baf478b3ba8913f3c241e585f24a47875c9ba"
},
{
"member": "reviews/boundary-order-independent-audit-20260925.json",
"bytes": 158196,
"sha256": "914e39cf9297ab025927b9ba9bae02adee157e3dc7061058e67ee280cb6cb212"
},
{
"member": "reviews/boundary-order-result-review-20260925.md",
"bytes": 7224,
"sha256": "b9cc9692c094d35f88f71f43b035d08d375e4350c88bad35ee07420c10bfb9e9"
},
{
"member": "reviews/boundary-order-source-review-20260925.md",
"bytes": 9618,
"sha256": "21eb621bca634a8ab0fbc122092a3c41281d121078b40651bf66eae7f6556165"
},
{
"member": "run01/boundary-extra/receipt.json",
"bytes": 903,
"sha256": "12c11a00ed7931e77034ad69ef35b2f53ea08960ff836c9bd2605b632fc4673b"
},
{
"member": "run01/boundary-extra/stderr.log",
"bytes": 0,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
{
"member": "run01/boundary-extra/stdout.jsonl",
"bytes": 488611,
"sha256": "5695e09f332a2876592134a4b0621e6a6c22b03ab5dbee6d21393a48e3e2d99b"
},
{
"member": "run01/boundary-small/receipt.json",
"bytes": 897,
"sha256": "78dce798e743b1a3547ba81ec8c86644d1a99b8e5f46ff6640db6151a426ad4f"
},
{
"member": "run01/boundary-small/stderr.log",
"bytes": 0,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
{
"member": "run01/boundary-small/stdout.jsonl",
"bytes": 222634,
"sha256": "fadde557248cdf0cbef1a127683f7fb2244608e9940a385b490caf242ab93ed2"
},
{
"member": "run01/capture.json",
"bytes": 5928,
"sha256": "7346d81f31a09ea660121deaee25215829d7d559a6e08f762394500c36a4ced4"
},
{
"member": "run01/mixed_dispatch-scc-control/receipt.json",
"bytes": 1140,
"sha256": "43eb6ce30ee2f9efd27da2b0366618c44e6630f93d2162fdb217b6d2e41088e5"
},
{
"member": "run01/mixed_dispatch-scc-control/stderr.log",
"bytes": 0,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
{
"member": "run01/mixed_dispatch-scc-control/stdout.jsonl",
"bytes": 93898,
"sha256": "28a7f598746f4c0c58afd0321861e417b2715976cde52bf55361dce8c35b4d74"
},
{
"member": "run01/mixed_dispatch/receipt.json",
"bytes": 1108,
"sha256": "b18abee69afd6097a54bad381fc56e6e7f842efb7130043b1af4656af11d1e8d"
},
{
"member": "run01/mixed_dispatch/stderr.log",
"bytes": 0,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
{
"member": "run01/mixed_dispatch/stdout.jsonl",
"bytes": 94148,
"sha256": "ac99f7222fbabdf19cdd9fd64d77251a8bd66f2cc54f285415ffae655df574cb"
},
{
"member": "run01/mixed_dispatch_edge_ids_reversed-scc-control/receipt.json",
"bytes": 1212,
"sha256": "6739ca9bc76cf7deed0579e74ab784453bce0c2a223cae3e3e041d0e9a5c4b6a"
},
{
"member": "run01/mixed_dispatch_edge_ids_reversed-scc-control/stderr.log",
"bytes": 0,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
{
"member": "run01/mixed_dispatch_edge_ids_reversed-scc-control/stdout.jsonl",
"bytes": 93308,
"sha256": "6a723a4899026c2df80327fb76c5dda8e5dc5e4e15e9e066991413c2246bade5"
},
{
"member": "run01/mixed_dispatch_edge_ids_reversed/receipt.json",
"bytes": 1181,
"sha256": "e20402a551f8b1a0e3bb37880aba6efbeee935f7e33b07e0c2c30852c66873dd"
},
{
"member": "run01/mixed_dispatch_edge_ids_reversed/stderr.log",
"bytes": 0,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
{
"member": "run01/mixed_dispatch_edge_ids_reversed/stdout.jsonl",
"bytes": 93558,
"sha256": "2c6fc18cc10edf82b67db666ebbf5a143f3f0090558cb37e6d941c23b5aa49b3"
},
{
"member": "run01/receipt.json",
"bytes": 28320,
"sha256": "9aeaeedd81d968d353c48fc32126c7644c452e3b0679ac67ab4fbad7f985ba49"
},
{
"member": "run01/route-control/receipt.json",
"bytes": 1050,
"sha256": "3bbd687895501ef451d51c300ce084eb0368bd8ecb3fff3de58573c9187cedff"
},
{
"member": "run01/route-control/stderr.log",
"bytes": 0,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
{
"member": "run01/route-control/stdout.jsonl",
"bytes": 470144,
"sha256": "9a31a676f23dea2f41daec39497f569d68231fa0fd66e5a1a476be97d5d61ac2"
},
{
"member": "run01/scc-small-control/receipt.json",
"bytes": 900,
"sha256": "138fc549ae60641c5eb9fd77b9ca379e2ffb42d21fc365ccdbc967803cdeb44a"
},
{
"member": "run01/scc-small-control/stderr.log",
"bytes": 0,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
{
"member": "run01/scc-small-control/stdout.jsonl",
"bytes": 220188,
"sha256": "0c8b9ef7888cd5ba99733f15d22478cdb2878ef02e22d297d02c8b3eea2e9e15"
},
{
"member": "run01/small-controls/receipt.json",
"bytes": 868,
"sha256": "475254bd941a3a39296c1b251a1d90f0a540b49d652c9c40ee7a928412439af3"
},
{
"member": "run01/small-controls/stderr.log",
"bytes": 0,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
{
"member": "run01/small-controls/stdout.jsonl",
"bytes": 425661,
"sha256": "0ec2bc1f51542b41b5df0b89d2a87306716a49a22951c4dfeb128dd75247d074"
},
{
"member": "run01/subsystems_40_sparse-scc-control/receipt.json",
"bytes": 1165,
"sha256": "181eb29b33c4b976dddedf12f9527fa2ae1381e588c7af620ad88d5ee47b1706"
},
{
"member": "run01/subsystems_40_sparse-scc-control/stderr.log",
"bytes": 0,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
{
"member": "run01/subsystems_40_sparse-scc-control/stdout.jsonl",
"bytes": 361147,
"sha256": "452690f23dda57f62d115b6d88e42d7f0283b4c86081d5149d8738ba6052e53b"
},
{
"member": "run01/subsystems_40_sparse/receipt.json",
"bytes": 1134,
"sha256": "6ed2ca8414aa4bb56c3c34a2af7832b844c51cc89e74834bc8800dcb92cdd7ba"
},
{
"member": "run01/subsystems_40_sparse/stderr.log",
"bytes": 0,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
{
"member": "run01/subsystems_40_sparse/stdout.jsonl",
"bytes": 361397,
"sha256": "26925d461fb34af1a728912d143d70f05172823ebf760d2386d2ebea63cfa72d"
},
{
"member": "run01/workshop_sa_folded-scc-control/receipt.json",
"bytes": 1157,
"sha256": "6de602ddd035e3ee667cb28490da5a6f22e31bc429a8e399c82a55699bc72334"
},
{
"member": "run01/workshop_sa_folded-scc-control/stderr.log",
"bytes": 0,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
{
"member": "run01/workshop_sa_folded-scc-control/stdout.jsonl",
"bytes": 194613,
"sha256": "ddf05a2ca36950305090bd5d91ec2a6c617d2eae373c33ad84e9ff4157581c0c"
},
{
"member": "run01/workshop_sa_folded/receipt.json",
"bytes": 1126,
"sha256": "a8a889e96fb85762531352e331b7486cb297f931a89a6ed57c9464ac294e16fb"
},
{
"member": "run01/workshop_sa_folded/stderr.log",
"bytes": 0,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
{
"member": "run01/workshop_sa_folded/stdout.jsonl",
"bytes": 195257,
"sha256": "f24b1412784c6985b79ead9f3fa35dc7818fc20d5ca895d4a03473afa9b3497b"
},
{
"member": "run01/workshop_sa_original-scc-control/receipt.json",
"bytes": 1165,
"sha256": "896734b2abdfef5cd53a98aac2f5770c870bff0a43d7a510168dd275083d4237"
},
{
"member": "run01/workshop_sa_original-scc-control/stderr.log",
"bytes": 0,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
{
"member": "run01/workshop_sa_original-scc-control/stdout.jsonl",
"bytes": 198272,
"sha256": "e30a2a6a30d1522cb2f8e9377e7eb8f0a81d1fe8b5aa18550f78c4829b8cc408"
},
{
"member": "run01/workshop_sa_original/receipt.json",
"bytes": 1134,
"sha256": "b2242a1dddb4811c1d795ef9a3d7a0b6c8bb6eade992b674cfa9a1055a6899d1"
},
{
"member": "run01/workshop_sa_original/stderr.log",
"bytes": 0,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
{
"member": "run01/workshop_sa_original/stdout.jsonl",
"bytes": 199200,
"sha256": "0517a3088f456fed2ee995bf610e2dbfb0cdc3e4d2e7085c2fbf32d02be15464"
}
]
}
@@ -0,0 +1,23 @@
# Final packaging and claim review
Reviewed the final results and copied source/result/conservation/native reviews, plus the completed evidence archive. This is a bounded publication gate for the existing experiment; no compiler, application, new candidate or native investigation was executed.
**No material packaging or claim error found.** The package is ready for the parent's publication/checkpoint step. This review does not assert that publication has already succeeded, that the candidate is adopted, or that issue32 is complete.
## Archive verification
- `evidence/boundary-order-3e21c1bc.zip`: exactly4,310,136bytes; SHA256 `bb6b896ff2401290869a904f7768ebb52b6f0ab36fada06badbe9abb301c37b8`.
- Exactly156 unique ZIP members; member-name set exactly equals `evidence/manifest.json`, with no duplicate or unexpected member.
- ZIP CRC readback passes for all members. Every uncompressed member's byte size and SHA256 equals its manifest entry.
- Five named native PNG captures are present. The independent result audit script and its explicit SCC helper are both included; the helper dependency is not omitted.
- All relative Markdown links in the topic documents resolve at this checkpoint. The new final review is outside the frozen archive and does not imply a replacement archive hash.
## Claim and attribution checks
Results identify generator/test source `3e21c1bca245944ae0ec7bb02aaa920f45f2cc30`, distinguish final15 focused tests/strict Clippy/release gates from earlier14-test evidence at42f595a5, and retain20 candidate outcomes,15 child processes and eight unchanged control streams as distinct inventories. No full-suite, controlled-performance or production acceptance claim is substituted for these scoped checks.
The comparison retains simple-case and chained-parent improvements alongside expanded-workshop crossing/sharing regressions, folded sharing growth, and anchored/local-SCC label problems. Flat full-Project/metric equality and model conservation are attributed to the relevant mechanical audits. Conservation coverage limits are explicit: dormant attachment/translation behavior is not claimed exercised merely because the checker supports it.
Native attribution correctly separates the3e21 candidate Project generator from the reused `d6f45a13a7b8ab8dc9b84ee432a832609f5af6ed` GUI renderer, executable SHA256 `133375d57bdecf3f3effe5ecc13ec121e1257b90838f2cdb64fb91aa9a530d1b`. The planner was not invoked through native Arrange. Four imported Projects and five captures are the observed scope; two other staged controls are not represented as viewed. The independent native review explicitly handles stale window-title metadata, distinguishes readable text from label-background interruption of the parent outline, and limits the local-SCC finding to overview/selection-assisted tracing. No Undo/Redo or production command-integration claim is inferred from these captures.
The final results link the real archive/manifest and bounded reviews, rather than retaining the earlier forthcoming-evidence placeholder. Publication remains the parent's final step. Stop after that requested publication/checkpoint; this review authorizes no further implementation or experiment.
@@ -0,0 +1,19 @@
# One-pass boundary ordering implementation
Owned module only: `examples/auto_layout_probe/condensed/boundary.rs`, preformat SHA256 `d2b1a5882c4b64d79d09d5cca586ad4cef5dac086e5a8fd438f0218e6119889d`. Parent owns integration/format/compilation. No builds, rustfmt, commits or native execution by implementation agent.
Exact requested public-to-parent Block/Band/Group/reorder API. Validates child-parent membership, complete unique band inventory, finite positive block geometry and existing undirected-bundle contiguity. Undirected connectivity is evaluated within each band, preserving existing block order within each indivisible unit; noncontiguous input fails explicitly. Directed incoming edges anchor a unit only when the target lifts into it and source does not lift into the current parent. One vote per distinct visible source placement ID, stable-ID order, f64 arithmetic mean of current absolute source centerY. Full offset ancestry and root40 are composed; parallel edges do not multiply votes.
Only anchored units exchange their previous ordinal slots, using score then canonical unit order. Unanchored unit ordinals remain fixed. Whole-band block heights/gaps are repacked as a prefix, so unequal-height units do not reuse incompatible old Y slots. X, sizes, SCC memberships/ranks, perimeter/member offsets and root layout are untouched. Every member's offset receives its block Y delta; descendants move by ancestor composition rather than duplicate direct edits.
A group is active only if some band contains at least two anchored units. Dependency H→G exists whenever active groupH owns any offset on an actual external source's complete ancestor chain. No additional ancestry-only arcs. Stable parent-ID order feeds SCC condensation; process the dependency DAG once. Every score in one dependency SCC is snapshotted before any member's offset updates. Cycles are therefore a one-pass snapshot heuristic, not a fixed point or a stability guarantee.
Prefix accumulation is f64; conversion and occupied-envelope agreement are checked with explicit0.001units tolerance. Existing canonical prefix positions must also agree within that tolerance. Unchanged orders retain their original f32 positions exactly. No envelope resizing occurs; parent final reprojection remains authoritative. Missing/cyclic/nonfinite structures and excessive charged work return errors. The enclosing candidate owns the private scratch offsets, so it must discard them on failure; this helper does not promise transactional rollback of mutable arguments after an error.
Diagnostics include active parent IDs, dependency edges with source/ancestor witnesses, dependency SCCs/cyclic status, before/after block orders, source-owner sets, actual edge hints, scores, changed units/blocks and tolerance. Pure unrun tests cover anchored sorting with a fixed unanchored ordinal, heterogeneous100/20height repacking, and a source center changing under a grandparent offset. Parent's actual fixtures must test dependency chains/opposition, node/route preservation, repeat behavior and quality; no acceptance is inferred from implementation.
Pre-build review follow-up: module SHA256 `77522e22daf4058a07aafe6b246211bf652d9fc99e2a556cf9f8b20c297eaf0e`. Absolute source Y now follows the final plan's precise f32 order (own offset, ancestors upward, then+40); convert that Y to f64 before adding half-height and averaging votes. Member-offset updates reject actual f32 movement differing from requested delta by more than0.001. Added three unrun tests: actual reorder dependency on a grandparent-owned offset with adversarial parent-ID ordering; actual undirected bundle preservation around an unanchored ordinal with heterogeneous heights and deduplicated sources; noninteger f32 composition plus accepted/rejected translation precision. Six module tests now exist. No policy constants, corpus outcomes or rank/packing heuristics were tuned.
## Parent acceptance addendum
Final tested source3e21c1bca245944ae0ec7bb02aaa920f45f2cc30 corrects full member displacement precision (including origin subtraction), with the real large-origin packing regression. Focused example tests15pass, strict Clippy and optimized Windows build pass with unchanged inputs (gates29–31). Prior gates27–28 at42f595a5 retain14tests/Clippy success. All20candidate outcomes supported inrun01; eightcontrols exact. Result/conservation/native reviews define quality limits; no production adoption.
@@ -0,0 +1,41 @@
# Native renderer observation
Candidate Project generator: `3e21c1bca245944ae0ec7bb02aaa920f45f2cc30`.
Reused native executable: `d6f45a13a7b8ab8dc9b84ee432a832609f5af6ed`,
SHA256 `133375d57bdecf3f3effe5ecc13ec121e1257b90838f2cdb64fb91aa9a530d1b`.
This observes saved experimental Projects in the existing renderer. It does not
exercise the experimental planner through a native Arrange command.
The parent used the Computer skill and native Open dialog to import four exact
Project extractions, without editing or saving. Five screenshots record:
1. `01-two-container-boundary`: Fit102%, Left A→Right B and Left B→Right A
appear as separate straight horizontal connections. Both labels are readable
between containers, with no shared route or apparent endpoint ambiguity.
2. `02-two-container-scc`: same viewport/scale, prior SCC ordering. Routes
visually meet at the right container boundary and run alongside its border;
the apparent straight rows misleadingly suggest the opposite endpoint pairing.
This is a concrete improvement from the first image's reordered children.
3. `03-anchored-boundary`: authored anchors retain bent routes. The two label
backgrounds interrupt the right parent border; the label text remains readable
and does not obscure child names. The emitted two label/node intersections
should not be described as two unreadable labels or child-name occlusions.
4. `04-local-scc-boundary`: Fit55%, intact Y1/Y2 block above X1/X2, Z later.
Small text and multiple adjoining/reused strokes make tracing harder than
the simple two-edge example. A structurally correct block order is insufficient
to establish native usability for this case.
5. `05-local-scc-selected-lower-boundary`: selecting the lower source connection
highlights its path to X1 and displays Source B / X1 in Properties. Selection
helps resolve the route among neighboring strokes. No endpoint edit occurred.
The process was closed by Alt+F4. The immediate window list briefly retained the
closing window; a subsequent capture reported no foreground process. The owned
observer then returned exit0, empty stdout/stderr, unchanged immutable inputs,
and no running process. All six staged working JSONs remain unchanged (only four
were opened). No save, undo/redo, new planner invocation, API integration, general
accessibility acceptance, or whole-application acceptance is inferred.
Evidence includes stage/source extraction receipts, per-run process records,
five screenshot/metadata pairs, and the launcher. Package binaries are hash-bound
in the manifest rather than duplicated in the research archive. Windows native
inspection only; Linux remains excluded.
@@ -0,0 +1,41 @@
# Boundary-order native evidence review
Independent review, 2026-09-25. Inspected all five archived PNGs with `view_image`, their capture metadata, the staging/launch/close records, package and source attribution, and the private profile's recent-model list. No new GUI session, import, build, experimental Arrange invocation or source change. This review stops at the existing evidence as requested by the user.
**The simple boundary case is visibly easier to trace than its SCC control. Authored anchors retain a small label-background/container-border conflict, while the local-SCC overview still needs selection to disambiguate a boundary route. This is limited native observation of imported geometry, not acceptance of a production Arrange replacement.**
## Attribution and integrity
- Reused GUI source: `d6f45a13a7b8ab8dc9b84ee432a832609f5af6ed`; executable SHA256 `133375d57bdecf3f3effe5ecc13ec121e1257b90838f2cdb64fb91aa9a530d1b`; DLL SHA256 `aa282fa47bb2c4e210cea5e470cdbdca58568f0be0bc73e404d86ab9cd95ef6a`.
- Imported candidate and paired SCC Projects were generated at `3e21c1bca245944ae0ec7bb02aaa920f45f2cc30`. The existing native renderer displays those raw Projects. The experimental boundary planner was not executed through the GUI's Auto-layout control.
- Independently rehashed 21 referenced records covering the manifest, launcher, source-run/child receipts and stdout, reused attribution, staged executable/DLL, six input Projects, launch preflight and native logs: no mismatch. All six working Project hashes agree between the recorded preflight and close inventories; no working-file change was recorded.
- Manifest SHA256 is `940a7605b5a83308261e1fde0ecd7291cc855614a515e87e084823c8fc4e9016`. Private `LOCALAPPDATA` and unrelated working directory are under `boundary-native-20260925/run01`.
- Start/close records identify PID 5492, observed exit code 0, no observer failure, unchanged immutable inputs and empty stdout/stderr. The parent reports closing with Alt+F4. The receipts independently establish observed clean process exit; they do not by themselves establish the particular close gesture or GUI quality.
- The private recent-model list corroborates four imports: simple boundary, simple SCC control, anchored boundary and local-SCC boundary. Two additional staged SCC controls were not observed natively and are not claimed here.
- Several capture JSON window-title fields lag the actual screenshot title, including the first capture's `Untitled mission` and the fourth capture's old two-container title. These fields alone do not identify the loaded Project. The image's visible title/status path and the recent-model entries provide consistent attribution. All capture metadata names the staged executable and the same window ID.
## Visual findings
**01 — Simple boundary, 102% zoom.** Left A and Left B remain in their canonical upper/lower order. The right children are Right B above Right A. Each connection is a separate horizontal line into its matching child. Both route labels are readable between the containers, and their association with the two separate lines is visually clear. This screenshot supports the intended local ordering improvement without a selection aid.
**02 — Paired simple SCC control, 102%.** Right A is above Right B, so the two connections take opposing vertical travel beside the right children. Their vertical portions occupy the same visible corridor near the right-container boundary; direction and connection identity are substantially harder to follow than in 01. The labels remain legible, so the defect should be described as route association/shared travel, not unreadable label text. No native selection trace of this control is archived; exact endpoints continue to be established by the input model and routed evidence.
**03 — Anchored boundary, 102%.** The improved child order remains, but the authored source/target port fractions produce short downward bends at the right side instead of straight connections. Both label strings remain readable. The white label backgrounds reach and interrupt the Right group's left border near the route corridors. This is a real local visual conflict, but describing it as text obscuring the Right child name, an unreadable label, or a blocked arrowhead would overstate what the image shows. The artifact is principally label-background occlusion of the parent outline. The screenshot supports retaining that limitation rather than promoting the metric's label/node count into an unqualified severe text-collision claim.
**04 — Local SCC boundary, Fit 55%.** The Y1/Y2 pair is grouped above the X1/X2 pair, with Z to the right. The grouping and rank structure are visible, but labels are small at this fitted scale and several routes run close together or merge visually. In particular, the unselected Lower boundary approaches the Y2 region before continuing down toward X1; an unaided quick reading could assign it to the wrong nearby node. Whole-diagram fit therefore preserves overview at a readability cost. This image does not support a general claim that coherent SCC blocks make all individual routes easy to trace.
**05 — Selected Lower boundary, 55%.** The highlighted route visibly runs from Source B, turns downward beside Y2 and ends at X1. The properties pane independently names `Lower boundary`, source `Source B · @source_b`, and target `X1 · @x1`. This confirms that the selected relation has the correct endpoint and that selection supplies a useful tracing aid for this witness. It does not eliminate the ambiguity of the unselected overview or establish the readability of every other local route.
## Scope of the result
The existing five observations support a narrow positive result for the simple two-container case, a modest remaining border/label-background conflict with authored anchors, and an unresolved overview/tracing cost for the local-SCC case. They do not test experimental command integration, Undo/Redo, routing edits, repeated Arrange in the GUI, automatic-side capacity cases, all frozen cases, or general accessibility/responsiveness. Existing numerical and conservation reviews remain separate evidence. No further native investigation is proposed as part of this completed checkpoint.
Screenshot SHA256:
| File | SHA256 |
| --- | --- |
| `01-two-container-boundary.png` | `bc4e28a0fcc37cdc4c904993b67e5b018c66a3a0a2ff7c2511fec324e03d5cd7` |
| `02-two-container-scc.png` | `e1c68d66a4eae12c2e2c056858a1d4fc6b5eec8c387ee39b19337abbce7f49cf` |
| `03-anchored-boundary.png` | `8038656c660d826426c36e059997eb955ef8e84db4bdc2d23bcc214f64580c1d` |
| `04-local-scc-boundary.png` | `677057c6870103daeeb31d48a11a5f9b8b8ebb604a7864e71a541a28d6024342` |
| `05-local-scc-selected-lower-boundary.png` | `732cc188a8e8fbe3632d3bd824f218560c5a40afac053d5f8936e62be2699861` |
@@ -0,0 +1,93 @@
# Incoming boundary order experiment
Issue32, following the SCC checkpoint f4fa0c11. Keep the full OA/SA product goal
and production/native acceptance requirements unchanged. This is one isolated
planner comparison with released routing, not a quality-selected parameter sweep.
## Question and fixed candidate
The SCC planner drops a connection from a sibling group's local graph when the
source is outside that group. Root SCC ranking knows which container is upstream,
but the children have no information for matching order across the boundary.
The existing two-container witness has164 shared units and one label/node
collision, while the old layered candidate's incoming-neighbor order has zero
for both. Preserve the SCC candidate's sparse40 structural improvement while
restoring this missing information.
Start from the same canonical SCC plan, before import. Never change its SCC
membership, condensation ranks, internal perimeter walks, node sizes, gaps or
root layout. In each nonroot same-rank band, bundle undirected-connected SCC
blocks as indivisible units with their existing internal order. Unanchored units
retain their ordinal slots. Reorder only anchored units among their old slots,
then repack by prefix sums of unchanged heights and gaps. Old absolute Y slots
cannot be reused for differently sized blocks. Envelopes are mathematically
invariant; finite arithmetic and exact final projection remain checked bounds.
An anchor is an oriented incoming projected edge whose target lies under the
current sibling group and whose source does not. One distinct visible source
placement contributes one vote per unit, independent of parallel relation count.
Scores are the mean of current planned absolute source-center Y coordinates,
summed in stable placement-ID order using f64. Break equal scores by original
unit order. Undirected external edges supply no invented direction or score.
This is a stated center-based heuristic, not a claim to match final port slots.
A group can change only when a band contains at least two anchored units.
Create dependencies from every such active group that owns an offset along an
external source placement's entire ancestor chain to the consuming group.
Include the immediate containing group; do not invent additional hierarchy arcs.
Condense this influence graph and process its SCC DAG deterministically. Before
each SCC, compose a complete absolute-position snapshot from current offsets.
All member groups calculate proposals from that snapshot before committing any
of them. Each group commits once. Cyclic influence therefore has one simultaneous
step, an explicit limitation rather than an unbounded convergence loop.
Recompose descendants through their ancestor offsets. Perform one final original-
to-final placement import, preserving the existing reset/hidden/retained-route
contract and conditional effective-minimum restoration. Exact final projection
and actual mandatory leads must pass; no size/gap/anchor repair is added.
Apply the existing graph, work and extent bounds to new dependency/snapshot work.
## Controls, falsifiers and acceptance
Retain all14 original cases, their SCC run02 outputs at2426d286 and the original,
Arrange-reset and old-layered controls. Flat/no-boundary outputs, particularly
sparse40 and both mixed-ID cases, must preserve complete Project, geometry and
metrics exactly (new diagnostics are separate). Existing SCC-mode and released
route-mode output must remain byte exact after adding the new observer mode.
The existing two-container witness requires Left A before Left B, Right B before
Right A, with actual route sharing/contact/label improvement assessed against
the old zero-sharing witness. Swapping both groups is not that success.
Add three independent topology falsifiers before execution:
1. Opposing parents: L.A→R.B, L.B→R.A, R.A→L.A, R.B→L.B. Contradictory order
demands must remain deterministic, bounded and explicit; zero crossings is
not an achievable requirement inferred from those demands.
2. Three-parent chain: L.A→M.B, L.B→M.A, M.B→R.A, M.A→R.B. Preserve upstream
L.A<L.B, obtain M.B<M.A and R.A<R.B, and preserve root ranks L<M<R.
Compare relation-ID reversal and a uniform authored subtree translation.
3. Two local two-node SCCs X/Y feeding a singleton Z in a later rank, with
external Source.A→Y1 and Source.B→X1. Order Y before X without splitting
either cycle or changing Z's rank/perimeter geometry.
Also run the existing two-container witness with authored source/target port
anchors. Use meaningful pure tests for heterogeneous-height prefix packing,
undirected bundle preservation and complete source-ancestor influence. These
tests supplement rather than replace actual projected Project cases.
Record complete before/after Projects, dependency graph and SCC batches, source
placement/edge hints, scores, before/after unit and block orders, final derived
ports, geometry and warnings. Reconstruct preservation independently, including
folded latent offsets, anchors, other views and eligible route resets. Check
same-input equality, JSON/source roundtrips and ten full repeated calls. Report
explicit refusals without treating a successful observer process as adoption.
Preserve crossings, all contact types, raw and edge-deduplicated shared intervals,
label/node/label/stroke conflicts, enclosure/penetration, route length and area.
Boundary improvement may worsen internal tracing; cyclic influence can retain
crossings; source centers may disagree with fixed or derived ports. Keep those
counterexamples. Stop this one comparison after independent source/result review.
Production command/API/history integration and native route tracing remain
required before accepting an Arrange replacement. Windows only; Linux remains
excluded, and the existing prerelease remains unchanged.
@@ -0,0 +1,53 @@
# Independent boundary-order result review
Primary `boundary-order-20260925/run01`, source3e21c1bca245944ae0ec7bb02aaa920f45f2cc30. Read-only audit of frozen records; no compiler, routing, engine or native execution. **The boundary-order hypothesis passes its exact simple tracing and propagation witnesses while preserving flat SCC results. It is not a monotonic quality improvement or an accepted Arrange implementation.**
## Integrity and all20 outcomes
Verified15 child receipts exit0/not timed out with matching source pin and stdout/stderr hashes; bound inputs exactly unchanged; capture/build/source agreement; eight control streams independently byte-identical to their frozen released/SCC controls. All20 candidate outcomes are supported. All20 recorded same-input comparisons, nine subsequent calls and JSON/source roundtrips are exact, with empty necessary lead diagnostics. These are recorded executable observations, not independently rerun model operations.
Ten actually flat cases preserve their complete candidate Project and full metrics exactly against the SCC baseline: flat_chain_4, chain_7_row_wrap, diamond, crossed_bipartite, authored_detour_and_label, fixed_ports, long_ordinary_labels, mixed_dispatch, mixed_dispatch_edge_ids_reversed and subsystems_40_sparse. Sparse's32-unit sharing and coherent SCC gains are therefore unchanged, as are mixed's known relation-ID sensitivity and other prior limitations.
The remaining ten outcomes are nested_cross_boundary (metrics unchanged), two_containers_reversed_children, both workshop cases, and six new structural fixtures described below. No failed/unsupported row was omitted. Full Project conservation beyond the flat exact comparisons, including folded latent geometry/anchor policy, is the parent's separate audit; this report does not replace it.
## Exact independent witnesses
- Existing two-container: LeftA<LeftB and RightB<RightA both hold in final projected centerY. Sharing164→0, contacts2→0, label/node1→0, route length788→460, area339852 unchanged. No new crossing, node penetration or warning. The result solves the declared ambiguous trace witness without swapping both groups.
- Three-parent chain and its translated/reidentified variants: LeftA<LeftB, MiddleB<MiddleA, RightA<RightB and rootX Left<Middle<Right all hold. Sharing328→0, contacts4→0, label/node3→0, length1492→836, area523604 unchanged. Complete projected node geometry is exact between the ordinary, translated-subtree and relation-ID-reversed variants. This supports propagation and absence of authored-parent-position dependence for these examples, not all graphs.
- Opposing parents: diagnostics correctly show one cyclic dependency component. One simultaneous snapshot changes the right order; the resulting aggregate quality remains baseline sharing68, contacts2, label/node2, length1028 and area336730. Repeated full calls are stable. The contradictory demands were not falsely asserted to be simultaneously satisfiable or zero-cost.
- Two local SCCs: independently reconstructed exact quotient SCCs/ranks remain intact. Y lies wholly above X and Z remains in a later X rank. This passes the structural criterion, but sharing270→278 and label/node1→3 regress; contacts14→9, label/stroke4→3 and length2956→2656 improve. Area1155248 remains unchanged. Correct order is not sufficient quality acceptance.
- Anchored two-container: expected orders pass, sharing111.200012→0 and contacts2→0; length788→565.599976, area unchanged. **Label/node increases1→2.** Preserve this counterexample to a blanket claim that anchoring plus correct order improves all presentation quality.
For all20 supported results, independently reconstructed sibling quotient SCC membership and condensation rank ordering agree with diagnostics. Independent interval sweeps reproduce all emitted raw pairwise sharing totals and also retain distinct-edge physical sharing. No contact/crossing numbers were independently recomputed in this pass; they are explicitly emitted observations. The inherited folded retrace/raw-sharing distinction remains visible rather than silently changing metric definitions.
For the six extra fixtures, independently verified all edge-hint tuples against original projected edges and recomputed21 unit scores from projected source-owner centerY. Applied dependency components in reported order, taking a common pre-update snapshot inside a cyclic component and updating descendants only after its scores. Every score agrees exactly, including chain scores366/202 followed by202/366 downstream. This verifies the concrete evidence chain for these tests; it is not a general proof of dependency scheduling correctness.
## Real workshop tradeoffs
| Metric | Expanded SCC→boundary | Folded SCC→boundary |
| --- | ---: | ---: |
| Proper crossings |6→7|8→6|
| Point contacts |16→15|28→28|
| Raw shared length |344→352|726.5→1077.5|
| Label/node |3→3|3→3|
| Label/stroke |1→1|2→1|
| Route length |7950→7794|8137→8137|
| Drawing area |1600772→1524784|1169964→1127988.592529|
Both retain zero emitted unrelated node penetration/overlap; expanded warnings remain0 and folded warnings remain the same four promoted-endpoint warnings. Expanded gains in length/area/contact count coexist with a new crossing and more sharing. Folded fewer crossings/label-stroke contacts coexist with a351-unit sharing increase. The real-case evidence therefore rules out unconditional quality adoption from the synthetic success alone.
## Decision and next boundary
Retain incoming boundary ordering as a useful mechanism and preserve the one-run evidence. It meets the declared two-container and chained-parent order/tracing targets without disturbing flat SCC geometry. Do not tune this run to remove the counterexamples, and do not describe20 supported/stable cases as20 improved layouts.
Before production integration, investigate the specific new label/node collisions in the anchored and local-SCC witnesses and the newly shared folded workshop routes. Any selection policy must preserve hard geometry/model constraints and make quality tradeoffs explicit; merely choosing the lowest crossing count would favor the folded result while hiding substantially more coincident routing. A local label-aware coordinate/route check or conditional order acceptance is a distinct future design decision, not validation supplied by this run. Native tracing, renderer glyph/header legibility, navigation cost, command/history integration and release acceptance remain outstanding.
No production source change or additional candidate execution was performed by this audit.
- .local\audit-boundary-order-20260925.py: SHA256 96bb938961255b59add3922f2cc8b30ddc7ec127d77ab1d7b61974e116930432.
- .local\audit-scc-placement-v3-20260925.py: SHA256 8b238f010eeb81720a59a0ad290544f87934acb8070e58a3fd25f9eda9f47a0d.
- .local\boundary-order-independent-audit-20260925.json: SHA256 914e39cf9297ab025927b9ba9bae02adee157e3dc7061058e67ee280cb6cb212.
- .local\boundary-order-20260925\run01\receipt.json: SHA256 9aeaeedd81d968d353c48fc32126c7644c452e3b0679ac67ab4fbad7f985ba49.
@@ -0,0 +1,65 @@
# Incoming boundary ordering: experiment results
**Incoming boundary ordering solves the simple two-container and chained-parent tracing witnesses while preserving the flat SCC results. It does not improve every view and is not an accepted replacement for Arrange.** The candidate remains isolated in the example probe; production commands and released routing are unchanged.
Tested source: `3e21c1bca245944ae0ec7bb02aaa920f45f2cc30`. The fixed candidate, scope and alternatives are in [plan](plan.md). See [source review](source-review.md), [independent result review](result-review.md), [conservation review](conservation-review.md), and [native observations](native-observation.md) for their separate evidence scopes.
## Question and method
The preceding SCC candidate made cyclic groups coherent but discarded external incoming-neighbor information when ordering children inside different parents. The new mechanism orders eligible sibling SCC blocks using those incoming sources, without changing condensation ranks, internal SCC walks, stored hierarchy or the router. Parent dependency components determine the processing order; cyclic components use one common snapshot. This is a single predeclared candidate, not a parameter sweep or fixed-point convergence claim.
The run contains15 child processes: released routing control, existing small controls, SCC small control, boundary small fixtures, six paired extra fixtures in one stream, and SCC/boundary pairs for five frozen corpus cases. All15 exit0 with unchanged bound inputs. Eight control streams are byte-identical to their previous released/SCC versions. Candidate outcomes are reported separately from process exit status.
All20 cases return candidates and record exact same-input reproduction, nine subsequent calls equal to the first result, and exact JSON/source roundtrips. Necessary lead diagnostics are empty. Independent analysis reconstructs SCC membership and condensation order and reproduces raw collinear-sharing totals; it also retains edge-deduplicated physical sharing so folded route retracing is not hidden. Twenty-one extra-fixture ordering scores and their edge-hint endpoints were independently checked against projected source positions.
## Outcomes against the SCC control
| Case or group | Observed result |
| --- | --- |
| Ten flat cases | Full candidate Project and full metrics exactly unchanged. Includes sparse40 and both mixed relation-ID variants. |
| Nested cross-boundary small fixture | Quality metrics unchanged. |
| Two containers, reversed children | Required LeftA<LeftB / RightB<RightA order; sharing164→0, contacts2→0, label/node1→0, length788→460; area unchanged. |
| Three-parent chain and its translated/reidentified variants | Required orders propagate through all three parents; sharing328→0, contacts4→0, label/node3→0, length1492→836; area unchanged. Projected node geometry is exact across all three variants. |
| Opposing parent dependencies | Stable bounded result under conflicting demands; aggregate quality unchanged: sharing68, contacts2, label/node2. No claim that all demands become simultaneously satisfiable. |
| Two local SCCs | Y precedes X without splitting SCCs or changing Z's later rank. Contacts14→9 and length2956→2656 improve; sharing270→278 and label/node1→3 worsen. |
| Anchored two-container variant | Required order and exact authored-anchor contract; sharing111.200012→0 and contacts2→0, but label/node1→2. |
| Expanded workshop | Crossings6→7, sharing344→352; contacts16→15, length7950→7794 and area1600772→1524784. Label/node3 and label/stroke1 remain. |
| Folded workshop | Crossings8→6 and label/stroke2→1; sharing726.5→1077.5, contacts28 unchanged. Four promoted-endpoint warnings remain. |
The ten flat cases are flat_chain_4, chain_7_row_wrap, diamond, crossed_bipartite, authored_detour_and_label, fixed_ports, long_ordinary_labels, mixed_dispatch, mixed_dispatch_edge_ids_reversed and subsystems_40_sparse. Their exact preservation retains both the preceding sparse improvement and its unresolved label issues, as well as mixed's existing ID-sensitive routing differences.
All candidates report zero unrelated node penetration and node overlap. This necessary geometric safety is not proof of label readability or easy tracing. The anchored/local-SCC label regressions and workshop sharing/crossing tradeoffs prevent a blanket quality-pass claim. Complete model conservation, dormant routing metadata and folded latent geometry are assessed separately in the linked conservation review.
## Native observation and limits
The parent's observation covers four Projects and five captures, including a selected lower boundary connection. It supports the improved simple-case trace appearance. In the anchored case, readable label text/backgrounds interrupt a parent border; the local-SCC traces remain difficult at55% zoom. These are bounded visual observations, not evidence that all labels or real workshop tasks pass. The linked native note records the exact artifacts/actions and is authoritative for those claims.
Compilation, successful candidate construction and repeated stability do not establish production readiness. The mechanism does not solve every conflict, and lower crossing count can coexist with more shared ink. No release replacement, issue closure, production command/history integration or general readability acceptance follows. Stop this frozen experiment; retain its counterexamples before selecting further label/connection placement work.
## Reproduction
Use the tested source pin and the recorded Windows native toolchain/environment. Focused gates29–31 pass15 example tests, strict Clippy and the optimized probe build:
```text
cargo test --locked --offline -p oa-core --example auto_layout_probe
cargo clippy --locked --offline -p oa-core --example auto_layout_probe -- -D warnings
cargo build --release --locked --offline -p oa-core --example auto_layout_probe
```
Earlier gates27–28 at `42f595a5d6df92432aaf6965194bf651cd9b8b6d` retain14 passing focused tests and strict Clippy. They are earlier-source evidence, not additional tests at the final pin. No broader test-suite or performance claim is made.
The portable probe interfaces used by the runner are:
```text
auto_layout_probe --route-corpus MANIFEST
auto_layout_probe
auto_layout_probe --scc-fixtures
auto_layout_probe --boundary-fixtures
auto_layout_probe --boundary-extra-fixtures
auto_layout_probe --scc-placement PROJECT_JSON DIAGRAM_UUID
auto_layout_probe --boundary-placement PROJECT_JSON DIAGRAM_UUID
```
The extra-fixture stream carries paired baseline/boundary records. Named corpus rows are associated with their runner case paths, not the generic external_case label. Each child has a90-second timeout; measured durations here are process receipts, not controlled latency benchmarks.
The [evidence manifest](evidence/manifest.json) binds all156 archive members: exact inputs, receipts/logs, audits, native captures and collectors. [Archive](evidence/boundary-order-3e21c1bc.zip):4,310,136bytes, SHA256 `bb6b896ff2401290869a904f7768ebb52b6f0ab36fada06badbe9abb301c37b8`. The [independent native review](native-review.md) bounds the visual claims and documents stale capture-title metadata. Reproduce the independent result audit with `audit-boundary-order-20260925.py RUN_ROOT UNIQUE_OUTPUT_JSON`; its explicit `audit-scc-placement-v3-20260925.py` helper must accompany it. Original controls and full candidate Projects/geometry must remain available; summary tables alone are insufficient to reproduce the comparison.
@@ -0,0 +1,62 @@
# Incoming boundary order source review
**Final source verdict at `3e21c1bca245944ae0ec7bb02aaa920f45f2cc30`: no unresolved material findings.** The complete-displacement precision defect found in the initial pin is repaired and independently re-reviewed below. This accepts the source for the bounded experiment, not layout quality, repeated-call stability, production integration or release readiness.
Independent source review of `42f595a5d6df92432aaf6965194bf651cd9b8b6d`, 2026-09-25. Inspected the clean pinned worktree, `harness/experiments/boundary-order-20260925/plan.md`, `crates/oa-core/examples/auto_layout_probe/condensed.rs`, its `condensed/boundary.rs` module, `boundary_fixtures.rs`, and the observer/CLI changes in `auto_layout_probe.rs`. No source edits, compiler, engine/router execution or GUI work. One read-only PowerShell single-precision arithmetic calculation confirms the finding below. Runtime gates belong to the parent and are not claimed by this review.
## Initial verdict at 42f595a5
The implementation follows the settled dependency-SCC ordering policy, with one bounded numerical preservation finding: the final member translation guard checks addition precision but does not check the precision lost while subtracting the old block origin. Fix or explicitly narrow that claimed bound before treating source review as complete. No semantic SCC, projected endpoint, dependency scheduling, undirected bundle or importer-boundary defect was found by this review.
## Resolved finding: guard the complete intended displacement
**P2, `condensed/boundary.rs:358` and `:73`.** `let delta = y - old` rounds the requested block-origin displacement to f32. `translate(value, delta)` then compares the actual addition against that already-rounded displacement. An exact addition can therefore pass even when the member no longer preserves its original offset from its block origin within the declared 0.001 bound.
Concrete single-precision witness:
| Quantity | Value |
| --- | ---: |
| Old block Y | 65536 |
| New block Y, f32 representation of 100.002 | 100.00199890136719 |
| Original member Y, block + 32 | 65568 |
| Computed f32 delta | -65435.99609375 |
| Returned member Y | 132.00390625 |
| Current translate-check error | 0 |
| Member-relative-offset error | 0.0019073486328125 |
These coordinates fit the planner's 100000 extent. A reachable prefix setup uses heights `[65404, 68, 100, 68.002]`, gap 32, original block origins `[0, 65436, 65536, 65668]`, and reversed order `[3,2,1,0]`. The original third block moves from 65536 to approximately 100.002. Every new starting prefix is small, so the packing cast bound does not independently eliminate the witness. The importer checks the resulting node plan against projection; it does not compare the members against their declared block-origin displacement, so that final guard is not a substitute.
Recommended narrow repair: give the translation helper old and new block origins and compare the final f32 member coordinate to `f64(value) + f64(new_origin) - f64(old_origin)`. Reject if the total displacement error exceeds 0.001. A direct regression should cover the witness, alongside the existing addition-rounding case. This is a correctness guard repair, not a layout-quality change or a reason to relax projection equality.
## Confirmed control flow and invariants
- Root isolation is structural: only `Some(parent)` groups are passed to the new module. Root offsets, sizes, semantic component membership, ranks, perimeter walks and gaps remain owned by the existing canonical planner.
- Units come from connected components of bidirectional same-band undirected arcs. The existing canonical BFS order is retained, and noncontiguous bundles are rejected. Sorting permutes whole units; unanchored unit identities retain ordinal slots. Flattening a reordered unit sequence retains each bundle's internal block order.
- Boundary membership uses projected edge endpoints and the same ancestry lift operation as the SCC planner. Only oriented edges entering the group from outside supply anchors. Deduplication uses visible source placement IDs in a BTreeSet, rather than relation IDs or unprojected semantic owners. Edge hints affect diagnostics only.
- `offset_owner` maps every direct child placement to its ordering group. Dependency walks begin at the source placement itself and inspect every ancestor, including the immediate containing group. Restricting graph vertices to groups with at least two anchored units is sound under fixed unanchored slots. No additional hierarchy arcs are invented.
- Dependency SCCs are condensed and processed in deterministic topological order. Every score for a complete SCC is materialized before any offset commits. Commits touch disjoint directly owned offsets; source center recomposition subsequently reads all current ancestor offsets. Cyclic SCCs therefore use the declared common snapshot without a hidden traversal-order preference.
- Center composition now follows the final plan's f32 child-to-root addition order, then adds the root origin and converts to f64 for half-height and mean computation. This resolves the preliminary review concern about scoring a different absolute-position arithmetic path.
- Packing uses unchanged dimensions and gaps, rebuilds prefix positions rather than reusing old heterogeneous slots, and checks canonical prefix consistency, cast precision and total extent. An unchanged order retains its original f32 origins. Mathematical envelope invariance is supported; the unresolved member-displacement finding above is distinct.
- Edge scanning, ancestry traversals, component discovery, dependency traversal, topological scans, source-center reconstruction and member commits charge the shared bounded work counter. The existing visible/stored graph bounds and final extent guard remain. This is a weighted work bound, not an instruction-count or elapsed-time guarantee.
- The module only mutates the local plan. `candidate_policy` imports once after all reordering; the existing exact reprojection, effective-minimum restoration, original-to-final route handling and final mandatory-lead check remain unchanged. A refusal cannot partially mutate the caller's Project.
- The original SCC mode takes the false policy branch. By source inspection its planner and emitted observer structure are unchanged; byte equivalence still requires the planned controls. The new extra-fixture CLI emits both baseline and boundary observations rather than presenting its process exit as candidate acceptance.
## Test and fixture assessment
The pinned source adds six module tests. The ancestor-dependency test deliberately gives the downstream parent a lower ID, so omitting the grandparent influence arc would produce a wrong downstream permutation; it exercises actual `reorder`, not merely center arithmetic. The undirected test exercises a whole bundle around an unanchored unit, heterogeneous prefix packing, and deduplicated source owners. Other tests cover anchored ties, ordinary heterogeneous swapping, ancestor coordinate composition, final-plan f32 evaluation order and addition precision rejection. These are meaningful mechanism checks by inspection; this review does not claim they have run.
Projected fixture additions cover opposing parents, a three-parent propagation chain, relation-ID reversal, authored subtree translation, two local SCCs with a downstream singleton, and authored endpoint anchors. They support the declared experiment. They do not establish general automatic-side capacity stability, dormant authored-route coverage, native readability, production command/history integration or release readiness.
The result review must retain cyclic conflicts, source-center versus attachment-order mismatch, internal-rank versus boundary preference conflict, exact repeated-call stability, semantic/route conservation, and the complete quality vector. The mathematical ordering properties alone do not establish those outcomes.
## Repair re-review at 3e21c1bc
Independently inspected the clean pinned worktree and the complete diff from `42f595a5d6df92432aaf6965194bf651cd9b8b6d` to `3e21c1bca245944ae0ec7bb02aaa920f45f2cc30`. The only source change is the intended precision repair and its regression. No source edits or compiler/engine execution were performed by this reviewer.
`translate` now receives the old and new block origins. It calculates `f64(value) + (f64(new_origin) - f64(old_origin))`, converts that complete intended coordinate to f32, and rejects if the final coordinate differs by more than 0.001. The commit site no longer constructs or adds a rounded f32 delta. This closes the demonstrated subtraction-rounding loophole without changing ordering, scores, sizes, gaps or projection rules.
The new `member_offset_survives_large_origin_subtraction` test uses the complete four-block prefix witness, reverses its order, and checks preservation of the third block member's 32-unit offset after the move. The earlier precision rejection and center-composition tests remain. There are now seven boundary module tests by source inspection. Their execution is parent-owned; this source review does not import earlier gate success from another commit as evidence for this pin.
Reviewed `condensed/boundary.rs` SHA256 at the repaired pin: `c6b17ac1e16bc1d12fe3625d4633c83891f8c0bd7716c96c31295cae0f8588da`.
The initial finding above is retained as evidence and is **resolved at 3e21c1bc**. No further material source issue was identified. The runtime/quality and coverage limits stated above remain unchanged.
@@ -0,0 +1,36 @@
# Independent reference-adapter review
2026-09-25. Static review only; no engine/compiler/router/GUI execution or source edits. Parent owns JavaScript review and execution. Reviewed local experiment plan, Python supervisor, Rust importer diff and adjacent baseline/prototype observation contracts. Read the JavaScript output-check interface only to establish where importer preconditions are enforced.
**Verdict: no blocking source defect found for the frozen five-case diagnostic run, subject to consuming structurally passing outputs and the explicit post-import checks below.** This is readiness for experiment execution, not adapter fidelity, native traceability, or production acceptance. Compilation remains unverified here.
Reviewed SHA256:
- Plan `.local/connection-layout-reference-plan-20260925.md`: `41a420db7ee039fa424f60b61097bddc6eff787e2705004e1c88b19f8b97c371`.
- Supervisor `.local/run-elk-reference-20260925.py`: `114d2fd4dbc9a55cd57c4b5175989686dc413863fc2a62fa4a71278792937066`.
- Importer `crates/oa-core/examples/auto_layout_probe.rs`: `95666ce866111d0626e964483fc954276e258bd0ef61a4f96b9857a0ceab468e`.
- Rehashed frozen released geometry: `9a31a676f23dea2f41daec39497f569d68231fa0fd66e5a1a476be97d5d61ac2`, matching the parent's declared input.
## Concrete checks and result-consumption requirements
1. **Freeze pin improvement:** the supervisor asserts manifest and bundle pins and records all other bound hashes, including geometry, before/after. It does not assert the expected geometry SHA above. Add that single expected-input assertion before execution; otherwise a different preexisting geometry file can be consistently recorded without matching the intended frozen dataset. Current inspected bytes do match, so this is a narrow provenance improvement, not an observed wrong input. No generalized dependency framework is needed.
2. **Only import structurally passing output.** Rust `import_placement` rejects unsupported Scenario, schema mismatch, excess nodes/edges, missing/extra/duplicate IDs, nonfinite/nonpositive rectangles and translated nonfinite positions. It deliberately has no independent minimum-size/header/child-containment checks and no parent field in `PlacementPlan`; those gates are supplied by JavaScript inspection. Parent must require the per-case structural result and success receipt before extracting `{schema:1,nodes:[{id,position,size}]}`. Do not rank a transferred failed structural result as an ordinary candidate. Core validation is not a substitute for those geometric checks. Reprojected Rust bounds should also be compared with intended transferred bounds to detect core size normalization.
3. **Compare final physical ports, not only unchanged anchors.** Importer does not import ELK port positions; its final projected ports are therefore the right raw material for comparing owner/semantic-port identity and final intended tip to ELK's output. Unchanged authored fraction and unchanged ELK relative pixel offset can still diverge after compound resizing. Plan correctly declares this a transfer limitation requiring assessment. A candidate with such divergence is not evidence of faithful fixed-port optimization, even if both separate geometries validate.
4. **Use the matching baseline.** The existing `observe` result's `after` is released Layout without the new reset. The comparable baseline is `reset_only_control`, which clears effective routes after that Layout. The old prototype and importer clear using pre-operation eligibility, while the control's legacy helper uses post-Layout routed `!custom_suspended`. For the frozen unchanged-visibility cases this may agree, but verify actual eligible edge sets and resulting route records before attributing differences solely to placement. Do not relabel the old `after` metrics as reset-only.
5. **Conservation summary does not prove the route contract.** `conservation` masks routes and node geometry for broad semantic comparisons; `authored_routes_exact=false` is expected after reset. Independently compare complete per-edge records: entry/order, attachments, exact eligible reset, and retained `translated_routes` result. Roundtrip and conservation booleans are emitted observations, not asserted by the CLI; consuming analysis must inspect them. Exit0 alone does not pass them.
## Importer correctness within this scope
Eligibility is captured from the original projection before movement and requires actual owners equal projected endpoints with both visible. The importer clones the original, changes only selected-view node geometry, clears eligible pins/full labels, and applies the existing original-to-final `translated_routes` helper only to retained routes. Entries are updated in place; route order and endpoint attachments are preserved. Other views and semantic records remain untouched by construction.
The revised collapsed rule is appropriate: compare proposed size to the original compact projected size exactly, update position, preserve stored expanded size. Temporary unfolding is used only to recover core visual ancestry, including Allocation grouping; it is not returned or used to change visibility. Hidden nodes found under a visible collapsed ancestor receive that ancestor's translation, preserving their latent relative geometry. The ancestry walk is bounded and detects cycles/missing parents. This implementation does not establish a general policy for unrelated filtered descendants omitted even from the unfolded projection; the stated policy leaves unrelated omitted placements exact. Do not extrapolate beyond the frozen fixtures.
The importer cannot reverse semantic edges or accept replacement ports because neither exists in its plan schema. Its 256 stored-node /1024 visible-edge limits and Scenario refusal are explicit. Original-to-final positional deltas are calculated before retained-route translation; no intermediate Layout or independently validated Batch prefix is introduced. This remains an example-only geometry transfer, not a new production command or history implementation.
## Supervisor and attribution
The supervisor creates a fresh output directory and exclusive log files; each engine case has a separate subprocess with a90-second timeout. Python's `subprocess.run` timeout kills/waits for its child and the loop retains an explicit timeout/error receipt before proceeding. The reviewed contract uses the bundled engine in that Node process; this is not a general descendant-process supervisor. Before/after bindings cover Node, adapter script, geometry, manifest, bundle, dependency receipt, plan and supervisor. JavaScript receipts separately hash raw case/graph/mapping/result/observation files. Parent should retain both receipt layers.
The supervisor can exit0 even when individual cases fail or time out; its final record explicitly preserves those outcomes. Require every expected case receipt, no timeout, success plus structural checks before treating that case as transferable. Failed/unsupported cases stay visible in the comparison. Timing includes process work and is correctly disclaimed against earlier Rust route-only timings.
The plan correctly limits the claim to whether engine placement transfers usefully through released routing and label placement. Raw ELK routes/labels are diagnostic, never secretly turned into authored constraints. Estimated label extents, fixed-position port drift, prototype folded refusal and absent native trace acceptance remain real limitations. A failed placement transfer cannot reject ELK's complete coupled drawing architecture; a scalar gain cannot establish an engineer's correct trace or label association. No request is made for new fixtures, option sweeps, production dependencies or additional architecture before this bounded run.
@@ -0,0 +1,40 @@
'use strict';
const assert = require('node:assert/strict');
const fs = require('node:fs');
const crypto = require('node:crypto');
const {adapt, inspect} = require('./reference_probe.cjs');
const nodes = [
{id:'parent', element:'ep', visual_parent:null, collapsed:false, position:{x:100,y:200},size:{width:300,height:300}},
{id:'child', element:'ec', visual_parent:'parent', collapsed:false, position:{x:124,y:264},size:{width:100,height:80}},
{id:'sink', element:'es', visual_parent:null, collapsed:true, position:{x:600,y:264},size:{width:100,height:80}},
];
const ports = [
{owner:'child',element:'out',side:'Right',position:{x:224,y:304}},
{owner:'sink',element:'in',side:'Left',position:{x:600,y:304}},
];
const edges = ['one','two'].map(id=>({id,oriented:true,source:'child',target:'sink',source_port:'out',target_port:'in'}));
const row = {fixture:'synthetic',diagram:'d',before_project:{diagrams:{d:{nodes:Object.fromEntries(nodes.map(n=>[n.id,n]))}}},
before:{projected_nodes:nodes,projected_ports:ports,projected_edges:edges,
routed_edges:edges.map(e=>({id:e.id,label:{text:e.id,size:{width:30,height:24}}}))}};
const {graph,mapping} = adapt(row);
assert.equal(mapping.ports.length,2);
assert.equal(graph.edges[0].sources[0],graph.edges[1].sources[0]);
const result = inspect(graph,mapping);
assert.equal(result.structural_checks_passed,true);
assert.deepEqual(result.nodes.find(n=>n.id==='child').position,{x:124,y:264});
assert.deepEqual(result.ports.find(p=>p.owner==='child').position,{x:224,y:304});
const badSize=structuredClone(graph); badSize.children.find(n=>n.id==='sink').height+=1;
assert(inspect(badSize,mapping).checks.some(c=>c.name==='collapsed_compact_size_exact'&&!c.passed));
const underHeader=structuredClone(graph); underHeader.children[0].children[0].y=2;
assert(inspect(underHeader,mapping).checks.some(c=>c.name==='child_inside_parent_body'&&!c.passed));
const reversed=structuredClone(graph); reversed.edges[0].sources=['sink'];
assert(inspect(reversed,mapping).checks.some(c=>c.name==='edge_orientation_and_endpoints'&&!c.passed));
const missing=structuredClone(row); missing.before.projected_edges[0].source_port='unknown';
assert.throws(()=>adapt(missing),/Missing projected semantic port/);
const undirected=structuredClone(row); undirected.before.projected_edges[0].oriented=false;
assert.throws(()=>adapt(undirected),/Unsupported undirected/);
const duplicate=structuredClone(row); duplicate.before.projected_nodes.push(duplicate.before.projected_nodes[0]);
assert.throws(()=>adapt(duplicate),/duplicate/);
console.log(JSON.stringify({checks:'passed',scope:'synthetic adapter mapping only; engine not invoked',
checks_exercised:['nested coordinate roundtrip','shared physical port reuse','collapsed size rejection','header intrusion rejection','edge reversal rejection','missing semantic port rejection','undirected rejection','duplicate node rejection'],
adapter_sha256:crypto.createHash('sha256').update(fs.readFileSync(require.resolve('./reference_probe.cjs'))).digest('hex')}));
@@ -0,0 +1,56 @@
# Connection-aware arrangement: independent criteria and next experiment
Proposed engineering decision for issue32, 2026-09-25; not an accepted requirement, production adoption, or native acceptance. Read the frozen independent routing results, decision-review and adoption-rubric under `harness/experiments/routing-independent-20260925` at local auto-layout HEAD `ac5d7ef34a567f45b72adbd30d1a243f5811ba73`, and reset `crates/oa-core/src/session.rs::layout` at `956e33e568c6b799d0330803c74927e205977dfd`. No code, compiler, router or GUI execution in this review.
**Recommendation: run the bounded ELK placement-reference comparison on the existing frozen five-case corpus.** Compare current Arrange/reset placement, the existing native layered prototype, and ELK-derived node placement through the unchanged released Rust router and label placement. Freeze one ELK configuration and adapter policy before results. This tests a missing architectural choice; further J/L coefficient or lane tuning does not.
## What the existing evidence establishes
Current Layout orders roots from projected connections, but places roots in rows of four and stacks children vertically; it does not use routed conflicts or label extents when placing them. Reset removes eligible authored bends and labels, making a new route possible; it does not make those node positions connection/label-aware.
The routing-only results are useful counterexamples to a scalar objective. L reduces workshop shared length1042→602 while adding a label/node and label/stroke collision. Reversed mixed IDs change132→233.5999756 shared length; sparse reduces496→372 but proper crossings24→25. Timing screens pass, and these finite outcomes do not identify either J or L as a general improvement. The retained released router remains a controlled reference, not a quality oracle.
Raw contacts, shared intervals, crossings and collision identities locate potential reading problems. They do not measure whether an engineer follows the intended connection: a short ambiguous junction at a decision point can matter more than a long common-terminal stub; a correct readable crossing can be preferable to coincident edges. Pairwise shared length also counts pair multiplicity, not just unique occupied ink. Area, bends and length expose costs; none identifies correct label-to-edge association or legible arrowheads. Keep raw quantities and concrete identities, with no weighted quality-pass total.
## Architecture choices and falsifiers
| Choice | Useful hypothesis | Smallest decisive contrary observation |
| --- | --- | --- |
| Connection-aware placement, then released routing and label placement | Better sibling order, layering and space allocation remove the dominant conflicts without a new routing architecture. | A faithful placement improves graph order but the released routing/label stage repeatedly recreates the named ambiguity, with no trace-task gain. This falsifies sufficiency on these cases, not every possible placement algorithm. |
| Bounded placement/routing/label feedback | A small number of specific gap/order corrections can repair conflicts left by the first pass. | A repair relocates ambiguity to another required task, or repeated passes oscillate/expand the scene without eliminating the conflict. Do not start this loop before identifying a concrete failed constraint from the placement comparison. |
| Coupled external engine output, including routes and labels | Jointly reserving routes and label space achieves benefits lost by independent downstream routing. | Engine output cannot faithfully express fixed ports, compound geometry or projection semantics, or its apparent visual gain depends on violating them. A placement-only failure is insufficient evidence against this choice. |
ELK Layered is a relevant reference because it explicitly supports ports, labels and compound graphs; this is a capability basis for the experiment, not a predicted win. [ELK Layered](https://eclipse.dev/elk/reference/algorithms/org-eclipse-elk-layered.html)
## One bounded comparison
1. Keep the existing five input Projects and their manifest hashes. Reuse the declared source-to-target tasks, including companion fan-out branches and the fixed ID permutation. Do not add favorable graphs or change names/IDs after viewing outputs.
2. Use one common reset policy for all three placement arms: eligibility captured from the original staged projection; eligible stored pins/full label cleared; attachments retained. Apply existing `translated_routes` to retained routes using each arm's actual final deltas. Current Arrange/reset is the baseline. Comparing an unreset baseline with a reset candidate would confound placement with stale-constraint removal.
3. Freeze engine/version, options, seed, input ordering, dimensions and mapping policy. Run the existing native prototype unchanged. Its folded/omitted-node rejection is an unsupported result, not permission to unfold the Project or omit the case from the table. Report supported subsets explicitly.
4. Emit the final cloned Project plus real Rust projection/routes/labels for each supported arm, and the unchanged raw ELK input/output. Preserve ELK edge sections and label locations for diagnosis, but do not store them as authored Project bends/labels in the placement-only arm. Engine labels can influence spacing while the displayed Rust label lands elsewhere; such a result must not be described as successful label placement.
5. Audit adapter invariants before quality ranking. Reuse independent contact metrics; inspect every changed collision/warning identity. Run the same predeclared trace tasks against the actual rendered result for any candidate that advances beyond geometric screening. Keep all failures and unsupported outputs.
6. Stop after this fixed comparison and concrete adapter-defect repairs. No option sweep, per-case best seed or penalty tuning. A faithful ELK placement gain motivates a production-placement decision; a faithful failure localizes what placement alone did not solve. If raw ELK output looks better but Rust rerouting loses the benefit, the next bounded question is transferring or reproducing that coupling, not more node-order heuristics. Raw output is diagnostic evidence only until rendered and audited with equal constraints.
## Adapter gates that can invalidate the experiment
- **Placement identity and hierarchy:** map by placement identity, not semantic element alone; a view can place the same semantic entity more than once. Use actual projected visual parents and visible endpoints, while preserving the authored containment model. Convert every nested local coordinate to Project coordinates exactly once. ELK distinguishes child shapes and edge sections; preserve its raw coordinate representation for diagnosis. [ELK JSON format](https://eclipse.dev/elk/documentation/tooldevelopers/graphdatastructure/jsonformat.html)
- **Cross-container edges:** explicitly configure hierarchy handling. Root default behavior separates children, whereas `INCLUDE_CHILDREN` allows one run across hierarchy levels. A silently flattened graph or independently laid-out children would not test compound connection-aware arrangement. [ELK hierarchy handling](https://eclipse.dev/elk/reference/options/org-eclipse-elk-hierarchyHandling.html)
- **Port identity:** one adapter port per physical projected placement/semantic-port identity, reused by its multiple edges. Duplicating shared ports per edge makes fan-out artificially easy; merging distinct ports changes the problem. Keep direction and semantic edge identity through any engine cycle reversal; never write a reversed semantic source/target back to Project.
- **Port position and resizing:** preserve authored side/fraction and dormant explicit attachments. Compare final Rust physical port tips to the adapter's final intended tips, not to original global coordinates that should move with nodes. ELK exposes both `FIXED_RATIO` and `FIXED_POS`; select a verified policy rather than treating them as synonyms. Fixed original pixel offsets on a resized container can cease to match our fractional anchor. Either demonstrate final-size fraction mapping, or mark that case unsupported; post-correcting only Rust ports makes the optimizer's assumed geometry stale. [ELK port constraints](https://eclipse.dev/elk/reference/options/org-eclipse-elk-portConstraints.html)
- **Node sizes and occupied content:** preserve leaf minimum sizes, container body/header/footer clearances, port extents and State row geometry where applicable. Compound resizing is an explicit geometric output and must contain children below the header. ELK supports fixed size constraints, but these do not by themselves supply ARCADIA's content rules. [ELK node-size constraints](https://eclipse.dev/elk/reference/options/org-eclipse-elk-nodeSize-constraints.html)
- **Labels:** supply the same declared label text/dimension policy across arms; ELK generally expects label dimensions from its caller. Estimated rectangles remain screening evidence. Preserve full text and use actual rendered ink for readability acceptance. Do not promote ELK label coordinates into persisted authored overrides to improve this arm. [ELK JSON labels](https://eclipse.dev/elk/documentation/tooldevelopers/graphdatastructure/jsonformat.html)
- **Folded and dormant geometry:** use promoted visible endpoints for planning, but do not replace actual semantic ownership. Retain dormant route metadata with the existing translation rule, and preserve collapse/filter state. If moving/resizing a folded owner requires a hidden-placement transform the adapter cannot represent faithfully, report unsupported rather than shifting hidden geometry opportunistically. Other views remain exact.
An identity import/export without layout should recover the exact Project; only explicitly permitted geometry/reset fields may differ after layout. Validate Projects and independently audit output IDs, finite geometry, container containment, endpoints and anchors. Passing `validate_candidate` alone cannot prove geometric containment or adapter fidelity.
## Proposed acceptance gates
**Preservation is a hard gate.** Semantic elements, relations, endpoints, roles, authored ownership, aliases/text/source meaning, projection/filter/fold state, route entry order and attachment data remain exact. Only selected-view arrangement geometry and eligible pin/full-label reset change; ineligible pins follow existing translation rules, including the State boundary exception. Hidden placement handling follows the declared Layout scope, not a blanket “hidden never moves” rule. Missing actual owners, filtered edges and promoted endpoints are ineligible for reset. Other views stay exact. After integration, enclosing Batch remains one transaction; a valid pending property Apply is a separate history action; invalid drafts block Arrange; clean Undo/Redo and persistence/source round trips retain the established contract. This experiment does not change Process/Scenario path behavior.
**Geometric screening is identity-aware.** Reject new missing routes, nonfinite geometry, invalid containment, unrelated node/header penetrations, or new warnings absent a specifically documented unavoidable constraint. Retain existing failures per edge. Report actual new/resolved label collisions and contacts, not net totals. Do not require zero crossings on arbitrary graphs or count intended ancestor-body containment as an obstacle defect.
**Traceability is the adoption gate.** For every frozen named task, the reviewer must identify the correct source, target, direction and associated connection label in the rendered diagram. Include both branches of shared ports and both distinct ports; inspect actual arrowheads and text. Record wrong turns, ambiguity, selections, pan/zoom and completion time. Normal selection is allowed but record whether unselected geometry was ambiguous and whether selection repaired it. Use the same declared readable scale and viewport, plus Fit for overview; unlimited zoom must not hide unusable navigation cost. Require an observed improvement in a declared task or correction of a baseline failure, with no new failed/ambiguous task. If both layouts already complete every task cleanly, do not manufacture a correctness win from a smaller scalar score; report clarity/action costs descriptively.
**Robustness and cost are separate.** Same-input repeats must be deterministic; ten repeated Arrange applications should settle rather than ratchet size or oscillate. The fixed edge-ID permutation must preserve semantics and task correctness; pixel-identical output for every graph symmetry is not required. Timing must cover placement plus Rust routing/label work, and report engine startup/serialization separately and as end-to-end cost. Existing route-only timing ceilings do not establish acceptable total Arrange latency. Declare any new end-to-end budget before timing; disclose unsupported cases and censored runs.
This comparison can reject an architecture assumption or justify one next prototype. It cannot establish universal optimality, all-Unicode label measurement, arbitrary graph scalability, or native usability from screenshots/metrics alone. It requires no production dependency on JavaScript and makes no recommendation to ship ELK before the adapter and trace evidence exist.
@@ -0,0 +1,32 @@
{
"package": "elkjs",
"version": "0.12.0",
"registry_url": "https://registry.npmjs.org/elkjs/0.12.0",
"tarball_url": "https://registry.npmjs.org/elkjs/-/elkjs-0.12.0.tgz",
"integrity": "sha512-YZcKynxVxYoKIOEpywEPwCFdg+BTbxQRNf3pbwdDCvc8O3kQD8bmIwSxKU1eOTVc4Xo+VG9Te+575mlfvOrhEQ==",
"tarball_sha256": "c1d7719723e020b10724e3ccbc935696a2884f1e78d361cdd03766309e8e8e2a",
"license": "EPL-2.0 OR GPL-3.0-or-later",
"members": [
{
"path": "lib/elk.bundled.js",
"bytes": 1609707,
"sha256": "1222e44f953ce7746af23801e723708f8e6f436b8b377a6a5fc7552f34a307b3"
},
{
"path": "package.json",
"bytes": 1687,
"sha256": "4614a8db12f1c46a1d988d55e92012286c5a25dde3ea69be7695013f4d23cf02"
},
{
"path": "LICENSE.md",
"bytes": 14443,
"sha256": "637e81f4a1b6b4079535c499fca05e238cf7605c1ff5b76d60d7da7ce96700c9"
},
{
"path": "README.md",
"bytes": 15957,
"sha256": "a83dbb420b0b4f335f4b9d2ca65c335be3fdcf83248bff49f81582b9c0c30712"
}
],
"scope": "Isolated reference experiment; no install scripts or production dependency."
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,9 @@
# Final connection-layout claim review
Read-only review; no material blocker to archiving the bounded research report. Reviewed `.local/connection-layout-results-20260925.md` SHA256 `ddf74fe85c9764f6b5162562d0de5b51489b29ef1e765337ed59da01871bc669` against frozen transfer audit `18274aff78240bed1ccfbbe49491e49465156db6cca6a934149a699e603459e4`, geometry result review, and sparse diagnosis `44d30974fe30183459412e177acc3729afeb448cf69ea05097afe037ecc1d93d`.
The report accurately separates final fc5c22c9 observer from initial8a2010d8, released routing from experimental placement, and independent Arrange-reset work. Counts, preserved identity correction, automatic-port drift, explicit anchored-port fidelity and pure-transform limitations agree with my readback. The width1 failed negative expectation is preserved; the separate height1 rejection is accurately described. Final strict-Clippy receipt was inspected. This is not full-workspace, native readability, production adoption, or publication acceptance.
The clearance diagnosis appropriately supersedes the earlier narrower uninflated-corridor inference. The result does not claim that raw ELK is feasible under core18-unit leads/12-unit clearance, or that increasing search budgets would repair a rejected terminal pair. Necessary30-unit separation is explicitly not sufficient for an entire route. Both unsupported workshop cases remain coverage refusals, not engine failures or compound successes.
Two nonblocking wording improvements were sent to parent: say “10 of25 total port positions change, all automatic” because the denominator includes six authored ports; say “policy-aware conservation” rather than implying every raw conservation boolean is true (mixed authored_routes_exact is intentionally false after reset). No report edits by reviewer. The artifact is ready for bounded local archival with existing failed attempts and limitations retained; no reruns performed.
@@ -0,0 +1,7 @@
# Identity transfer correction after first run
The initial source8a2010d8 importer wrote every expanded node's projected size to its stored size. Independent audit of five identity plans showed unwanted source normalization:3 nodes in workshop original,2 in folded,10 in each mixed case and40 in sparse. Projection minimum sizing had made those effective rectangles larger than stored authored sizes. Positions, effective geometry, semantics, route policy and collapsed stored size were not implicated, but a no-layout identity transfer did not recover the original Project apart from eligible reset.
Correct the adapter by preserving stored size whenever the planned rectangle equals the original projected rectangle. Only an actual effective size change is imported. Keep collapsed compact-size equality mandatory and never replace its stored expanded size. After transfer, reproject and require the entire intended node ID/position/size map to match exactly, rejecting a requested rectangle that Rust would renormalize. Preserve first-run receipts and outputs; rerun the identical Rust control/import/negative cases against the corrected source without rerunning or tuning ELK.
This is an example-only adapter repair, not a production layout change. It cannot resolve the independently observed automatic-port drift:10/25 ports in each mixed case and48/110 in sparse differ materially from the engine's assumed endpoints, with7/32 side changes respectively. All six authored anchors in each mixed case remain exact. Geometry comparisons remain diagnostic until that mapping limitation is addressed.

Some files were not shown because too many files have changed in this diff Show More