Files
ARCADIA-Studio/examples/release1/model.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

1041 lines
28 KiB
Rust

//! Reproducible teaching model. The SA counterpart is produced by the real planner.
use oa_core::{source_project, transitions::*, validation, view_options::*, *};
use std::collections::BTreeMap;
pub fn id(alias: &str) -> Id {
Id::new_v5(
&Id::NAMESPACE_URL,
format!("https://arcadia-studio.example/lessons/pressure/{alias}").as_bytes(),
)
}
fn element(
p: &mut Project,
alias: &str,
name: &str,
kind: Kind,
layer: Layer,
parent: Option<Id>,
) -> Id {
let id = id(alias);
p.elements.insert(
id,
Element {
id,
alias: alias.into(),
name: name.into(),
description: String::new(),
parent,
body: ElementBody::new(kind, layer).unwrap(),
},
);
id
}
fn shared(p: &mut Project, id: Id, body: SharedBody) {
*p.elements.get_mut(&id).unwrap().shared_mut().unwrap() = body;
}
fn relation(p: &mut Project, alias: &str, name: &str, body: RelationBody) -> Id {
let id = id(alias);
p.relations.insert(
id,
Relation {
id,
alias: alias.into(),
name: name.into(),
description: String::new(),
body,
},
);
id
}
fn link(p: &mut Project, alias: &str, kind: RelationKind, source: Id, target: Id) -> Id {
let name = format!("{kind}: {}", p.named(target));
relation(p, alias, &name, RelationBody::new(kind, source, target))
}
fn occurrence(p: &mut Project, owner: Id, alias: &str, name: &str, body: OccurrenceBody) -> Id {
let layer = p.elements[&owner].layer();
let id = element(p, alias, name, Kind::Occurrence, layer, Some(owner));
shared(
p,
id,
SharedBody::Occurrence {
body,
precondition: String::new(),
postcondition: String::new(),
},
);
p.elements
.get_mut(&owner)
.unwrap()
.steps_mut()
.unwrap()
.push(id);
id
}
fn line(p: &mut Project, owner: Id, alias: &str, name: &str, represented: Id) -> Id {
let id = element(p, alias, name, Kind::Lifeline, Layer::Oa, Some(owner));
shared(
p,
id,
SharedBody::Lifeline {
represented: Some(represented),
},
);
id
}
fn scenario(p: &mut Project, alias: &str, name: &str, capability: Id) -> Id {
let id = element(p, alias, name, Kind::Scenario, Layer::Oa, None);
shared(
p,
id,
SharedBody::Scenario {
viewpoint: ScenarioViewpoint::ParticipantInteraction,
context: Some(capability),
precondition: "A pressure alarm has been assessed".into(),
postcondition: "The controller has received the measured pressure after repair".into(),
steps: vec![],
},
);
id
}
fn message(
exchange: Id,
sender: Id,
receiver: Id,
reply_to: Option<Id>,
field: Id,
value: &str,
) -> OccurrenceBody {
OccurrenceBody::Exchange {
operation: None,
exchange: Some(exchange),
sender: Some(sender),
receiver: Some(receiver),
message_kind: if reply_to.is_some() {
MessageKind::Reply
} else {
MessageKind::Call
},
reply_to,
values: vec![MessageValue {
path: vec![],
field,
expression: value.into(),
}],
}
}
fn diagram(
p: &mut Project,
alias: &str,
name: &str,
layer: Layer,
projection: ProjectionKind,
members: &[(&str, f32, f32)],
) -> Id {
let diagram_id = id(alias);
let nodes = members
.iter()
.map(|(member, x, y)| {
let alias = format!("{alias}_{member}");
let node = NodePlacement {
id: id(&alias),
alias,
element: id(member),
position: Position { x: *x, y: *y },
size: Size {
width: 245.,
height: 110.,
},
collapsed: false,
port_anchors: vec![],
};
(node.id, node)
})
.collect();
p.diagrams.insert(diagram_id, Diagram { id: diagram_id, alias: alias.into(), name: name.into(), description: "Teaching view: positions and filters do not change ownership or exchange identities.".into(), layer, projection, view_options: ViewOptions::default(), nodes, routes: vec![] });
diagram_id
}
pub struct Lesson {
pub project: Project,
pub before_transition: Project,
pub request: TransitionRequest,
}
pub fn build() -> Lesson {
let mut p = Project::empty();
p.id = id("pressure_restoration");
p.alias = "pressure_restoration".into();
p.name = "Restore safe water pressure — release 1 walkthrough".into();
p.description = "A controller either resets a valve remotely or requests field repair. Compare the operational need with the explicitly chosen coordination-service boundary. Start with README.md beside project.arcadia. Units and guards are descriptive; this model does not simulate a real water network.".into();
let control = element(
&mut p,
"oa_control",
"Control room",
Kind::Entity,
Layer::Oa,
None,
);
let technician = element(
&mut p,
"oa_technician",
"Field technician",
Kind::Actor,
Layer::Oa,
None,
);
let parent = element(
&mut p,
"oa_restore_work",
"Restore safe pressure",
Kind::Activity,
Layer::Oa,
None,
);
let mut works = vec![];
for (alias, name, performer) in [
("oa_assess", "Assess pressure alarm", control),
("oa_reset", "Reset valve remotely", control),
("oa_repair", "Repair valve in the field", technician),
("oa_confirm", "Confirm safe pressure", control),
] {
let work = element(&mut p, alias, name, Kind::Activity, Layer::Oa, Some(parent));
link(
&mut p,
&format!("{alias}_allocation"),
RelationKind::Allocation,
performer,
work,
);
works.push(work);
}
let [assess, reset, repair, confirm] = works[..] else {
unreachable!()
};
let mission = element(
&mut p,
"oa_mission",
"Maintain safe water supply",
Kind::OperationalMission,
Layer::Oa,
None,
);
let capability = element(
&mut p,
"oa_restore",
"Restore a safe supply",
Kind::Capability,
Layer::Oa,
None,
);
let triage = element(
&mut p,
"oa_triage",
"Assess an alarm",
Kind::Capability,
Layer::Oa,
None,
);
link(
&mut p,
"oa_mission_delivery",
RelationKind::MissionCapability,
mission,
capability,
);
link(
&mut p,
"oa_mission_participant",
RelationKind::MissionInvolvement,
control,
mission,
);
link(
&mut p,
"oa_include_triage",
RelationKind::CapabilityInclude,
capability,
triage,
);
link(
&mut p,
"oa_assessment_support",
RelationKind::CapabilityInvolvement,
assess,
triage,
);
let pressure = element(
&mut p,
"oa_pressure",
"Measured pressure",
Kind::DataType,
Layer::Oa,
None,
);
shared(
&mut p,
pressure,
SharedBody::DataType {
definition: DataTypeDefinition::Real {
minimum: Some(0.),
maximum: Some(600.),
},
default: None,
unit: "kPa".into(),
},
);
let ticket = element(
&mut p,
"oa_repair_operation",
"Valve repair",
Kind::ExchangeItem,
Layer::Oa,
None,
);
shared(
&mut p,
ticket,
SharedBody::ExchangeItem {
kind: ExchangeItemKind::Information,
mechanism: ExchangeMechanism::Operation,
supertype: None,
},
);
let measured = element(
&mut p,
"oa_pressure_parameter",
"Pressure before / after repair",
Kind::DataField,
Layer::Oa,
Some(ticket),
);
shared(
&mut p,
measured,
SharedBody::DataField {
type_ref: TypeRef::Named(pressure),
minimum: 1,
maximum: Some(1),
unit: String::new(),
direction: ParameterDirection::InOut,
storage: FieldStorage::Value,
default: None,
},
);
let request_exchange = relation(
&mut p,
"oa_repair_request",
"Request field repair",
RelationBody::Interaction {
source: assess,
target: repair,
items: vec![ticket],
},
);
let report_exchange = relation(
&mut p,
"oa_repair_report",
"Report repair result",
RelationBody::Interaction {
source: repair,
target: confirm,
items: vec![ticket],
},
);
let channel = relation(
&mut p,
"oa_contact",
"Technician service conversation",
RelationBody::CommunicationMean {
source: control,
target: technician,
oriented: false,
items: vec![ticket],
},
);
for (alias, exchange) in [
("oa_request_carriage", request_exchange),
("oa_report_carriage", report_exchange),
] {
link(
&mut p,
alias,
RelationKind::ExchangeAllocation,
channel,
exchange,
);
}
for (alias, name, family, exchanges) in [
(
"oa_repair_traffic",
"Repair work exchanges",
ExchangeFamily::Work,
vec![request_exchange, report_exchange],
),
(
"oa_contact_category",
"Field contact",
ExchangeFamily::Participant,
vec![channel],
),
] {
let category = element(&mut p, alias, name, Kind::ExchangeCategory, Layer::Oa, None);
shared(
&mut p,
category,
SharedBody::ExchangeCategory { family, exchanges },
);
}
let assessment = element(
&mut p,
"oa_assessment_procedure",
"Assess an alarm procedure",
Kind::Process,
Layer::Oa,
None,
);
occurrence(
&mut p,
assessment,
"oa_assessment_action",
"Assess pressure alarm",
OccurrenceBody::Action {
action: Some(assess),
lifeline: None,
},
);
link(
&mut p,
"oa_assessment_delivery",
RelationKind::ChainInvolvement,
assessment,
triage,
);
let field = element(
&mut p,
"oa_field_procedure",
"Field repair procedure",
Kind::Process,
Layer::Oa,
None,
);
let ask = occurrence(
&mut p,
field,
"oa_request_step",
"Request the field repair",
OccurrenceBody::Exchange {
operation: None,
exchange: Some(request_exchange),
sender: None,
receiver: None,
message_kind: MessageKind::Asynchronous,
reply_to: None,
values: vec![],
},
);
let fix = occurrence(
&mut p,
field,
"oa_repair_step",
"Repair the valve",
OccurrenceBody::Action {
action: Some(repair),
lifeline: None,
},
);
let report = occurrence(
&mut p,
field,
"oa_report_step",
"Report the measured result",
OccurrenceBody::Exchange {
operation: None,
exchange: Some(report_exchange),
sender: None,
receiver: None,
message_kind: MessageKind::Asynchronous,
reply_to: None,
values: vec![],
},
);
link(
&mut p,
"oa_request_then_repair",
RelationKind::SequenceLink,
ask,
fix,
);
link(
&mut p,
"oa_repair_then_report",
RelationKind::SequenceLink,
fix,
report,
);
let process = element(
&mut p,
"oa_response",
"Handle a pressure alarm",
Kind::Process,
Layer::Oa,
None,
);
link(
&mut p,
"oa_response_delivery",
RelationKind::ChainInvolvement,
process,
capability,
);
let start = occurrence(
&mut p,
process,
"oa_assess_step",
"Assess the alarm",
OccurrenceBody::SequenceReference {
sequence: Some(assessment),
},
);
let choice = occurrence(
&mut p,
process,
"oa_decision",
"Can a remote reset safely recover pressure?",
OccurrenceBody::Control {
kind: ControlKind::Choice,
},
);
let remote = occurrence(
&mut p,
process,
"oa_reset_step",
"Try the authorized remote reset",
OccurrenceBody::Action {
action: Some(reset),
lifeline: None,
},
);
let reuse = occurrence(
&mut p,
process,
"oa_use_field",
"Use the field repair procedure",
OccurrenceBody::SequenceReference {
sequence: Some(field),
},
);
let merge = occurrence(
&mut p,
process,
"oa_merge",
"Recovery path completed",
OccurrenceBody::Control {
kind: ControlKind::Merge,
},
);
let end = occurrence(
&mut p,
process,
"oa_confirm_step",
"Confirm safe pressure",
OccurrenceBody::Action {
action: Some(confirm),
lifeline: None,
},
);
for (alias, a, b, guard) in [
("oa_assess_then_decide", start, choice, ""),
(
"oa_remote_path",
choice,
remote,
"remote reset is authorized",
),
("oa_field_path", choice, reuse, "else"),
("oa_remote_merge", remote, merge, ""),
("oa_field_merge", reuse, merge, ""),
("oa_confirm_path", merge, end, ""),
] {
relation(
&mut p,
alias,
if guard.is_empty() { "Continue" } else { guard },
RelationBody::SequenceLink {
source: a,
target: b,
guard: guard.into(),
},
);
}
let conversation = scenario(
&mut p,
"oa_conversation",
"Repair request and measured reply",
capability,
);
let caller = line(&mut p, conversation, "oa_caller", "Controller", control);
let responder = line(
&mut p,
conversation,
"oa_responder",
"Technician",
technician,
);
let call = occurrence(
&mut p,
conversation,
"oa_call",
"Request valve repair",
message(channel, caller, responder, None, measured, "420.0"),
);
occurrence(
&mut p,
conversation,
"oa_reply",
"Return pressure after repair",
message(channel, responder, caller, Some(call), measured, "320.0"),
);
let full = scenario(
&mut p,
"oa_full_scenario",
"Restore supply through field repair",
capability,
);
let outer_control = line(&mut p, full, "oa_outer_controller", "Controller", control);
let outer_tech = line(
&mut p,
full,
"oa_outer_technician",
"Technician",
technician,
);
occurrence(
&mut p,
full,
"oa_use_conversation",
"Use the reviewed repair conversation",
OccurrenceBody::ScenarioUse {
scenario: Some(conversation),
bindings: vec![
LifelineBinding {
referenced: caller,
local: outer_control,
},
LifelineBinding {
referenced: responder,
local: outer_tech,
},
],
},
);
let machine = element(
&mut p,
"oa_monitor",
"Pressure supervision",
Kind::StateMachine,
Layer::Oa,
Some(control),
);
let region = element(
&mut p,
"oa_monitor_region",
"Supervision cycle",
Kind::Region,
Layer::Oa,
Some(machine),
);
let initial = element(
&mut p,
"oa_monitor_initial",
"Start",
Kind::Initial,
Layer::Oa,
Some(region),
);
let watch = element(
&mut p,
"oa_monitoring",
"Monitoring",
Kind::State,
Layer::Oa,
Some(region),
);
let waiting = element(
&mut p,
"oa_waiting",
"Awaiting repair",
Kind::State,
Layer::Oa,
Some(region),
);
let high = element(
&mut p,
"oa_high_pressure",
"Pressure alarm detected",
Kind::Event,
Layer::Oa,
None,
);
shared(
&mut p,
high,
SharedBody::Event {
body: EventBody::Change {
expression: "measured_pressure > operating_limit".into(),
},
},
);
link(
&mut p,
"oa_start_monitoring",
RelationKind::StateTransition,
initial,
watch,
);
let alarm = link(
&mut p,
"oa_alarm_transition",
RelationKind::StateTransition,
watch,
waiting,
);
if let RelationBody::StateTransition { triggers, .. } =
&mut p.relations.get_mut(&alarm).unwrap().body
{
triggers.push(high);
}
let normal = link(
&mut p,
"oa_normal_transition",
RelationKind::StateTransition,
waiting,
watch,
);
if let RelationBody::StateTransition { guard, .. } =
&mut p.relations.get_mut(&normal).unwrap().body
{
*guard = "repair confirmed and pressure is safe".into();
}
relation(
&mut p,
"oa_assess_on_entry",
"Assess when repair is needed",
RelationBody::StateAction {
source: waiting,
target: assess,
phase: StateActionKind::Entry,
},
);
// The engineer chooses this system boundary; the planner must never infer it.
let system = element(
&mut p,
"sa_control",
"Pressure coordination service",
Kind::System,
Layer::Sa,
None,
);
let external = element(
&mut p,
"sa_technician",
"Field technician",
Kind::SystemActor,
Layer::Sa,
None,
);
let mut request = TransitionRequest::new(
TransitionDirection::OaToSa,
p.elements
.values()
.filter(|e| e.layer() == Layer::Oa)
.map(|e| e.id)
.chain(
p.relations
.values()
.filter(|r| r.kind() != RelationKind::ExchangeAllocation)
.map(|r| r.id),
)
.collect(),
);
request.fields.clear(); // Existing selected participants retain their deliberately different names.
request.mappings = vec![
TransitionMapping {
source: control,
target: Some(system),
target_kind: None,
},
TransitionMapping {
source: technician,
target: Some(external),
target_kind: None,
},
];
let port = |alias: &str, name: &str| CommunicationPortChoice::Create {
id: id(alias),
alias: alias.into(),
name: name.into(),
description: "Explicitly chosen Standard component port for the repair service.".into(),
kind: ComponentPortKind::Standard,
orientation: ComponentPortOrientation::Unset,
};
request
.communication_endpoints
.push(CommunicationEndpointDecision {
source: channel,
source_port: port("sa_control_port", "Repair service client"),
target_port: port("sa_technician_port", "Repair service provider"),
});
let before_transition = p.clone();
let plan = plan_transition(&p, &request).expect("well-formed teaching transition");
assert!(
plan.conflicts.is_empty(),
"teaching transition conflicts: {:?}",
plan.conflicts
);
p = apply_transition(&p, &plan).expect("teaching transition is structurally valid");
// Canonical lesson aliases/UUIDs replace only generator-created identities.
// The real planner still determines every counterpart and function-port contract.
for change in &plan.changes {
let alias = before_transition
.identities()
.into_iter()
.find(|(id, _)| *id == change.source)
.unwrap()
.1
.replacen("oa_", "sa_", 1);
if let Some(e) = p.elements.get_mut(&change.target) {
e.alias = alias.clone();
}
if let Some(r) = p.relations.get_mut(&change.target) {
r.alias = alias;
}
}
let functions: Vec<_> = p
.relations
.values()
.filter_map(|r| {
if let RelationBody::FunctionalExchange {
source_port,
target_port,
..
} = r.body
{
Some((r.alias.clone(), source_port, target_port))
} else {
None
}
})
.collect();
for (alias, out, input) in functions {
p.elements.get_mut(&out).unwrap().alias = format!("{alias}_out");
p.elements.get_mut(&input).unwrap().alias = format!("{alias}_in");
}
let aliases: BTreeMap<_, _> = p
.identities()
.into_iter()
.map(|(id, alias)| (id, alias.to_owned()))
.collect();
for trace in p.traces.values_mut() {
trace.alias = format!("trace_{}", aliases[&trace.oa]);
}
let identities: Vec<_> = p
.identities()
.into_iter()
.map(|(old, alias)| (old, id(alias)))
.collect();
for (old, new) in identities {
session::reidentify(&mut p, old, new).unwrap();
}
let interface = element(
&mut p,
"sa_repair_interface",
"Valve repair service",
Kind::Interface,
Layer::Sa,
None,
);
relation(
&mut p,
"sa_repair_interface_item",
"Repair operation contract",
RelationBody::InterfaceItemAllocation {
source: interface,
target: id("sa_repair_operation"),
order: 0,
transmission: TransmissionProtocol::Synchronous,
acquisition: AcquisitionProtocol::Accept,
},
);
link(
&mut p,
"sa_client_requires",
RelationKind::InterfaceRequirement,
id("sa_control_port"),
interface,
);
link(
&mut p,
"sa_provider_provides",
RelationKind::InterfaceProvision,
id("sa_technician_port"),
interface,
);
for (name, component, port) in [
("request_out", "sa_control_port", "sa_repair_request_out"),
("request_in", "sa_technician_port", "sa_repair_request_in"),
("report_out", "sa_technician_port", "sa_repair_report_out"),
("report_in", "sa_control_port", "sa_repair_report_in"),
] {
link(
&mut p,
&format!("sa_port_{name}"),
RelationKind::PortAllocation,
id(component),
id(port),
);
}
for (alias, exchange) in [
("sa_request_carriage", "sa_repair_request"),
("sa_report_carriage", "sa_repair_report"),
] {
link(
&mut p,
alias,
RelationKind::ExchangeAllocation,
id("sa_contact"),
id(exchange),
);
}
let audit = element(
&mut p,
"sa_audit_thresholds",
"Audit pressure thresholds",
Kind::Function,
Layer::Sa,
None,
);
p.elements.get_mut(&audit).unwrap().description = "Deliberately unfinished: assigned to the service, but no behavior context yet. Model checks should expose this one work_without_behavior_context warning.".into();
link(
&mut p,
"sa_audit_allocation",
RelationKind::Allocation,
id("sa_control"),
audit,
);
let evidence = id("repair_test_evidence");
p.artifact_links.insert(
evidence,
ArtifactLink {
id: evidence,
alias: "repair_test_evidence".into(),
name: "Repair conversation acceptance notes".into(),
description: "A local teaching artifact, not proof of a real valve's safety.".into(),
owner: id("sa_conversation"),
relation: ArtifactRelation::Verifies,
uri: "evidence/repair-check.md".into(),
revision: Some("lesson-1".into()),
fragment: Some("acceptance".into()),
},
);
let layout = [
("control", 60., 80.),
("technician", 850., 80.),
("assess", 100., 170.),
("reset", 100., 330.),
("confirm", 100., 490.),
("repair", 890., 170.),
];
for prefix in ["oa", "sa"] {
let names: Vec<_> = layout
.iter()
.map(|(n, x, y)| (format!("{prefix}_{n}"), *x, *y))
.collect();
let members: Vec<_> = names.iter().map(|(n, x, y)| (n.as_str(), *x, *y)).collect();
diagram(
&mut p,
&format!("{prefix}_allocation_view"),
if prefix == "oa" {
"1 · OA responsibility and repair traffic"
} else {
"2 · SA service boundary and functions"
},
if prefix == "oa" { Layer::Oa } else { Layer::Sa },
ProjectionKind::Allocation,
&members,
);
}
let mut focused = p.diagrams[&id("sa_allocation_view")].clone();
focused.id = id("sa_service_view");
focused.alias = "sa_service_view".into();
focused.name = "3 · SA service conversation only".into();
focused.nodes = focused
.nodes
.into_values()
.map(|mut n| {
n.alias = n.alias.replacen("sa_allocation_view", "sa_service_view", 1);
n.id = id(&n.alias);
(n.id, n)
})
.collect();
focused.view_options.work_categories = CategorySelection::Any(vec![]);
focused.view_options.participant_categories =
CategorySelection::Any(vec![id("sa_contact_category")]);
focused.view_options.exchange_labels = ExchangeLabels::NameAndItems;
p.diagrams.insert(focused.id, focused);
diagram(
&mut p,
"sa_capability_view",
"4 · SA mission and included capability",
Layer::Sa,
ProjectionKind::Capabilities,
&[
("sa_control", 60., 60.),
("sa_mission", 420., 60.),
("sa_restore", 420., 260.),
("sa_triage", 800., 260.),
],
);
diagram(
&mut p,
"sa_state_view",
"5 · SA pressure supervision",
Layer::Sa,
ProjectionKind::StateMachine {
machine: id("sa_monitor"),
},
&[
("sa_monitor", 30., 30.),
("sa_monitor_region", 70., 95.),
("sa_monitor_initial", 100., 200.),
("sa_monitoring", 220., 200.),
("sa_waiting", 650., 200.),
],
);
Lesson {
project: p,
before_transition,
request,
}
}
pub fn checked_bundle(project: &Project) -> source_project::SourceBundle {
let issues = validation::validate(project);
let errors: Vec<_> = issues
.iter()
.filter(|i| i.severity == validation::Severity::Error)
.collect();
assert!(errors.is_empty(), "teaching model errors: {errors:?}");
let bundle = source_project::format_bundle(project);
let compiled =
source_project::compile_bundle(&bundle, source_project::ProjectLimits::default()).unwrap();
assert_eq!(&compiled.decoded.project, project);
bundle
}