677 lines
23 KiB
Rust
677 lines
23 KiB
Rust
//! Independent native creation oracle; no desktop producer is imported.
|
|
//! self-check BASE | check STAGE BASE ACTUAL ROLES | same EXPECTED ACTUAL [UNDO_FROM]
|
|
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,
|
|
}
|
|
}
|
|
}
|
|
|
|
type DiskSources = BTreeMap<String, Vec<u8>>;
|
|
|
|
fn source_paths(root: &Path, here: &Path, paths: &mut DiskSources) {
|
|
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('\\', "/"),
|
|
fs::read(entry.path()).unwrap(),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
fn load_checkpoint(entry: &Path) -> (CompiledSourceProject, DiskSources) {
|
|
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 = BTreeMap::new();
|
|
let root = entry.parent().unwrap();
|
|
source_paths(root, root, &mut disk);
|
|
(compiled, disk)
|
|
}
|
|
fn predicted_disk(reachable: &SourceBundle, undo_from: Option<&DiskSources>) -> DiskSources {
|
|
// Saving Undo writes the restored reachable files and deliberately keeps
|
|
// detached files from the previously saved checkpoint. Never infer their
|
|
// bytes from the actual Undo directory: its explicit origin is the oracle.
|
|
let mut expected = undo_from.cloned().unwrap_or_default();
|
|
expected.extend(
|
|
reachable
|
|
.files
|
|
.iter()
|
|
.map(|(path, source)| (path.clone(), source.as_bytes().to_vec())),
|
|
);
|
|
expected
|
|
}
|
|
fn load(entry: &Path) -> CompiledSourceProject {
|
|
let (compiled, disk) = load_checkpoint(entry);
|
|
assert_eq!(
|
|
disk,
|
|
predicted_disk(&compiled.bundle, None),
|
|
"Ordinary checkpoints must contain exactly their reachable source files; retained Undo files require an explicit origin"
|
|
);
|
|
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);
|
|
let before = predict(base, Stage::Scenario, &carrier);
|
|
let created = predict(base, Stage::Machine, &carrier);
|
|
let origin = predicted_disk(&created.bundle, None);
|
|
let detached: Vec<_> = created
|
|
.bundle
|
|
.files
|
|
.keys()
|
|
.filter(|path| !before.bundle.files.contains_key(*path))
|
|
.cloned()
|
|
.collect();
|
|
assert_eq!(
|
|
detached.len(),
|
|
1,
|
|
"A machine creation adds one source module"
|
|
);
|
|
// Independently simulate the saver's observable result: restored reachable
|
|
// bytes plus the one new module left on disk by the prior creation.
|
|
let mut retained = predicted_disk(&before.bundle, None);
|
|
retained.insert(
|
|
detached[0].clone(),
|
|
created.bundle.files[&detached[0]].as_bytes().to_vec(),
|
|
);
|
|
assert_eq!(
|
|
retained[&detached[0]], origin[&detached[0]],
|
|
"Retained bytes must come from the exact created checkpoint"
|
|
);
|
|
assert_ne!(
|
|
retained,
|
|
predicted_disk(&before.bundle, None),
|
|
"Ordinary comparison must not accept detached history files"
|
|
);
|
|
assert_eq!(
|
|
retained,
|
|
predicted_disk(&before.bundle, Some(&origin)),
|
|
"Explicit Undo origin permits only the expected retained bytes"
|
|
);
|
|
let mut unknown = retained.clone();
|
|
unknown.insert("unknown.arcadia".into(), b"// Unclaimed file\n".to_vec());
|
|
assert_ne!(
|
|
unknown,
|
|
predicted_disk(&before.bundle, Some(&origin)),
|
|
"Reject unknown detached files"
|
|
);
|
|
let mut changed = retained.clone();
|
|
changed.get_mut(&detached[0]).unwrap().push(b' ');
|
|
assert_ne!(
|
|
changed,
|
|
predicted_disk(&before.bundle, Some(&origin)),
|
|
"Reject changed detached bytes"
|
|
);
|
|
let mut missing = retained.clone();
|
|
missing.remove(&detached[0]);
|
|
assert_ne!(
|
|
missing,
|
|
predicted_disk(&before.bundle, Some(&origin)),
|
|
"The retained file set is exact, not optional"
|
|
);
|
|
let mut reachable_drift = retained.clone();
|
|
reachable_drift
|
|
.get_mut("rationale.arcadia")
|
|
.unwrap()
|
|
.push(b' ');
|
|
assert_ne!(
|
|
reachable_drift,
|
|
predicted_disk(&before.bundle, Some(&origin)),
|
|
"An Undo exemption cannot hide reachable-source changes"
|
|
);
|
|
println!(
|
|
"PASS explicit Undo disk contract: known detached retention accepted; unknown, changed, missing and reachable drift rejected"
|
|
);
|
|
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!(
|
|
matches!(args.len(), 4 | 5),
|
|
"same EXPECTED_CHECKPOINT ACTUAL_CHECKPOINT [UNDO_FROM_CHECKPOINT]"
|
|
);
|
|
let expected = load(Path::new(&args[2]));
|
|
let (actual, disk) = load_checkpoint(Path::new(&args[3]));
|
|
assert_equal(&expected, &actual);
|
|
let origin = args.get(4).map(|entry| {
|
|
// The first committed checkpoint is itself exact. This keeps
|
|
// arbitrary pre-existing detached files out of the exception.
|
|
let committed = load(Path::new(entry));
|
|
predicted_disk(&committed.bundle, None)
|
|
});
|
|
assert_eq!(
|
|
disk,
|
|
predicted_disk(&expected.bundle, origin.as_ref()),
|
|
"Disk source files differ from the exact checkpoint plus explicitly proven retained Undo files"
|
|
);
|
|
println!(
|
|
"PASS exact checkpoint identity/model/{}-file SourceBundle equality; {} exact disk files ({} known retained)",
|
|
actual.bundle.files.len(),
|
|
disk.len(),
|
|
disk.len() - actual.bundle.files.len()
|
|
);
|
|
}
|
|
_ => panic!(
|
|
"self-check BASE | check STAGE BASE ACTUAL ROLES | same EXPECTED ACTUAL [UNDO_FROM]"
|
|
),
|
|
}
|
|
}
|