Rust checks / check (ubuntu-latest) (push) Canceled after 0s
Rust checks / check (windows-latest) (push) Canceled after 0s
Preserve current core, source, REST, desktop, recovery, documentation and validation work, including AGENTS.md and the development summary. This is a work-in-progress checkpoint: the new compact palette has outstanding compilation errors and native acceptance; earlier passing gates retain their documented scope.
515 lines
16 KiB
Rust
515 lines
16 KiB
Rust
//! Independent native basic-editing fixture and complete source7 oracle.
|
|
//! generate DIR | check baseline|entity|basic|transition ENTRY
|
|
use oa_core::{source_project::*, transitions::*, *};
|
|
use std::{
|
|
collections::{BTreeMap, BTreeSet},
|
|
fs,
|
|
path::Path,
|
|
};
|
|
|
|
fn id(n: u128) -> Id {
|
|
Id::from_u128(0xb451c000000040008000000000000000 + n)
|
|
}
|
|
fn seed(p: &mut Project, n: u128, kind: Kind, layer: Layer, name: &str, parent: Option<Id>) -> Id {
|
|
let mut e = p.make_element(kind, layer, name, parent).unwrap();
|
|
e.id = id(n);
|
|
e.alias = format!("basic_{n}");
|
|
p.elements.insert(e.id, e);
|
|
id(n)
|
|
}
|
|
fn fixture() -> Project {
|
|
let mut p = Project::empty();
|
|
p.id = id(1);
|
|
p.alias = "basic_editing".into();
|
|
p.name = "Basic rescue architecture".into();
|
|
let machine = seed(
|
|
&mut p,
|
|
20,
|
|
Kind::StateMachine,
|
|
Layer::Sa,
|
|
"Decoy operating modes",
|
|
None,
|
|
);
|
|
let region = seed(&mut p, 22, Kind::Region, Layer::Sa, "Main", Some(machine));
|
|
seed(&mut p, 21, Kind::State, Layer::Sa, "Standby", Some(region));
|
|
seed(&mut p, 30, Kind::Activity, Layer::Oa, "Report status", None);
|
|
seed(
|
|
&mut p,
|
|
31,
|
|
Kind::Capability,
|
|
Layer::Oa,
|
|
"Rescue capability",
|
|
None,
|
|
);
|
|
seed(
|
|
&mut p,
|
|
32,
|
|
Kind::Function,
|
|
Layer::Sa,
|
|
"Unrelated function",
|
|
None,
|
|
);
|
|
for (n, layer, projection, name) in [
|
|
(
|
|
2,
|
|
Layer::Sa,
|
|
ProjectionKind::StateMachine { machine },
|
|
"A · Operating modes (not an architecture destination)",
|
|
),
|
|
(
|
|
3,
|
|
Layer::Sa,
|
|
ProjectionKind::Capabilities,
|
|
"B · Capabilities (not an allocation view)",
|
|
),
|
|
(
|
|
4,
|
|
Layer::Oa,
|
|
ProjectionKind::Allocation,
|
|
"Operational rescue architecture",
|
|
),
|
|
(
|
|
5,
|
|
Layer::Sa,
|
|
ProjectionKind::Allocation,
|
|
"System rescue architecture",
|
|
),
|
|
] {
|
|
p.diagrams.insert(
|
|
id(n),
|
|
Diagram {
|
|
id: id(n),
|
|
alias: format!("basic_view_{n}"),
|
|
name: name.into(),
|
|
description: String::new(),
|
|
layer,
|
|
projection,
|
|
routes: vec![],
|
|
view_options: Default::default(),
|
|
nodes: BTreeMap::new(),
|
|
},
|
|
);
|
|
}
|
|
execute_batch(&p, &[]).unwrap()
|
|
}
|
|
fn bundle(p: &Project) -> CompiledSourceProject {
|
|
let source = language::format(p).replacen("{\n", "{\n include \"rationale.arcadia\";\n", 1);
|
|
let bundle = SourceBundle {
|
|
entry: "project.arcadia".into(),
|
|
files: BTreeMap::from([
|
|
("project.arcadia".into(), source),
|
|
(
|
|
"rationale.arcadia".into(),
|
|
"// Preserve this included engineering note: rescue 雨.\r\n".into(),
|
|
),
|
|
]),
|
|
};
|
|
let result = compile_bundle(&bundle, ProjectLimits::default()).unwrap();
|
|
assert_eq!(result.bundle, bundle);
|
|
assert_eq!(&result.decoded.project, p);
|
|
result
|
|
}
|
|
fn load(entry: &Path) -> CompiledSourceProject {
|
|
let files = source_files::load_project(entry, ProjectLimits::default()).unwrap();
|
|
let raw = files.reachable_bundle();
|
|
let result = compile_bundle(&raw, ProjectLimits::default()).unwrap();
|
|
assert_eq!(
|
|
result.bundle, raw,
|
|
"No source migration or normalization is permitted"
|
|
);
|
|
execute_batch(&result.decoded.project, &[]).unwrap();
|
|
result
|
|
}
|
|
fn advance(base: &mut CompiledSourceProject, commands: &[Command]) {
|
|
let expected = execute_batch(&base.decoded.project, commands).unwrap();
|
|
*base = update_bundle(base, &expected, ProjectLimits::default()).unwrap();
|
|
}
|
|
struct Roles<'a> {
|
|
actual: &'a Project,
|
|
seed: Project,
|
|
used: BTreeSet<Id>,
|
|
}
|
|
impl Roles<'_> {
|
|
fn fresh(&mut self, candidates: Vec<Id>, role: &str) -> Id {
|
|
assert_eq!(candidates.len(), 1, "Exactly one identity for {role}");
|
|
let key = candidates[0];
|
|
assert_eq!(
|
|
key.get_version_num(),
|
|
4,
|
|
"{role} must be a native fresh UUIDv4"
|
|
);
|
|
assert!(
|
|
!self.seed.contains(key),
|
|
"{role} cannot reuse a seed identity"
|
|
);
|
|
assert!(
|
|
self.used.insert(key),
|
|
"Fresh roles must be distinct: {role}"
|
|
);
|
|
key
|
|
}
|
|
fn element(&mut self, kind: Kind, layer: Layer, name: &str) -> Id {
|
|
self.fresh(
|
|
self.actual
|
|
.elements
|
|
.values()
|
|
.filter(|e| e.kind() == kind && e.layer() == layer && e.name == name)
|
|
.map(|e| e.id)
|
|
.collect(),
|
|
name,
|
|
)
|
|
}
|
|
fn node(&mut self, diagram: Id, element: Id) -> Id {
|
|
self.fresh(
|
|
self.actual.diagrams[&diagram]
|
|
.nodes
|
|
.values()
|
|
.filter(|n| n.element == element)
|
|
.map(|n| n.id)
|
|
.collect(),
|
|
"exact diagram/element placement",
|
|
)
|
|
}
|
|
fn relation(&mut self, kind: RelationKind, source: Id, target: Id) -> Id {
|
|
self.fresh(
|
|
self.actual
|
|
.relations
|
|
.values()
|
|
.filter(|r| r.kind() == kind && r.body.endpoints() == (source, target))
|
|
.map(|r| r.id)
|
|
.collect(),
|
|
"exact typed relationship endpoints",
|
|
)
|
|
}
|
|
}
|
|
fn place(p: &Project, diagram: Id, element: Id, key: Id, x: f32, y: f32) -> Command {
|
|
Command::CreateNode {
|
|
diagram,
|
|
node: NodePlacement {
|
|
id: key,
|
|
alias: p.fresh_alias(&format!("view_{}", p.named(element)), "node", key),
|
|
element,
|
|
position: Position { x, y },
|
|
size: Size {
|
|
width: 224.,
|
|
height: 94.,
|
|
},
|
|
collapsed: false,
|
|
port_anchors: vec![],
|
|
},
|
|
}
|
|
}
|
|
fn create(
|
|
base: &mut CompiledSourceProject,
|
|
roles: &mut Roles<'_>,
|
|
kind: Kind,
|
|
name: &str,
|
|
x: f32,
|
|
y: f32,
|
|
) -> Id {
|
|
let final_name = if name == "Assess incident" {
|
|
"Assess rescue incident"
|
|
} else {
|
|
name
|
|
};
|
|
let key = roles.element(kind, Layer::Oa, final_name);
|
|
let node = roles.node(id(4), key);
|
|
let mut element = base
|
|
.decoded
|
|
.project
|
|
.make_element(kind, Layer::Oa, name, None)
|
|
.unwrap();
|
|
element.id = key;
|
|
let mut scratch = base.decoded.project.clone();
|
|
scratch.elements.insert(key, element.clone());
|
|
advance(
|
|
base,
|
|
&[
|
|
Command::CreateElement(element),
|
|
place(&scratch, id(4), key, node, x, y),
|
|
],
|
|
);
|
|
key
|
|
}
|
|
fn relation(
|
|
base: &mut CompiledSourceProject,
|
|
roles: &mut Roles<'_>,
|
|
kind: RelationKind,
|
|
name: &str,
|
|
source: Id,
|
|
target: Id,
|
|
) {
|
|
let key = roles.relation(kind, source, target);
|
|
let p = &base.decoded.project;
|
|
let record = Relation {
|
|
id: key,
|
|
alias: p.fresh_alias(name, "relation", key),
|
|
name: name.into(),
|
|
description: String::new(),
|
|
body: RelationBody::new(kind, source, target),
|
|
};
|
|
advance(base, &[Command::CreateRelation(record)]);
|
|
}
|
|
fn expected(mode: &str, actual: &Project) -> CompiledSourceProject {
|
|
let initial = fixture();
|
|
let mut result = bundle(&initial);
|
|
if mode == "baseline" {
|
|
return result;
|
|
}
|
|
let mut roles = Roles {
|
|
actual,
|
|
seed: initial,
|
|
used: BTreeSet::new(),
|
|
};
|
|
let entity = create(
|
|
&mut result,
|
|
&mut roles,
|
|
Kind::Entity,
|
|
"Rescue team",
|
|
120.,
|
|
120.,
|
|
);
|
|
if mode == "entity" {
|
|
return result;
|
|
}
|
|
create(
|
|
&mut result,
|
|
&mut roles,
|
|
Kind::Actor,
|
|
"Control centre",
|
|
680.,
|
|
120.,
|
|
);
|
|
let activity = create(
|
|
&mut result,
|
|
&mut roles,
|
|
Kind::Activity,
|
|
"Assess incident",
|
|
160.,
|
|
300.,
|
|
);
|
|
let node = roles.node(id(4), id(30));
|
|
let command = place(&result.decoded.project, id(4), id(30), node, 680., 300.);
|
|
advance(&mut result, &[command]);
|
|
relation(
|
|
&mut result,
|
|
&mut roles,
|
|
RelationKind::Allocation,
|
|
"Performs",
|
|
entity,
|
|
activity,
|
|
);
|
|
relation(
|
|
&mut result,
|
|
&mut roles,
|
|
RelationKind::Interaction,
|
|
"Interaction: Assess incident → Report status",
|
|
activity,
|
|
id(30),
|
|
);
|
|
// Rename retains the creation-time alias; do this after drawing Interaction.
|
|
let mut renamed = result.decoded.project.elements[&activity].clone();
|
|
renamed.name = "Assess rescue incident".into();
|
|
advance(&mut result, &[Command::EditElement(activity, renamed)]);
|
|
if mode == "basic" {
|
|
return result;
|
|
}
|
|
assert_eq!(mode, "transition");
|
|
let p = &result.decoded.project;
|
|
let mut request = TransitionRequest::new(TransitionDirection::OaToSa, vec![entity]);
|
|
request.scope = DependencyScope::SelectedOnly;
|
|
request.destination_diagram = Some(id(5));
|
|
request.mappings.push(TransitionMapping {
|
|
source: entity,
|
|
target: None,
|
|
target_kind: Some(Kind::SystemActor),
|
|
});
|
|
let plan = plan_transition(p, &request).unwrap();
|
|
assert!(plan.conflicts.is_empty(), "{:?}", plan.conflicts);
|
|
let mut target = apply_transition(p, &plan).unwrap();
|
|
let generated = plan
|
|
.changes
|
|
.iter()
|
|
.find(|c| c.source == entity)
|
|
.unwrap()
|
|
.target;
|
|
let counterpart = roles.element(Kind::SystemActor, Layer::Sa, "Rescue team");
|
|
reidentify(&mut target, generated, counterpart).unwrap();
|
|
let trace = roles.fresh(
|
|
actual
|
|
.traces
|
|
.values()
|
|
.filter(|t| t.oa == entity && t.sa == counterpart)
|
|
.map(|t| t.id)
|
|
.collect(),
|
|
"counterpart trace",
|
|
);
|
|
let generated_trace = target
|
|
.traces
|
|
.values()
|
|
.find(|t| t.oa == entity && t.sa == counterpart)
|
|
.unwrap()
|
|
.id;
|
|
reidentify(&mut target, generated_trace, trace).unwrap();
|
|
let placement = roles.node(id(5), counterpart);
|
|
let generated_node = target.diagrams[&id(5)]
|
|
.nodes
|
|
.values()
|
|
.find(|n| n.element == counterpart)
|
|
.unwrap()
|
|
.id;
|
|
// The OA placement already owns view_rescue_team. Its SA counterpart's
|
|
// collision suffix is derived from the new placement UUID, never adopted
|
|
// from the observed alias or left at the planner's temporary UUID.
|
|
target
|
|
.diagrams
|
|
.get_mut(&id(5))
|
|
.unwrap()
|
|
.nodes
|
|
.get_mut(&generated_node)
|
|
.unwrap()
|
|
.alias = p.fresh_alias("view_Rescue team", "node", placement);
|
|
reidentify(&mut target, generated_node, placement).unwrap();
|
|
assert_eq!(
|
|
target.diagrams[&id(2)],
|
|
p.diagrams[&id(2)],
|
|
"State-machine decoy must remain untouched"
|
|
);
|
|
assert_eq!(
|
|
target.diagrams[&id(3)],
|
|
p.diagrams[&id(3)],
|
|
"Capability decoy must remain untouched"
|
|
);
|
|
result = update_bundle(&result, &target, ProjectLimits::default()).unwrap();
|
|
result
|
|
}
|
|
fn self_check() {
|
|
// Role carrier supplies identities only. Deliberately wrong geometry must
|
|
// never become the expected placement: the oracle predicts literal values.
|
|
let mut carrier = fixture();
|
|
let mut keys = vec![];
|
|
for (kind, name) in [
|
|
(Kind::Entity, "Rescue team"),
|
|
(Kind::Actor, "Control centre"),
|
|
(Kind::Activity, "Assess rescue incident"),
|
|
] {
|
|
let e = carrier.make_element(kind, Layer::Oa, name, None).unwrap();
|
|
keys.push(e.id);
|
|
carrier.elements.insert(e.id, e);
|
|
carrier.place(id(4), *keys.last().unwrap(), Position { x: 999., y: 999. });
|
|
}
|
|
carrier.place(id(4), id(30), Position { x: 999., y: 999. });
|
|
carrier.relate(
|
|
RelationKind::Allocation,
|
|
"identity carrier only",
|
|
keys[0],
|
|
keys[2],
|
|
);
|
|
carrier.relate(
|
|
RelationKind::Interaction,
|
|
"identity carrier only",
|
|
keys[2],
|
|
id(30),
|
|
);
|
|
let basic = expected("basic", &carrier);
|
|
assert_ne!(
|
|
basic.decoded.project, carrier,
|
|
"Carrier metadata must not be adopted"
|
|
);
|
|
assert_eq!(
|
|
expected("basic", &basic.decoded.project).bundle,
|
|
basic.bundle
|
|
);
|
|
let mut r = TransitionRequest::new(TransitionDirection::OaToSa, vec![keys[0]]);
|
|
r.mappings.push(TransitionMapping {
|
|
source: keys[0],
|
|
target: None,
|
|
target_kind: Some(Kind::SystemActor),
|
|
});
|
|
r.destination_diagram = Some(id(5));
|
|
let plan = plan_transition(&basic.decoded.project, &r).unwrap();
|
|
assert!(plan.conflicts.is_empty(), "{:?}", plan.conflicts);
|
|
let transitioned = apply_transition(&basic.decoded.project, &plan).unwrap();
|
|
let oracle = expected("transition", &transitioned);
|
|
assert_eq!(oracle.decoded.project, transitioned);
|
|
assert_eq!(
|
|
expected("transition", &oracle.decoded.project).bundle,
|
|
oracle.bundle
|
|
);
|
|
r.destination_diagram = Some(id(2));
|
|
let invalid = plan_transition(&basic.decoded.project, &r).unwrap();
|
|
assert!(
|
|
!invalid.conflicts.is_empty(),
|
|
"State-machine decoy must reject a foreign SystemActor"
|
|
);
|
|
// Capabilities legitimately includes participants. It is an unchosen,
|
|
// non-Allocation view, not an invalid type; explicit selection wins over it.
|
|
r.destination_diagram = Some(id(3));
|
|
assert!(
|
|
plan_transition(&basic.decoded.project, &r)
|
|
.unwrap()
|
|
.conflicts
|
|
.is_empty()
|
|
);
|
|
let mut altered = transitioned.clone();
|
|
altered.elements.get_mut(&keys[1]).unwrap().description = "unrequested modification".into();
|
|
assert_ne!(expected("transition", &altered).decoded.project, altered);
|
|
println!(
|
|
"PASS self-check: complete basic/transition source fixed points, independent geometry/metadata, state-view refusal and unchanged unchosen Capabilities view"
|
|
);
|
|
}
|
|
fn main() {
|
|
assert_eq!(SCHEMA_VERSION, 7);
|
|
let args: Vec<_> = std::env::args().collect();
|
|
if args.get(1).is_some_and(|s| s == "self-check") {
|
|
self_check();
|
|
return;
|
|
}
|
|
if args.get(1).is_some_and(|s| s == "generate") {
|
|
assert_eq!(args.len(), 3);
|
|
let dir = Path::new(&args[2]);
|
|
assert!(
|
|
!dir.exists(),
|
|
"Generate into a new directory; existing evidence is immutable"
|
|
);
|
|
let p = fixture();
|
|
let base = bundle(&p);
|
|
fs::create_dir_all(dir).unwrap();
|
|
for (name, bytes) in base.bundle.files {
|
|
fs::write(dir.join(name), bytes).unwrap();
|
|
}
|
|
fs::write(
|
|
dir.join("expected-baseline.json"),
|
|
serde_json::to_vec_pretty(&p).unwrap(),
|
|
)
|
|
.unwrap();
|
|
println!(
|
|
"PASS generated independent baseline: {} elements, {} diagrams; user objects absent",
|
|
p.elements.len(),
|
|
p.diagrams.len()
|
|
);
|
|
return;
|
|
}
|
|
assert_eq!(
|
|
args.len(),
|
|
4,
|
|
"check baseline|entity|basic|transition ENTRY"
|
|
);
|
|
assert_eq!(args[1], "check");
|
|
let actual = load(Path::new(&args[3]));
|
|
let prediction = expected(&args[2], &actual.decoded.project);
|
|
assert_eq!(
|
|
actual.decoded.project, prediction.decoded.project,
|
|
"Complete model differs from typed role-based prediction"
|
|
);
|
|
assert_eq!(
|
|
actual.bundle, prediction.bundle,
|
|
"Complete source bundle differs (including comments and included bytes)"
|
|
);
|
|
println!(
|
|
"PASS {}: complete model and {} exact sources; only fresh role-proven UUIDs borrowed",
|
|
args[2],
|
|
actual.bundle.files.len()
|
|
);
|
|
}
|