Merge Process and Allocation group authoring into the prerelease candidate

This commit is contained in:
2026-09-24 01:05:22 -07:00
122 changed files with 17409 additions and 478 deletions
@@ -0,0 +1,768 @@
//! Deterministic native fixture and literal complete-Project Allocation oracle.
//! No desktop planner, inferred output delta, or GUI input is used by this oracle.
use oa_core::{source_project::*, *};
use std::{collections::BTreeMap, fs, io::Write, path::Path};
type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
const MODES: [&str; 6] = [
"unchanged",
"oa-assigned",
"sa-assigned",
"oa-arranged",
"sa-arranged",
"oa-performers",
];
fn id(alias: &str) -> Id {
if alias == "oa_assign" {
return Id::from_u128(0x292609230001);
}
Id::new_v5(
&Id::from_u128(0xc1192026_0923_5000_8000_000000002931),
alias.as_bytes(),
)
}
fn xy(x: f32, y: f32) -> Position {
Position { x, y }
}
fn named(scope: &str, suffix: &str) -> String {
format!("{scope}_{suffix}")
}
fn need(ok: bool, message: impl Into<String>) -> Result<()> {
if ok {
Ok(())
} else {
Err(message.into().into())
}
}
fn element(p: &mut Project, alias: &str, name: &str, kind: Kind, layer: Layer) {
p.elements.insert(
id(alias),
Element {
id: id(alias),
alias: alias.into(),
name: name.into(),
description: format!("Preserve engineering record {alias}."),
parent: None,
body: ElementBody::new(kind, layer).unwrap(),
},
);
}
fn diagram(p: &mut Project, alias: &str, name: &str, layer: Layer, projection: ProjectionKind) {
p.diagrams.insert(
id(alias),
Diagram {
id: id(alias),
alias: alias.into(),
name: name.into(),
description: "Native group fixture; preserve unrelated records exactly.".into(),
layer,
projection,
layout: None,
view_options: Default::default(),
nodes: Default::default(),
routes: vec![],
},
);
}
fn place(p: &mut Project, scope: &str, suffix: &str, position: Position) {
let alias = named(scope, &format!("node_{suffix}"));
p.diagrams.get_mut(&id(scope)).unwrap().nodes.insert(
id(&alias),
NodePlacement {
id: id(&alias),
alias,
element: id(&named(scope, suffix)),
position,
size: Size {
width: 224.,
height: 94.,
},
collapsed: false,
port_anchors: vec![],
},
);
}
fn relation(
p: &mut Project,
scope: &str,
suffix: &str,
kind: RelationKind,
from: &str,
to: &str,
) -> Result<()> {
let alias = named(scope, suffix);
let old = p.relate(
kind,
&format!("{scope} {suffix}"),
id(&named(scope, from)),
id(&named(scope, to)),
);
reidentify(p, old, id(&alias))?;
let r = p.relations.get_mut(&id(&alias)).unwrap();
r.alias = alias.clone();
r.description = format!("Reviewed identity and provenance {alias} must survive.");
// The actual constructor creates proper SA function contracts. Normalize
// every generated identity/alias; no random UUID can leak into the fixture.
let ports = match r.body {
RelationBody::FunctionalExchange {
source_port,
target_port,
..
} => Some([source_port, target_port]),
_ => None,
};
if let Some(ports) = ports {
for (old_port, suffix) in ports.into_iter().zip(["output", "input"]) {
let alias = format!("{alias}_{suffix}");
reidentify(p, old_port, id(&alias))?;
let port = p.elements.get_mut(&id(&alias)).unwrap();
port.alias = alias.clone();
port.name = format!("{scope} {suffix}");
port.description = format!("Preserved typed endpoint {alias}.");
}
}
Ok(())
}
fn route(scope: &str, pins: [Position; 2]) -> EdgeRoute {
EdgeRoute {
edge: id(&named(scope, "exchange")),
waypoints: pins.to_vec(),
source_attachment: None,
target_attachment: None,
label: Some(EdgeLabel {
fraction: 0.45,
offset: xy(7., -19.),
}),
}
}
fn fixture() -> Result<Project> {
let mut p = Project::empty();
p.id = id("project");
p.alias = "allocation_multiselect_native".into();
p.name = "Allocation group native".into();
p.description = "Four independent compact OA/SA assignment and Arrange trials; ordinary performers use fresh OA assign baseline.".into();
for (layer, prefix, work, performer) in [
(Layer::Oa, "oa", Kind::Activity, Kind::Entity),
(Layer::Sa, "sa", Kind::Function, Kind::SystemActor),
] {
for arrange in [false, true] {
let scope = format!("{prefix}_{}", if arrange { "arrange" } else { "assign" });
let number = match (prefix, arrange) {
("oa", false) => "01",
("sa", false) => "02",
("oa", true) => "03",
_ => "04",
};
diagram(
&mut p,
&scope,
&format!(
"{number} {} {}",
prefix.to_uppercase(),
if arrange { "arrange" } else { "assign" }
),
layer,
ProjectionKind::Allocation,
);
let records: Vec<_> = if arrange {
vec![
("p", "P - team", performer, xy(40., 40.)),
("u", "U - contained work", work, xy(80., 140.)),
("v", "V - independent work", work, xy(440., 140.)),
]
} else {
vec![
("a", "A - origin team", performer, xy(43., 47.)),
// Leave room for SA Y's automatic top port stub below
// B's 64-unit header plus 12-unit routing clearance.
("b", "B - other team", performer, xy(43., 337.)),
("c", "C - destination", performer, xy(500., 40.)),
("x", "X - receive", work, xy(80., 140.)),
("y", "Y - assess", work, xy(80., 440.)),
]
};
for (suffix, label, kind, pos) in records {
// SA admits one System for the entire project, not one per view.
let kind = if layer == Layer::Sa && !arrange && suffix == "a" {
Kind::System
} else {
kind
};
element(&mut p, &named(&scope, suffix), label, kind, layer);
place(&mut p, &scope, suffix, pos);
}
let (from, to, pins) = if arrange {
relation(&mut p, &scope, "pu", RelationKind::Allocation, "p", "u")?;
("u", "v", [xy(360., 180.), xy(360., 230.)])
} else {
p.diagrams
.get_mut(&id(&scope))
.unwrap()
.nodes
.get_mut(&id(&named(&scope, "node_c")))
.unwrap()
.size = Size {
width: 360.,
height: 600.,
};
relation(&mut p, &scope, "ax", RelationKind::Allocation, "a", "x")?;
relation(&mut p, &scope, "by", RelationKind::Allocation, "b", "y")?;
("x", "y", [xy(340., 260.), xy(340., 400.)])
};
relation(
&mut p,
&scope,
"exchange",
if layer == Layer::Oa {
RelationKind::Interaction
} else {
RelationKind::FunctionalExchange
},
from,
to,
)?;
p.diagrams
.get_mut(&id(&scope))
.unwrap()
.routes
.push(route(&scope, pins));
}
}
diagram(
&mut p,
"other_view",
"05 Other saved view - preserve",
Layer::Oa,
ProjectionKind::Overview,
);
for (suffix, pos) in [("x", xy(40., 60.)), ("y", xy(410., 180.))] {
let alias = named("other_view", suffix);
p.diagrams.get_mut(&id("other_view")).unwrap().nodes.insert(
id(&alias),
NodePlacement {
id: id(&alias),
alias,
element: id(&named("oa_assign", suffix)),
position: pos,
size: Size {
width: 224.,
height: 94.,
},
collapsed: false,
port_anchors: vec![],
},
);
}
validate_candidate(&p)?;
Ok(p)
}
fn node_mut<'a>(p: &'a mut Project, scope: &str, suffix: &str) -> &'a mut NodePlacement {
p.diagrams
.get_mut(&id(scope))
.unwrap()
.nodes
.get_mut(&id(&named(scope, &format!("node_{suffix}"))))
.unwrap()
}
fn expected(before: &Project, mode: &str) -> Result<Project> {
let mut p = before.clone();
match mode {
"unchanged" => {}
"oa-assigned" | "sa-assigned" => {
let scope = if mode == "oa-assigned" {
"oa_assign"
} else {
"sa_assign"
};
for (rel, work) in [("ax", "x"), ("by", "y")] {
p.relations.get_mut(&id(&named(scope, rel))).unwrap().body =
RelationBody::Allocation {
source: id(&named(scope, "c")),
target: id(&named(scope, work)),
};
}
node_mut(&mut p, scope, "x").position = xy(560., 140.);
node_mut(&mut p, scope, "y").position = xy(560., 440.);
p.diagrams.get_mut(&id(scope)).unwrap().routes[0].waypoints =
vec![xy(820., 260.), xy(820., 400.)];
}
"oa-arranged" | "sa-arranged" => {
let scope = if mode == "oa-arranged" {
"oa_arrange"
} else {
"sa_arrange"
};
for (suffix, pos) in [
("p", xy(40., 280.)),
("u", xy(80., 380.)),
("v", xy(440., 380.)),
] {
node_mut(&mut p, scope, suffix).position = pos;
}
p.diagrams.get_mut(&id(scope)).unwrap().routes[0].waypoints =
vec![xy(360., 420.), xy(360., 470.)];
}
"oa-performers" => {
for (suffix, pos) in [
("a", xy(120., 90.)),
("b", xy(120., 380.)),
("x", xy(157., 183.)),
("y", xy(157., 483.)),
] {
node_mut(&mut p, "oa_assign", suffix).position = pos;
}
p.diagrams.get_mut(&id("oa_assign")).unwrap().routes[0].waypoints =
vec![xy(417., 303.), xy(417., 443.)];
}
_ => return Err(format!("Unknown mode {mode}; expected {MODES:?}").into()),
}
Ok(p)
}
fn ancestor(view: &projection::ProjectedView, parent: Id, child: Id) -> bool {
let mut next = view
.nodes
.iter()
.find(|n| n.id == child)
.and_then(|n| n.visual_parent);
for _ in 0..view.nodes.len() {
let Some(current) = next else {
return false;
};
if current == parent {
return true;
}
next = view
.nodes
.iter()
.find(|n| n.id == current)
.and_then(|n| n.visual_parent);
}
false
}
/// Fixture-usability preflight only: never calculates expected mutations.
fn preflight(p: &Project) -> Result<serde_json::Value> {
validate_candidate(p)?;
let mut scenes = Vec::new();
for scope in ["oa_assign", "sa_assign", "oa_arrange", "sa_arrange"] {
let d = &p.diagrams[&id(scope)];
let view = projection::project_view(p, d.id);
need(
view.nodes.len() == d.nodes.len(),
format!("{scope}: hidden/missing fixture placement"),
)?;
for n in &view.nodes {
need(
n.position == d.nodes[&n.id].position,
format!("{scope}: unexpectedly shifted projected origin {}", n.id),
)?;
need(
n.position.x >= 0.
&& n.position.y >= 0.
&& n.position.x + n.size.width <= 900.
&& n.position.y + n.size.height <= 750.,
format!("{scope}: fixture exceeds declared compact world envelope: {n:?}"),
)?;
if p.elements[&n.element].kind().is_work() {
let height = if d.layer == Layer::Sa { 132. } else { 94. };
need(
n.size
== Size {
width: 224.,
height,
},
format!("{scope}: unexpected work/port extent {n:?}"),
)?;
}
}
for (i, a) in view.nodes.iter().enumerate() {
for b in &view.nodes[i + 1..] {
if ancestor(&view, a.id, b.id) || ancestor(&view, b.id, a.id) {
continue;
}
let width = (a.position.x + a.size.width).min(b.position.x + b.size.width)
- a.position.x.max(b.position.x);
let height = (a.position.y + a.size.height).min(b.position.y + b.size.height)
- a.position.y.max(b.position.y);
need(
width <= 0. || height <= 0.,
format!("{scope}: unrelated projected overlap {} {}", a.id, b.id),
)?;
}
}
let routed = routing::route_diagram(p, d, &view);
let edge = id(&named(scope, "exchange"));
let r = routed
.edges
.get(&edge)
.ok_or("Authored exchange route is missing")?;
need(
!r.custom_suspended && r.pins == d.routes[0].waypoints,
format!("{scope}: authored pins are not active"),
)?;
need(
r.warnings.is_empty(),
format!(
"{scope}: route warnings require fixture review: {:?}; ports={:?}; points={:?}; nodes={:?}",
r.warnings, view.ports, r.points, view.nodes
),
)?;
need(
view.ports.len() == if d.layer == Layer::Sa { 2 } else { 0 },
format!("{scope}: unexpected visible typed ports"),
)?;
scenes.push(serde_json::json!({"diagram":scope,"nodes":view.nodes,"ports":view.ports,"active_pins":r.pins,"route_warnings":r.warnings.iter().map(ToString::to_string).collect::<Vec<_>>()}));
}
Ok(serde_json::json!(scenes))
}
fn load(path: &Path) -> Result<Project> {
if path
.extension()
.is_some_and(|ext| ext.eq_ignore_ascii_case("arcadia"))
{
let disk = source_files::load_project(path, ProjectLimits::default())?;
need(
!disk
.diagnostics
.iter()
.any(|d| d.diagnostic.severity == validation::Severity::Error),
format!("Source discovery errors: {:?}", disk.diagnostics),
)?;
let compiled = compile_bundle(&disk.reachable_bundle(), ProjectLimits::default())
.map_err(|e| format!("Source compile: {e:?}"))?;
Ok(compiled.decoded.project)
} else {
Ok(persistence::load(path)?)
}
}
fn equal(expected: &Project, actual: &Project) -> Result<()> {
if expected == actual {
return Ok(());
}
let e = serde_json::to_value(expected)?;
let a = serde_json::to_value(actual)?;
let fields: Vec<_> = e
.as_object()
.unwrap()
.keys()
.filter(|key| e[*key] != a[*key])
.cloned()
.collect();
Err(format!("Whole Project mismatch in {fields:?}; no partial PASS").into())
}
fn roundtrip(p: &Project) -> Result<SourceBundle> {
validate_candidate(p)?;
equal(p, &persistence::decode(&serde_json::to_vec(p)?)?)?;
let mut bundle = format_bundle(p);
for (path, text) in &mut bundle.files {
*text = format!("// Native preservation sentinel: {path}\n{text}");
}
let parsed = compile_bundle(&bundle, ProjectLimits::default())
.map_err(|e| format!("Source roundtrip: {e:?}"))?;
equal(p, &parsed.decoded.project)?;
Ok(bundle)
}
fn write_new(path: &Path, bytes: &[u8]) -> Result<()> {
fs::create_dir_all(path.parent().ok_or("Missing parent directory")?)?;
fs::OpenOptions::new()
.write(true)
.create_new(true)
.open(path)?
.write_all(bytes)?;
Ok(())
}
fn generate(out: &Path) -> Result<()> {
need(!out.exists(), "Output exists; choose a fresh directory")?;
let p = fixture()?;
let bundle = roundtrip(&p)?;
let mut geometry = BTreeMap::new();
// All literal candidates must satisfy the same compact-fixture prerequisites
// before any bytes are published for a native run.
for mode in MODES {
let candidate = expected(&p, mode)?;
roundtrip(&candidate)?;
geometry.insert(mode, preflight(&candidate)?);
}
let mut ids = BTreeMap::from([(p.alias.clone(), p.id)]);
ids.extend(p.elements.values().map(|v| (v.alias.clone(), v.id)));
ids.extend(p.relations.values().map(|v| (v.alias.clone(), v.id)));
ids.extend(p.diagrams.values().map(|v| (v.alias.clone(), v.id)));
ids.extend(
p.diagrams
.values()
.flat_map(|d| d.nodes.values().map(|v| (v.alias.clone(), v.id))),
);
write_new(&out.join("baseline.json"), &serde_json::to_vec_pretty(&p)?)?;
for (path, text) in &bundle.files {
write_new(&out.join("source").join(path), text.as_bytes())?;
}
let paths: BTreeMap<_, _> = ids
.iter()
.map(|(alias, id)| {
let needle = id.to_string();
let files: Vec<_> = bundle
.files
.iter()
.filter(|(_, text)| text.contains(&needle))
.map(|(path, _)| path.clone())
.collect();
(alias.clone(), files)
})
.collect();
write_new(
&out.join("manifest.json"),
&serde_json::to_vec_pretty(&serde_json::json!({
"fixture":"allocation-multiselect-native-20260923","source_entry":bundle.entry,"ids":ids,
"source_files":bundle.files.keys().collect::<Vec<_>>(),"id_text_occurrence_files":paths,
"modes":MODES,"geometry_preflight":geometry,
"literal_deltas":{"oa-assigned":[480,0],"sa-assigned":[480,0],"oa-arranged":[0,240],"sa-arranged":[0,240],"oa-performers":[77,43]},
"selection":{"assigned":["X","Y"],"arranged":["P","V"],"ordinary_performers":["A","B"]},
"ordinary_anchor_target":[120,90],
"evaluation":"Literal complete Project oracle, never inferred from saved geometry; baseline pinned to deterministic original",
"limits":"Geometry preflight is not GUI/planner admission. Source comments/history bytes, focus, exact selection, pointer delivery and one Undo/Redo require separate observation/checks. Each mode is one operation from baseline."
}))?,
)?;
equal(&p, &load(&out.join("baseline.json"))?)?;
equal(&p, &load(&out.join("source").join(&bundle.entry))?)?;
println!("PASS generated and reloaded fixture: {}", out.display());
Ok(())
}
fn check(mode: &str, baseline: &Path, saved: &Path) -> Result<()> {
let before = load(baseline)?;
equal(&fixture()?, &before).map_err(|e| format!("Baseline is not immutable original: {e}"))?;
let actual = load(saved)?;
validate_candidate(&actual)?;
equal(&expected(&before, mode)?, &actual)?;
roundtrip(&actual)?;
println!(
"{}",
serde_json::to_string_pretty(&serde_json::json!({
"result":"PASS","mode":mode,"baseline":baseline,"saved":saved,
"comparison":"complete Project equality; literal fixed record edits",
"limits":"No pointer, selection, source-byte preservation, focus, Undo/Redo, or native acceptance claim"
}))?
);
Ok(())
}
fn main() -> Result<()> {
let args: Vec<_> = std::env::args().skip(1).collect();
match args.as_slice() {
[command,out] if command=="generate" => generate(Path::new(out)),
[command,mode,baseline,saved] if command=="check" => check(mode,Path::new(baseline),Path::new(saved)),
_=>Err(format!("Usage: allocation_multiselect_native generate DIRECTORY | check MODE BASELINE SAVED; modes={MODES:?}").into()),
}
}
#[cfg(test)]
mod tests {
use super::*;
fn through_json(mode: &str, before: &Project, actual: &Project) -> Result<()> {
validate_candidate(before).expect("baseline valid");
validate_candidate(actual).expect("falsifier must remain core-valid");
let dir = tempfile::tempdir()?;
let baseline = dir.path().join("baseline.json");
let saved = dir.path().join("saved.json");
persistence::save(&baseline, before)?;
persistence::save(&saved, actual)?;
check(mode, &baseline, &saved)
}
fn mismatch(mode: &str, before: &Project, actual: &Project) {
let error = through_json(mode, before, actual).unwrap_err().to_string();
assert!(
error.contains("Whole Project mismatch"),
"wrong failure cause: {error}"
);
}
#[test]
fn deterministic_create_only_generation_and_all_literal_modes() {
let p = fixture().unwrap();
assert_eq!(p, fixture().unwrap());
let dir = tempfile::tempdir().unwrap();
let out = dir.path().join("fixture");
generate(&out).unwrap();
let before = fs::read(out.join("baseline.json")).unwrap();
assert!(generate(&out).is_err());
assert_eq!(before, fs::read(out.join("baseline.json")).unwrap());
for mode in MODES {
let candidate = expected(&p, mode).unwrap();
preflight(&candidate).unwrap();
through_json(mode, &p, &candidate).unwrap();
if mode != "unchanged" {
mismatch(mode, &p, &p);
}
}
assert!(expected(&p, "invented").is_err());
let changed = expected(&p, "oa-assigned").unwrap();
assert!(
through_json("unchanged", &changed, &changed)
.unwrap_err()
.to_string()
.contains("immutable original")
);
}
#[test]
fn actual_core_assignment_batch_matches_literal_oracle_and_one_step_history() {
let before = fixture().unwrap();
for (mode, scope) in [("oa-assigned", "oa_assign"), ("sa-assigned", "sa_assign")] {
let mut commands = Vec::new();
for (alias, work) in [("ax", "x"), ("by", "y")] {
let relation = id(&named(scope, alias));
let mut value = before.relations[&relation].clone();
value.body = RelationBody::Allocation {
source: id(&named(scope, "c")),
target: id(&named(scope, work)),
};
commands.push(Command::EditRelation(relation, value));
}
for (suffix, position) in [("x", xy(560., 140.)), ("y", xy(560., 440.))] {
let original = id(&named(scope, &format!("node_{suffix}")));
let mut node = before.diagrams[&id(scope)].nodes[&original].clone();
node.position = position;
commands.push(Command::EditNode {
diagram: id(scope),
original,
node,
});
}
let mut route = before.diagrams[&id(scope)].routes[0].clone();
route.waypoints = vec![xy(820., 260.), xy(820., 400.)];
commands.push(Command::SetEdgeRoute {
diagram: id(scope),
route,
});
let mut session = Session::new(before.clone());
session.execute(Command::Batch(commands)).unwrap();
through_json(mode, &before, session.project()).unwrap();
let after = session.project().clone();
session.undo();
assert_eq!(session.project(), &before);
assert!(!session.can_undo());
session.redo();
assert_eq!(session.project(), &after);
assert!(!session.can_redo());
}
}
#[test]
fn assignment_rejects_singleton_semantic_identity_and_route_drift() {
let before = fixture().unwrap();
for mode in ["oa-assigned", "sa-assigned"] {
let scope = if mode == "oa-assigned" {
"oa_assign"
} else {
"sa_assign"
};
let good = expected(&before, mode).unwrap();
let mut bad = good.clone();
node_mut(&mut bad, scope, "y").position = xy(80., 440.);
mismatch(mode, &before, &bad);
let mut bad = good.clone();
bad.relations.insert(
id(&named(scope, "by")),
before.relations[&id(&named(scope, "by"))].clone(),
);
mismatch(mode, &before, &bad);
let mut bad = good.clone();
reidentify(
&mut bad,
id(&named(scope, "ax")),
id("replacement_allocation"),
)
.unwrap();
mismatch(mode, &before, &bad);
let mut bad = good.clone();
bad.relations
.get_mut(&id(&named(scope, "ax")))
.unwrap()
.description
.push_str(" drift");
mismatch(mode, &before, &bad);
for pins in [
vec![xy(340., 260.), xy(340., 400.)],
vec![xy(1300., 260.), xy(1300., 400.)],
] {
let mut bad = good.clone();
bad.diagrams.get_mut(&id(scope)).unwrap().routes[0].waypoints = pins;
mismatch(mode, &before, &bad);
}
}
}
#[test]
fn arrange_and_ordinary_closure_falsifiers_remain_core_valid() {
let before = fixture().unwrap();
for mode in ["oa-arranged", "sa-arranged"] {
let scope = if mode == "oa-arranged" {
"oa_arrange"
} else {
"sa_arrange"
};
let good = expected(&before, mode).unwrap();
for (suffix, pos) in [("u", xy(80., 140.)), ("v", xy(440., 140.))] {
let mut bad = good.clone();
node_mut(&mut bad, scope, suffix).position = pos;
mismatch(mode, &before, &bad);
}
let mut bad = good.clone();
bad.relations.remove(&id(&named(scope, "pu")));
mismatch(mode, &before, &bad);
}
let good = expected(&before, "oa-performers").unwrap();
let mut bad = good.clone();
node_mut(&mut bad, "oa_assign", "x").position = xy(80., 140.);
mismatch("oa-performers", &before, &bad);
let mut bad = good;
bad.relations.get_mut(&id("oa_assign_ax")).unwrap().body = RelationBody::Allocation {
source: id("oa_assign_c"),
target: id("oa_assign_x"),
};
mismatch("oa-performers", &before, &bad);
}
#[test]
fn whole_project_sentinels_and_real_saved_source_are_checked() {
let before = fixture().unwrap();
let dir = tempfile::tempdir().unwrap();
let baseline = dir.path().join("baseline.json");
persistence::save(&baseline, &before).unwrap();
for mode in MODES {
let good = expected(&before, mode).unwrap();
for corrupt in [false, true] {
let mut actual = good.clone();
if corrupt {
actual
.elements
.get_mut(&id("sa_arrange_v"))
.unwrap()
.description
.push_str(" drift");
}
let bundle = roundtrip(&actual).unwrap();
let folder = dir.path().join(format!("{mode}-{corrupt}"));
for (path, text) in &bundle.files {
write_new(&folder.join(path), text.as_bytes()).unwrap();
}
let result = check(mode, &baseline, &folder.join(&bundle.entry));
if corrupt {
assert!(
result
.unwrap_err()
.to_string()
.contains("Whole Project mismatch")
);
} else {
result.unwrap();
}
}
let mut bad = good;
bad.diagrams
.get_mut(&id("other_view"))
.unwrap()
.nodes
.get_mut(&id("other_view_x"))
.unwrap()
.position
.x += 10.;
mismatch(mode, &before, &bad);
}
let invalid = dir.path().join("invalid.arcadia");
write_new(&invalid, b"project {").unwrap();
assert!(load(&invalid).is_err());
}
}
@@ -0,0 +1,548 @@
//! Deterministic Process native fixture and independent complete-Project oracle.
//! Expected order is literal; no desktop reorder/layout planner is used here.
use oa_core::{source_project::*, *};
use std::{fs, io::Write, path::Path};
type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
fn id(alias: &str) -> Id {
Id::new_v5(
&Id::from_u128(0xc1192026_0923_5000_8000_000000002930),
alias.as_bytes(),
)
}
fn element(p: &mut Project, alias: &str, name: &str, kind: Kind, layer: Layer, parent: Option<Id>) {
p.elements.insert(
id(alias),
Element {
id: id(alias),
alias: alias.into(),
name: name.into(),
description: format!("Preserve engineering record {alias}."),
parent,
body: ElementBody::new(kind, layer).unwrap(),
},
);
}
fn occurrence(
p: &mut Project,
alias: &str,
name: &str,
layer: Layer,
owner: &str,
value: OccurrenceBody,
) {
element(p, alias, name, Kind::Occurrence, layer, Some(id(owner)));
let Some(SharedBody::Occurrence { body, .. }) =
p.elements.get_mut(&id(alias)).unwrap().shared_mut()
else {
panic!("Occurrence body");
};
*body = value;
}
fn set_steps(p: &mut Project, owner: &str, aliases: &[&str]) {
*p.elements.get_mut(&id(owner)).unwrap().steps_mut().unwrap() =
aliases.iter().map(|alias| id(alias)).collect();
}
fn sequence_link(
p: &mut Project,
alias: &str,
name: &str,
source: &str,
target: &str,
) -> Result<()> {
let old = p.relate(RelationKind::SequenceLink, name, id(source), id(target));
reidentify(p, old, id(alias))?;
let relation = p.relations.get_mut(&id(alias)).unwrap();
relation.alias = alias.into();
relation.description = format!("Preserve link {alias} exactly.");
if let RelationBody::SequenceLink { guard, .. } = &mut relation.body {
*guard = "ready".into();
}
Ok(())
}
fn fixture() -> Result<Project> {
let mut p = Project::empty();
p.id = id("project");
p.alias = "process_multiselect_native".into();
p.name = "Process group native".into();
p.description = "Select Card C then Shift-select Card A; drop after Card B. Other columns and reusable engineering records remain exact.".into();
for (layer, prefix, owner, name, owner_kind, work_kind) in [
(
Layer::Oa,
"oa",
"oa_process",
"01 OA group order",
Kind::Process,
Kind::Activity,
),
(
Layer::Sa,
"sa",
"sa_chain",
"SA chain - preserve",
Kind::FunctionalChain,
Kind::Function,
),
] {
element(&mut p, owner, name, owner_kind, layer, None);
let shared = format!("{prefix}_shared_work");
let downstream = format!("{prefix}_downstream_work");
element(
&mut p,
&shared,
"Shared request work",
work_kind,
layer,
None,
);
element(
&mut p,
&downstream,
"Downstream verification",
work_kind,
layer,
None,
);
for (suffix, label) in [
("a", "Card A"),
("u", "Downstream U"),
("b", "Card B"),
("v", "Downstream V"),
("c", "Card C"),
] {
let body = if suffix == "b" {
OccurrenceBody::Control {
kind: ControlKind::Choice,
}
} else {
OccurrenceBody::Action {
action: Some(id(if suffix == "a" || suffix == "c" {
&shared
} else {
&downstream
})),
lifeline: None,
}
};
occurrence(
&mut p,
&format!("{prefix}_{suffix}"),
label,
layer,
owner,
body,
);
}
let order: Vec<_> = ["a", "u", "b", "v", "c"]
.map(|suffix| format!("{prefix}_{suffix}"))
.into_iter()
.collect();
set_steps(
&mut p,
owner,
&order.iter().map(String::as_str).collect::<Vec<_>>(),
);
sequence_link(
&mut p,
&format!("{prefix}_a_u"),
"A to U",
&format!("{prefix}_a"),
&format!("{prefix}_u"),
)?;
sequence_link(
&mut p,
&format!("{prefix}_b_v"),
"B to V",
&format!("{prefix}_b"),
&format!("{prefix}_v"),
)?;
}
element(
&mut p,
"reusable",
"Reusable request detail",
Kind::Process,
Layer::Oa,
None,
);
occurrence(
&mut p,
"reused_leaf",
"Shared detail",
Layer::Oa,
"reusable",
OccurrenceBody::Action {
action: Some(id("oa_shared_work")),
lifeline: None,
},
);
set_steps(&mut p, "reusable", &["reused_leaf"]);
element(
&mut p,
"reuse_trial",
"02 Read-only preview trial",
Kind::Process,
Layer::Oa,
None,
);
for (alias, name) in [
("reference_one", "First use"),
("reference_two", "Second use"),
] {
occurrence(
&mut p,
alias,
name,
Layer::Oa,
"reuse_trial",
OccurrenceBody::SequenceReference {
sequence: Some(id("reusable")),
},
);
}
set_steps(&mut p, "reuse_trial", &["reference_one", "reference_two"]);
p.traces.insert(
id("trace"),
Trace {
id: id("trace"),
alias: "preserved_work_refinement".into(),
name: "Shared request refinement".into(),
description: "Process arrangement must preserve cross-layer correspondence.".into(),
oa: id("oa_shared_work"),
sa: id("sa_shared_work"),
kind: TraceKind::Refinement,
baseline: Some(TraceBaseline {
oa: p.properties(id("oa_shared_work")).unwrap(),
sa: p.properties(id("sa_shared_work")).unwrap(),
semantic: None,
}),
},
);
validate_candidate(&p)?;
Ok(p)
}
fn load(path: &Path) -> Result<Project> {
if path
.extension()
.is_some_and(|ext| ext.eq_ignore_ascii_case("arcadia"))
{
let disk = source_files::load_project(path, ProjectLimits::default())?;
if disk
.diagnostics
.iter()
.any(|d| d.diagnostic.severity == validation::Severity::Error)
{
return Err(format!("Source discovery diagnostics: {:?}", disk.diagnostics).into());
}
let compiled = compile_bundle(&disk.reachable_bundle(), ProjectLimits::default())
.map_err(|e| format!("Source compile diagnostics: {e:?}"))?;
Ok(compiled.decoded.project)
} else {
Ok(persistence::load(path)?)
}
}
fn equal(expected: &Project, actual: &Project) -> Result<()> {
if expected == actual {
return Ok(());
}
let expected = serde_json::to_value(expected)?;
let actual = serde_json::to_value(actual)?;
let fields: Vec<_> = expected
.as_object()
.unwrap()
.keys()
.filter(|key| expected[*key] != actual[*key])
.cloned()
.collect();
Err(format!("Whole Project mismatch in {fields:?}; no partial PASS").into())
}
fn roundtrip(p: &Project) -> Result<SourceBundle> {
validate_candidate(p)?;
equal(p, &persistence::decode(&serde_json::to_vec(p)?)?)?;
let bundle = format_bundle(p);
let compiled = compile_bundle(&bundle, ProjectLimits::default())
.map_err(|e| format!("Roundtrip: {e:?}"))?;
equal(p, &compiled.decoded.project)?;
Ok(bundle)
}
fn write_new(path: &Path, bytes: &[u8]) -> Result<()> {
fs::create_dir_all(path.parent().ok_or("Missing parent directory")?)?;
fs::OpenOptions::new()
.write(true)
.create_new(true)
.open(path)?
.write_all(bytes)?;
Ok(())
}
fn generate(out: &Path) -> Result<()> {
if out.exists() {
return Err("Output directory already exists; choose a fresh path".into());
}
let p = fixture()?;
let bundle = roundtrip(&p)?;
write_new(&out.join("baseline.json"), &serde_json::to_vec_pretty(&p)?)?;
for (path, source) in &bundle.files {
write_new(&out.join("source").join(path), source.as_bytes())?;
}
let ids: std::collections::BTreeMap<_, _> = [
"project",
"oa_process",
"oa_a",
"oa_u",
"oa_b",
"oa_v",
"oa_c",
"oa_a_u",
"oa_b_v",
"oa_shared_work",
"oa_downstream_work",
"sa_chain",
"sa_shared_work",
"sa_downstream_work",
"sa_a",
"sa_u",
"sa_b",
"sa_v",
"sa_c",
"sa_a_u",
"sa_b_v",
"trace",
"reusable",
"reused_leaf",
"reuse_trial",
"reference_one",
"reference_two",
]
.into_iter()
.map(|name| (name, id(name)))
.collect();
write_new(
&out.join("manifest.json"),
&serde_json::to_vec_pretty(&serde_json::json!({
"fixture": "process-multiselect-native-20260923", "ids": ids, "source_entry": bundle.entry,
"original_oa_steps": ["oa_a", "oa_u", "oa_b", "oa_v", "oa_c"],
"reordered_oa_steps": ["oa_b", "oa_u", "oa_a", "oa_v", "oa_c"],
"gesture": "Select Card C then add Card A; Inspect Card C without changing membership, then drag Card C to After Card B",
"fixed_slots": {"1": "oa_u", "3": "oa_v"},
"read_only_trial": "02 Read-only preview trial has two qualified tiles of reused_leaf through distinct references",
"receipt": "Pin baseline.json, manifest.json and generation command externally before interaction. Never replace baseline with a later saved snapshot.",
"evaluation": "Literal expected OA steps and complete Project equality; all SA/reuse/trace records exact",
"limits": "No pointer, focus, selection-paint, history or source-byte acceptance claim",
}))?,
)?;
equal(&p, &load(&out.join("baseline.json"))?)?;
equal(&p, &load(&out.join("source").join(&bundle.entry))?)?;
println!("PASS generated JSON and source bundle: {}", out.display());
Ok(())
}
fn check(mode: &str, baseline: &Path, saved: &Path) -> Result<()> {
let before = load(baseline)?;
let actual = load(saved)?;
validate_candidate(&before)?;
validate_candidate(&actual)?;
// Pin the exact deterministic original, not merely a reusable project UUID.
equal(&fixture()?, &before)
.map_err(|e| format!("Baseline is not the immutable original: {e}"))?;
let mut expected = before;
match mode {
"unchanged" => {}
"reordered" => set_steps(
&mut expected,
"oa_process",
&["oa_b", "oa_u", "oa_a", "oa_v", "oa_c"],
),
_ => return Err("Mode must be reordered or unchanged".into()),
}
equal(&expected, &actual)?;
roundtrip(&actual)?;
println!(
"{}",
serde_json::to_string_pretty(&serde_json::json!({
"result": "PASS", "mode": mode, "baseline": baseline, "saved": saved,
"comparison": "complete Project equality; literal expected order",
"limits": "No pointer/action/focus/Undo/source-byte acceptance claim",
}))?
);
Ok(())
}
fn main() -> Result<()> {
let args: Vec<_> = std::env::args().skip(1).collect();
match args.as_slice() {
[command, out] if command == "generate" => generate(Path::new(out)),
[command, mode, baseline, saved] if command == "check" => check(mode, Path::new(baseline), Path::new(saved)),
_ => Err("Usage: process_multiselect_native generate DIRECTORY | check reordered|unchanged BASELINE SAVED".into()),
}
}
#[cfg(test)]
mod tests {
use super::*;
fn reordered(before: &Project) -> Project {
let mut session = Session::new(before.clone());
session
.execute(Command::SetSteps(
id("oa_process"),
["oa_b", "oa_u", "oa_a", "oa_v", "oa_c"].map(id).to_vec(),
))
.unwrap();
session.project().clone()
}
fn through_json(mode: &str, baseline: &Project, actual: &Project) -> Result<()> {
validate_candidate(baseline).expect("Baseline must be valid");
validate_candidate(actual).expect("Corruption must remain semantically valid");
let dir = tempfile::tempdir()?;
let before = dir.path().join("before.json");
let saved = dir.path().join("saved.json");
persistence::save(&before, baseline)?;
persistence::save(&saved, actual)?;
check(mode, &before, &saved)
}
fn mismatch(before: &Project, actual: &Project) {
let error = through_json("reordered", before, actual)
.unwrap_err()
.to_string();
assert!(
error.contains("Whole Project mismatch"),
"Unexpected rejection: {error}"
);
}
#[test]
fn generate_is_deterministic_create_only_and_checks_real_source() {
equal(&fixture().unwrap(), &fixture().unwrap()).unwrap();
let dir = tempfile::tempdir().unwrap();
let out = dir.path().join("fixture");
generate(&out).unwrap();
let baseline = out.join("baseline.json");
let bytes = fs::read(&baseline).unwrap();
check("unchanged", &baseline, &out.join("source/project.arcadia")).unwrap();
assert!(generate(&out).is_err());
assert_eq!(fs::read(&baseline).unwrap(), bytes);
let invalid = dir.path().join("invalid.arcadia");
fs::write(&invalid, "project {").unwrap();
assert!(load(&invalid).is_err());
}
#[test]
fn real_set_steps_passes_but_unchanged_and_mutated_baseline_do_not() {
let before = fixture().unwrap();
let actual = reordered(&before);
through_json("reordered", &before, &actual).unwrap();
through_json("unchanged", &before, &before).unwrap();
mismatch(&before, &before);
assert!(through_json("unchanged", &before, &actual).is_err());
let error = through_json("unchanged", &actual, &actual)
.unwrap_err()
.to_string();
assert!(error.contains("immutable original"), "{error}");
}
#[test]
fn global_splice_reversed_block_and_singleton_c_results_are_rejected() {
let before = fixture().unwrap();
// Native primary/pressed C is already after B, so a singleton C move
// is unchanged. Group {C,A} instead puts B before both selected cards.
// If A were primary, its singleton result could equal the group result.
mismatch(&before, &before);
for order in [
["oa_u", "oa_b", "oa_a", "oa_c", "oa_v"],
["oa_b", "oa_u", "oa_c", "oa_v", "oa_a"],
["oa_a", "oa_u", "oa_b", "oa_c", "oa_v"],
] {
let mut actual = before.clone();
set_steps(&mut actual, "oa_process", &order);
mismatch(&before, &actual);
}
}
#[test]
fn semantic_link_work_sa_reuse_and_trace_changes_are_rejected() {
let before = fixture().unwrap();
let correct = reordered(&before);
let mutations: [fn(&mut Project); 7] = [
|p| {
if let RelationBody::SequenceLink { guard, .. } =
&mut p.relations.get_mut(&id("oa_a_u")).unwrap().body
{
*guard = "changed".into();
}
},
|p| {
if let RelationBody::SequenceLink { target, .. } =
&mut p.relations.get_mut(&id("oa_a_u")).unwrap().body
{
*target = id("oa_v");
}
},
|p| {
p.elements
.get_mut(&id("oa_shared_work"))
.unwrap()
.name
.push_str(" drift");
},
|p| {
p.elements
.get_mut(&id("sa_shared_work"))
.unwrap()
.description
.push_str(" drift");
},
|p| {
set_steps(p, "sa_chain", &["sa_b", "sa_u", "sa_a", "sa_v", "sa_c"]);
},
|p| {
p.elements
.get_mut(&id("reused_leaf"))
.unwrap()
.description
.push_str(" drift");
},
|p| {
p.traces.get_mut(&id("trace")).unwrap().baseline = None;
},
];
for mutate in mutations {
let mut bad = correct.clone();
mutate(&mut bad);
mismatch(&before, &bad);
}
}
#[test]
fn changed_source_roundtrip_preserves_both_layers_and_checker_rejects_source_drift() {
let before = fixture().unwrap();
let actual = reordered(&before);
let dir = tempfile::tempdir().unwrap();
let baseline = dir.path().join("baseline.json");
persistence::save(&baseline, &before).unwrap();
for corrupt in [false, true] {
let mut saved = actual.clone();
if corrupt {
saved
.elements
.get_mut(&id("sa_c"))
.unwrap()
.name
.push_str(" drift");
}
let bundle = roundtrip(&saved).unwrap();
let folder = dir.path().join(if corrupt { "corrupt" } else { "saved" });
for (path, text) in &bundle.files {
write_new(&folder.join(path), text.as_bytes()).unwrap();
}
let parsed = load(&folder.join(&bundle.entry)).unwrap();
assert_eq!(parsed.elements[&id("oa_process")].kind(), Kind::Process);
assert_eq!(
parsed.elements[&id("sa_chain")].kind(),
Kind::FunctionalChain
);
let result = check("reordered", &baseline, &folder.join(&bundle.entry));
if corrupt {
assert!(
result
.unwrap_err()
.to_string()
.contains("Whole Project mismatch")
);
} else {
result.unwrap();
}
}
}
}