Add named behavior creation and verified textual authoring examples
This commit is contained in:
@@ -0,0 +1,568 @@
|
||||
//! Independent native creation oracle; no desktop producer is imported.
|
||||
//! self-check BASE | check STAGE BASE ACTUAL ROLES | same EXPECTED ACTUAL
|
||||
use oa_core::{source_project::*, *};
|
||||
use std::{
|
||||
collections::{BTreeMap, BTreeSet},
|
||||
fs,
|
||||
path::Path,
|
||||
};
|
||||
|
||||
const PROCESS: &str = "Coordinate rescue";
|
||||
const SCENARIO: &str = "Rescue coordination conversation";
|
||||
const MACHINE: &str = "Rescue team operating modes";
|
||||
const VIEW: &str = "Rescue team operating modes · states";
|
||||
const CAPABILITY: u128 = 0xb451c00000004000800000000000001f;
|
||||
const OWNER: u128 = 0xc062944912094b50afacf558a9a0fc7f;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
enum Stage {
|
||||
Baseline,
|
||||
Process,
|
||||
Scenario,
|
||||
Machine,
|
||||
}
|
||||
impl Stage {
|
||||
fn parse(value: &str) -> Self {
|
||||
match value {
|
||||
"baseline" => Self::Baseline,
|
||||
"process" => Self::Process,
|
||||
"scenario" => Self::Scenario,
|
||||
"machine" => Self::Machine,
|
||||
_ => panic!("Stage must be baseline, process, scenario or machine"),
|
||||
}
|
||||
}
|
||||
fn additions(self) -> usize {
|
||||
match self {
|
||||
Self::Baseline => 0,
|
||||
Self::Process => 1,
|
||||
Self::Scenario => 2,
|
||||
Self::Machine => 3,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn source_paths(root: &Path, here: &Path, paths: &mut BTreeSet<String>) {
|
||||
for entry in fs::read_dir(here).unwrap() {
|
||||
let entry = entry.unwrap();
|
||||
let kind = entry.file_type().unwrap();
|
||||
assert!(
|
||||
!kind.is_symlink(),
|
||||
"Native checkpoint must contain ordinary files"
|
||||
);
|
||||
if kind.is_dir() {
|
||||
source_paths(root, &entry.path(), paths);
|
||||
} else if entry.path().extension().is_some_and(|ext| ext == "arcadia") {
|
||||
assert!(kind.is_file());
|
||||
paths.insert(
|
||||
entry
|
||||
.path()
|
||||
.strip_prefix(root)
|
||||
.unwrap()
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.replace('\\', "/"),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
fn load(entry: &Path) -> CompiledSourceProject {
|
||||
let files = source_files::load_project(entry, ProjectLimits::default()).unwrap();
|
||||
let raw = files.reachable_bundle();
|
||||
let compiled = compile_bundle(&raw, ProjectLimits::default()).unwrap();
|
||||
assert_eq!(
|
||||
compiled.bundle, raw,
|
||||
"No migration, normalization or implicit identity insertion"
|
||||
);
|
||||
execute_batch(&compiled.decoded.project, &[]).unwrap();
|
||||
let mut disk = BTreeSet::new();
|
||||
let root = entry.parent().unwrap();
|
||||
source_paths(root, root, &mut disk);
|
||||
assert_eq!(
|
||||
disk,
|
||||
raw.files.keys().cloned().collect(),
|
||||
"Reject detached extra source files too"
|
||||
);
|
||||
compiled
|
||||
}
|
||||
fn fnv1a(bytes: &[u8]) -> u64 {
|
||||
bytes.iter().fold(0xcbf29ce484222325, |hash, byte| {
|
||||
(hash ^ u64::from(*byte)).wrapping_mul(0x100000001b3)
|
||||
})
|
||||
}
|
||||
fn pinned_baseline(base: &CompiledSourceProject) -> bool {
|
||||
// Fixed byte fingerprints of the root-preserved transition/reopen input,
|
||||
// recorded before any creation. SHA-256 provenance is in the fixture README.
|
||||
let pins = [
|
||||
("project.arcadia", 4844, 0x450c6ffd8225af32),
|
||||
("rationale.arcadia", 57, 0xf87e3c2e303f9b62),
|
||||
("traceability.arcadia", 395, 0xb47992dbdacab344),
|
||||
("oa/participants.arcadia", 403, 0x1e109eef81968b5f),
|
||||
(
|
||||
"oa/activities/activity_assess_incident.arcadia",
|
||||
376,
|
||||
0x0e67e533db3cff78,
|
||||
),
|
||||
("sa/participants.arcadia", 124, 0xcadb35025aea18cc),
|
||||
];
|
||||
base.bundle.entry == "project.arcadia"
|
||||
&& base.bundle.files.len() == pins.len()
|
||||
&& pins.iter().all(|(path, length, hash)| {
|
||||
base.bundle
|
||||
.files
|
||||
.get(*path)
|
||||
.is_some_and(|text| text.len() == *length && fnv1a(text.as_bytes()) == *hash)
|
||||
})
|
||||
}
|
||||
fn baseline(entry: &Path) -> CompiledSourceProject {
|
||||
let base = load(entry);
|
||||
assert!(
|
||||
pinned_baseline(&base),
|
||||
"The six-file baseline differs from the frozen pre-creation input"
|
||||
);
|
||||
let p = &base.decoded.project;
|
||||
assert_eq!(
|
||||
(
|
||||
p.elements.len(),
|
||||
p.relations.len(),
|
||||
p.diagrams.len(),
|
||||
p.traces.len()
|
||||
),
|
||||
(10, 2, 4, 1)
|
||||
);
|
||||
let context = &p.elements[&Id::from_u128(CAPABILITY)];
|
||||
assert_eq!(
|
||||
(
|
||||
context.name.as_str(),
|
||||
context.kind(),
|
||||
context.layer(),
|
||||
context.parent
|
||||
),
|
||||
("Rescue capability", Kind::Capability, Layer::Oa, None)
|
||||
);
|
||||
let owner = &p.elements[&Id::from_u128(OWNER)];
|
||||
assert_eq!(
|
||||
(
|
||||
owner.name.as_str(),
|
||||
owner.kind(),
|
||||
owner.layer(),
|
||||
owner.parent
|
||||
),
|
||||
("Rescue team", Kind::SystemActor, Layer::Sa, None)
|
||||
);
|
||||
for name in [PROCESS, SCENARIO, MACHINE] {
|
||||
assert!(
|
||||
!p.elements.values().any(|e| e.name == name),
|
||||
"Baseline already contains requested creation"
|
||||
);
|
||||
}
|
||||
base
|
||||
}
|
||||
|
||||
struct Roles<'a> {
|
||||
actual: &'a Project,
|
||||
baseline: &'a Project,
|
||||
used: BTreeSet<Id>,
|
||||
}
|
||||
impl Roles<'_> {
|
||||
fn fresh(&mut self, choices: Vec<Id>, role: &str) -> Id {
|
||||
assert_eq!(
|
||||
choices.len(),
|
||||
1,
|
||||
"Exactly one literal identity role required: {role}"
|
||||
);
|
||||
let id = choices[0];
|
||||
assert_eq!(id.get_version_num(), 4, "{role} must be a fresh UUIDv4");
|
||||
assert!(
|
||||
!self.baseline.contains(id),
|
||||
"{role} reuses a baseline identity"
|
||||
);
|
||||
assert!(self.used.insert(id), "{role} reuses another fresh role");
|
||||
id
|
||||
}
|
||||
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 view(&mut self, machine: Id) -> Id {
|
||||
self.fresh(
|
||||
self.actual
|
||||
.diagrams
|
||||
.values()
|
||||
.filter(|d| {
|
||||
d.layer == Layer::Sa
|
||||
&& d.name == VIEW
|
||||
&& d.projection == ProjectionKind::StateMachine { machine }
|
||||
})
|
||||
.map(|d| d.id)
|
||||
.collect(),
|
||||
VIEW,
|
||||
)
|
||||
}
|
||||
fn node(&mut self, diagram: Id, machine: Id) -> Id {
|
||||
self.fresh(
|
||||
self.actual.diagrams[&diagram]
|
||||
.nodes
|
||||
.values()
|
||||
.filter(|node| node.element == machine)
|
||||
.map(|node| node.id)
|
||||
.collect(),
|
||||
"new machine's exact view placement",
|
||||
)
|
||||
}
|
||||
}
|
||||
fn element(p: &Project, id: Id, name: &str, parent: Option<Id>, body: ElementBody) -> Element {
|
||||
Element {
|
||||
id,
|
||||
alias: p.fresh_alias(name, "behavior", id),
|
||||
name: name.into(),
|
||||
description: String::new(),
|
||||
parent,
|
||||
body,
|
||||
}
|
||||
}
|
||||
fn advance(base: &mut CompiledSourceProject, commands: Vec<Command>) {
|
||||
let project = execute_batch(&base.decoded.project, &[Command::Batch(commands)]).unwrap();
|
||||
*base = update_bundle(base, &project, ProjectLimits::default()).unwrap();
|
||||
}
|
||||
fn predict(base: &CompiledSourceProject, stage: Stage, carrier: &Project) -> CompiledSourceProject {
|
||||
let mut result = base.clone();
|
||||
let mut roles = Roles {
|
||||
actual: carrier,
|
||||
baseline: &base.decoded.project,
|
||||
used: BTreeSet::new(),
|
||||
};
|
||||
if stage >= Stage::Process {
|
||||
let id = roles.element(Kind::Process, Layer::Oa, PROCESS);
|
||||
let record = element(
|
||||
&result.decoded.project,
|
||||
id,
|
||||
PROCESS,
|
||||
None,
|
||||
ElementBody::Oa(OaBody::Process { steps: vec![] }),
|
||||
);
|
||||
advance(&mut result, vec![Command::CreateElement(record)]);
|
||||
}
|
||||
if stage >= Stage::Scenario {
|
||||
let id = roles.element(Kind::Scenario, Layer::Oa, SCENARIO);
|
||||
let record = element(
|
||||
&result.decoded.project,
|
||||
id,
|
||||
SCENARIO,
|
||||
None,
|
||||
ElementBody::shared_in(
|
||||
Layer::Oa,
|
||||
SharedBody::Scenario {
|
||||
viewpoint: ScenarioViewpoint::ParticipantInteraction,
|
||||
context: Some(Id::from_u128(CAPABILITY)),
|
||||
precondition: String::new(),
|
||||
postcondition: String::new(),
|
||||
steps: vec![],
|
||||
},
|
||||
),
|
||||
);
|
||||
advance(&mut result, vec![Command::CreateElement(record)]);
|
||||
}
|
||||
if stage == Stage::Machine {
|
||||
let machine = roles.element(Kind::StateMachine, Layer::Sa, MACHINE);
|
||||
let diagram = roles.view(machine);
|
||||
let placement = roles.node(diagram, machine);
|
||||
let record = element(
|
||||
&result.decoded.project,
|
||||
machine,
|
||||
MACHINE,
|
||||
Some(Id::from_u128(OWNER)),
|
||||
ElementBody::shared_in(Layer::Sa, SharedBody::StateMachine),
|
||||
);
|
||||
let p = execute_batch(
|
||||
&result.decoded.project,
|
||||
&[Command::CreateElement(record.clone())],
|
||||
)
|
||||
.unwrap();
|
||||
// Published first-view contract, independently constructed: one root,
|
||||
// default options, no routes/regions/states. Shared core Layout owns the
|
||||
// final geometry; no observed geometry or desktop planner is consulted.
|
||||
let view = Diagram {
|
||||
id: diagram,
|
||||
alias: p.fresh_alias(VIEW, "state_view", diagram),
|
||||
name: VIEW.into(),
|
||||
description: String::new(),
|
||||
layer: Layer::Sa,
|
||||
projection: ProjectionKind::StateMachine { machine },
|
||||
routes: vec![],
|
||||
view_options: Default::default(),
|
||||
nodes: BTreeMap::from([(
|
||||
placement,
|
||||
NodePlacement {
|
||||
id: placement,
|
||||
alias: p.fresh_alias(&format!("{MACHINE} placement"), "placement", placement),
|
||||
element: machine,
|
||||
position: Position { x: 60., y: 60. },
|
||||
size: Size {
|
||||
width: 336.,
|
||||
height: 104.,
|
||||
},
|
||||
collapsed: false,
|
||||
port_anchors: vec![],
|
||||
},
|
||||
)]),
|
||||
};
|
||||
advance(
|
||||
&mut result,
|
||||
vec![
|
||||
Command::CreateElement(record),
|
||||
Command::CreateDiagram(view),
|
||||
Command::Layout(diagram),
|
||||
],
|
||||
);
|
||||
}
|
||||
let p = &result.decoded.project;
|
||||
assert_eq!(
|
||||
p.elements.len(),
|
||||
base.decoded.project.elements.len() + stage.additions()
|
||||
);
|
||||
assert_eq!(
|
||||
p.diagrams.len(),
|
||||
base.decoded.project.diagrams.len() + usize::from(stage == Stage::Machine)
|
||||
);
|
||||
assert_eq!(p.relations, base.decoded.project.relations);
|
||||
assert_eq!(p.traces, base.decoded.project.traces);
|
||||
result
|
||||
}
|
||||
fn equal(left: &CompiledSourceProject, right: &CompiledSourceProject) -> bool {
|
||||
left.decoded.project == right.decoded.project && left.bundle == right.bundle
|
||||
}
|
||||
fn assert_equal(expected: &CompiledSourceProject, actual: &CompiledSourceProject) {
|
||||
assert_eq!(
|
||||
actual.decoded.project, expected.decoded.project,
|
||||
"Complete Project differs from independent prediction"
|
||||
);
|
||||
assert_eq!(
|
||||
actual.bundle, expected.bundle,
|
||||
"Complete SourceBundle differs, including comments and all included bytes"
|
||||
);
|
||||
}
|
||||
fn test_id(n: u128) -> Id {
|
||||
Id::from_u128(0xc4ea7100000040008000000000000000 + n)
|
||||
}
|
||||
fn false_carrier(base: &Project) -> Project {
|
||||
let mut p = base.clone();
|
||||
for (n, kind, layer, name) in [
|
||||
(1, Kind::Process, Layer::Oa, PROCESS),
|
||||
(2, Kind::Scenario, Layer::Oa, SCENARIO),
|
||||
(3, Kind::StateMachine, Layer::Sa, MACHINE),
|
||||
] {
|
||||
p.elements.insert(
|
||||
test_id(n),
|
||||
Element {
|
||||
id: test_id(n),
|
||||
alias: format!("false_alias_{n}"),
|
||||
name: name.into(),
|
||||
description: "Must never be copied".into(),
|
||||
parent: None,
|
||||
body: ElementBody::new(kind, layer).unwrap(),
|
||||
},
|
||||
);
|
||||
}
|
||||
p.diagrams.insert(
|
||||
test_id(4),
|
||||
Diagram {
|
||||
id: test_id(4),
|
||||
alias: "false_view_alias".into(),
|
||||
name: VIEW.into(),
|
||||
description: "False view description".into(),
|
||||
layer: Layer::Sa,
|
||||
projection: ProjectionKind::StateMachine {
|
||||
machine: test_id(3),
|
||||
},
|
||||
routes: vec![],
|
||||
view_options: Default::default(),
|
||||
nodes: BTreeMap::from([(
|
||||
test_id(5),
|
||||
NodePlacement {
|
||||
id: test_id(5),
|
||||
alias: "false_node_alias".into(),
|
||||
element: test_id(3),
|
||||
position: Position {
|
||||
x: 9999.,
|
||||
y: -9999.,
|
||||
},
|
||||
size: Size {
|
||||
width: 1.,
|
||||
height: 1.,
|
||||
},
|
||||
collapsed: true,
|
||||
port_anchors: vec![],
|
||||
},
|
||||
)]),
|
||||
},
|
||||
);
|
||||
p
|
||||
}
|
||||
fn self_check(base: &CompiledSourceProject) {
|
||||
let original = base.clone();
|
||||
let carrier = false_carrier(&base.decoded.project);
|
||||
assert_equal(base, &predict(base, Stage::Baseline, &carrier));
|
||||
for stage in [Stage::Process, Stage::Scenario, Stage::Machine] {
|
||||
let expected = predict(base, stage, &carrier);
|
||||
assert_equal(&expected, &predict(base, stage, &expected.decoded.project));
|
||||
let parsed = compile_bundle(&expected.bundle, ProjectLimits::default()).unwrap();
|
||||
assert_equal(&expected, &parsed);
|
||||
let mut altered = expected.clone();
|
||||
altered
|
||||
.decoded
|
||||
.project
|
||||
.elements
|
||||
.get_mut(&test_id(1))
|
||||
.unwrap()
|
||||
.description = "Unrequested metadata".into();
|
||||
assert!(!equal(&expected, &altered));
|
||||
let mut altered = expected.clone();
|
||||
altered
|
||||
.decoded
|
||||
.project
|
||||
.elements
|
||||
.get_mut(&Id::from_u128(CAPABILITY))
|
||||
.unwrap()
|
||||
.name = "Changed unrelated baseline".into();
|
||||
assert!(!equal(&expected, &altered));
|
||||
let mut changed_source = expected.bundle.clone();
|
||||
changed_source
|
||||
.files
|
||||
.get_mut("rationale.arcadia")
|
||||
.unwrap()
|
||||
.push_str("// Extra comment\n");
|
||||
let altered = compile_bundle(&changed_source, ProjectLimits::default()).unwrap();
|
||||
assert_eq!(altered.decoded.project, expected.decoded.project);
|
||||
assert!(
|
||||
!equal(&expected, &altered),
|
||||
"Identical semantics do not excuse changed source bytes"
|
||||
);
|
||||
if stage >= Stage::Scenario {
|
||||
let mut altered = expected.clone();
|
||||
*altered
|
||||
.decoded
|
||||
.project
|
||||
.elements
|
||||
.get_mut(&test_id(2))
|
||||
.unwrap()
|
||||
.shared_mut()
|
||||
.unwrap() = SharedBody::Scenario {
|
||||
viewpoint: ScenarioViewpoint::OperationalActivity,
|
||||
context: None,
|
||||
precondition: String::new(),
|
||||
postcondition: String::new(),
|
||||
steps: vec![],
|
||||
};
|
||||
assert!(!equal(&expected, &altered));
|
||||
}
|
||||
if stage == Stage::Machine {
|
||||
assert_ne!(
|
||||
expected.decoded.project, carrier,
|
||||
"Never adopt role-carrier metadata/geometry"
|
||||
);
|
||||
assert_eq!(
|
||||
expected.decoded.project.elements[&test_id(3)].parent,
|
||||
Some(Id::from_u128(OWNER))
|
||||
);
|
||||
let view = &expected.decoded.project.diagrams[&test_id(4)];
|
||||
assert_eq!(view.nodes.len(), 1);
|
||||
let node = &view.nodes[&test_id(5)];
|
||||
println!(
|
||||
"PREDICT first view: {VIEW}; machine-root position {:?}, size {:?}; {} exact source files",
|
||||
node.position,
|
||||
node.size,
|
||||
expected.bundle.files.len()
|
||||
);
|
||||
let mut altered = expected.clone();
|
||||
altered
|
||||
.decoded
|
||||
.project
|
||||
.diagrams
|
||||
.get_mut(&test_id(4))
|
||||
.unwrap()
|
||||
.nodes
|
||||
.get_mut(&test_id(5))
|
||||
.unwrap()
|
||||
.position
|
||||
.x += 1.;
|
||||
assert!(!equal(&expected, &altered));
|
||||
let mut altered = expected.clone();
|
||||
let extra = element(
|
||||
&altered.decoded.project,
|
||||
test_id(6),
|
||||
"Unrequested region",
|
||||
Some(test_id(3)),
|
||||
ElementBody::shared_in(Layer::Sa, SharedBody::Region),
|
||||
);
|
||||
altered.decoded.project.elements.insert(extra.id, extra);
|
||||
assert!(!equal(&expected, &altered), "Reject extra semantic objects");
|
||||
let mut altered = expected.clone();
|
||||
altered
|
||||
.bundle
|
||||
.files
|
||||
.insert("unrequested.arcadia".into(), "// Extra file\n".into());
|
||||
assert!(!equal(&expected, &altered));
|
||||
}
|
||||
println!(
|
||||
"PASS self-check {stage:?}: full source fixed point; false metadata, unrelated edits and source-only drift rejected"
|
||||
);
|
||||
}
|
||||
let mut altered_base = base.clone();
|
||||
altered_base
|
||||
.bundle
|
||||
.files
|
||||
.get_mut("rationale.arcadia")
|
||||
.unwrap()
|
||||
.push(' ');
|
||||
assert!(
|
||||
!pinned_baseline(&altered_base),
|
||||
"Reject an altered baseline before use"
|
||||
);
|
||||
assert_equal(&original, base);
|
||||
println!("PASS baseline retained unchanged; no native acceptance claimed");
|
||||
}
|
||||
fn main() {
|
||||
assert_eq!(SCHEMA_VERSION, 7);
|
||||
let args: Vec<_> = std::env::args().collect();
|
||||
match args.get(1).map(String::as_str) {
|
||||
Some("self-check") => {
|
||||
assert_eq!(args.len(), 3, "self-check BASELINE_ENTRY");
|
||||
self_check(&baseline(Path::new(&args[2])));
|
||||
}
|
||||
Some("check") => {
|
||||
assert_eq!(
|
||||
args.len(),
|
||||
6,
|
||||
"check baseline|process|scenario|machine BASELINE_ENTRY ACTUAL_ENTRY ROLE_ENTRY"
|
||||
);
|
||||
let stage = Stage::parse(&args[2]);
|
||||
let base = baseline(Path::new(&args[3]));
|
||||
let actual = load(Path::new(&args[4]));
|
||||
let carrier = load(Path::new(&args[5]));
|
||||
let expected = predict(&base, stage, &carrier.decoded.project);
|
||||
assert_equal(&expected, &actual);
|
||||
println!(
|
||||
"PASS {stage:?}: complete Project + {} exact source files; only fresh literal-role UUIDs borrowed",
|
||||
actual.bundle.files.len()
|
||||
);
|
||||
}
|
||||
Some("same") => {
|
||||
assert_eq!(args.len(), 4, "same EXPECTED_CHECKPOINT ACTUAL_CHECKPOINT");
|
||||
let expected = load(Path::new(&args[2]));
|
||||
let actual = load(Path::new(&args[3]));
|
||||
assert_equal(&expected, &actual);
|
||||
println!(
|
||||
"PASS exact checkpoint identity/model/{}-file SourceBundle equality",
|
||||
actual.bundle.files.len()
|
||||
);
|
||||
}
|
||||
_ => panic!("self-check BASE | check STAGE BASE ACTUAL ROLES | same EXPECTED ACTUAL"),
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user