Files
ARCADIA-Studio/crates/oa-core/tests/source7_contract.rs
T
Codex e2ecfea69b
Rust checks / check (ubuntu-latest) (push) Canceled after 0s
Rust checks / check (windows-latest) (push) Canceled after 0s
Checkpoint source 7 workflows and in-progress diagram authoring redesign
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.
2026-09-10 10:06:55 -07:00

1295 lines
42 KiB
Rust

use oa_core::{
operations::*,
topology_context::*,
transitions::*,
validation::{self, Severity},
*,
};
fn valid(p: &Project) {
let errors: Vec<_> = validation::validate(p)
.into_iter()
.filter(|i| i.severity == Severity::Error)
.collect();
assert!(errors.is_empty(), "{errors:?}");
}
fn occurrence(p: &mut Project, owner: Id, name: &str, body: OccurrenceBody) -> Id {
let mut e = p
.make_element(
Kind::Occurrence,
p.elements[&owner].layer(),
name,
Some(owner),
)
.unwrap();
*e.shared_mut().unwrap() = SharedBody::Occurrence {
body,
precondition: String::new(),
postcondition: String::new(),
};
let id = e.id;
p.elements.insert(id, e);
p.elements
.get_mut(&owner)
.unwrap()
.steps_mut()
.unwrap()
.push(id);
id
}
fn endpoint(path: &[Id], occurrence: Id) -> OccurrenceEndpoint {
OccurrenceEndpoint {
references: path.to_vec(),
occurrence,
}
}
struct Chain {
p: Project,
outer: Id,
procedure: Id,
a: Id,
b: Id,
x: Id,
r1: Id,
r2: Id,
inner: Id,
}
fn chain(layer: Layer) -> Chain {
let mut p = Project::empty();
let kind = if layer == Layer::Oa {
Kind::Process
} else {
Kind::FunctionalChain
};
let work = if layer == Layer::Oa {
Kind::Activity
} else {
Kind::Function
};
let outer = p.add_element(kind, layer, "Caller", None).unwrap();
let procedure = p
.add_element(kind, layer, "Shared procedure", None)
.unwrap();
let nested = p.add_element(kind, layer, "Nested caller", None).unwrap();
let mut actions = vec![];
for (owner, name) in [
(procedure, "A"),
(procedure, "B"),
(procedure, "C"),
(outer, "X"),
] {
let action = p.add_element(work, layer, name, None).unwrap();
actions.push(occurrence(
&mut p,
owner,
name,
OccurrenceBody::Action {
action: Some(action),
lifeline: None,
},
));
}
let [a, b, c, x] = actions.try_into().unwrap();
p.relate(RelationKind::SequenceLink, "A then B", a, b);
p.relate(RelationKind::SequenceLink, "B then C", b, c);
let r1 = occurrence(
&mut p,
outer,
"First reuse",
OccurrenceBody::SequenceReference {
sequence: Some(procedure),
},
);
let r2 = occurrence(
&mut p,
outer,
"Second reuse",
OccurrenceBody::SequenceReference {
sequence: Some(procedure),
},
);
let inner = occurrence(
&mut p,
nested,
"Nested reuse",
OccurrenceBody::SequenceReference {
sequence: Some(procedure),
},
);
valid(&p);
Chain {
p,
outer,
procedure,
a,
b,
x,
r1,
r2,
inner,
}
}
fn link(f: &Chain, purpose: ContextualLinkPurpose) -> Relation {
Relation {
id: Id::new_v4(),
alias: "caller_connection".into(),
name: "Caller contribution".into(),
description: String::new(),
body: RelationBody::ContextualLink {
owner: f.outer,
source: endpoint(&[], f.x),
target: endpoint(&[f.r1], f.b),
purpose,
},
}
}
#[test]
fn source7_repeated_nested_qualified_precedence_and_cache_are_distinct() {
for layer in [Layer::Oa, Layer::Sa] {
let mut f = chain(layer);
let relation = link(
&f,
ContextualLinkPurpose::Precedence {
guard: "authorized".into(),
},
);
f.p = execute_batch(&f.p, &[Command::CreateRelation(relation.clone())]).unwrap();
let index = TopologyIndex::with_cache_capacity(&f.p, 1);
let one = index.resolve(f.outer, &endpoint(&[f.r1], f.b)).unwrap();
let two = index.resolve(f.outer, &endpoint(&[f.r2], f.b)).unwrap();
assert_ne!(one.address, two.address);
assert_eq!(index.resolve(f.outer, &one.address.endpoint).unwrap(), one);
let analysis = index.analyze(f.outer, AnalysisLimits::default());
assert!(analysis.complete);
let warnings: Vec<_> = analysis
.notices
.iter()
.filter(|n| n.code == "ambiguous_sequence_branch")
.collect();
assert_eq!(warnings.len(), 1);
assert_eq!(warnings[0].address, one.address);
assert!(
!analysis
.precedence
.iter()
.any(|e| e.source.address.endpoint.occurrence == f.x
&& e.target.address.endpoint == endpoint(&[f.r1], f.a))
);
assert!(
analysis
.precedence
.iter()
.any(|e| e.relation == Some(relation.id) && e.target.address == one.address)
);
assert!(
index
.analyze(f.procedure, AnalysisLimits::default())
.notices
.is_empty()
);
let limited = index.analyze(f.outer, AnalysisLimits { nodes: 1, edges: 1 });
assert!(!limited.complete);
assert!(
limited
.notices
.iter()
.any(|n| n.code == "context_analysis_limit")
);
assert!(index.analyze(f.outer, AnalysisLimits::default()).complete);
let nested_owner = f.p.elements[&f.inner].parent.unwrap();
let mut changed = f.p.elements[&f.r1].clone();
*changed.shared_mut().unwrap() = SharedBody::Occurrence {
body: OccurrenceBody::SequenceReference {
sequence: Some(nested_owner),
},
precondition: String::new(),
postcondition: String::new(),
};
assert!(execute_batch(&f.p, &[Command::EditElement(f.r1, changed.clone())]).is_err());
let mut repaired = relation.clone();
if let RelationBody::ContextualLink { target, .. } = &mut repaired.body {
target.references.push(f.inner);
}
let nested = execute_batch(
&f.p,
&[
Command::EditElement(f.r1, changed),
Command::EditRelation(relation.id, repaired),
],
)
.unwrap();
assert!(
TopologyIndex::new(&nested)
.resolve(f.outer, &endpoint(&[f.r1, f.inner], f.b))
.is_ok()
);
assert!(
TopologyIndex::new(&nested)
.resolve(f.outer, &endpoint(&[f.r2, f.inner], f.b))
.is_err()
);
}
}
#[test]
fn source7_context_lifecycle_source_history_and_closed_admission() {
let f = chain(Layer::Oa);
let relation = link(
&f,
ContextualLinkPurpose::Precedence {
guard: "ready".into(),
},
);
let mut session = Session::new(f.p.clone());
session
.execute(Command::CreateRelation(relation.clone()))
.unwrap();
let after = session.project().clone();
let source = language::format(&after);
assert_eq!(language::parse(&source).unwrap(), after);
assert_eq!(
persistence::decode(&serde_json::to_vec(&after).unwrap()).unwrap(),
after
);
session.undo();
assert_eq!(session.project(), &f.p);
session.redo();
assert_eq!(session.project(), &after);
session.execute(Command::Delete(f.r1)).unwrap();
assert!(!session.project().relations.contains_key(&relation.id));
session.undo();
assert_eq!(session.project(), &after);
let fresh = Id::new_v4();
let mut reidentified = after.clone();
references::remap_references(&mut reidentified, f.r1, fresh);
let mut e = reidentified.elements.remove(&f.r1).unwrap();
e.id = fresh;
reidentified.elements.insert(fresh, e);
valid(&reidentified);
assert!(
TopologyIndex::new(&reidentified)
.resolve(f.outer, &endpoint(&[fresh], f.b))
.is_ok()
);
for version in [4, 5, 6] {
let old = source.replacen("arcadia 7;", &format!("arcadia {version};"), 1);
assert!(language::parse(&old).is_err());
let mut json = serde_json::to_value(&after).unwrap();
json["schema_version"] = version.into();
assert!(persistence::decode(&serde_json::to_vec(&json).unwrap()).is_err());
}
let mut direct = relation.clone();
if let RelationBody::ContextualLink { target, .. } = &mut direct.body {
*target = endpoint(&[], f.x);
}
assert!(execute_batch(&f.p, &[Command::CreateRelation(direct)]).is_err());
}
fn operation_id(n: u128) -> Id {
Id::from_u128(0x52060906000040008000000000000000 + n)
}
fn operations_fixture() -> Project {
persistence::decode(include_bytes!(
"../../../harness/fixtures/interface-operation-probe/output/boundary-only.json"
))
.unwrap()
}
#[test]
fn source7_operation_reverse_unoriented_call_and_reply_use_actual_binding() {
let mut p = operations_fixture();
p.relations
.get_mut(&operation_id(34))
.unwrap()
.body
.set_endpoints(operation_id(13), operation_id(12));
for (call, allocation) in [
(operation_id(43), operation_id(30)),
(operation_id(44), operation_id(31)),
] {
p.elements
.insert(call, operation_edit(&p, call, Some(allocation)));
}
let reply = occurrence(
&mut p,
operation_id(40),
"Exact reset reply",
OccurrenceBody::Exchange {
exchange: Some(operation_id(34)),
sender: Some(operation_id(42)),
receiver: Some(operation_id(41)),
message_kind: MessageKind::Reply,
reply_to: Some(operation_id(43)),
values: vec![],
operation: None,
},
);
valid(&p);
for message in [operation_id(43), reply] {
let OperationResolution::Selected(proof) = resolve(&p, message).unwrap() else {
panic!()
};
assert_eq!(proof.invocation, operation_id(43));
assert_eq!(proof.allocation, operation_id(30));
assert_eq!(
(proof.caller_port, proof.provider_port),
(operation_id(12), operation_id(13))
);
}
let mut other = p.elements[&reply].clone();
if let Some(SharedBody::Occurrence {
body: OccurrenceBody::Exchange { reply_to, .. },
..
}) = other.shared_mut()
{
*reply_to = Some(operation_id(44));
}
let q = execute_batch(&p, &[Command::EditElement(reply, other)]).unwrap();
let OperationResolution::Selected(proof) = resolve(&q, reply).unwrap() else {
panic!()
};
assert_eq!(proof.invocation, operation_id(44));
assert_eq!(proof.allocation, operation_id(31));
let mut oriented = p.relations[&operation_id(34)].clone();
if let RelationBody::BoundaryExchange { oriented, .. } = &mut oriented.body {
*oriented = true;
}
assert!(execute_batch(&p, &[Command::EditRelation(oriented.id, oriented)]).is_err());
assert_eq!(
persistence::decode(&serde_json::to_vec(&q).unwrap()).unwrap(),
q
);
}
#[test]
fn source7_operation_inherited_exact_allocation_is_not_a_same_item_substitute() {
let mut p = operations_fixture();
let derived = p
.add_element(
Kind::Interface,
Layer::Sa,
"Specialized provider service",
None,
)
.unwrap();
*p.elements.get_mut(&derived).unwrap().shared_mut().unwrap() = SharedBody::Interface {
supertype: Some(operation_id(22)),
};
p.relations
.get_mut(&operation_id(33))
.unwrap()
.body
.set_endpoints(operation_id(13), derived);
let call = operation_id(43);
p.elements
.insert(call, operation_edit(&p, call, Some(operation_id(30))));
valid(&p);
let OperationResolution::Selected(proof) = resolve(&p, call).unwrap() else {
panic!()
};
assert_eq!(proof.allocation, operation_id(30));
let other = p
.add_element(Kind::Interface, Layer::Sa, "Separate reset contract", None)
.unwrap();
let mut separate = p.relations[&operation_id(30)].clone();
separate.id = Id::new_v4();
separate.alias = "separately_allocated_reset".into();
separate.body.set_endpoints(other, operation_id(20));
let separate_id = separate.id;
p.relations.insert(separate.id, separate);
p.relate(
RelationKind::InterfaceProvision,
"Also provides separate reset",
operation_id(13),
other,
);
valid(&p);
// Same item and actual provider are insufficient: the caller requires only
// the inherited allocation's interface, not this separately allocated one.
let candidate = operation_edit(&p, call, Some(separate_id));
let mut probe = p.clone();
probe.elements.insert(call, candidate.clone());
assert_eq!(resolve(&probe, call).unwrap_err().code, "operation_roles");
assert!(execute_batch(&p, &[Command::EditElement(call, candidate.clone())]).is_err());
p.relate(
RelationKind::InterfaceRequirement,
"Explicitly requires separate reset",
operation_id(12),
other,
);
let q = execute_batch(&p, &[Command::EditElement(call, candidate)]).unwrap();
let OperationResolution::Selected(proof) = resolve(&q, call).unwrap() else {
panic!()
};
assert_eq!(proof.allocation, separate_id);
assert_eq!(proof.item, operation_id(20));
}
#[test]
fn source7_operation_transmission_matrix_rejects_mismatch_and_warns_unspecified() {
for kind in [MessageKind::Call, MessageKind::Asynchronous] {
for protocol in [
TransmissionProtocol::Synchronous,
TransmissionProtocol::Asynchronous,
TransmissionProtocol::Unspecified,
] {
let p = operations_fixture();
let call = operation_id(43);
let mut candidate = operation_edit(&p, call, Some(operation_id(30)));
if let Some(SharedBody::Occurrence {
body: OccurrenceBody::Exchange { message_kind, .. },
..
}) = candidate.shared_mut()
{
*message_kind = kind.clone();
}
let mut allocation = p.relations[&operation_id(30)].clone();
if let RelationBody::InterfaceItemAllocation { transmission, .. } = &mut allocation.body
{
*transmission = protocol;
}
let commands = [
Command::EditElement(call, candidate.clone()),
Command::EditRelation(allocation.id, allocation.clone()),
];
let allowed = matches!(
(&kind, protocol),
(_, TransmissionProtocol::Unspecified)
| (MessageKind::Call, TransmissionProtocol::Synchronous)
| (
MessageKind::Asynchronous,
TransmissionProtocol::Asynchronous
)
);
let result = execute_batch(&p, &commands);
if allowed {
let q = result.unwrap();
assert!(matches!(
resolve(&q, call),
Ok(OperationResolution::Selected(_))
));
let warned = validation::validate(&q).iter().any(|i| {
i.code == "operation_transmission_unspecified"
&& i.severity == Severity::Warning
});
assert_eq!(warned, protocol == TransmissionProtocol::Unspecified);
} else {
assert!(result.is_err(), "{kind:?} with {protocol:?}");
let mut probe = p.clone();
probe.elements.insert(call, candidate);
probe.relations.insert(allocation.id, allocation);
assert_eq!(
resolve(&probe, call).unwrap_err().code,
"operation_transmission"
);
}
}
}
}
#[test]
fn source7_operation_allocation_delete_repairs_both_call_and_separate_reply_channels() {
let mut p = operations_fixture();
let call = operation_id(43);
let allocation = operation_id(30);
let field = p
.add_element(
Kind::DataField,
Layer::Sa,
"Retained bidirectional value",
Some(operation_id(20)),
)
.unwrap();
if let Some(SharedBody::DataField { direction, .. }) =
p.elements.get_mut(&field).unwrap().shared_mut()
{
*direction = ParameterDirection::InOut;
}
let value = MessageValue {
path: vec![],
field,
expression: "unfinished(\n preserve exactly".into(),
};
let mut invocation = operation_edit(&p, call, Some(allocation));
if let Some(SharedBody::Occurrence {
body: OccurrenceBody::Exchange { values, .. },
..
}) = invocation.shared_mut()
{
values.push(value.clone());
}
p.elements.insert(call, invocation);
let mut response = p.relations[&operation_id(34)].clone();
response.id = Id::new_v4();
response.alias = "explicit_reset_response".into();
response
.body
.set_endpoints(operation_id(13), operation_id(12));
if let RelationBody::BoundaryExchange { items, .. } = &mut response.body {
*items = vec![operation_id(20)];
}
let response_id = response.id;
p.relations.insert(response.id, response);
let reply = occurrence(
&mut p,
operation_id(40),
"Explicit reset response",
OccurrenceBody::Exchange {
exchange: Some(response_id),
sender: Some(operation_id(42)),
receiver: Some(operation_id(41)),
message_kind: MessageKind::Reply,
reply_to: Some(call),
values: vec![value.clone()],
operation: None,
},
);
valid(&p);
let repair = |id| {
let mut channel = p.relations[&id].clone();
if let RelationBody::BoundaryExchange { items, .. } = &mut channel.body {
items.retain(|item| *item != operation_id(20));
}
Command::EditRelation(id, channel)
};
let mut session = Session::new(p.clone());
let source = language::format(&p);
assert!(
session
.execute(Command::Batch(vec![
Command::Delete(allocation),
repair(operation_id(34))
]))
.is_err()
);
assert_eq!(session.project(), &p);
assert_eq!(language::format(session.project()), source);
session
.execute(Command::Batch(vec![
Command::Delete(allocation),
repair(operation_id(34)),
repair(response_id),
]))
.unwrap();
let after = session.project().clone();
for message in [call, reply] {
assert!(
matches!(resolve(&after, message), Ok(OperationResolution::Unresolved { invocation, .. }) if invocation == call)
);
let Some(SharedBody::Occurrence {
body: OccurrenceBody::Exchange { values, .. },
..
}) = after.elements[&message].shared()
else {
panic!()
};
assert_eq!(values, &vec![value.clone()]);
}
session.undo();
assert_eq!(session.project(), &p);
assert_eq!(language::format(session.project()), source);
session.redo();
assert_eq!(session.project(), &after);
}
fn operation_edit(p: &Project, message: Id, allocation: Option<Id>) -> Element {
let mut e = p.elements[&message].clone();
if let Some(SharedBody::Occurrence {
body: OccurrenceBody::Exchange { operation, .. },
..
}) = e.shared_mut()
{
*operation = Some(OperationUse { allocation });
}
e
}
#[test]
fn source7_parameterless_operations_have_exact_protocol_identity_and_deletion_intent() {
let p = operations_fixture();
let a = operation_id(43);
let b = operation_id(44);
let reset = operation_id(30);
let status = operation_id(31);
let q = execute_batch(
&p,
&[
Command::EditElement(a, operation_edit(&p, a, Some(reset))),
Command::EditElement(b, operation_edit(&p, b, Some(status))),
],
)
.unwrap();
let OperationResolution::Selected(first) = resolve(&q, a).unwrap() else {
panic!()
};
let OperationResolution::Selected(second) = resolve(&q, b).unwrap() else {
panic!()
};
assert_ne!(first.allocation, second.allocation);
assert_ne!(first.item, second.item);
assert_eq!(language::parse(&language::format(&q)).unwrap(), q);
assert!(execute_batch(&q, &[Command::Delete(reset)]).is_err());
let mut q = q;
let another = q
.add_element(Kind::Interface, Layer::Sa, "Other protocol", None)
.unwrap();
q.relate(
RelationKind::InterfaceProvision,
"Other provided protocol",
operation_id(13),
another,
);
q.relate(
RelationKind::InterfaceRequirement,
"Other required protocol",
operation_id(12),
another,
);
let mut retained = q.relations[&reset].clone();
retained.body.set_endpoints(another, operation_id(20));
retained.id = Id::new_v4();
retained.alias = "remaining_operation_allocation".into();
q.relations.insert(retained.id, retained);
valid(&q);
let mut session = Session::new(q.clone());
session.execute(Command::Delete(reset)).unwrap();
assert!(matches!(
resolve(session.project(), a),
Ok(OperationResolution::Unresolved { .. })
));
assert!(matches!(
resolve(session.project(), b),
Ok(OperationResolution::Selected(_))
));
session.undo();
assert_eq!(session.project(), &q);
session.redo();
assert!(matches!(
resolve(session.project(), a),
Ok(OperationResolution::Unresolved { .. })
));
let mut wrong = q.elements[&a].clone();
if let Some(SharedBody::Occurrence {
body: OccurrenceBody::Exchange { operation, .. },
..
}) = wrong.shared_mut()
{
*operation = Some(OperationUse {
allocation: Some(first.item),
});
}
assert!(execute_batch(&q, &[Command::EditElement(a, wrong)]).is_err());
let source = language::format(&q);
for version in [5, 6] {
assert!(
language::parse(&source.replacen("arcadia 7;", &format!("arcadia {version};"), 1))
.is_err()
);
}
}
#[test]
fn source7_ordinary_old_calls_keep_bytes_and_explicit_null_is_not_old_admission() {
let bytes = include_bytes!(
"../../../harness/fixtures/interface-operation-probe/output/boundary-only.json"
);
let old: serde_json::Value = serde_json::from_slice(bytes).unwrap();
let p = operations_fixture();
let current = serde_json::to_value(&p).unwrap();
assert_eq!(old["elements"], current["elements"]);
for version in [5, 6] {
let mut old = old.clone();
old["schema_version"] = version.into();
let current = persistence::decode(&serde_json::to_vec(&old).unwrap()).unwrap();
assert_eq!(
serde_json::to_value(&current).unwrap()["elements"],
old["elements"]
);
let e = &mut old["elements"][operation_id(43).to_string()]["body"]["Sa"]["Shared"]["Occurrence"]
["body"]["Exchange"];
e["operation"] = serde_json::Value::Null;
assert!(persistence::decode(&serde_json::to_vec(&old).unwrap()).is_err());
}
}
#[test]
fn source7_allocation_removal_requires_independent_channel_repair_and_keeps_values() {
let mut p = operations_fixture();
let call = operation_id(43);
let allocation = operation_id(30);
let mut field = p
.make_element(
Kind::DataField,
Layer::Sa,
"Reset reason",
Some(operation_id(20)),
)
.unwrap();
if let Some(SharedBody::DataField { direction, .. }) = field.shared_mut() {
*direction = ParameterDirection::In;
}
let field_id = field.id;
p.elements.insert(field.id, field);
let mut selected = operation_edit(&p, call, Some(allocation));
if let Some(SharedBody::Occurrence {
body: OccurrenceBody::Exchange { values, .. },
..
}) = selected.shared_mut()
{
values.push(MessageValue {
path: vec![],
field: field_id,
expression: "unfinished expression (\n preserve".into(),
});
}
p = execute_batch(&p, &[Command::EditElement(call, selected)]).unwrap();
let before = p.clone();
assert!(execute_batch(&p, &[Command::Delete(allocation)]).is_err());
assert_eq!(p, before);
let mut channel = p.relations[&operation_id(34)].clone();
if let RelationBody::BoundaryExchange { items, .. } = &mut channel.body {
items.retain(|id| *id != operation_id(20));
}
let after = execute_batch(
&p,
&[
Command::Delete(allocation),
Command::EditRelation(channel.id, channel),
],
)
.unwrap();
assert!(matches!(
resolve(&after, call),
Ok(OperationResolution::Unresolved { .. })
));
let values = |p: &Project| match p.elements[&call].shared().unwrap() {
SharedBody::Occurrence {
body: OccurrenceBody::Exchange { values, .. },
..
} => values.clone(),
_ => panic!(),
};
assert_eq!(values(&after), values(&p));
assert!(
execute_batch(
&after,
&[Command::EditElement(
call,
operation_edit(&after, call, Some(operation_id(31)))
)]
)
.is_err()
);
}
#[test]
fn source7_reply_derives_call_signature_and_void_return_channel_still_carries_operation() {
let mut p = operations_fixture();
let call = operation_id(43);
p = execute_batch(
&p,
&[Command::EditElement(
call,
operation_edit(&p, call, Some(operation_id(30))),
)],
)
.unwrap();
let reply = occurrence(
&mut p,
operation_id(40),
"Reset reply",
OccurrenceBody::Exchange {
exchange: Some(operation_id(34)),
sender: Some(operation_id(42)),
receiver: Some(operation_id(41)),
message_kind: MessageKind::Reply,
reply_to: Some(call),
values: vec![],
operation: None,
},
);
valid(&p);
let OperationResolution::Selected(resolved) = resolve(&p, reply).unwrap() else {
panic!()
};
assert_eq!(resolved.invocation, call);
assert_eq!(resolved.provider_port, operation_id(13));
assert_eq!(resolved.item, operation_id(20));
assert!(
execute_batch(
&p,
&[Command::EditElement(reply, operation_edit(&p, reply, None))]
)
.is_err()
);
let mut response = p.relations[&operation_id(34)].clone();
response.id = Id::new_v4();
response.alias = "separate_response".into();
response
.body
.set_endpoints(operation_id(13), operation_id(12));
if let RelationBody::BoundaryExchange { items, .. } = &mut response.body {
*items = vec![operation_id(21)];
}
let response_id = response.id;
p.relations.insert(response.id, response);
let mut changed = p.elements[&reply].clone();
if let Some(SharedBody::Occurrence {
body: OccurrenceBody::Exchange { exchange, .. },
..
}) = changed.shared_mut()
{
*exchange = Some(response_id);
}
assert!(execute_batch(&p, &[Command::EditElement(reply, changed.clone())]).is_err());
if let RelationBody::BoundaryExchange { items, .. } =
&mut p.relations.get_mut(&response_id).unwrap().body
{
*items = vec![operation_id(20)];
}
assert!(execute_batch(&p, &[Command::EditElement(reply, changed)]).is_ok());
}
#[test]
fn source7_transition_preserves_qualified_paths_and_requires_reviewed_operation_omission() {
let mut f = chain(Layer::Oa);
let relation = link(
&f,
ContextualLinkPurpose::Precedence {
guard: "ready".into(),
},
);
f.p = execute_batch(&f.p, &[Command::CreateRelation(relation.clone())]).unwrap();
let mut request = TransitionRequest::new(TransitionDirection::OaToSa, vec![f.outer]);
request.scope = DependencyScope::Required;
let plan = plan_transition(&f.p, &request).unwrap();
assert!(plan.conflicts.is_empty(), "{:?}", plan.conflicts);
let q = execute_batch(&f.p, &plan.commands).unwrap();
let mapped = |id| plan.changes.iter().find(|c| c.source == id).unwrap().target;
let RelationBody::ContextualLink { owner, target, .. } =
&q.relations[&mapped(relation.id)].body
else {
panic!()
};
assert_eq!(*owner, mapped(f.outer));
assert_eq!(target.references, vec![mapped(f.r1)]);
assert_eq!(target.occurrence, mapped(f.b));
let p = operations_fixture();
let call = operation_id(43);
let q = execute_batch(
&p,
&[Command::EditElement(
call,
operation_edit(&p, call, Some(operation_id(30))),
)],
)
.unwrap();
let mut request = TransitionRequest::new(TransitionDirection::SaToOa, vec![operation_id(40)]);
request.scope = DependencyScope::Required;
let missing = plan_transition(&q, &request).unwrap();
assert!(
missing
.conflicts
.iter()
.any(|c| c.source == Some(call) && c.message.contains("omission"))
);
request.mappings = vec![
TransitionMapping {
source: operation_id(10),
target: None,
target_kind: Some(Kind::Actor),
},
TransitionMapping {
source: operation_id(11),
target: None,
target_kind: Some(Kind::Entity),
},
];
request.operation_omissions.push(OperationOmission {
source: call,
rationale: "Operational conversation abstracts the technical service signature.".into(),
});
let plan = plan_transition(&q, &request).unwrap();
assert!(plan.conflicts.is_empty(), "{:?}", plan.conflicts);
let after = execute_batch(&q, &plan.commands).unwrap();
let target = plan
.changes
.iter()
.find(|c| c.source == call)
.unwrap()
.target;
assert!(matches!(
resolve(&after, target),
Ok(OperationResolution::Ordinary)
));
assert!(matches!(
resolve(&after, call),
Ok(OperationResolution::Selected(_))
));
request
.operation_omissions
.push(request.operation_omissions[0].clone());
assert!(!plan_transition(&q, &request).unwrap().conflicts.is_empty());
}
#[test]
fn source7_assembly_purposes_do_not_invent_precedence_or_global_coverage() {
for layer in [Layer::Oa, Layer::Sa] {
let mut f = chain(layer);
let index = TopologyIndex::new(&f.p);
let x = index
.resolve(f.outer, &endpoint(&[], f.x))
.unwrap()
.work
.unwrap();
let b = index
.resolve(f.outer, &endpoint(&[f.r1], f.b))
.unwrap()
.work
.unwrap();
let exchange = Id::new_v4();
if layer == Layer::Oa {
f.p.relations.insert(
exchange,
Relation {
id: exchange,
alias: "assembly_exchange".into(),
name: "Supply B".into(),
description: String::new(),
body: RelationBody::Interaction {
source: x,
target: b,
items: vec![],
},
},
);
} else {
let request = FunctionalExchangeRequest {
id: exchange,
alias: "assembly_exchange".into(),
name: "Supply B".into(),
description: String::new(),
source: x,
target: b,
source_port: PortChoice::Auto,
target_port: PortChoice::Auto,
items: vec![],
};
f.p = execute_batch(&f.p, &plan_functional_exchange(&f.p, &request).unwrap()).unwrap();
}
let relation = link(&f, ContextualLinkPurpose::Exchange { exchange });
let p = execute_batch(&f.p, &[Command::CreateRelation(relation.clone())]).unwrap();
let analysis = TopologyIndex::new(&p).analyze(f.outer, AnalysisLimits::default());
assert!(analysis.complete);
assert_eq!(analysis.assembly.len(), 1);
assert!(
!analysis
.precedence
.iter()
.any(|e| e.relation == Some(relation.id))
);
assert!(
!analysis
.notices
.iter()
.any(|n| n.code == "ambiguous_sequence_branch")
);
assert!(
view_options::behavior_evidence(&p, [f.outer])
.exchanges
.contains(&exchange)
);
assert!(
!view_options::behavior_evidence(&p, [f.procedure])
.exchanges
.contains(&exchange)
);
let mut reverse = relation.clone();
if let RelationBody::ContextualLink { source, target, .. } = &mut reverse.body {
std::mem::swap(source, target);
}
assert!(execute_batch(&p, &[Command::EditRelation(relation.id, reverse)]).is_err());
let mut common = relation.clone();
common.body = RelationBody::ContextualLink {
owner: f.outer,
source: endpoint(&[f.r1], f.b),
target: endpoint(&[f.r2], f.b),
purpose: ContextualLinkPurpose::CommonWork { work: b },
};
let q = execute_batch(&p, &[Command::EditRelation(relation.id, common.clone())]).unwrap();
let analysis = TopologyIndex::new(&q).analyze(f.outer, AnalysisLimits::default());
assert_eq!(analysis.assembly.len(), 1);
assert!(
!analysis
.notices
.iter()
.any(|n| n.code == "ambiguous_sequence_branch")
);
assert!(
!view_options::behavior_evidence(&q, [f.outer])
.exchanges
.contains(&exchange)
);
if let RelationBody::ContextualLink { purpose, .. } = &mut common.body {
*purpose = ContextualLinkPurpose::CommonWork { work: x };
}
assert!(execute_batch(&q, &[Command::EditRelation(relation.id, common)]).is_err());
let removed = execute_batch(&p, &[Command::Delete(exchange)]).unwrap();
assert!(!removed.relations.contains_key(&relation.id));
}
}
#[test]
fn source7_source_bundle_migration_is_token_only_and_new_link_stays_with_caller() {
let f = chain(Layer::Oa);
let mut bundle = source_project::format_bundle(&f.p);
let entry = bundle.files.get_mut(&bundle.entry).unwrap();
*entry = entry
.replacen("arcadia 7;", "arcadia // preserve this header\r\n 6;", 1)
.replace('\n', "\r\n");
let old = bundle.clone();
let compiled = source_project::compile_bundle(&bundle, Default::default()).unwrap();
assert_eq!(compiled.decoded.project, f.p);
assert_eq!(compiled.decoded.migration.as_ref().unwrap().from_schema, 6);
for (path, source) in &old.files {
let expected = if path == &old.entry {
source.replacen(" 6;", " 7;", 1)
} else {
source.clone()
};
assert_eq!(compiled.bundle.files[path], expected);
}
let relation = link(
&f,
ContextualLinkPurpose::Precedence {
guard: "caller only".into(),
},
);
let after = execute_batch(&f.p, &[Command::CreateRelation(relation.clone())]).unwrap();
let updated = source_project::update_bundle(&compiled, &after, Default::default()).unwrap();
assert_eq!(
updated.index.declarations[&relation.id].path,
updated.index.declarations[&f.outer].path
);
let procedure_file = &compiled.index.declarations[&f.procedure].path;
assert_eq!(
compiled.bundle.files[procedure_file],
updated.bundle.files[procedure_file]
);
assert_eq!(
source_project::compile_bundle(&updated.bundle, Default::default())
.unwrap()
.decoded
.project,
after
);
}
#[test]
fn source7_semantic_facets_retain_typed_context_and_operation_references() {
let f = chain(Layer::Oa);
let relation = link(
&f,
ContextualLinkPurpose::Precedence {
guard: "uuid-shaped prose remains prose".into(),
},
);
let p = execute_batch(&f.p, &[Command::CreateRelation(relation.clone())]).unwrap();
let facet = capture_facet(
&p,
relation.id,
relation.id,
&FacetSelector::Relation(RelationFacet::ContextTarget),
)
.unwrap();
let mut baseline = SemanticBaseline {
facets: vec![facet],
};
let new = Id::new_v4();
visit_semantic_references(&mut baseline, |id| {
if *id == f.r1 {
*id = new
}
});
let FacetReview::Relation(RelationReview::ContextTarget(review)) = &baseline.facets[0] else {
panic!()
};
let Observed::Value(target) = &review.oa else {
panic!()
};
assert_eq!(target.references, vec![new]);
assert_eq!(target.occurrence, f.b);
let source = capture_facet(
&p,
relation.id,
relation.id,
&FacetSelector::Relation(RelationFacet::Source),
);
assert!(
source
.unwrap_err()
.contains("must apply on at least one side")
);
let p = operations_fixture();
let call = operation_id(43);
let facet = capture_facet(
&p,
Id::nil(),
call,
&FacetSelector::Occurrence(OccurrenceFacet::Operation),
)
.unwrap();
assert!(matches!(
facet,
FacetReview::Occurrence(OccurrenceReview::Operation(Reviewed {
oa: Observed::NotApplicable,
sa: Observed::Value(None)
}))
));
let q = execute_batch(
&p,
&[Command::EditElement(
call,
operation_edit(&p, call, Some(operation_id(30))),
)],
)
.unwrap();
let facet = capture_facet(
&q,
Id::nil(),
call,
&FacetSelector::Occurrence(OccurrenceFacet::Operation),
)
.unwrap();
let mut baseline = SemanticBaseline {
facets: vec![facet],
};
visit_semantic_references(&mut baseline, |id| {
if *id == operation_id(30) {
*id = new
}
});
let FacetReview::Occurrence(OccurrenceReview::Operation(review)) = &baseline.facets[0] else {
panic!()
};
assert_eq!(
review.sa,
Observed::Value(Some(OperationUse {
allocation: Some(new)
}))
);
}
#[test]
fn source7_selected_operation_requires_channel_lifelines_and_proven_direction() {
let p = operations_fixture();
let call = operation_id(43);
for missing in ["channel", "lifeline", "direction"] {
let mut candidate = p.clone();
let mut message = operation_edit(&candidate, call, Some(operation_id(30)));
let Some(SharedBody::Occurrence {
body: OccurrenceBody::Exchange {
exchange, sender, ..
},
..
}) = message.shared_mut()
else {
panic!()
};
match missing {
"channel" => *exchange = None,
"lifeline" => *sender = None,
_ => {
let Some(SharedBody::Lifeline { represented }) = candidate
.elements
.get_mut(&operation_id(41))
.unwrap()
.shared_mut()
else {
panic!()
};
*represented = None;
}
}
candidate.elements.insert(call, message.clone());
let code = resolve(&candidate, call).unwrap_err().code;
assert_eq!(
code,
match missing {
"channel" => "operation_channel",
"lifeline" => "operation_lifelines",
_ => "operation_direction",
}
);
assert!(execute_batch(&p, &[Command::Replace(candidate.clone())]).is_err());
let unresolved = operation_edit(&candidate, call, None);
candidate.elements.insert(call, unresolved);
valid(&candidate);
assert!(matches!(
resolve(&candidate, call),
Ok(OperationResolution::Unresolved { .. })
));
}
}
#[test]
fn source7_context_resource_limits_are_per_request_and_count_authored_paths() {
let mut f = chain(Layer::Oa);
for n in 0..100 {
occurrence(
&mut f.p,
f.outer,
&format!("Reuse {n}"),
OccurrenceBody::SequenceReference {
sequence: Some(f.procedure),
},
);
}
let relation = link(
&f,
ContextualLinkPurpose::Precedence {
guard: String::new(),
},
);
f.p = execute_batch(&f.p, &[Command::CreateRelation(relation)]).unwrap();
let old_steps: usize =
f.p.elements
.values()
.map(|e| e.steps().map_or(0, Vec::len))
.sum();
let mut limits = language::ParseLimits::UNLIMITED;
limits.max_step_references = old_steps;
assert!(limits.check_model(&f.p).is_err());
limits.max_step_references += 1;
assert!(limits.check_model(&f.p).is_ok());
let cold = TopologyIndex::with_cache_capacity(&f.p, 0);
let hot = TopologyIndex::with_cache_capacity(&f.p, 1);
let full = cold.analyze(
f.outer,
AnalysisLimits {
nodes: 1000,
edges: 2000,
},
);
assert!(full.complete);
assert_eq!(full.nodes.len(), 409);
assert!(
!hot.analyze(
f.outer,
AnalysisLimits {
nodes: 300,
edges: 2000
}
)
.complete
);
let again = hot.analyze(
f.outer,
AnalysisLimits {
nodes: 1000,
edges: 2000,
},
);
assert!(again.complete);
assert_eq!(again.nodes, full.nodes);
assert_eq!(again.precedence, full.precedence);
assert_eq!(again.notices, full.notices);
let bad = endpoint(&vec![f.r1; 65], f.b);
assert_eq!(
hot.resolve(f.outer, &bad).unwrap_err().code,
"context_depth"
);
assert_eq!(hot.resolve_scope(f.outer, &[f.r1]).unwrap(), f.procedure);
}