Compare commits

...
Author SHA1 Message Date
sorokin.oleg.edu 55198ee511 Record bidirectional native lifecycle acceptance and review 2026-09-22 23:43:32 -07:00
sorokin.oleg.edu 05e4c83054 Keep transition decisions visible and add native lifecycle oracle 2026-09-22 15:06:46 -07:00
sorokin.oleg.edu d0b7c28501 Record native counterpart search verification and lifecycle checkpoint 2026-09-13 08:33:03 -07:00
sorokin.oleg.edu fe43730bec Record transition lifecycle and picker validation gates 2026-09-13 08:28:33 -07:00
sorokin.oleg.edu d46854c2dc Format inherited diagram focus context comparison 2026-09-13 08:18:32 -07:00
sorokin.oleg.edu acbfb6cc92 Make ordinary counterpart selection searchable and verify transition lifecycle 2026-09-13 08:12:59 -07:00
sorokin.oleg.edu 3f9e6a18af Record portable ordinary-text native acceptance evidence 2026-09-13 07:38:59 -07:00
sorokin.oleg.edu 7836a93edb Record portable ordinary-text integration gate evidence 2026-09-13 07:31:29 -07:00
sorokin.oleg.edu f714a25bbc Retire the obsolete source close event and use current drawer controls in tests 2026-09-13 07:10:23 -07:00
sorokin.oleg.edu cb941367d7 Keep portable evidence hashes valid after Git newline normalization 2026-09-13 06:55:39 -07:00
sorokin.oleg.edu 96b756bece Publish the reviewed ordinary and Text compatibility checkpoint 2026-09-13 06:54:43 -07:00
sorokin.oleg.edu e624d5b9c4 Align the teaching source and language contract with compact defaults 2026-09-13 06:46:08 -07:00
sorokin.oleg.edu d7c065991d Compose ordinary source defaults with the reviewed Text workspace 2026-09-13 06:33:04 -07:00
102 changed files with 14707 additions and 675 deletions
+49 -15
View File
@@ -3,6 +3,8 @@
mod semantic_source;
#[path = "migration_v2/source.rs"]
mod v2_source;
pub(crate) use crate::source_defaults::Mode as DeclarationFormat;
use crate::source_defaults::{self, Context as DefaultContext};
use crate::*;
use serde_json::{Map, Value};
use std::{collections::BTreeMap, fmt};
@@ -645,25 +647,34 @@ fn offset_for(source: &str, line: usize, column: usize) -> usize {
source.len()
}
/// Canonical output uses persistent aliases and explicit identities. Typed
/// nested records are ordinary language fields; JSON documents are not embedded.
/// Canonical output uses persistent aliases and explicit identities, omitting
/// only values supplied by source defaults. Typed nested records are ordinary
/// language fields; JSON documents are not embedded.
pub fn format(project: &Project) -> String {
let mut out = std::format!(
"arcadia {SCHEMA_VERSION};\nproject {} {} id \"{}\" {{\n description {};\n\n",
"arcadia {SCHEMA_VERSION};\nproject {} {} id \"{}\" {{\n",
project.alias,
quote(&project.name),
project.id,
quote(&project.description)
);
for (_, declaration) in format_declarations(project) {
if !project.description.is_empty() {
out.push_str(&std::format!(
" description {};\n",
quote(&project.description)
));
}
out.push('\n');
for (_, declaration) in format_declarations(project, DeclarationFormat::Compact) {
out.push_str(&declaration);
}
out.push_str("}\n");
out
}
/// The same typed declaration writer is used by flat and modular source output.
pub(crate) fn format_declarations(project: &Project) -> Vec<(Id, String)> {
/// Shared typed writer for canonical output and lossless editing. Complete edit
/// comparisons retain defaults; emitted declarations and value fragments apply
/// only the omission rules valid in their selected source context.
pub(crate) fn format_declarations(project: &Project, mode: DeclarationFormat) -> Vec<(Id, String)> {
let aliases = project
.identities()
.into_iter()
@@ -675,6 +686,7 @@ pub(crate) fn format_declarations(project: &Project) -> Vec<(Id, String)> {
let mut fields = payload(&serde_json::to_value(&e.body).unwrap());
fields.insert("description".into(), Value::String(e.description.clone()));
fields.insert("parent".into(), serde_json::to_value(e.parent).unwrap());
let fields = source_defaults::fields(DefaultContext::Element(e.kind()), &fields, mode);
write_declaration(
&mut out,
&element_word(e.kind(), e.layer()),
@@ -690,6 +702,7 @@ pub(crate) fn format_declarations(project: &Project) -> Vec<(Id, String)> {
let mut out = String::new();
let mut fields = payload(&serde_json::to_value(&r.body).unwrap());
fields.insert("description".into(), Value::String(r.description.clone()));
let fields = source_defaults::fields(DefaultContext::Relation(r.kind()), &fields, mode);
write_declaration(
&mut out,
&relation_word(r.kind()),
@@ -705,6 +718,7 @@ pub(crate) fn format_declarations(project: &Project) -> Vec<(Id, String)> {
let mut out = String::new();
let mut fields = record_fields(d);
fields.remove("nodes");
let fields = source_defaults::fields(DefaultContext::Diagram, &fields, mode);
write_declaration(
&mut out,
"diagram",
@@ -719,6 +733,7 @@ pub(crate) fn format_declarations(project: &Project) -> Vec<(Id, String)> {
let mut out = String::new();
let mut fields = record_fields(n);
fields.insert("diagram".into(), Value::String(d.id.to_string()));
let fields = source_defaults::fields(DefaultContext::Node, &fields, mode);
write_declaration(&mut out, "node", &n.alias, None, n.id, &fields, &aliases);
declarations.push((n.id, out));
}
@@ -731,7 +746,7 @@ pub(crate) fn format_declarations(project: &Project) -> Vec<(Id, String)> {
&a.alias,
Some(&a.name),
a.id,
&record_fields(a),
&source_defaults::fields(DefaultContext::Artifact, &record_fields(a), mode),
&aliases,
);
declarations.push((a.id, out));
@@ -744,7 +759,7 @@ pub(crate) fn format_declarations(project: &Project) -> Vec<(Id, String)> {
&t.alias,
Some(&t.name),
t.id,
&record_fields(t),
&source_defaults::fields(DefaultContext::Trace, &record_fields(t), mode),
&aliases,
);
declarations.push((t.id, out));
@@ -1742,7 +1757,9 @@ pub(crate) fn resolve_declarations(
*viewpoint = ScenarioViewpoint::MixedLegacy;
}
let mut body = serde_json::to_value(initial).unwrap();
let defaults = if legacy {
let defaults = if project.schema_version >= 7 {
source_defaults::parser_fields(DefaultContext::Element(kind))
} else if legacy {
crate::persistence::v3::legacy_defaults(kind)
.and_then(|value| value.as_object().cloned())
.unwrap_or_else(|| payload(&body))
@@ -1799,7 +1816,12 @@ pub(crate) fn resolve_declarations(
let mut body =
serde_json::to_value(RelationBody::new(kind, Id::nil(), Id::nil()))
.unwrap();
let merged = merge_fields(payload(&body), fields).map_err(&conversion_error)?;
let defaults = if project.schema_version >= 7 {
source_defaults::parser_fields(DefaultContext::Relation(kind))
} else {
payload(&body)
};
let merged = merge_fields(defaults, fields).map_err(&conversion_error)?;
replace_payload(&mut body, merged);
let body = serde_json::from_value(body)
.map_err(|e| conversion_error(e.to_string()))?;
@@ -1824,7 +1846,10 @@ pub(crate) fn resolve_declarations(
}
let defaults =
serde_json::json!({"description":"","layer":"Oa","projection":"Overview","routes":[],"view_options":crate::view_options::ViewOptions::default()});
let mut record = merge_fields(defaults.as_object().unwrap().clone(), fields)
let defaults = if project.schema_version >= 7 {
source_defaults::parser_fields(DefaultContext::Diagram)
} else { defaults.as_object().unwrap().clone() };
let mut record = merge_fields(defaults, fields)
.map_err(&conversion_error)?;
header(&mut record, d, true);
record.insert("nodes".into(), serde_json::json!({}));
@@ -1839,7 +1864,10 @@ pub(crate) fn resolve_declarations(
return Err(conversion_error("Missing required field 'element'.".into()));
}
let defaults = serde_json::json!({"element":null,"position":{"x":0.0,"y":0.0},"size":{"width":224.0,"height":94.0},"collapsed":false,"port_anchors":[]});
let mut record = merge_fields(defaults.as_object().unwrap().clone(), fields)
let defaults = if project.schema_version >= 7 {
source_defaults::parser_fields(DefaultContext::Node)
} else { defaults.as_object().unwrap().clone() };
let mut record = merge_fields(defaults, fields)
.map_err(&conversion_error)?;
header(&mut record, d, false);
let node: NodePlacement = serde_json::from_value(Value::Object(record))
@@ -1851,7 +1879,10 @@ pub(crate) fn resolve_declarations(
return Err(conversion_error("Missing required field 'owner'.".into()));
}
let defaults = serde_json::json!({"description":"","owner":null,"relation":"References","uri":"","revision":null,"fragment":null});
let mut record = merge_fields(defaults.as_object().unwrap().clone(), fields)
let defaults = if project.schema_version >= 7 {
source_defaults::parser_fields(DefaultContext::Artifact)
} else { defaults.as_object().unwrap().clone() };
let mut record = merge_fields(defaults, fields)
.map_err(&conversion_error)?;
header(&mut record, d, true);
let artifact = serde_json::from_value(Value::Object(record))
@@ -1867,7 +1898,10 @@ pub(crate) fn resolve_declarations(
}
}
let defaults = serde_json::json!({"description":"","oa":null,"sa":null,"kind":"Refinement","baseline":null});
let mut record = merge_fields(defaults.as_object().unwrap().clone(), fields)
let defaults = if project.schema_version >= 7 {
source_defaults::parser_fields(DefaultContext::Trace)
} else { defaults.as_object().unwrap().clone() };
let mut record = merge_fields(defaults, fields)
.map_err(&conversion_error)?;
header(&mut record, d, true);
let trace = if project.schema_version < 5 {
+1
View File
@@ -11,6 +11,7 @@ pub mod references;
pub mod routing;
pub mod semantic;
pub mod session;
mod source_defaults;
pub mod source_files;
pub mod source_project;
pub mod topology_context;
+382
View File
@@ -0,0 +1,382 @@
//! Ordinary source defaults shared by current parsing and declaration rendering.
//!
//! These are source omission defaults, not graphical creation defaults. The
//! policy operates on complete typed declaration maps. Required named fields
//! and selected constructor payloads remain distinct from omission defaults.
//! Historical versions use their existing frozen parser paths. Semantic baseline
//! history is never traversed or normalized here.
use crate::{Kind, RelationKind};
use serde_json::{Map, Value, json};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Context {
Element(Kind),
Relation(RelationKind),
Diagram,
Node,
Artifact,
Trace,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Mode {
CompleteForEdit,
Compact,
/// Internal lossless-edit fragments: retain every top-level value while
/// compacting its selected constructor's optional nested fields.
CompactValues,
}
/// Top-level field inventory for the current ordinary reader. Callers must
/// check required fields before merging; null entries only admit their names.
/// This never supplies defaults inside a selected constructor.
pub(crate) fn parser_fields(context: Context) -> Map<String, Value> {
let mut fields = declaration_defaults(context);
let required: &[&str] = match context {
Context::Element(kind) => {
fields.remove("description");
fields.remove("parent");
if kind == Kind::Port {
&["contract"]
} else {
&[]
}
}
Context::Relation(kind) => {
fields.remove("description");
match kind {
RelationKind::ContextualLink => &["owner", "source", "target", "purpose"],
RelationKind::FunctionalExchange => {
&["source", "target", "source_port", "target_port"]
}
_ => &["source", "target"],
}
}
Context::Diagram => &[],
Context::Node => &["element"],
Context::Artifact => &["owner"],
Context::Trace => &["oa", "sa"],
};
for name in required {
fields.insert((*name).into(), Value::Null);
}
fields
}
pub(crate) fn fields(
context: Context,
input: &Map<String, Value>,
mode: Mode,
) -> Map<String, Value> {
let mut result = input.clone();
restore_serialization_skips(context, &mut result);
if mode != Mode::CompleteForEdit {
compact_nested(context, &mut result);
}
if mode == Mode::Compact {
// Compact a composite and its declared default by the same nested rules
// before whole-field comparison. This also makes compact input stable:
// e.g. String{minimum_length:0} still denotes the default definition.
let mut defaults = declaration_defaults(context);
compact_nested(context, &mut defaults);
omit_defaults(&mut result, &defaults);
}
result
}
fn restore_serialization_skips(context: Context, fields: &mut Map<String, Value>) {
match context {
Context::Element(Kind::Occurrence) => {
if let Some(exchange) = constructor_payload(fields, "body", "Exchange") {
exchange
.entry("operation".to_owned())
.or_insert(Value::Null);
}
}
Context::Node => {
fields.entry("port_anchors".to_owned()).or_insert(json!([]));
}
Context::Trace => {
if let Some(baseline) = fields.get_mut("baseline").and_then(Value::as_object_mut) {
baseline.entry("semantic".to_owned()).or_insert(Value::Null);
}
}
_ => {}
}
}
fn declaration_defaults(context: Context) -> Map<String, Value> {
let mut defaults = match context {
Context::Element(kind) => element_defaults(kind),
Context::Relation(kind) => relation_defaults(kind),
Context::Diagram => object(json!({
"description": "", "layer": "Oa", "projection": "Overview",
"routes": [], "view_options": {
"hidden_edges": [], "relation_kinds": "All",
"work_categories": "All", "participant_categories": "All",
"coverage": "All", "work_contexts": "All", "exchange_labels": "Name"
}
})),
Context::Node => object(json!({
"position": {"x": 0.0, "y": 0.0},
"size": {"width": 224.0, "height": 94.0},
"collapsed": false, "port_anchors": []
})),
Context::Artifact => object(json!({
"description": "", "relation": "References", "uri": "",
"revision": null, "fragment": null
})),
Context::Trace => object(json!({
"description": "", "kind": "Refinement", "baseline": null
})),
};
match context {
Context::Element(_) => {
defaults.insert("description".into(), json!(""));
defaults.insert("parent".into(), Value::Null);
}
Context::Relation(_) => {
defaults.insert("description".into(), json!(""));
}
_ => {}
}
defaults
}
fn element_defaults(kind: Kind) -> Map<String, Value> {
if let Some(state_kind) = kind.state_kind() {
// The declaration selects this discriminator; no GUI factory is used.
return object(json!({
"kind": state_kind, "entry": "", "exit": "", "do_activity": ""
}));
}
match kind {
Kind::Process | Kind::FunctionalChain => object(json!({"steps": []})),
Kind::ExchangeItem => object(json!({
"kind": "Information", "mechanism": "Unspecified", "supertype": null
})),
Kind::ExchangeCategory => object(json!({"family": "Work", "exchanges": []})),
Kind::DataType => object(json!({
"definition": {"String": {"minimum_length": 0, "maximum_length": null}},
"default": null, "unit": ""
})),
Kind::DataField => object(json!({
"type_ref": "String", "minimum": 1, "maximum": 1, "unit": "",
"direction": "Unspecified", "storage": "Value", "default": null
})),
Kind::Interface => object(json!({"supertype": null})),
// Port.contract is required, even when its explicit value is Legacy.
Kind::Port => object(json!({"legacy": null})),
Kind::Scenario => object(json!({
"viewpoint": "MixedLegacy", "context": null,
"precondition": "", "postcondition": "", "steps": []
})),
Kind::Lifeline => object(json!({"represented": null})),
Kind::Occurrence => object(json!({
"body": {"Exchange": {
"exchange": null, "sender": null, "receiver": null,
"message_kind": "Asynchronous", "reply_to": null,
"values": [], "operation": null
}},
"precondition": "", "postcondition": ""
})),
Kind::Event => object(json!({"body": {"Signal": {"items": []}}})),
Kind::Constraint => object(json!({"expression": "", "constrained": []})),
// Unit-body declarations have no additional omission defaults. A future
// Kind reaches this branch without gaining speculative payload rules.
_ => Map::new(),
}
}
fn relation_defaults(kind: RelationKind) -> Map<String, Value> {
match kind {
RelationKind::InterfaceItemAllocation => object(json!({
"order": 0, "transmission": "Unspecified", "acquisition": "Unspecified"
})),
RelationKind::DataAssociation => object(json!({
"source_role": "", "target_role": "",
"source_minimum": 0, "target_minimum": 0,
"source_maximum": null, "target_maximum": null,
"source_navigable": false, "target_navigable": false
})),
RelationKind::Interaction | RelationKind::FunctionalExchange => {
object(json!({"items": []}))
}
RelationKind::BoundaryExchange => object(json!({"items": [], "oriented": true})),
RelationKind::CommunicationMean => object(json!({"items": [], "oriented": false})),
RelationKind::CapabilityExtend => object(json!({"condition": ""})),
RelationKind::SequenceLink => object(json!({"guard": ""})),
RelationKind::StateTransition => object(json!({
"transition_kind": "External", "trigger": "", "guard": "", "effect": "",
"triggers": [], "effects": []
})),
RelationKind::StateAction => object(json!({"phase": "Do"})),
// source/target, FunctionalExchange ports and ContextualLink's owner,
// endpoints and purpose are required, despite factory placeholder values.
_ => Map::new(),
}
}
fn compact_nested(context: Context, fields: &mut Map<String, Value>) {
match context {
Context::Element(Kind::DataType) => compact_data_definition(fields),
Context::Element(Kind::Port) => {
if let Some(legacy) = fields.get_mut("legacy").and_then(Value::as_object_mut) {
omit_nulls(legacy, &["interface"]);
}
// Every selected PortContract payload field remains required.
}
Context::Element(Kind::Occurrence) => compact_occurrence(fields),
Context::Diagram => {
if let Some(options) = fields
.get_mut("view_options")
.and_then(Value::as_object_mut)
{
omit_defaults(
options,
&object(json!({
"hidden_edges": [], "relation_kinds": "All",
"work_categories": "All", "participant_categories": "All",
"coverage": "All", "work_contexts": "All", "exchange_labels": "Name"
})),
);
}
if let Some(routes) = fields.get_mut("routes").and_then(Value::as_array_mut) {
for route in routes {
if let Some(route) = route.as_object_mut() {
omit_defaults(route, &object(json!({"waypoints": [], "label": null})));
}
}
}
// Route entries, EdgeLabel coordinates, projection machine IDs, and
// Only([])/Any([]) selectors remain even when their values are empty.
}
Context::Trace => {
if let Some(baseline) = fields.get_mut("baseline").and_then(Value::as_object_mut) {
omit_nulls(baseline, &["semantic"]);
}
// No recursion through snapshots or semantic historical facets.
}
// DataValue/TypeRef payloads, EventBody fields, Node geometry/anchors,
// contextual endpoints/purposes, and other required records stay intact.
_ => {}
}
}
fn compact_data_definition(fields: &mut Map<String, Value>) {
for tag in ["Integer", "Real"] {
if let Some(payload) = constructor_payload(fields, "definition", tag) {
omit_nulls(payload, &["minimum", "maximum"]);
}
}
if let Some(payload) = constructor_payload(fields, "definition", "String") {
omit_nulls(payload, &["maximum_length"]);
// minimum_length is required inside a selected String constructor.
}
if let Some(payload) = constructor_payload(fields, "definition", "Record") {
omit_nulls(payload, &["supertype"]);
}
// Boolean has no payload; Enumeration.literals remains required even [].
}
fn compact_occurrence(fields: &mut Map<String, Value>) {
if let Some(exchange) = constructor_payload(fields, "body", "Exchange") {
omit_defaults(
exchange,
&object(json!({
"exchange": null, "sender": null, "receiver": null,
"message_kind": "Asynchronous", "reply_to": null,
"values": [], "operation": null
})),
);
if let Some(operation) = exchange.get_mut("operation").and_then(Value::as_object_mut) {
omit_nulls(operation, &["allocation"]);
// Keep the containing operation field even if its record becomes {}:
// explicit unresolved operation intent is not an absent operation.
}
if let Some(values) = exchange.get_mut("values").and_then(Value::as_array_mut) {
for value in values {
if let Some(message_value) = value.as_object_mut() {
omit_defaults(message_value, &object(json!({"path": []})));
}
}
}
// MessageKind::Timer.expression, MessageValue.field/expression remain.
}
for (tag, optional) in [
("Action", &["action", "lifeline"][..]),
("State", &["state", "lifeline"][..]),
("SequenceReference", &["sequence"][..]),
("ScenarioUse", &["scenario"][..]),
] {
if let Some(payload) = constructor_payload(fields, "body", tag) {
omit_nulls(payload, optional);
}
}
// ScenarioUse.bindings, Control.kind, Fragment.operator/branches and
// Branch.guard/occurrences are required, including empty strings/lists.
}
fn constructor_payload<'a>(
fields: &'a mut Map<String, Value>,
key: &str,
tag: &str,
) -> Option<&'a mut Map<String, Value>> {
let wrapper = fields.get_mut(key)?.as_object_mut()?;
if wrapper.len() != 1 {
return None;
}
wrapper.get_mut(tag)?.as_object_mut()
}
fn omit_nulls(fields: &mut Map<String, Value>, names: &[&str]) {
for name in names {
if fields.get(*name).is_some_and(Value::is_null) {
fields.remove(*name);
}
}
}
fn omit_defaults(fields: &mut Map<String, Value>, defaults: &Map<String, Value>) {
for (name, default) in defaults {
if fields.get(name).is_some_and(|value| same(value, default)) {
fields.remove(name);
}
}
}
// Comparison recurses only to compare a known field with its exact declared
// default. It never traverses arbitrary data looking for values to remove.
fn same(value: &Value, default: &Value) -> bool {
match (value, default) {
(Value::Object(a), Value::Object(b)) => {
a.len() == b.len()
&& a.iter()
.all(|(key, value)| b.get(key).is_some_and(|d| same(value, d)))
}
(Value::Array(a), Value::Array(b)) => {
a.len() == b.len() && a.iter().zip(b).all(|(value, d)| same(value, d))
}
(Value::Number(a), Value::Number(b)) => {
// serde_json numeric equality alone can equate -0.0 with +0.0.
// Extracted f32 coordinates are represented exactly as f64 here.
a == b
&& match (a.as_f64(), b.as_f64()) {
(Some(x), Some(y)) if x == 0.0 && y == 0.0 => x.to_bits() == y.to_bits(),
_ => true,
}
}
_ => value == default,
}
}
fn object(value: Value) -> Map<String, Value> {
match value {
Value::Object(fields) => fields,
_ => unreachable!("literal source default is an object"),
}
}
#[cfg(test)]
#[path = "source_defaults_tests.rs"]
mod tests;
+718
View File
@@ -0,0 +1,718 @@
//! Independent literal oracles for the production source-default policy.
use super::{self as policy, Context, Mode};
use crate::{Kind, RelationKind};
use serde_json::{Value, json};
#[test]
fn complete_comparison_keeps_populated_serializer_skip_values() {
for (context, input) in [
(
Context::Element(Kind::Occurrence),
json!({"body":{"Exchange":{"operation":{"allocation":"allocation-id"},"values":[]}}}),
),
(
Context::Node,
json!({"port_anchors":[{"port":"port-id","side":"Left","offset":0.0}]}),
),
(
Context::Trace,
json!({"baseline":{"semantic":{"historical":"payload"}}}),
),
] {
assert_eq!(rendered(context, &input, Mode::CompleteForEdit), input);
}
}
#[test]
fn compact_value_fragments_keep_selected_constructor_requirements() {
for (context, input, expected) in [
(
Context::Element(Kind::DataType),
json!({"definition":{"String":{"minimum_length":0,"maximum_length":null}},"default":null,"unit":""}),
json!({"definition":{"String":{"minimum_length":0}},"default":null,"unit":""}),
),
(
Context::Element(Kind::Event),
json!({"body":{"Signal":{"items":[]}}}),
json!({"body":{"Signal":{"items":[]}}}),
),
(
Context::Element(Kind::Occurrence),
json!({"body":{"Exchange":{"operation":{"allocation":null},"values":[]}},"precondition":""}),
json!({"body":{"Exchange":{"operation":{}}},"precondition":""}),
),
(
Context::Element(Kind::Occurrence),
json!({"body":{"ScenarioUse":{"scenario":null,"bindings":[]}}}),
json!({"body":{"ScenarioUse":{"bindings":[]}}}),
),
] {
assert_eq!(rendered(context, &input, Mode::CompactValues), expected);
}
}
#[test]
fn source_scenario_default_is_independent_of_graphical_creation() {
use crate::{ElementBody, Layer, ScenarioViewpoint, SharedBody, language};
let project = language::parse(
"arcadia 7; project p \"P\" { oa_scenario o \"O\" {} sa_scenario s \"S\" {} }",
)
.unwrap();
for element in project.elements.values() {
assert!(matches!(
element.body.shared(),
Some(SharedBody::Scenario {
viewpoint: ScenarioViewpoint::MixedLegacy,
..
})
));
assert!(!matches!(
ElementBody::new(Kind::Scenario, element.layer())
.unwrap()
.shared(),
Some(SharedBody::Scenario {
viewpoint: ScenarioViewpoint::MixedLegacy,
..
})
));
}
let source = language::format(&project);
assert!(!source.contains("description"));
assert!(!source.contains("parent"));
assert!(!source.contains("viewpoint"));
assert_eq!(language::parse(&source).unwrap(), project);
let mut typed = project;
for element in typed.elements.values_mut() {
let viewpoint = if element.layer() == Layer::Oa {
ScenarioViewpoint::OperationalActivity
} else {
ScenarioViewpoint::Functional
};
*element.body.shared_mut().unwrap() = SharedBody::Scenario {
viewpoint,
context: None,
precondition: String::new(),
postcondition: String::new(),
steps: vec![],
};
}
let source = language::format(&typed);
assert!(source.contains("viewpoint OperationalActivity;"));
assert!(source.contains("viewpoint Functional;"));
assert_eq!(language::parse(&source).unwrap(), typed);
}
#[test]
fn shared_parser_defaults_preserve_required_fields_and_kind_checks() {
for (declaration, message) in [
(
"oa_port p0 \"Port\" {}",
"Missing required field 'contract'",
),
(
"allocation r \"R\" { source @owner; }",
"Missing required field 'target'",
),
(
"functional_exchange r \"R\" { source @owner; target @work; }",
"Missing required field 'source_port'",
),
(
"contextual_link r \"R\" {}",
"Missing required field 'owner'",
),
("node n { diagram @d; }", "Missing required field 'element'"),
("artifact a \"A\" {}", "Missing required field 'owner'"),
("trace t \"T\" {}", "Missing required field 'oa'"),
(
"oa_state s \"S\" { kind Initial; }",
"Declaration kind disagrees",
),
(
"oa_activity a \"A\" { unknown true; }",
"Unknown field 'unknown'",
),
(
"oa_data_type t \"T\" { definition { String {}; }; }",
"minimum_length",
),
(
"oa_occurrence o \"O\" { body { ScenarioUse {}; }; }",
"bindings",
),
] {
let source = format!(
"arcadia 7; project p \"P\" {{ oa_entity owner \"Owner\" {{}} oa_activity work \"Work\" {{}} diagram d \"D\" {{}} {declaration} }}"
);
let errors = crate::language::parse(&source).unwrap_err();
assert!(
errors.iter().any(|error| error.message.contains(message)),
"{declaration}: {errors:?}"
);
}
}
fn rendered(context: Context, input: &Value, mode: Mode) -> Value {
Value::Object(policy::fields(context, input.as_object().unwrap(), mode))
}
fn compact(context: Context, input: Value, expected: Value) {
let original = input.clone();
let result = rendered(context, &input, Mode::Compact);
assert_eq!(result, expected, "context {context:?}");
assert_eq!(input, original, "the input remains immutable");
assert_eq!(
rendered(context, &result, Mode::Compact),
result,
"compact policy is idempotent"
);
}
#[test]
fn complete_comparison_restores_only_the_three_known_skips() {
let exchange = json!({"body":{"Exchange":{"exchange":null,"sender":null,"receiver":null,"message_kind":"Asynchronous","reply_to":null,"values":[]}}});
let mut complete = exchange.clone();
complete["body"]["Exchange"]["operation"] = Value::Null;
assert_eq!(
rendered(
Context::Element(Kind::Occurrence),
&exchange,
Mode::CompleteForEdit
),
complete
);
assert_eq!(
rendered(
Context::Node,
&json!({"element":"e","diagram":"d"}),
Mode::CompleteForEdit
),
json!({"element":"e","diagram":"d","port_anchors":[]})
);
assert_eq!(
rendered(
Context::Trace,
&json!({"baseline":{"oa":{"name":"","description":""},"sa":{"name":"","description":""}}}),
Mode::CompleteForEdit
),
json!({"baseline":{"oa":{"name":"","description":""},"sa":{"name":"","description":""},"semantic":null}})
);
for input in [
json!({"body":{"Action":{"action":null,"lifeline":null}}}),
json!({"body":{"State":{"state":null,"lifeline":null}}}),
] {
assert_eq!(
rendered(
Context::Element(Kind::Occurrence),
&input,
Mode::CompleteForEdit
),
input
);
}
for input in [
json!({"baseline":null}),
json!({"baseline":{"semantic":{"historical":[null,[],false,0,""]},"oa":{"name":"n","description":""},"sa":{"name":"s","description":""}}}),
] {
assert_eq!(
rendered(Context::Trace, &input, Mode::CompleteForEdit),
input
);
}
}
#[test]
fn every_element_omits_only_empty_common_defaults() {
for kind in Kind::ALL {
// Future/unknown payloads are preservation sentinels, not admitted source.
compact(
Context::Element(kind),
json!({"description":"","parent":null,"future":{"empty":[],"absent":null,"zero":0,"flag":false}}),
json!({"future":{"empty":[],"absent":null,"zero":0,"flag":false}}),
);
compact(
Context::Element(kind),
json!({"description":" \n","parent":"owner"}),
json!({"description":" \n","parent":"owner"}),
);
}
}
#[test]
fn ordinary_element_defaults_are_context_specific() {
for kind in [Kind::Process, Kind::FunctionalChain] {
compact(Context::Element(kind), json!({"steps":[]}), json!({}));
compact(
Context::Element(kind),
json!({"steps":["second","first"]}),
json!({"steps":["second","first"]}),
);
}
compact(
Context::Element(Kind::ExchangeItem),
json!({"kind":"Information","mechanism":"Unspecified","supertype":null}),
json!({}),
);
compact(
Context::Element(Kind::ExchangeItem),
json!({"kind":"Energy","mechanism":"Flow","supertype":"base"}),
json!({"kind":"Energy","mechanism":"Flow","supertype":"base"}),
);
compact(
Context::Element(Kind::ExchangeCategory),
json!({"family":"Work","exchanges":[]}),
json!({}),
);
compact(
Context::Element(Kind::ExchangeCategory),
json!({"family":"Participant","exchanges":["x"]}),
json!({"family":"Participant","exchanges":["x"]}),
);
compact(
Context::Element(Kind::Interface),
json!({"supertype":null}),
json!({}),
);
compact(
Context::Element(Kind::Lifeline),
json!({"represented":null}),
json!({}),
);
compact(
Context::Element(Kind::Constraint),
json!({"expression":"","constrained":[]}),
json!({}),
);
}
#[test]
fn scenario_source_default_never_uses_the_graphical_viewpoint_default() {
compact(
Context::Element(Kind::Scenario),
json!({"viewpoint":"MixedLegacy","context":null,"precondition":"","postcondition":"","steps":[]}),
json!({}),
);
for viewpoint in [
"OperationalActivity",
"Functional",
"ParticipantInteraction",
"BoundaryInterface",
] {
compact(
Context::Element(Kind::Scenario),
json!({"viewpoint":viewpoint,"context":"scope","precondition":"ready","postcondition":"","steps":["two","one"]}),
json!({"viewpoint":viewpoint,"context":"scope","precondition":"ready","steps":["two","one"]}),
);
}
}
#[test]
fn each_state_header_supplies_its_own_kind() {
for (kind, name) in [
(Kind::State, "State"),
(Kind::Mode, "Mode"),
(Kind::Initial, "Initial"),
(Kind::Final, "Final"),
(Kind::Choice, "Choice"),
(Kind::Junction, "Junction"),
(Kind::Fork, "Fork"),
(Kind::Join, "Join"),
(Kind::ShallowHistory, "ShallowHistory"),
(Kind::DeepHistory, "DeepHistory"),
(Kind::EntryPoint, "EntryPoint"),
(Kind::ExitPoint, "ExitPoint"),
(Kind::Terminate, "Terminate"),
] {
compact(
Context::Element(kind),
json!({"kind":name,"entry":"","exit":"","do_activity":""}),
json!({}),
);
}
// Invalid mismatches must not be silently normalized by the writer policy.
compact(
Context::Element(Kind::Mode),
json!({"kind":"State","entry":"work"}),
json!({"kind":"State","entry":"work"}),
);
}
#[test]
fn data_field_unbounded_and_present_empty_values_are_not_defaults() {
compact(
Context::Element(Kind::DataField),
json!({"type_ref":"String","minimum":1,"maximum":1,"unit":"","direction":"Unspecified","storage":"Value","default":null}),
json!({}),
);
compact(
Context::Element(Kind::DataField),
json!({"type_ref":{"Named":"record"},"minimum":0,"maximum":null,"unit":"m","direction":"Out","storage":"Reference","default":[]}),
json!({"type_ref":{"Named":"record"},"minimum":0,"maximum":null,"unit":"m","direction":"Out","storage":"Reference","default":[]}),
);
for value in [
json!({"Boolean":false}),
json!({"Integer":0}),
json!({"Real":0.0}),
json!({"String":""}),
] {
compact(
Context::Element(Kind::DataField),
json!({"default":[value.clone()]}),
json!({"default":[value]}),
);
}
}
#[test]
fn data_definition_whole_default_and_selected_required_fields_differ() {
compact(
Context::Element(Kind::DataType),
json!({"definition":{"String":{"minimum_length":0,"maximum_length":null}},"default":null,"unit":""}),
json!({}),
);
for (input, expected) in [
(
json!({"Integer":{"minimum":null,"maximum":null}}),
json!({"Integer":{}}),
),
(
json!({"Real":{"minimum":null,"maximum":2.5}}),
json!({"Real":{"maximum":2.5}}),
),
(
json!({"String":{"minimum_length":4,"maximum_length":null}}),
json!({"String":{"minimum_length":4}}),
),
(json!({"Record":{"supertype":null}}), json!({"Record":{}})),
(
json!({"Enumeration":{"literals":[]}}),
json!({"Enumeration":{"literals":[]}}),
),
] {
compact(
Context::Element(Kind::DataType),
json!({"definition":input}),
json!({"definition":expected}),
);
}
compact(
Context::Element(Kind::DataType),
json!({"definition":"Boolean","default":{"Boolean":false}}),
json!({"definition":"Boolean","default":{"Boolean":false}}),
);
}
#[test]
fn port_contract_and_its_required_payload_stay_explicit() {
compact(
Context::Element(Kind::Port),
json!({"contract":"Legacy","legacy":{"direction":"Out","interface":null}}),
json!({"contract":"Legacy","legacy":{"direction":"Out"}}),
);
for contract in [
json!({"FunctionInput":{"incoming_items":[]}}),
json!({"FunctionOutput":{"outgoing_items":[]}}),
json!({"Component":{"kind":"Standard","orientation":"Unset"}}),
] {
compact(
Context::Element(Kind::Port),
json!({"contract":contract.clone(),"legacy":null}),
json!({"contract":contract}),
);
}
}
#[test]
fn occurrence_whole_default_is_distinct_from_selected_constructor_payload() {
compact(
Context::Element(Kind::Occurrence),
json!({"body":{"Exchange":{"exchange":null,"sender":null,"receiver":null,"message_kind":"Asynchronous","reply_to":null,"values":[]}},"precondition":"","postcondition":""}),
json!({}),
);
for (input, expected) in [
(
json!({"Action":{"action":null,"lifeline":null}}),
json!({"Action":{}}),
),
(
json!({"State":{"state":null,"lifeline":null}}),
json!({"State":{}}),
),
(
json!({"SequenceReference":{"sequence":null}}),
json!({"SequenceReference":{}}),
),
(
json!({"ScenarioUse":{"scenario":null,"bindings":[]}}),
json!({"ScenarioUse":{"bindings":[]}}),
),
(
json!({"Branch":{"guard":"","occurrences":[]}}),
json!({"Branch":{"guard":"","occurrences":[]}}),
),
(
json!({"Fragment":{"operator":"Parallel","branches":[]}}),
json!({"Fragment":{"operator":"Parallel","branches":[]}}),
),
(
json!({"Control":{"kind":"Choice"}}),
json!({"Control":{"kind":"Choice"}}),
),
] {
compact(
Context::Element(Kind::Occurrence),
json!({"body":input}),
json!({"body":expected}),
);
}
}
#[test]
fn unresolved_operation_and_required_message_value_are_preserved() {
compact(
Context::Element(Kind::Occurrence),
json!({"body":{"Exchange":{"exchange":null,"sender":null,"receiver":null,"message_kind":"Asynchronous","reply_to":null,"values":[{"field":"f","path":[],"expression":""}],"operation":{"allocation":null}}}}),
json!({"body":{"Exchange":{"values":[{"field":"f","expression":""}],"operation":{}}}}),
);
compact(
Context::Element(Kind::Occurrence),
json!({"body":{"Exchange":{"exchange":"flow","sender":"a","receiver":"b","message_kind":{"Timer":{"expression":""}},"reply_to":"request","values":[],"operation":null}}}),
json!({"body":{"Exchange":{"exchange":"flow","sender":"a","receiver":"b","message_kind":{"Timer":{"expression":""}},"reply_to":"request"}}}),
);
}
#[test]
fn event_body_whole_default_does_not_prune_selected_required_fields() {
compact(
Context::Element(Kind::Event),
json!({"body":{"Signal":{"items":[]}}}),
json!({}),
);
for body in [
json!({"Signal":{"items":["i"]}}),
json!({"Time":{"kind":"After","expression":""}}),
json!({"Change":{"expression":""}}),
] {
compact(
Context::Element(Kind::Event),
json!({"body":body.clone()}),
json!({"body":body}),
);
}
}
#[test]
fn relation_required_endpoints_and_contextual_payloads_remain() {
for kind in RelationKind::ALL {
compact(
Context::Relation(kind),
json!({"description":"","source":"s","target":"t","future":[]}),
json!({"source":"s","target":"t","future":[]}),
);
}
compact(
Context::Relation(RelationKind::FunctionalExchange),
json!({"source":"s","target":"t","source_port":"sp","target_port":"tp","items":[]}),
json!({"source":"s","target":"t","source_port":"sp","target_port":"tp"}),
);
let contextual = json!({"owner":"o","source":{"references":[],"occurrence":"s"},"target":{"references":[],"occurrence":"t"},"purpose":{"Precedence":{"guard":""}}});
compact(
Context::Relation(RelationKind::ContextualLink),
contextual.clone(),
contextual,
);
}
#[test]
fn relation_defaults_preserve_contextual_orientation_and_multiplicity() {
for (kind, orientation) in [
(RelationKind::BoundaryExchange, true),
(RelationKind::CommunicationMean, false),
] {
compact(
Context::Relation(kind),
json!({"oriented":orientation,"items":[]}),
json!({}),
);
compact(
Context::Relation(kind),
json!({"oriented":!orientation}),
json!({"oriented":!orientation}),
);
}
compact(
Context::Relation(RelationKind::DataAssociation),
json!({"source_role":"","target_role":"","source_minimum":0,"target_minimum":0,"source_maximum":null,"target_maximum":null,"source_navigable":false,"target_navigable":false}),
json!({}),
);
compact(
Context::Relation(RelationKind::DataAssociation),
json!({"source_maximum":0,"target_maximum":1,"source_navigable":true}),
json!({"source_maximum":0,"target_maximum":1,"source_navigable":true}),
);
compact(
Context::Relation(RelationKind::InterfaceItemAllocation),
json!({"order":0,"transmission":"Unspecified","acquisition":"Unspecified"}),
json!({}),
);
compact(
Context::Relation(RelationKind::CapabilityExtend),
json!({"condition":""}),
json!({}),
);
compact(
Context::Relation(RelationKind::SequenceLink),
json!({"guard":""}),
json!({}),
);
compact(
Context::Relation(RelationKind::StateAction),
json!({"phase":"Do"}),
json!({}),
);
compact(
Context::Relation(RelationKind::StateTransition),
json!({"transition_kind":"External","trigger":"","guard":"","effect":"","triggers":[],"effects":[]}),
json!({}),
);
compact(
Context::Relation(RelationKind::StateTransition),
json!({"transition_kind":"Internal","triggers":["t"],"effects":["e"]}),
json!({"transition_kind":"Internal","triggers":["t"],"effects":["e"]}),
);
}
#[test]
fn diagram_options_remove_only_unrestricted_defaults() {
let defaults = json!({"hidden_edges":[],"relation_kinds":"All","work_categories":"All","participant_categories":"All","coverage":"All","work_contexts":"All","exchange_labels":"Name"});
compact(
Context::Diagram,
json!({"description":"","layer":"Oa","projection":"Overview","routes":[],"view_options":defaults}),
json!({}),
);
compact(
Context::Diagram,
json!({"layer":"Sa","projection":{"StateMachine":{"machine":"m"}},"view_options":{"hidden_edges":[],"relation_kinds":{"Only":[]},"work_categories":{"Any":[]},"participant_categories":"All","coverage":"All","work_contexts":{"Any":[]},"exchange_labels":"Name"}}),
json!({"layer":"Sa","projection":{"StateMachine":{"machine":"m"}},"view_options":{"relation_kinds":{"Only":[]},"work_categories":{"Any":[]},"work_contexts":{"Any":[]}}}),
);
compact(
Context::Diagram,
json!({"routes":[{"edge":"e","waypoints":[],"label":null}]}),
json!({"routes":[{"edge":"e"}]}),
);
}
#[test]
fn node_whole_defaults_preserve_negative_zero_and_required_coordinates() {
compact(
Context::Node,
json!({"diagram":"d","element":"e","position":{"x":0.0,"y":0.0},"size":{"width":224.0,"height":94.0},"collapsed":false}),
json!({"diagram":"d","element":"e"}),
);
for position in [
json!({"x":-0.0,"y":0.0}),
json!({"x":0.0,"y":-0.0}),
json!({"x":1.0,"y":0.0}),
] {
let result = rendered(
Context::Node,
&json!({"position":position.clone()}),
Mode::Compact,
);
assert_eq!(result, json!({"position":position.clone()}));
for coordinate in ["x", "y"] {
assert_eq!(
result["position"][coordinate].as_f64().unwrap().to_bits(),
position[coordinate].as_f64().unwrap().to_bits(),
"coordinate sign is part of fidelity"
);
}
}
compact(
Context::Node,
json!({"size":{"width":300.0,"height":94.0},"port_anchors":[{"port":"p","side":"Left","offset":0.0}]}),
json!({"size":{"width":300.0,"height":94.0},"port_anchors":[{"port":"p","side":"Left","offset":0.0}]}),
);
}
#[test]
fn artifact_present_empty_strings_and_trace_history_stay_distinct() {
compact(
Context::Artifact,
json!({"owner":"o","description":"","relation":"References","uri":"","revision":null,"fragment":null}),
json!({"owner":"o"}),
);
compact(
Context::Artifact,
json!({"owner":"o","relation":"Implements","revision":"","fragment":""}),
json!({"owner":"o","relation":"Implements","revision":"","fragment":""}),
);
compact(
Context::Trace,
json!({"oa":"o","sa":"s","description":"","kind":"Refinement","baseline":null}),
json!({"oa":"o","sa":"s"}),
);
compact(
Context::Trace,
json!({"baseline":{"oa":{"name":"","description":""},"sa":{"name":"s","description":""}}}),
json!({"baseline":{"oa":{"name":"","description":""},"sa":{"name":"s","description":""}}}),
);
let baseline = json!({"oa":{"name":"o","description":""},"sa":{"name":"s","description":""},"semantic":{"facets":[{"Value":null},{"Value":[]},{"Value":false},{"Value":0},{"Value":""}]}});
compact(
Context::Trace,
json!({"kind":"Manual","baseline":baseline.clone()}),
json!({"kind":"Manual","baseline":baseline}),
);
}
#[test]
fn nested_future_fields_prevent_whole_default_removal() {
// These sentinels exercise preservation, not current parser admission of
// unknown fields. Only known slots may lose their redundant defaults.
compact(
Context::Element(Kind::DataType),
json!({"definition":{"String":{"minimum_length":0,"maximum_length":null,"future":[]}}}),
json!({"definition":{"String":{"minimum_length":0,"future":[]}}}),
);
compact(
Context::Element(Kind::Occurrence),
json!({"body":{"Exchange":{"exchange":null,"sender":null,"receiver":null,"message_kind":"Asynchronous","reply_to":null,"values":[],"operation":null,"future":false}}}),
json!({"body":{"Exchange":{"future":false}}}),
);
compact(
Context::Diagram,
json!({"view_options":{"hidden_edges":[],"relation_kinds":"All","work_categories":"All","participant_categories":"All","coverage":"All","work_contexts":"All","exchange_labels":"Name","future":null}}),
json!({"view_options":{"future":null}}),
);
}
#[test]
fn unknown_and_multikey_constructor_wrappers_are_not_reinterpreted() {
for definition in [
json!({"Future":{"minimum":null,"maximum":null}}),
json!({"String":{"minimum_length":0,"maximum_length":null},"Future":{}}),
] {
compact(
Context::Element(Kind::DataType),
json!({"definition":definition.clone()}),
json!({"definition":definition}),
);
}
for body in [
json!({"Future":{"operation":null,"values":[]}}),
json!({"Exchange":{"exchange":null,"values":[]},"Future":{}}),
] {
let fields = json!({"body":body});
assert_eq!(
rendered(
Context::Element(Kind::Occurrence),
&fields,
Mode::CompleteForEdit
),
fields,
"do not invent a selected Exchange constructor in an unknown wrapper",
);
compact(Context::Element(Kind::Occurrence), fields.clone(), fields);
}
}
+131 -20
View File
@@ -726,7 +726,8 @@ fn apply_edits(bundle: &mut SourceBundle, edits: Edits) -> Result<(), Vec<Source
pub fn format_bundle(project: &Project) -> SourceBundle {
let ownership = default_ownership(project);
let aliases: BTreeMap<_, _> = project.identities().into_iter().collect();
let mut declarations = language::format_declarations(project);
let mut declarations =
language::format_declarations(project, language::DeclarationFormat::Compact);
declarations.sort_by_key(|(id, _)| declaration_order(project, *id, &aliases));
let mut files: BTreeMap<LogicalPath, String> = BTreeMap::new();
for (id, declaration) in declarations {
@@ -735,11 +736,13 @@ pub fn format_bundle(project: &Project) -> SourceBundle {
.or_default()
.push_str(&unindent(&declaration));
}
let mut entry = format!(
"arcadia {SCHEMA_VERSION};\n{}{{\n description {};\n",
project_header(project),
quote(&project.description)
);
let mut entry = format!("arcadia {SCHEMA_VERSION};\n{}{{\n", project_header(project));
if !project.description.is_empty() {
entry.push_str(&format!(
" description {};\n",
quote(&project.description)
));
}
for path in files.keys() {
entry.push_str(&format!(" include {};\n", quote(path)));
}
@@ -850,8 +853,25 @@ pub fn update_bundle_with_identity_map(
)]);
}
let origins: BTreeMap<_, _> = identities.iter().map(|(old, new)| (*new, *old)).collect();
let before: BTreeMap<_, _> = language::format_declarations(old).into_iter().collect();
let after: BTreeMap<_, _> = language::format_declarations(project).into_iter().collect();
// Comparison must include defaults even when persistence serialization or
// compact output omits them. Otherwise an unrelated payload edit erases
// unchanged explicit fields from the authored document.
let before: BTreeMap<_, _> =
language::format_declarations(old, language::DeclarationFormat::CompleteForEdit)
.into_iter()
.collect();
let after: BTreeMap<_, _> =
language::format_declarations(project, language::DeclarationFormat::CompleteForEdit)
.into_iter()
.collect();
let compact: BTreeMap<_, _> =
language::format_declarations(project, language::DeclarationFormat::Compact)
.into_iter()
.collect();
let compact_values: BTreeMap<_, _> =
language::format_declarations(project, language::DeclarationFormat::CompactValues)
.into_iter()
.collect();
let old_aliases: BTreeMap<_, _> = old
.identities()
.into_iter()
@@ -907,6 +927,27 @@ pub fn update_bundle_with_identity_map(
};
let mut previous = parse(old_text)?;
let mut next = parse(new_text)?;
let next_id = identities[&id];
let value_text = &compact_values[&next_id];
let insertion = InsertionSyntax::new(
&next.fields,
&parse(value_text)?.fields,
value_text,
&parse(&compact[&next_id])?.fields,
);
let old_kind = previous.header["kind"];
let new_kind = next.header["kind"];
if old_text[old_kind.start..old_kind.end] != new_text[new_kind.start..new_kind.end]
&& let language::EditKind::Record(actual_fields) = &actual.fields.kind
&& let language::EditKind::Record(previous_fields) = &mut previous.fields.kind
{
// An implicit value is supplied by its declaration context.
// After a kind change, an unchanged value may need explicit
// syntax (e.g. BoundaryExchange true -> CommunicationMean true).
// Reconsider only absent fields; authored explicit values still
// compare against their complete old values and keep trivia.
previous_fields.retain(|key, _| actual_fields.contains_key(key));
}
if actual.lexical_owner.is_some()
&& let language::EditKind::Record(fields) = &actual.fields.kind
&& !fields.contains_key("parent")
@@ -923,6 +964,7 @@ pub fn update_bundle_with_identity_map(
source,
before: old_text,
after: new_text,
insertion: Some(&insertion),
old_aliases: &old_aliases,
new_aliases: &new_aliases,
legacy_value_paths: false,
@@ -953,6 +995,7 @@ pub fn update_bundle_with_identity_map(
source,
before: &before,
after: &after,
insertion: None,
old_aliases: &old_aliases,
new_aliases: &new_aliases,
legacy_value_paths: false,
@@ -1011,7 +1054,7 @@ pub fn update_bundle_with_identity_map(
project,
identities,
&original_order,
&after,
&compact,
limits,
)?;
let result = compile_bundle(&bundle, limits)?;
@@ -1119,10 +1162,73 @@ fn inserted_value(text: &str, indent: &str, source: &str) -> String {
output
}
// Value fragments retain required payload fields even when the enclosing
// declaration field is itself omittable. For example, a selected String type
// needs minimum_length; an Event Signal needs items. The complete tree remains
// the comparison authority; this parallel tree is only an insertion template.
struct InsertionSyntax<'a> {
source: &'a str,
values: BTreeMap<usize, Span>,
omitted_fields: BTreeSet<usize>,
}
impl<'a> InsertionSyntax<'a> {
fn new(
complete: &language::EditValue,
compact_values: &language::EditValue,
source: &'a str,
compact: &language::EditValue,
) -> Self {
let mut result = Self {
source,
values: BTreeMap::new(),
omitted_fields: BTreeSet::new(),
};
result.collect(complete, compact_values);
if let (language::EditKind::Record(all), language::EditKind::Record(kept)) =
(&complete.kind, &compact.kind)
{
for (key, field) in all {
if !kept.contains_key(key) {
result.omitted_fields.insert(field.value.span.start);
}
}
}
result
}
fn collect(&mut self, complete: &language::EditValue, compact: &language::EditValue) {
// Borrow the existing compact text once. Copying each containing record
// here would duplicate a large nested payload at every depth.
self.values.insert(complete.span.start, compact.span);
match (&complete.kind, &compact.kind) {
(language::EditKind::Record(all), language::EditKind::Record(kept)) => {
for (key, field) in all {
if let Some(next) = kept.get(key) {
self.collect(&field.value, &next.value);
} else {
self.omitted_fields.insert(field.value.span.start);
}
}
}
(language::EditKind::List(all), language::EditKind::List(kept)) => {
assert_eq!(
all.len(),
kept.len(),
"compaction preserves array membership"
);
for (field, next) in all.iter().zip(kept) {
self.collect(field, next);
}
}
_ => {}
}
}
}
struct SyntaxUpdate<'a> {
source: &'a str,
before: &'a str,
after: &'a str,
insertion: Option<&'a InsertionSyntax<'a>>,
old_aliases: &'a BTreeMap<String, String>,
new_aliases: &'a BTreeMap<String, String>,
// Source3 migration only adds path qualification; the existing field identity
@@ -1131,6 +1237,16 @@ struct SyntaxUpdate<'a> {
identity_origins: Option<&'a BTreeMap<Id, Id>>,
}
impl SyntaxUpdate<'_> {
fn inserted(&self, value: &language::EditValue) -> &str {
self.insertion
.and_then(|syntax| {
syntax
.values
.get(&value.span.start)
.map(|span| &syntax.source[span.start..span.end])
})
.unwrap_or(&self.after[value.span.start..value.span.end])
}
fn header(
&self,
actual: &BTreeMap<&str, Span>,
@@ -1243,7 +1359,7 @@ impl SyntaxUpdate<'_> {
_ => {
erase_syntax(self.source, actual.span, edits);
let text = inserted_value(
&self.after[after.span.start..after.span.end],
self.inserted(after),
indent_at(self.source, actual.span.start),
self.source,
);
@@ -1308,6 +1424,9 @@ impl SyntaxUpdate<'_> {
.iter()
.filter(|(key, field)| {
!actual.contains_key(*key)
&& self.insertion.is_none_or(|syntax| {
!syntax.omitted_fields.contains(&field.value.span.start)
})
&& before
.and_then(|fields| fields.get(*key))
.is_none_or(|old| !self.same(&old.value, &field.value))
@@ -1326,11 +1445,7 @@ impl SyntaxUpdate<'_> {
.unwrap_or_else(|| format!("{} ", indent_at(self.source, span.start)));
let mut text = String::new();
for (key, field) in added {
let value = inserted_value(
&self.after[field.value.span.start..field.value.span.end],
&indent,
self.source,
);
let value = inserted_value(self.inserted(&field.value), &indent, self.source);
if multiline {
text.push_str(&indent);
text.push_str(&format!("{key} {value};{}", newline(self.source)));
@@ -1450,11 +1565,7 @@ impl SyntaxUpdate<'_> {
} else {
text.push(' ');
}
text.push_str(&inserted_value(
&self.after[next.span.start..next.span.end],
&indent,
self.source,
));
text.push_str(&inserted_value(self.inserted(next), &indent, self.source));
suffix = "";
}
if i + 1 < after.len() {
@@ -11,7 +11,10 @@ pub(super) fn upgrade(
.as_ref()
.expect("an explicit source migration report");
let project = &current.decoded.project;
let canonical: BTreeMap<_, _> = language::format_declarations(project).into_iter().collect();
let canonical: BTreeMap<_, _> =
language::format_declarations(project, language::DeclarationFormat::CompleteForEdit)
.into_iter()
.collect();
let changed: BTreeSet<_> = report
.semantic_defaults
.iter()
@@ -59,6 +62,7 @@ pub(super) fn upgrade(
source,
before: source,
after,
insertion: None,
old_aliases: &aliases,
new_aliases: &aliases,
legacy_value_paths: true,
+12 -2
View File
@@ -750,7 +750,7 @@ pub fn plan_transition(
);
continue;
}
if !compatible_existing(project, *source, target, request.direction) {
if !counterpart_compatible(project, *source, target, request.direction) {
conflict(
&mut plan,
Some(*source),
@@ -1317,12 +1317,22 @@ fn compatible_kinds(source: Kind, target: Kind, direction: TransitionDirection)
}
inferred_kind(source, direction) == Some(target)
}
fn compatible_existing(
/// Whether existing records have compatible layers, categories and kinds for
/// an ordinary transition. This is candidate eligibility, not approval of a
/// complete plan: properties, dependencies and structural conflicts still need
/// the normal preview and apply validation. Manual traces are not restricted
/// by this predicate.
pub fn counterpart_compatible(
p: &Project,
source: Id,
target: Id,
direction: TransitionDirection,
) -> bool {
if p.layer_of(source) != Some(direction.source_layer())
|| p.layer_of(target) != Some(direction.target_layer())
{
return false;
}
match (p.elements.get(&source), p.elements.get(&target)) {
(Some(a), Some(b)) => compatible_kinds(a.kind(), b.kind(), direction),
_ => match (p.relations.get(&source), p.relations.get(&target)) {
@@ -0,0 +1,344 @@
//! Existing source-7 nested omission compatibility, independently of formatting.
//!
//! Selected constructors use their own field contracts. These handwritten
//! sources and literal models distinguish nullable optional slots from required
//! fields; they do not prescribe compact canonical output or broaden admission.
use oa_core::{source_project::*, *};
use std::collections::BTreeMap;
fn id(number: u8) -> Id {
Id::parse_str(&format!("00000000-0000-4000-8000-{number:012}")).unwrap()
}
fn bundle(declarations: &str) -> SourceBundle {
let source = format!(
"arcadia 7;\nproject p \"Admission\" id \"00000000-0000-4000-8000-000000000001\" {{\n{declarations}\n}}\n"
);
SourceBundle {
entry: "project.arcadia".into(),
files: BTreeMap::from([("project.arcadia".into(), source)]),
}
}
fn element(number: u8, alias: &str, name: &str, parent: Option<Id>, body: ElementBody) -> Element {
Element {
id: id(number),
alias: alias.into(),
name: name.into(),
description: String::new(),
parent,
body,
}
}
fn assert_admitted(input: &SourceBundle, elements: Vec<Element>) -> Project {
let expected = Project {
schema_version: 7,
id: id(1),
alias: "p".into(),
name: "Admission".into(),
description: String::new(),
elements: elements.into_iter().map(|e| (e.id, e)).collect(),
relations: BTreeMap::new(),
diagrams: BTreeMap::new(),
artifact_links: BTreeMap::new(),
traces: BTreeMap::new(),
};
let compiled = compile_bundle(input, ProjectLimits::default()).unwrap();
assert_eq!(compiled.decoded.project, expected, "complete literal model");
assert_eq!(
compiled.bundle, *input,
"fixed IDs require no source stabilization"
);
compiled.decoded.project
}
fn prefix(layer: Layer) -> &'static str {
match layer {
Layer::Oa => "oa",
Layer::Sa => "sa",
}
}
fn data_bundle(layer: Layer, definition: &str) -> SourceBundle {
bundle(&format!(
" {}_data_type subject \"Subject\" id \"00000000-0000-4000-8000-000000000002\" {{ definition {{ {definition}; }}; }}",
prefix(layer)
))
}
fn admit_data(layer: Layer, source: &str, definition: DataTypeDefinition) -> Project {
assert_admitted(
&data_bundle(layer, source),
vec![element(
2,
"subject",
"Subject",
None,
ElementBody::shared_in(
layer,
SharedBody::DataType {
definition,
default: None,
unit: String::new(),
},
),
)],
)
}
fn assert_missing(input: &SourceBundle, field: &str) {
let errors = compile_bundle(input, ProjectLimits::default()).unwrap_err();
assert_eq!(
errors.len(),
1,
"expect the targeted admission error: {errors:?}"
);
let diagnostic = &errors[0].diagnostic;
assert_eq!(diagnostic.code, "invalid_source", "{errors:?}");
assert!(
diagnostic.message.to_lowercase().contains("missing") && diagnostic.message.contains(field),
"must reject the missing constructor field {field}, not an unrelated model rule: {errors:?}"
);
}
#[test]
fn integer_optional_bounds_can_be_omitted_individually_and_together() {
for layer in [Layer::Oa, Layer::Sa] {
for (omitted, explicit, minimum, maximum) in [
(
"Integer {}",
"Integer { minimum none; maximum none; }",
None,
None,
),
(
"Integer { maximum 9; }",
"Integer { minimum none; maximum 9; }",
None,
Some(9),
),
(
"Integer { minimum -3; }",
"Integer { minimum -3; maximum none; }",
Some(-3),
None,
),
] {
let expected = DataTypeDefinition::Integer { minimum, maximum };
assert_eq!(
admit_data(layer, omitted, expected.clone()),
admit_data(layer, explicit, expected),
"omitted and explicit optional Integer bounds agree"
);
}
}
}
#[test]
fn real_optional_bounds_can_be_omitted_individually_and_together() {
for layer in [Layer::Oa, Layer::Sa] {
for (omitted, explicit, minimum, maximum) in [
(
"Real {}",
"Real { minimum none; maximum none; }",
None,
None,
),
(
"Real { maximum 2.5; }",
"Real { minimum none; maximum 2.5; }",
None,
Some(2.5),
),
(
"Real { minimum -1.25; }",
"Real { minimum -1.25; maximum none; }",
Some(-1.25),
None,
),
] {
let expected = DataTypeDefinition::Real { minimum, maximum };
assert_eq!(
admit_data(layer, omitted, expected.clone()),
admit_data(layer, explicit, expected),
"omitted and explicit optional Real bounds agree"
);
}
}
}
#[test]
fn string_optional_maximum_can_be_omitted_without_resetting_required_minimum() {
for layer in [Layer::Oa, Layer::Sa] {
let expected = DataTypeDefinition::String {
minimum_length: 4,
maximum_length: None,
};
assert_eq!(
admit_data(layer, "String { minimum_length 4; }", expected.clone()),
admit_data(
layer,
"String { minimum_length 4; maximum_length none; }",
expected,
),
);
}
}
#[test]
fn explicitly_selected_string_requires_minimum_even_when_whole_definition_has_a_default() {
for layer in [Layer::Oa, Layer::Sa] {
assert_missing(&data_bundle(layer, "String {}"), "minimum_length");
assert_missing(
&data_bundle(layer, "String { maximum_length none; }"),
"minimum_length",
);
admit_data(
layer,
"String { minimum_length 0; }",
DataTypeDefinition::String {
minimum_length: 0,
maximum_length: None,
},
);
}
}
#[test]
fn absent_numeric_maximum_is_distinct_from_an_explicit_zero_bound() {
for layer in [Layer::Oa, Layer::Sa] {
let unbounded = admit_data(
layer,
"Integer { minimum -3; }",
DataTypeDefinition::Integer {
minimum: Some(-3),
maximum: None,
},
);
let bounded = admit_data(
layer,
"Integer { minimum -3; maximum 0; }",
DataTypeDefinition::Integer {
minimum: Some(-3),
maximum: Some(0),
},
);
assert_ne!(unbounded, bounded, "zero is a bound, not omitted syntax");
}
}
#[test]
fn record_has_no_required_payload_field_and_omitted_supertype_means_none() {
for layer in [Layer::Oa, Layer::Sa] {
let expected = DataTypeDefinition::Record { supertype: None };
assert_eq!(
admit_data(layer, "Record {}", expected.clone()),
admit_data(layer, "Record { supertype none; }", expected),
);
let errors = compile_bundle(
&data_bundle(layer, "Record { fields []; }"),
ProjectLimits::default(),
)
.unwrap_err();
assert_eq!(errors.len(), 1, "{errors:?}");
assert_eq!(errors[0].diagnostic.code, "invalid_source", "{errors:?}");
assert!(
errors[0]
.diagnostic
.message
.contains("unknown field `fields`"),
"Record remains a closed constructor: {errors:?}"
);
}
}
#[test]
fn enumeration_still_requires_its_list_even_when_empty_list_is_admitted() {
for layer in [Layer::Oa, Layer::Sa] {
assert_missing(&data_bundle(layer, "Enumeration {}"), "literals");
admit_data(
layer,
"Enumeration { literals []; }",
DataTypeDefinition::Enumeration { literals: vec![] },
);
}
}
fn port_bundle(layer: Layer, fields: &str) -> SourceBundle {
let owner_kind = match layer {
Layer::Oa => "oa_entity",
Layer::Sa => "sa_system",
};
bundle(&format!(
" {owner_kind} owner \"Owner\" id \"00000000-0000-4000-8000-000000000002\" {{}}\n {}_port subject \"Subject\" id \"00000000-0000-4000-8000-000000000003\" {{ parent @owner; {fields} }}",
prefix(layer)
))
}
fn admit_port(layer: Layer, fields: &str) -> Project {
let owner_body = match layer {
Layer::Oa => ElementBody::Oa(OaBody::Entity),
Layer::Sa => ElementBody::Sa(SaBody::System),
};
assert_admitted(
&port_bundle(layer, fields),
vec![
element(2, "owner", "Owner", None, owner_body),
element(
3,
"subject",
"Subject",
Some(id(2)),
ElementBody::shared_in(
layer,
SharedBody::Port {
contract: PortContract::Legacy,
legacy: Some(LegacyPortContract {
direction: PortDirection::Out,
interface: None,
}),
},
),
),
],
)
}
#[test]
fn legacy_port_interface_can_be_omitted_but_preserves_direction() {
for layer in [Layer::Oa, Layer::Sa] {
assert_eq!(
admit_port(layer, "contract Legacy; legacy { direction Out; };"),
admit_port(
layer,
"contract Legacy; legacy { direction Out; interface none; };"
),
);
}
}
#[test]
fn legacy_port_direction_remains_required_inside_selected_record() {
for layer in [Layer::Oa, Layer::Sa] {
assert_missing(
&port_bundle(layer, "contract Legacy; legacy {};"),
"direction",
);
assert_missing(
&port_bundle(layer, "contract Legacy; legacy { interface none; };"),
"direction",
);
}
}
#[test]
fn port_declaration_still_requires_explicit_contract() {
for layer in [Layer::Oa, Layer::Sa] {
assert_missing(
&port_bundle(layer, "legacy { direction Out; };"),
"contract",
);
}
}
@@ -0,0 +1,445 @@
//! Public flat/modular formatter fidelity over frozen input and bounded mutations.
//! Legacy fixture migration uses the public parser. The original adapter tests
//! and exact inputs remain archived at source commit 2a64344; this suite exercises
//! production formatters, not that frozen experiment or lossless model edits.
use oa_core::{language, source_project::*, *};
use std::collections::BTreeMap;
const COASTAL: &str = include_str!("../../../examples/coastal-rescue.arcadia");
const KEYED: &str =
include_str!("../../../harness/fixtures/release1-native/semantic-keyed.arcadia");
const STATES: &str =
include_str!("../../../harness/fixtures/release1-native/state-actions.arcadia");
const DENSE: &str =
include_str!("../../../harness/fixtures/release1-native/dense-40/dense-40.arcadia");
const OPERATIONS: &str = include_str!(
"../../../harness/fixtures/interface-operation-probe/output/boundary-only.arcadia"
);
fn read(source: &str) -> Project {
language::parse(source).unwrap()
}
fn identity(project: &Project, alias: &str) -> Id {
project
.identities()
.into_iter()
.find(|(_, name)| *name == alias)
.unwrap()
.0
}
fn assert_common_omissions(project: &Project, compiled: &CompiledSourceProject) {
if project.description.is_empty() {
assert!(compiled.index.project_description.is_none());
}
let descriptions = project
.elements
.values()
.map(|e| (e.id, &e.description))
.chain(project.relations.values().map(|r| (r.id, &r.description)))
.chain(project.diagrams.values().map(|d| (d.id, &d.description)))
.chain(
project
.artifact_links
.values()
.map(|a| (a.id, &a.description)),
)
.chain(project.traces.values().map(|t| (t.id, &t.description)));
for (id, description) in descriptions {
if description.is_empty() {
assert!(
!compiled
.index
.fields
.contains_key(&(id, "description".into()))
);
}
}
for element in project.elements.values().filter(|e| e.parent.is_none()) {
assert!(
!compiled
.index
.fields
.contains_key(&(element.id, "parent".into()))
);
}
}
fn assert_fidelity(label: &str, project: &Project) -> (Project, String) {
let flat = language::format(project);
let compact_model = read(&flat);
assert_eq!(
compact_model, *project,
"{label}: every persistent field survives"
);
let input = SourceBundle {
entry: "project.arcadia".into(),
files: BTreeMap::from([("project.arcadia".into(), flat.clone())]),
};
let compiled = compile_bundle(&input, ProjectLimits::default()).unwrap();
assert_eq!(
compiled.decoded.project, *project,
"{label}: source-project compile"
);
assert_eq!(
compiled.bundle, input,
"{label}: compact identities/source are stable"
);
assert_common_omissions(project, &compiled);
assert_eq!(
language::format(&compact_model),
flat,
"{label}: flat idempotence"
);
let modular = format_bundle(project);
assert!(
modular.files.len() > 1,
"{label}: exercise generated includes"
);
let modular_compiled = compile_bundle(&modular, ProjectLimits::default()).unwrap();
assert_eq!(
modular_compiled.decoded.project, *project,
"{label}: complete modular model"
);
assert_eq!(
modular_compiled.bundle, modular,
"{label}: modular source/IDs are stable"
);
assert_common_omissions(project, &modular_compiled);
assert_eq!(
format_bundle(&compact_model),
modular,
"{label}: flat-to-modular determinism"
);
assert_eq!(
format_bundle(&modular_compiled.decoded.project),
modular,
"{label}: modular idempotence"
);
// Pinned measured compact bytes from the published fidelity-1 stderr.log at
// 2a64344, not a comparison against today's (now compact) canonical writer.
// A small header-layout allowance avoids enforcing incidental blank lines.
let measured_compact_bytes = match label {
"coastal-source3" => 28_465,
"keyed-source5-history" => 12_066,
"state-actions-source5" => 6_673,
"dense40-source6" => 19_342,
"boundary-operations-source6" => 3_660,
"state-negative-zero" => 6_672,
"keyed-present-empty-default" => 12_108,
"coastal-present-empty-artifact-strings" => 28_483,
"boundary-selected-and-unresolved-operations" => 3_806,
_ => panic!("missing independent compact measurement for {label}"),
};
assert!(
flat.len() <= measured_compact_bytes + 16,
"{label}: {} bytes exceeds compact evidence budget {}",
flat.len(),
measured_compact_bytes + 16
);
(compact_model, flat)
}
#[test]
fn migrated_coastal_oa_sa_views_ports_artifacts_and_traces_roundtrip() {
assert!(COASTAL.starts_with("arcadia 3;"));
let project = read(COASTAL);
assert!(project.elements.values().any(|e| e.layer() == Layer::Oa));
assert!(project.elements.values().any(|e| e.layer() == Layer::Sa));
assert!(
project
.elements
.values()
.any(|e| matches!(e.shared(), Some(SharedBody::Port { .. })))
);
assert!(project.elements.values().any(
|e| matches!(e.shared(), Some(SharedBody::Scenario { steps, .. }) if !steps.is_empty())
));
assert!(project.diagrams.values().any(|d| d.layer == Layer::Oa
&& d.projection == ProjectionKind::FunctionalDataflow
&& !d.nodes.is_empty()));
assert!(project.diagrams.values().any(|d| d.layer == Layer::Sa
&& d.projection == ProjectionKind::Allocation
&& !d.nodes.is_empty()));
assert!(!project.artifact_links.is_empty());
assert!(project.traces.values().any(|t| t.baseline.is_some()));
assert_fidelity("coastal-source3", &project);
}
#[test]
fn keyed_scenarios_records_and_historical_binding_annotation_facets_roundtrip() {
let project = read(KEYED);
for layer in [Layer::Oa, Layer::Sa] {
assert!(project.elements.values().any(|e| e.layer() == layer
&& matches!(
e.shared(),
Some(SharedBody::DataType {
definition: DataTypeDefinition::Record { .. },
..
})
)));
assert!(project.elements.values().any(|e| e.layer() == layer && matches!(e.shared(), Some(SharedBody::Occurrence { body: OccurrenceBody::ScenarioUse { bindings, .. }, .. }) if !bindings.is_empty())));
}
assert!(project.elements.values().any(|e| matches!(e.shared(), Some(SharedBody::Occurrence { body: OccurrenceBody::Exchange { values, .. }, .. }) if values.iter().any(|v| !v.path.is_empty() && !v.expression.is_empty()))));
let facets: Vec<_> = project
.traces
.values()
.filter_map(|t| t.baseline.as_ref()?.semantic.as_ref())
.flat_map(|baseline| &baseline.facets)
.collect();
assert!(
facets
.iter()
.any(|facet| matches!(facet, semantic::FacetReview::Binding { .. }))
);
assert!(
facets
.iter()
.any(|facet| matches!(facet, semantic::FacetReview::Annotation { .. }))
);
assert!(
KEYED.contains("uuid \""),
"fixture contains historical identity atoms"
);
let (_, source) = assert_fidelity("keyed-source5-history", &project);
assert!(
source.contains("uuid \""),
"historical atoms remain historical"
);
}
#[test]
fn state_machine_projection_typed_actions_and_bounded_integer_roundtrip() {
let project = read(STATES);
let machine = identity(&project, "operating");
let view = &project.diagrams[&identity(&project, "state_view")];
assert_eq!(view.projection, ProjectionKind::StateMachine { machine });
assert!(!view.nodes.is_empty());
for phase in [
StateActionKind::Entry,
StateActionKind::Do,
StateActionKind::Exit,
] {
assert!(project.relations.values().any(|r| matches!(&r.body, RelationBody::StateAction { phase: actual, .. } if *actual == phase)));
}
assert!(project.relations.values().any(|r| matches!(&r.body, RelationBody::StateTransition { transition_kind: TransitionKind::Internal, triggers, effects, .. } if !triggers.is_empty() && !effects.is_empty())));
assert!(
matches!(project.elements[&identity(&project, "retry_budget")].shared(), Some(SharedBody::DataType { definition: DataTypeDefinition::Integer { minimum: Some(0), maximum: Some(4) }, unit, .. }) if unit == "attempts")
);
assert_fidelity("state-actions-source5", &project);
}
#[test]
fn forty_exchange_diagram_with_categories_and_processes_roundtrip() {
let project = read(DENSE);
assert_eq!(
project
.relations
.values()
.filter(|r| r.kind() == RelationKind::Interaction)
.count(),
40
);
assert_eq!(
project
.elements
.values()
.filter(|e| e.kind() == Kind::ExchangeCategory)
.count(),
5
);
assert_eq!(
project
.elements
.values()
.filter(|e| e.kind() == Kind::Process)
.count(),
3
);
let view = &project.diagrams[&identity(&project, "forty_exchange_view")];
assert_eq!(view.projection, ProjectionKind::FunctionalDataflow);
assert_eq!(view.nodes.len(), 2);
assert!(view.routes.is_empty());
assert_eq!(
view.view_options.relation_kinds,
view_options::RelationKinds::All
);
assert_fidelity("dense40-source6", &project);
}
#[test]
fn component_ports_interface_operations_and_boundary_calls_roundtrip() {
let project = read(OPERATIONS);
assert_eq!(
project
.relations
.values()
.filter(|r| r.kind() == RelationKind::InterfaceItemAllocation)
.count(),
2
);
assert_eq!(
project
.elements
.values()
.filter(|e| matches!(
e.shared(),
Some(SharedBody::Port {
contract: PortContract::Component {
kind: ComponentPortKind::Standard,
..
},
..
})
))
.count(),
2
);
assert!(project.elements.values().any(|e| matches!(
e.shared(),
Some(SharedBody::ExchangeItem {
mechanism: ExchangeMechanism::Operation,
..
})
)));
assert!(project.elements.values().any(|e| matches!(e.shared(), Some(SharedBody::Scenario { viewpoint: ScenarioViewpoint::BoundaryInterface, steps, .. }) if steps.len() == 2)));
assert_eq!(
project
.elements
.values()
.filter(|e| matches!(
e.shared(),
Some(SharedBody::Occurrence {
body: OccurrenceBody::Exchange {
message_kind: MessageKind::Call,
operation: None,
..
},
..
})
))
.count(),
2
);
assert_fidelity("boundary-operations-source6", &project);
}
#[test]
fn negative_zero_node_coordinate_survives_canonical_compact_and_bundle_compile() {
let mut project = read(STATES);
let diagram = identity(&project, "state_view");
let node = identity(&project, "view_machine");
project
.diagrams
.get_mut(&diagram)
.unwrap()
.nodes
.get_mut(&node)
.unwrap()
.position = Position { x: -0.0, y: 0.0 };
let (decoded, flat) = assert_fidelity("state-negative-zero", &project);
let bundle = SourceBundle {
entry: "project.arcadia".into(),
files: BTreeMap::from([("project.arcadia".into(), flat)]),
};
let compiled = compile_bundle(&bundle, ProjectLimits::default()).unwrap();
let modular = compile_bundle(&format_bundle(&project), ProjectLimits::default()).unwrap();
// Project/Position PartialEq alone equates the signs of zero. Check bits in
// each independently decoded representation, including source-project compile.
for actual in [
&decoded,
&compiled.decoded.project,
&modular.decoded.project,
] {
let position = &actual.diagrams[&diagram].nodes[&node].position;
assert_eq!(position.x.to_bits(), (-0.0_f32).to_bits());
assert_eq!(position.y.to_bits(), 0.0_f32.to_bits());
}
}
#[test]
fn present_empty_field_default_and_unbounded_maximum_remain_distinct_from_omission() {
let mut project = read(KEYED);
let field_id = identity(&project, "oa_navigation_latitude");
let Some(SharedBody::DataField {
minimum,
maximum,
default,
storage,
..
}) = project.elements.get_mut(&field_id).unwrap().shared_mut()
else {
panic!("fixture must contain the claimed DataField");
};
assert_eq!(*minimum, 0);
assert_eq!(*maximum, Some(1));
assert_eq!(*default, None);
assert_eq!(*storage, FieldStorage::Value);
*maximum = None;
*default = Some(vec![]);
let (decoded, _) = assert_fidelity("keyed-present-empty-default", &project);
assert!(
matches!(decoded.elements[&field_id].shared(), Some(SharedBody::DataField { maximum: None, default: Some(values), .. }) if values.is_empty())
);
}
#[test]
fn present_empty_artifact_revision_and_fragment_survive() {
let mut project = read(COASTAL);
let artifact_id = identity(&project, "console_enclosure_cad");
let artifact = project.artifact_links.get_mut(&artifact_id).unwrap();
artifact.revision = Some(String::new());
artifact.fragment = Some(String::new());
let (decoded, _) = assert_fidelity("coastal-present-empty-artifact-strings", &project);
assert_eq!(
decoded.artifact_links[&artifact_id].revision.as_deref(),
Some("")
);
assert_eq!(
decoded.artifact_links[&artifact_id].fragment.as_deref(),
Some("")
);
}
#[test]
fn selected_and_unresolved_operation_intent_survive_as_distinct_present_records() {
let mut project = read(OPERATIONS);
let allocation = identity(&project, "operation_probe_30");
assert_eq!(
project.relations[&allocation].kind(),
RelationKind::InterfaceItemAllocation
);
let selected = identity(&project, "operation_probe_43");
let unresolved = identity(&project, "operation_probe_44");
for (subject, value) in [(selected, Some(allocation)), (unresolved, None)] {
let Some(SharedBody::Occurrence {
body: OccurrenceBody::Exchange { operation, .. },
..
}) = project.elements.get_mut(&subject).unwrap().shared_mut()
else {
panic!("fixture must contain a Call Exchange");
};
assert_eq!(*operation, None);
*operation = Some(OperationUse { allocation: value });
}
let (decoded, _) = assert_fidelity("boundary-selected-and-unresolved-operations", &project);
assert!(
matches!(decoded.elements[&selected].shared(), Some(SharedBody::Occurrence { body: OccurrenceBody::Exchange { operation: Some(OperationUse { allocation: Some(actual) }), .. }, .. }) if *actual == allocation)
);
assert!(matches!(
decoded.elements[&unresolved].shared(),
Some(SharedBody::Occurrence {
body: OccurrenceBody::Exchange {
operation: Some(OperationUse { allocation: None }),
..
},
..
})
));
}
@@ -0,0 +1,256 @@
//! Focused public-output contracts. Field locations come from the real compiler;
//! token comparisons ignore layout while retaining typed tags and required syntax.
use oa_core::{language, source_project::*, *};
use std::collections::BTreeMap;
fn project(declarations: &str) -> Project {
language::parse(&format!(
"arcadia 7; project p \"P\" id \"00000000-0000-4000-8000-000000000001\" {{ {declarations} }}"
)).unwrap()
}
fn exports(project: &Project) -> Vec<CompiledSourceProject> {
let flat = language::format(project);
assert_eq!(language::parse(&flat).unwrap(), *project);
let flat_bundle = SourceBundle {
entry: "project.arcadia".into(),
files: BTreeMap::from([("project.arcadia".into(), flat)]),
};
[flat_bundle, format_bundle(project)]
.into_iter()
.map(|bundle| {
let compiled = compile_bundle(&bundle, ProjectLimits::default()).unwrap();
assert_eq!(compiled.decoded.project, *project);
assert_eq!(
compiled.bundle, bundle,
"all identities are explicit and stable"
);
compiled
})
.collect()
}
fn identity(project: &Project, alias: &str) -> Id {
project
.identities()
.into_iter()
.find(|(_, name)| *name == alias)
.unwrap()
.0
}
fn field(compiled: &CompiledSourceProject, alias: &str, name: &str) -> Option<String> {
let location = compiled
.index
.fields
.get(&(identity(&compiled.decoded.project, alias), name.into()))?;
let text = &compiled.bundle.files[&location.path][location.span.start..location.span.end];
Some(
language::tokenize(text)
.into_iter()
.filter(|token| {
!matches!(
token.class,
language::TokenClass::Whitespace | language::TokenClass::Comment
)
})
.map(|token| &text[token.span.start..token.span.end])
.collect::<Vec<_>>()
.join(" "),
)
}
#[test]
fn empty_project_has_no_synthetic_description_or_module() {
let empty = project("");
for compiled in exports(&empty) {
assert!(compiled.index.project_description.is_none());
assert!(compiled.index.fields.is_empty());
assert!(compiled.index.includes.is_empty());
assert_eq!(compiled.bundle.files.len(), 1);
assert_eq!(compiled.index.declarations.len(), 1);
}
}
#[test]
fn only_empty_descriptions_disappear_whitespace_and_text_survive_exactly() {
let mut model =
project("oa_activity work \"Work\" id \"00000000-0000-4000-8000-000000000002\" {}");
let work = identity(&model, "work");
for description in ["", " \t\n", "A quoted \"requirement\" — 雨"] {
model.description = description.into();
model.elements.get_mut(&work).unwrap().description = description.into();
for compiled in exports(&model) {
assert_eq!(
compiled.index.project_description.is_some(),
!description.is_empty()
);
assert_eq!(
field(&compiled, "work", "description").is_some(),
!description.is_empty()
);
assert!(field(&compiled, "work", "parent").is_none());
// exports compares the complete model, including exact whitespace
// and escapes in both project and element descriptions.
}
}
}
#[test]
fn sa_view_keeps_its_layer_and_node_references_but_omits_presentation_defaults() {
let model = project(
r#"
sa_function work "Work" id "00000000-0000-4000-8000-000000000002" {}
diagram view "View" id "00000000-0000-4000-8000-000000000003" { layer Sa; }
node placement id "00000000-0000-4000-8000-000000000004" {
diagram @view; element @work;
position { x 0.0; y 0.0; }; size { width 224.0; height 94.0; };
collapsed false; port_anchors [];
}
"#,
);
for compiled in exports(&model) {
assert_eq!(field(&compiled, "view", "layer").as_deref(), Some("Sa"));
for name in ["description", "projection", "routes", "view_options"] {
assert!(field(&compiled, "view", name).is_none(), "view.{name}");
}
assert_eq!(
field(&compiled, "placement", "diagram").as_deref(),
Some("@ view")
);
assert_eq!(
field(&compiled, "placement", "element").as_deref(),
Some("@ work")
);
for name in ["position", "size", "collapsed", "port_anchors"] {
assert!(
field(&compiled, "placement", name).is_none(),
"placement.{name}"
);
}
}
}
#[test]
fn nested_type_defaults_disappear_without_losing_required_or_present_empty_values() {
let model = project(
r#"
sa_data_type implicit "Implicit" id "00000000-0000-4000-8000-000000000002" {}
sa_data_type integer "Integer" id "00000000-0000-4000-8000-000000000003" {
definition { Integer { minimum none; maximum none; }; };
}
sa_data_type text "Text" id "00000000-0000-4000-8000-000000000004" {
definition { String { minimum_length 4; maximum_length none; }; };
}
sa_data_type record "Record" id "00000000-0000-4000-8000-000000000005" {
definition { Record { supertype none; }; };
}
sa_data_type flag "Flag" id "00000000-0000-4000-8000-000000000006" {
definition Boolean; default { Boolean false; };
}
sa_data_field optional "Optional" id "00000000-0000-4000-8000-000000000007" {
parent @record; minimum 0; maximum none; default [];
}
"#,
);
for compiled in exports(&model) {
assert!(field(&compiled, "implicit", "definition").is_none());
assert_eq!(
field(&compiled, "integer", "definition").as_deref(),
Some("{ Integer { } ; }")
);
assert_eq!(
field(&compiled, "text", "definition").as_deref(),
Some("{ String { minimum_length 4 ; } ; }")
);
assert_eq!(
field(&compiled, "record", "definition").as_deref(),
Some("{ Record { } ; }")
);
assert_eq!(
field(&compiled, "flag", "definition").as_deref(),
Some("Boolean")
);
assert_eq!(
field(&compiled, "flag", "default").as_deref(),
Some("{ Boolean false ; }")
);
assert_eq!(
field(&compiled, "optional", "minimum").as_deref(),
Some("0")
);
assert_eq!(
field(&compiled, "optional", "maximum").as_deref(),
Some("none")
);
assert_eq!(
field(&compiled, "optional", "default").as_deref(),
Some("[ ]")
);
assert!(field(&compiled, "optional", "type_ref").is_none());
}
}
#[test]
fn required_port_contract_and_empty_item_list_survive_while_optional_interface_disappears() {
let model = project(
r#"
sa_function work "Work" id "00000000-0000-4000-8000-000000000002" {}
sa_port sink "Sink" id "00000000-0000-4000-8000-000000000003" {
parent @work; contract { FunctionInput { incoming_items []; }; };
}
sa_system host "Host" id "00000000-0000-4000-8000-000000000004" {}
sa_port preserved "Preserved" id "00000000-0000-4000-8000-000000000005" {
parent @host; contract Legacy; legacy { direction Out; interface none; };
}
"#,
);
for compiled in exports(&model) {
assert_eq!(
field(&compiled, "sink", "contract").as_deref(),
Some("{ FunctionInput { incoming_items [ ] ; } ; }")
);
assert!(field(&compiled, "sink", "legacy").is_none());
assert_eq!(
field(&compiled, "preserved", "contract").as_deref(),
Some("Legacy")
);
assert_eq!(
field(&compiled, "preserved", "legacy").as_deref(),
Some("{ direction Out ; }")
);
}
}
#[test]
fn source_scenario_default_and_selected_occurrence_constructor_remain_distinct() {
let model = project(
r#"
sa_scenario mixed "Mixed" id "00000000-0000-4000-8000-000000000002" { viewpoint MixedLegacy; }
sa_scenario strict "Strict" id "00000000-0000-4000-8000-000000000003" { viewpoint Functional; }
oa_process flow "Flow" id "00000000-0000-4000-8000-000000000004" { steps [@exchange, @action]; }
oa_occurrence exchange "Exchange" id "00000000-0000-4000-8000-000000000005" {
parent @flow; body { Exchange {}; };
}
oa_occurrence action "Action" id "00000000-0000-4000-8000-000000000006" {
parent @flow; body { Action { action none; lifeline none; }; };
}
"#,
);
for compiled in exports(&model) {
assert!(field(&compiled, "mixed", "viewpoint").is_none());
assert_eq!(
field(&compiled, "strict", "viewpoint").as_deref(),
Some("Functional")
);
assert!(field(&compiled, "exchange", "body").is_none());
assert_eq!(
field(&compiled, "action", "body").as_deref(),
Some("{ Action { } ; }")
);
assert_eq!(
field(&compiled, "action", "parent").as_deref(),
Some("@ flow")
);
}
}
@@ -0,0 +1,612 @@
//! Exact-source regressions for fields skipped by persistence serialization.
//!
//! The archived Windows baseline confirmed three exact-source failures after
//! full model equality passed. Complete comparison now preserves these fields;
//! the former ignored witnesses are normal regression tests.
//! No-op and same-record header controls remain normal tests: they must preserve
//! the same fixtures without entering the changed-payload record-erasure branch.
use oa_core::{source_project::*, *};
use std::collections::BTreeMap;
const EXCHANGE_SOURCE: &str = r#"arcadia 7;
project p "Preservation" id "00000000-0000-4000-8000-000000000001" {
oa_activity first "First" id "00000000-0000-4000-8000-000000000002" {}
oa_activity second "Second" id "00000000-0000-4000-8000-000000000003" {}
interaction flow "Flow" id "00000000-0000-4000-8000-000000000004" {
source @first; target @second;
}
oa_process owner "Owner" id "00000000-0000-4000-8000-000000000005" {
steps [@subject];
}
oa_occurrence subject "Subject" id "00000000-0000-4000-8000-000000000006" {
parent @owner;
body { Exchange {
exchange none;
sender none; receiver none;
message_kind Asynchronous; reply_to none; values [];
operation none ; // Keep this authored default and its spelling.
}; };
}
}
"#;
const NODE_SOURCE: &str = r#"arcadia 7;
project p "Preservation" id "00000000-0000-4000-8000-000000000001" {
oa_activity work "Work" id "00000000-0000-4000-8000-000000000002" {}
diagram view "View" id "00000000-0000-4000-8000-000000000003" {}
node placement id "00000000-0000-4000-8000-000000000004" {
diagram @view; element @work;
position { x 12.5; y -4.0; };
port_anchors [] ; // Keep explicit automatic placement.
}
}
"#;
const TRACE_SOURCE: &str = r#"arcadia 7;
project p "Preservation" id "00000000-0000-4000-8000-000000000001" {
oa_activity work "Work" id "00000000-0000-4000-8000-000000000002" {}
sa_function function "Function" id "00000000-0000-4000-8000-000000000003" {}
trace mapping "Mapping" id "00000000-0000-4000-8000-000000000004" {
oa @work; sa @function; kind Manual;
baseline {
oa { name "Work"; description "Old OA snapshot"; };
sa { name "Function"; description "Old SA snapshot"; };
semantic none ; // Keep the explicit prose-only baseline.
};
}
}
"#;
const CONSTRUCTOR_SOURCE: &str = r#"arcadia 7;
project p "Preservation" id "00000000-0000-4000-8000-000000000001" {
oa_scenario owner "Owner" id "00000000-0000-4000-8000-000000000002" {
steps [@subject];
}
oa_occurrence subject "Subject" id "00000000-0000-4000-8000-000000000003" {
parent @owner;
body { Action {
action none;
lifeline none ; // Keep this shared field's distinctive spelling.
}; };
}
}
"#;
const RELATION_CONTEXT_SOURCE: &str = r#"arcadia 7;
project p "Preservation" id "00000000-0000-4000-8000-000000000001" {
oa_entity first "First" id "00000000-0000-4000-8000-000000000002" {}
oa_entity second "Second" id "00000000-0000-4000-8000-000000000003" {}
// These port declarations are untouched by the relation context change.
oa_port output "Output" id "00000000-0000-4000-8000-000000000005" {
parent @first; contract Legacy;
legacy { direction Out; interface none; };
}
oa_port input "Input" id "00000000-0000-4000-8000-000000000006" {
parent @second; contract Legacy;
legacy { direction In; interface none; };
}
boundary_exchange link "Link" id "00000000-0000-4000-8000-000000000004" {
source @output; target @input;
oriented true ; // Retain this rationale when changing the default value.
items [];
}
}
"#;
fn id(number: u8) -> Id {
Id::parse_str(&format!("00000000-0000-4000-8000-{number:012}")).unwrap()
}
fn source_bundle(source: &str) -> SourceBundle {
SourceBundle {
entry: "project.arcadia".into(),
files: BTreeMap::from([("project.arcadia".into(), source.into())]),
}
}
fn project() -> Project {
Project {
schema_version: 7,
id: id(1),
alias: "p".into(),
name: "Preservation".into(),
description: String::new(),
elements: BTreeMap::new(),
relations: BTreeMap::new(),
diagrams: BTreeMap::new(),
artifact_links: BTreeMap::new(),
traces: BTreeMap::new(),
}
}
fn element(number: u8, alias: &str, name: &str, parent: Option<Id>, body: ElementBody) -> Element {
Element {
id: id(number),
alias: alias.into(),
name: name.into(),
description: String::new(),
parent,
body,
}
}
// Handwritten typed expectations are independent of canonical serialization and
// source hydration. No model factories or generated/random identities are used.
fn exchange_project() -> Project {
let mut expected = project();
expected.elements = [
element(2, "first", "First", None, ElementBody::Oa(OaBody::Activity)),
element(
3,
"second",
"Second",
None,
ElementBody::Oa(OaBody::Activity),
),
element(
5,
"owner",
"Owner",
None,
ElementBody::Oa(OaBody::Process { steps: vec![id(6)] }),
),
element(
6,
"subject",
"Subject",
Some(id(5)),
ElementBody::Oa(OaBody::Shared(SharedBody::Occurrence {
body: OccurrenceBody::Exchange {
exchange: None,
sender: None,
receiver: None,
message_kind: MessageKind::Asynchronous,
reply_to: None,
values: vec![],
operation: None,
},
precondition: String::new(),
postcondition: String::new(),
})),
),
]
.into_iter()
.map(|element| (element.id, element))
.collect();
expected.relations.insert(
id(4),
Relation {
id: id(4),
alias: "flow".into(),
name: "Flow".into(),
description: String::new(),
body: RelationBody::Interaction {
source: id(2),
target: id(3),
items: vec![],
},
},
);
expected
}
fn node_project() -> Project {
let mut expected = project();
expected.elements.insert(
id(2),
element(2, "work", "Work", None, ElementBody::Oa(OaBody::Activity)),
);
expected.diagrams.insert(
id(3),
Diagram {
id: id(3),
alias: "view".into(),
name: "View".into(),
description: String::new(),
layer: Layer::Oa,
projection: ProjectionKind::Overview,
view_options: view_options::ViewOptions {
hidden_edges: vec![],
relation_kinds: view_options::RelationKinds::All,
work_categories: view_options::CategorySelection::All,
participant_categories: view_options::CategorySelection::All,
coverage: view_options::Coverage::All,
work_contexts: view_options::ContextSelection::All,
exchange_labels: view_options::ExchangeLabels::Name,
},
nodes: BTreeMap::from([(
id(4),
NodePlacement {
id: id(4),
alias: "placement".into(),
element: id(2),
position: Position { x: 12.5, y: -4.0 },
size: Size {
width: 224.0,
height: 94.0,
},
collapsed: false,
port_anchors: vec![],
},
)]),
routes: vec![],
},
);
expected
}
fn trace_project() -> Project {
let mut expected = project();
expected.elements = [
element(2, "work", "Work", None, ElementBody::Oa(OaBody::Activity)),
element(
3,
"function",
"Function",
None,
ElementBody::Sa(SaBody::Function),
),
]
.into_iter()
.map(|element| (element.id, element))
.collect();
expected.traces.insert(
id(4),
Trace {
id: id(4),
alias: "mapping".into(),
name: "Mapping".into(),
description: String::new(),
oa: id(2),
sa: id(3),
kind: TraceKind::Manual,
baseline: Some(TraceBaseline {
oa: TracedProperties {
name: "Work".into(),
description: "Old OA snapshot".into(),
},
sa: TracedProperties {
name: "Function".into(),
description: "Old SA snapshot".into(),
},
semantic: None,
}),
},
);
expected
}
fn checked_fixture(source: &str, expected: &Project) -> CompiledSourceProject {
let input = source_bundle(source);
let compiled = compile_bundle(&input, ProjectLimits::default()).unwrap();
assert_eq!(
compiled.decoded.project, *expected,
"fixture model admission"
);
assert_eq!(
compiled.bundle, input,
"fixed IDs must avoid stabilization edits"
);
compiled
}
fn replacement(source: &str, before: &str, after: &str) -> String {
assert_eq!(
source.matches(before).count(),
1,
"unique authored edit token"
);
source.replacen(before, after, 1)
}
fn assert_preserved_update(
original: &CompiledSourceProject,
expected: &Project,
expected_source: &str,
) {
// Independently admit the exact expected spelling before testing the updater.
let expected_bundle = source_bundle(expected_source);
let authored = compile_bundle(&expected_bundle, ProjectLimits::default()).unwrap();
assert_eq!(
authored.decoded.project, *expected,
"expected source/model agree"
);
assert_eq!(
authored.bundle, expected_bundle,
"expected source is already stable"
);
let updated = update_bundle(original, expected, ProjectLimits::default()).unwrap();
assert_eq!(
updated.decoded.project, *expected,
"entire intended model survives"
);
assert_eq!(
updated.bundle, expected_bundle,
"only the requested token may change; preserve explicit defaults and all trivia"
);
}
// Context changes may introduce/remove redundant default syntax. Admit each
// narrowly enumerated spelling against the same literal target model, and then
// compare complete source bundles. No normalization may mask edits to unrelated
// fields, declarations, whitespace, or comments.
fn assert_context_update(
original: &CompiledSourceProject,
expected: &Project,
allowed_sources: &[String],
) {
let allowed: Vec<_> = allowed_sources
.iter()
.map(|source| {
let bundle = source_bundle(source);
let authored = compile_bundle(&bundle, ProjectLimits::default()).unwrap();
assert_eq!(
authored.decoded.project, *expected,
"allowed source/model agree"
);
assert_eq!(authored.bundle, bundle, "allowed source is already stable");
bundle
})
.collect();
let updated = update_bundle(original, expected, ProjectLimits::default()).unwrap();
assert_eq!(
updated.decoded.project, *expected,
"entire context-change model survives"
);
assert!(
allowed.contains(&updated.bundle),
"preserve unchanged source exactly; only listed changed/default syntax alternatives are allowed\nactual: {:?}\nallowed: {:?}",
updated.bundle,
allowed
);
}
#[test]
fn explicit_exchange_operation_none_survives_noop() {
let expected = exchange_project();
let original = checked_fixture(EXCHANGE_SOURCE, &expected);
assert_preserved_update(&original, &expected, EXCHANGE_SOURCE);
}
#[test]
fn explicit_exchange_operation_none_survives_header_rename() {
let mut expected = exchange_project();
let original = checked_fixture(EXCHANGE_SOURCE, &expected);
expected.elements.get_mut(&id(6)).unwrap().name = "Renamed subject".into();
let source = replacement(EXCHANGE_SOURCE, "\"Subject\"", "\"Renamed subject\"");
assert_preserved_update(&original, &expected, &source);
}
#[test]
fn explicit_exchange_operation_none_survives_exchange_binding_edit() {
let mut expected = exchange_project();
let original = checked_fixture(EXCHANGE_SOURCE, &expected);
let Some(SharedBody::Occurrence {
body: OccurrenceBody::Exchange { exchange, .. },
..
}) = expected.elements.get_mut(&id(6)).unwrap().body.shared_mut()
else {
panic!("literal fixture must be an Exchange occurrence");
};
*exchange = Some(id(4));
let source = replacement(EXCHANGE_SOURCE, "exchange none;", "exchange @flow;");
assert_preserved_update(&original, &expected, &source);
}
#[test]
fn explicit_node_empty_port_anchors_survive_noop() {
let expected = node_project();
let original = checked_fixture(NODE_SOURCE, &expected);
assert_preserved_update(&original, &expected, NODE_SOURCE);
}
#[test]
fn explicit_node_empty_port_anchors_survive_header_alias_rename() {
let mut expected = node_project();
let original = checked_fixture(NODE_SOURCE, &expected);
// Nodes have no display-name header. Rename the same node's header alias.
expected
.diagrams
.get_mut(&id(3))
.unwrap()
.nodes
.get_mut(&id(4))
.unwrap()
.alias = "renamed_placement".into();
let source = replacement(
NODE_SOURCE,
"node placement id",
"node renamed_placement id",
);
assert_preserved_update(&original, &expected, &source);
}
#[test]
fn explicit_node_empty_port_anchors_survive_position_edit() {
let mut expected = node_project();
let original = checked_fixture(NODE_SOURCE, &expected);
expected
.diagrams
.get_mut(&id(3))
.unwrap()
.nodes
.get_mut(&id(4))
.unwrap()
.position
.x = 25.0;
let source = replacement(NODE_SOURCE, "x 12.5;", "x 25.0;");
assert_preserved_update(&original, &expected, &source);
}
#[test]
fn explicit_trace_semantic_none_survives_noop() {
let expected = trace_project();
let original = checked_fixture(TRACE_SOURCE, &expected);
assert_preserved_update(&original, &expected, TRACE_SOURCE);
}
#[test]
fn explicit_trace_semantic_none_survives_header_rename() {
let mut expected = trace_project();
let original = checked_fixture(TRACE_SOURCE, &expected);
expected.traces.get_mut(&id(4)).unwrap().name = "Renamed mapping".into();
let source = replacement(TRACE_SOURCE, "\"Mapping\"", "\"Renamed mapping\"");
assert_preserved_update(&original, &expected, &source);
}
#[test]
fn explicit_trace_semantic_none_survives_snapshot_prose_edit() {
let mut expected = trace_project();
let original = checked_fixture(TRACE_SOURCE, &expected);
expected
.traces
.get_mut(&id(4))
.unwrap()
.baseline
.as_mut()
.unwrap()
.oa
.description = "Revised OA snapshot".into();
let source = replacement(
TRACE_SOURCE,
"\"Old OA snapshot\"",
"\"Revised OA snapshot\"",
);
assert_preserved_update(&original, &expected, &source);
}
#[test]
fn constructor_change_removes_inapplicable_default_and_keeps_shared_default() {
let mut expected = project();
expected.elements = [
element(
2,
"owner",
"Owner",
None,
ElementBody::Oa(OaBody::Shared(SharedBody::Scenario {
viewpoint: ScenarioViewpoint::MixedLegacy,
context: None,
precondition: String::new(),
postcondition: String::new(),
steps: vec![id(3)],
})),
),
element(
3,
"subject",
"Subject",
Some(id(2)),
ElementBody::Oa(OaBody::Shared(SharedBody::Occurrence {
body: OccurrenceBody::Action {
action: None,
lifeline: None,
},
precondition: String::new(),
postcondition: String::new(),
})),
),
]
.into_iter()
.map(|element| (element.id, element))
.collect();
let original = checked_fixture(CONSTRUCTOR_SOURCE, &expected);
expected.elements.get_mut(&id(3)).unwrap().body =
ElementBody::Oa(OaBody::Shared(SharedBody::Occurrence {
body: OccurrenceBody::State {
state: None,
lifeline: None,
},
precondition: String::new(),
postcondition: String::new(),
}));
// Remove only the inapplicable field's syntax: its separating space and all
// surrounding trivia stay. The shared lifeline spelling/comment is exact.
let source = replacement(CONSTRUCTOR_SOURCE, "body { Action {", "body { State {");
let compact = replacement(&source, "action none;", " ");
let expanded = replacement(
&compact,
" }; };",
" state none;\n }; };",
);
// Omission of newly applicable state and explicit state none both mean the
// literal State target; neither may retain the old action field.
assert_context_update(&original, &expected, &[compact, expanded]);
}
#[test]
fn relation_kind_change_updates_explicit_value_when_context_default_changes() {
let mut expected = project();
expected.elements = [
element(2, "first", "First", None, ElementBody::Oa(OaBody::Entity)),
element(3, "second", "Second", None, ElementBody::Oa(OaBody::Entity)),
element(
5,
"output",
"Output",
Some(id(2)),
ElementBody::Oa(OaBody::Shared(SharedBody::Port {
contract: PortContract::Legacy,
legacy: Some(LegacyPortContract {
direction: PortDirection::Out,
interface: None,
}),
})),
),
element(
6,
"input",
"Input",
Some(id(3)),
ElementBody::Oa(OaBody::Shared(SharedBody::Port {
contract: PortContract::Legacy,
legacy: Some(LegacyPortContract {
direction: PortDirection::In,
interface: None,
}),
})),
),
]
.into_iter()
.map(|element| (element.id, element))
.collect();
expected.relations.insert(
id(4),
Relation {
id: id(4),
alias: "link".into(),
name: "Link".into(),
description: String::new(),
body: RelationBody::BoundaryExchange {
source: id(5),
target: id(6),
oriented: true,
items: vec![],
},
},
);
let original = checked_fixture(RELATION_CONTEXT_SOURCE, &expected);
// BoundaryExchange connects participant ports; CommunicationMean connects
// their OA entity owners. Endpoint changes are explicit parts of this target,
// not inferred or normalized away. Port records remain exactly unchanged.
expected.relations.get_mut(&id(4)).unwrap().body = RelationBody::CommunicationMean {
source: id(2),
target: id(3),
oriented: false,
items: vec![],
};
let source = replacement(
RELATION_CONTEXT_SOURCE,
"boundary_exchange link",
"communication_mean link",
);
let source = replacement(&source, "source @output;", "source @first;");
let source = replacement(&source, "target @input;", "target @second;");
let explicit = replacement(&source, "oriented true ;", "oriented false ;");
// Three spaces between name/value plus one before the semicolon survive
// syntax removal. The following rationale comment and all other bytes stay.
let omitted = replacement(&source, "oriented true ;", " ");
assert_context_update(&original, &expected, &[explicit, omitted]);
}
@@ -0,0 +1,222 @@
//! Public update witnesses for compact insertion and complete comparison.
use oa_core::{language, source_project::*, *};
use std::collections::BTreeMap;
fn compile(body: &str) -> CompiledSourceProject {
compile_bundle(
&SourceBundle {
entry: "project.arcadia".into(),
files: BTreeMap::from([
(
"project.arcadia".into(),
"arcadia 7; project p \"P\" { include \"model.arcadia\"; }".into(),
),
("model.arcadia".into(), body.into()),
]),
},
ProjectLimits::default(),
)
.unwrap()
}
fn id(project: &Project, alias: &str) -> Id {
project
.identities()
.into_iter()
.find(|(_, a)| *a == alias)
.unwrap()
.0
}
fn update(before: &CompiledSourceProject, project: &Project) -> CompiledSourceProject {
let result = update_bundle(before, project, ProjectLimits::default()).unwrap();
assert_eq!(result.decoded.project, *project);
assert_eq!(
result.bundle.files["project.arcadia"],
before.bundle.files["project.arcadia"]
);
assert_eq!(
compile_bundle(&result.bundle, ProjectLimits::default())
.unwrap()
.bundle,
result.bundle
);
assert_eq!(
update_bundle(&result, project, ProjectLimits::default())
.unwrap()
.bundle,
result.bundle
);
result
}
fn words(text: &str) -> Vec<&str> {
language::tokenize(text)
.into_iter()
.filter(|t| {
!matches!(
t.class,
language::TokenClass::Whitespace | language::TokenClass::Comment
)
})
.map(|t| &text[t.span.start..t.span.end])
.collect()
}
#[test]
fn omitted_exchange_body_changes_to_one_compact_action_constructor() {
let before = compile(
"oa_scenario s \"S\" { steps [@o]; }\noa_occurrence o \"O\" { parent @s; // keep this\n}\n",
);
let mut project = before.decoded.project.clone();
let occurrence = id(&project, "o");
let Some(SharedBody::Occurrence { body, .. }) =
project.elements.get_mut(&occurrence).unwrap().shared_mut()
else {
panic!("occurrence")
};
assert!(matches!(body, OccurrenceBody::Exchange { .. }));
*body = OccurrenceBody::Action {
action: None,
lifeline: None,
};
let result = update(&before, &project);
let text = &result.bundle.files["model.arcadia"];
let tokens = words(text);
assert_eq!(tokens.iter().filter(|&&t| t == "body").count(), 1);
assert!(tokens.contains(&"Action"));
for absent in ["Exchange", "action", "lifeline", "operation", "description"] {
assert!(!tokens.contains(&absent), "{absent}: {text}");
}
assert!(text.contains("parent @s; // keep this"));
}
#[test]
fn default_signal_constructor_still_inserts_its_required_empty_items() {
let before =
compile("oa_event e \"E\" { body { Time { expression \"1 s\"; kind After; }; }; }\n");
let mut project = before.decoded.project.clone();
let event = id(&project, "e");
let Some(SharedBody::Event { body }) = project.elements.get_mut(&event).unwrap().shared_mut()
else {
panic!("event")
};
*body = EventBody::Signal { items: vec![] };
let result = update(&before, &project);
let tokens = words(&result.bundle.files["model.arcadia"]);
assert!(tokens.contains(&"Signal"));
assert!(tokens.windows(4).any(|w| w == ["items", "[", "]", ";"]));
assert!(!tokens.contains(&"expression"));
assert!(!tokens.contains(&"kind"));
}
#[test]
fn scalar_to_default_string_record_retains_required_minimum_only() {
let before = compile("oa_data_type t \"T\" { definition Boolean; // typed rationale\n}\n");
let mut project = before.decoded.project.clone();
let datatype = id(&project, "t");
let Some(SharedBody::DataType { definition, .. }) =
project.elements.get_mut(&datatype).unwrap().shared_mut()
else {
panic!("data type")
};
*definition = DataTypeDefinition::String {
minimum_length: 0,
maximum_length: None,
};
let result = update(&before, &project);
let text = &result.bundle.files["model.arcadia"];
let tokens = words(text);
assert!(tokens.contains(&"String"));
assert!(tokens.windows(3).any(|w| w == ["minimum_length", "0", ";"]));
assert!(!tokens.contains(&"maximum_length"));
assert!(text.contains("// typed rationale"));
}
#[test]
fn inserted_route_list_item_omits_only_optional_fields() {
let before = compile(
"oa_activity a \"A\" {}\noa_activity b \"B\" {}\ninteraction flow \"Flow\" { source @a; target @b; }\ndiagram d \"D\" { routes []; // routing rationale\n}\n",
);
let mut project = before.decoded.project.clone();
let diagram = id(&project, "d");
let edge = id(&project, "flow");
// Deserialize only the explicit witness value, independently of omission policy.
project.diagrams.get_mut(&diagram).unwrap().routes =
serde_json::from_value(serde_json::json!([{"edge":edge,"waypoints":[],"label":null}]))
.unwrap();
let result = update(&before, &project);
let text = &result.bundle.files["model.arcadia"];
let tokens = words(text);
assert!(tokens.windows(4).any(|w| w == ["edge", "@", "flow", ";"]));
assert!(!tokens.contains(&"waypoints"));
assert!(!tokens.contains(&"label"));
assert!(text.contains("// routing rationale"));
}
#[test]
fn zero_sign_change_inserts_position_without_default_siblings() {
let before = compile(
"oa_activity a \"A\" {}\ndiagram d \"D\" {}\nnode n { diagram @d; element @a; // placement rationale\n}\n",
);
let mut project = before.decoded.project.clone();
let diagram = id(&project, "d");
let node = id(&project, "n");
assert_eq!(
project.diagrams[&diagram].nodes[&node].position.x.to_bits(),
0.0_f32.to_bits()
);
project
.diagrams
.get_mut(&diagram)
.unwrap()
.nodes
.get_mut(&node)
.unwrap()
.position
.x = -0.0;
let result = update(&before, &project);
assert_eq!(
result.decoded.project.diagrams[&diagram].nodes[&node]
.position
.x
.to_bits(),
(-0.0_f32).to_bits()
);
let text = &result.bundle.files["model.arcadia"];
let tokens = words(text);
assert!(tokens.windows(3).any(|w| w == ["x", "-0.0", ";"]));
for absent in ["size", "collapsed", "port_anchors"] {
assert!(!tokens.contains(&absent));
}
assert!(text.contains("// placement rationale"));
}
#[test]
fn kind_change_materializes_unchanged_value_when_its_old_omission_default_changes() {
let before = compile(
"oa_entity a \"A\" {}\noa_entity b \"B\" {}\noa_port out \"Out\" { parent @a; contract Legacy; legacy { direction Out; }; }\noa_port input \"In\" { parent @b; contract Legacy; legacy { direction In; }; }\nboundary_exchange flow \"Flow\" { source @out; target @input; // orientation inherited\n}\n",
);
let mut project = before.decoded.project.clone();
let flow = id(&project, "flow");
assert!(matches!(
project.relations[&flow].body,
RelationBody::BoundaryExchange { oriented: true, .. }
));
project.relations.get_mut(&flow).unwrap().body = RelationBody::CommunicationMean {
source: id(&before.decoded.project, "a"),
target: id(&before.decoded.project, "b"),
oriented: true,
items: vec![],
};
let result = update(&before, &project);
let text = &result.bundle.files["model.arcadia"];
assert!(
words(text)
.windows(3)
.any(|w| w == ["oriented", "true", ";"])
);
assert!(text.contains("// orientation inherited"));
let before_ports = &before.bundle.files["model.arcadia"];
assert_eq!(
text.split("communication_mean").next().unwrap(),
before_ports.split("boundary_exchange").next().unwrap()
);
}
+34 -13
View File
@@ -3,6 +3,16 @@ use oa_core::{language, persistence, validation, *};
fn read(source: &str) -> Project {
language::parse(source).unwrap_or_else(|e| panic!("{e:?}\n{source}"))
}
fn replace_once(source: &str, before: &str, after: &str) -> String {
assert_eq!(
source.matches(before).count(),
1,
"unique mutation anchor {before:?}"
);
let changed = source.replacen(before, after, 1);
assert_ne!(changed, source, "test mutation must change the source");
changed
}
fn rejected(source: &str, fragment: &str) {
let errors = language::parse(source).expect_err(source);
assert!(
@@ -753,8 +763,8 @@ fn typed_events_vertices_and_transition_kinds_round_trip_in_both_layers() {
source.push('}');
let model = read(&source);
let canonical = language::format(&model);
assert!(!canonical.contains("transition_kind External;"));
for fragment in [
"transition_kind External;",
"transition_kind Local;",
"transition_kind Internal;",
"triggers [@deadline, @assess];",
@@ -776,6 +786,7 @@ fn typed_events_vertices_and_transition_kinds_round_trip_in_both_layers() {
.find(|r| r.alias == "recover")
.unwrap();
let RelationBody::StateTransition {
transition_kind,
trigger,
guard,
effect,
@@ -784,32 +795,42 @@ fn typed_events_vertices_and_transition_kinds_round_trip_in_both_layers() {
else {
panic!("expected transition")
};
assert_eq!(*transition_kind, TransitionKind::External);
assert_eq!(trigger, "Operator confirms\nНе запускать код");
assert_eq!(guard, "available && status == \"Ready\"");
assert_eq!(effect, "Record the assessment");
// External is omitted in compact output. Insert an explicit invalid
// discriminator into Recover using its unique, nondefault trigger list.
let invalid_kind = replace_once(
&canonical,
"triggers [@deadline, @assess];",
"transition_kind Unsupported;\n triggers [@deadline, @assess];",
);
rejected(&invalid_kind, "Unsupported");
for invalid in [
canonical.replace("transition_kind External;", "transition_kind Unsupported;"),
canonical.replace(
replace_once(
&canonical,
"triggers [@deadline, @assess];",
"triggers [\"deadline\", @assess];",
),
canonical.replace(
replace_once(
&canonical,
"triggers [@deadline, @assess];",
"triggers [@deadline, @deadline];",
),
canonical.replace("effects [@assess];", "effects [@payload];"),
canonical.replace("kind After;", "kind Relative;"),
canonical.replace("items [@payload];", "items [@assess];"),
replace_once(&canonical, "effects [@assess];", "effects [@payload];"),
replace_once(&canonical, "kind After;", "kind Relative;"),
replace_once(&canonical, "items [@payload];", "items [@assess];"),
] {
assert!(
language::parse(&invalid).is_err(),
"invalid typed state/event source accepted"
);
}
let legacy = canonical
.replace("transition_kind External;", "")
.replace("triggers [@deadline, @assess];", "")
.replace("effects [@assess];", "");
// Canonical source already omits External. Removing the supplied lists
// independently exercises their empty omission defaults and retained prose.
let legacy = replace_once(&canonical, "triggers [@deadline, @assess];", "");
let legacy = replace_once(&legacy, "effects [@assess];", "");
let legacy = read(&legacy);
let RelationBody::StateTransition {
transition_kind,
@@ -829,7 +850,7 @@ fn typed_events_vertices_and_transition_kinds_round_trip_in_both_layers() {
(retained_trigger, retained_guard, retained_effect),
(trigger, guard, effect)
);
let draft = canonical.replace("expression \"30 s\";", "expression \"\";");
let draft = replace_once(&canonical, "expression \"30 s\";", "expression \"\";");
assert!(language::parse(&draft).is_ok());
let deadline = model
.elements
@@ -1038,7 +1059,7 @@ fn original_v2_text_and_json_preserve_identity_and_mixed_scenarios_after_extensi
"fixture must cover actual old scenario content"
);
let canonical = language::format(&from_source);
assert!(canonical.contains("viewpoint MixedLegacy;"));
assert!(!canonical.contains("viewpoint MixedLegacy;"));
for relation in from_source.relations.values() {
if let RelationBody::StateTransition {
transition_kind,
+9 -1
View File
@@ -767,7 +767,15 @@ fn resetting_last_source_anchors_preserves_comments_and_can_add_a_new_manual_rec
assert_eq!(cleared.decoded.project, prepared.project);
let text = &cleared.bundle.files["project.arcadia"];
assert!(text.contains("// first rationale") && text.contains("// second rationale"));
assert!(!text.contains("port_anchors"));
// Lossless editing may retain the authored empty field and its annotations.
// Canonical export, rather than a model update, normalizes default syntax.
assert!(!language::format(&cleared.decoded.project).contains("port_anchors"));
assert_eq!(
update_bundle(&cleared, &cleared.decoded.project, ProjectLimits::default())
.unwrap()
.bundle,
cleared.bundle
);
let prepared = prepare_command(
&cleared.decoded.project,
&Command::SetPortAnchor {
@@ -0,0 +1,353 @@
//! Existing counterpart eligibility is a layer/category/kind decision, not
//! validation or acceptance of an entire transition plan. The catalog fixtures
//! below intentionally contain partial records (unowned states, etc.). The
//! final regression separately starts from a structurally valid real project.
use oa_core::{transitions::*, *};
use std::collections::{BTreeMap, BTreeSet};
fn element(p: &mut Project, layer: Layer, kind: Kind) -> Id {
let id = Id::new_v4();
let body = if kind == Kind::Port {
// Legacy ports can exist in preserved OA models. This does not offer
// new typed SA implementation ports for creation in portless OA.
ElementBody::shared_in(
layer,
SharedBody::Port {
contract: PortContract::Legacy,
legacy: None,
},
)
} else {
ElementBody::new(kind, layer).unwrap()
};
p.elements.insert(
id,
Element {
id,
alias: format!("candidate_{}", id.simple()),
name: "Identical candidate label".into(),
description: String::new(),
parent: None,
body,
},
);
id
}
#[test]
fn existing_element_candidates_obey_explicit_engineering_families_in_both_directions() {
// Contract: language.rst §5 trace families and the explicit capability /
// mission alternative. State constructors remain eligible for explicit
// review across the state family; selecting a candidate does not accept a
// constructor change or make invalid ownership legal.
let mut allowed: BTreeSet<(Kind, Kind)> = [
(Kind::Activity, Kind::Function),
(Kind::Entity, Kind::System),
(Kind::Entity, Kind::SystemActor),
(Kind::Actor, Kind::System),
(Kind::Actor, Kind::SystemActor),
(Kind::Capability, Kind::SystemCapability),
(Kind::OperationalMission, Kind::Mission),
(Kind::Capability, Kind::Mission),
(Kind::Process, Kind::FunctionalChain),
]
.into_iter()
.collect();
for kind in [
Kind::ExchangeItem,
Kind::ExchangeCategory,
Kind::DataType,
Kind::DataField,
Kind::Interface,
Kind::Port,
Kind::Scenario,
Kind::Lifeline,
Kind::Occurrence,
Kind::StateMachine,
Kind::Region,
Kind::Event,
Kind::Constraint,
] {
allowed.insert((kind, kind));
}
let vertices = [
Kind::State,
Kind::Mode,
Kind::Initial,
Kind::Final,
Kind::Choice,
Kind::Junction,
Kind::Fork,
Kind::Join,
Kind::ShallowHistory,
Kind::DeepHistory,
Kind::EntryPoint,
Kind::ExitPoint,
Kind::Terminate,
];
for oa in vertices {
for sa in vertices {
allowed.insert((oa, sa));
}
}
let mut p = Project::empty();
let oa: BTreeMap<_, _> = Kind::ALL
.into_iter()
.filter(|kind| kind.supports(Layer::Oa))
.map(|kind| (kind, element(&mut p, Layer::Oa, kind)))
.collect();
let sa: BTreeMap<_, _> = Kind::ALL
.into_iter()
.filter(|kind| kind.supports(Layer::Sa))
.map(|kind| (kind, element(&mut p, Layer::Sa, kind)))
.collect();
let original = p.clone();
for (&oa_kind, &oa_id) in &oa {
for (&sa_kind, &sa_id) in &sa {
let expected = allowed.contains(&(oa_kind, sa_kind));
assert_eq!(
counterpart_compatible(&p, oa_id, sa_id, TransitionDirection::OaToSa),
expected,
"OA {oa_kind:?} → SA {sa_kind:?}"
);
assert_eq!(
counterpart_compatible(&p, sa_id, oa_id, TransitionDirection::SaToOa),
expected,
"SA {sa_kind:?} → OA {oa_kind:?}"
);
}
}
assert_eq!(
p, original,
"Eligibility must not establish correspondence or edit records"
);
}
fn relation(p: &mut Project, kind: RelationKind, anchor: Id) -> Id {
let id = Id::new_v4();
// Only the semantic layer and relation category participate in candidate
// eligibility. Endpoint compatibility belongs to full plan validation.
p.relations.insert(
id,
Relation {
id,
alias: format!("relation_{}", id.simple()),
name: "Identical relation label".into(),
description: String::new(),
body: RelationBody::new(kind, anchor, anchor),
},
);
id
}
#[test]
fn existing_relation_candidates_distinguish_work_boundary_and_shared_relations() {
// Work and boundary exchanges are different families. The boundary /
// boundary pair permits preserved legacy OA boundary records; ordinary
// portless OA communication means map to SA boundary exchanges.
let mut allowed = vec![
(RelationKind::Interaction, RelationKind::FunctionalExchange),
(
RelationKind::CommunicationMean,
RelationKind::BoundaryExchange,
),
(
RelationKind::BoundaryExchange,
RelationKind::BoundaryExchange,
),
];
let shared = [
RelationKind::Allocation,
RelationKind::InterfaceItemAllocation,
RelationKind::DataAssociation,
RelationKind::ExchangeAllocation,
RelationKind::CapabilityInvolvement,
RelationKind::CapabilityInclude,
RelationKind::CapabilityExtend,
RelationKind::CapabilityGeneralization,
RelationKind::MissionInvolvement,
RelationKind::MissionCapability,
RelationKind::SequenceLink,
RelationKind::ContextualLink,
RelationKind::ChainInvolvement,
RelationKind::InterfaceProvision,
RelationKind::InterfaceRequirement,
RelationKind::StateTransition,
RelationKind::Availability,
RelationKind::StateAction,
];
for kind in shared {
allowed.push((kind, kind));
}
let mut p = Project::empty();
let oa_anchor = element(&mut p, Layer::Oa, Kind::Activity);
let sa_anchor = element(&mut p, Layer::Sa, Kind::Function);
let oa: Vec<_> = shared
.into_iter()
.chain([
RelationKind::Interaction,
RelationKind::CommunicationMean,
RelationKind::BoundaryExchange,
RelationKind::RoleRealization,
RelationKind::RoleAssignment,
])
.map(|kind| (kind, relation(&mut p, kind, oa_anchor)))
.collect();
let sa: Vec<_> = shared
.into_iter()
.chain([
RelationKind::FunctionalExchange,
RelationKind::BoundaryExchange,
RelationKind::PortAllocation,
])
.map(|kind| (kind, relation(&mut p, kind, sa_anchor)))
.collect();
for &(oa_kind, oa_id) in &oa {
for &(sa_kind, sa_id) in &sa {
let expected = allowed.contains(&(oa_kind, sa_kind));
assert_eq!(
counterpart_compatible(&p, oa_id, sa_id, TransitionDirection::OaToSa),
expected,
"OA {oa_kind:?} → SA {sa_kind:?}"
);
assert_eq!(
counterpart_compatible(&p, sa_id, oa_id, TransitionDirection::SaToOa),
expected,
"SA {sa_kind:?} → OA {oa_kind:?}"
);
}
}
}
#[test]
fn candidate_eligibility_rejects_wrong_layers_categories_and_missing_identities() {
let mut p = Project::empty();
let oa = element(&mut p, Layer::Oa, Kind::Activity);
let sa = element(&mut p, Layer::Sa, Kind::Function);
let oa_exchange = relation(&mut p, RelationKind::Interaction, oa);
let sa_exchange = relation(&mut p, RelationKind::FunctionalExchange, sa);
let diagram = p.ensure_diagram(Layer::Sa);
let node = p.place(diagram, sa, Position { x: 1., y: 2. });
for (direction, source, target, source_relation, target_relation) in [
(
TransitionDirection::OaToSa,
oa,
sa,
oa_exchange,
sa_exchange,
),
(
TransitionDirection::SaToOa,
sa,
oa,
sa_exchange,
oa_exchange,
),
] {
assert!(counterpart_compatible(&p, source, target, direction));
assert!(counterpart_compatible(
&p,
source_relation,
target_relation,
direction
));
for (a, b) in [
(target, source),
(source, source),
(target, target),
(target_relation, source_relation),
(source_relation, source_relation),
(source, target_relation),
(source_relation, target),
] {
assert!(!counterpart_compatible(&p, a, b, direction));
}
for absent_or_nonsemantic in [Id::nil(), Id::new_v4(), p.id, diagram, node] {
assert!(!counterpart_compatible(
&p,
source,
absent_or_nonsemantic,
direction
));
assert!(!counterpart_compatible(
&p,
absent_or_nonsemantic,
target,
direction
));
}
}
p.elements.remove(&sa);
assert!(!counterpart_compatible(
&p,
oa,
sa,
TransitionDirection::OaToSa
));
assert!(!counterpart_compatible(
&p,
sa,
oa,
TransitionDirection::SaToOa
));
// The retained relation header cannot borrow a layer from a deleted owner.
assert!(!counterpart_compatible(
&p,
oa_exchange,
sa_exchange,
TransitionDirection::OaToSa
));
}
#[test]
fn activity_picker_rejects_real_sa_port_and_agrees_with_explicit_plan_refusal() {
let p = Project::demo();
let original = p.clone();
assert!(validation::validate(&p).is_empty());
let source = p
.elements
.values()
.find(|e| e.kind() == Kind::Activity)
.unwrap()
.id;
let port = p
.elements
.values()
.find(|e| e.layer() == Layer::Sa && e.kind() == Kind::Port)
.unwrap()
.id;
let function = p
.elements
.values()
.find(|e| e.kind() == Kind::Function)
.unwrap()
.id;
assert!(counterpart_compatible(
&p,
source,
function,
TransitionDirection::OaToSa
));
assert!(!counterpart_compatible(
&p,
source,
port,
TransitionDirection::OaToSa
));
let mut request = TransitionRequest::new(TransitionDirection::OaToSa, vec![source]);
request.mappings.push(TransitionMapping {
source,
target: Some(port),
target_kind: None,
});
let plan = plan_transition(&p, &request).unwrap();
assert!(
plan.conflicts
.iter()
.any(|conflict| conflict.source == Some(source)
&& conflict.target == Some(port)
&& conflict.field.is_none())
);
assert!(apply_transition(&p, &plan).is_err());
assert_eq!(p, original);
}
@@ -0,0 +1,628 @@
//! Endpoint lifecycle acceptance for the ordinary, prose-reviewed transition API.
//! OA is portless; the SA half contains typed ports. Deletion expectations below
//! are fixture-owned identity lists, never results of the deletion implementation.
use oa_core::{
language, persistence,
source_project::{ProjectLimits, SourceBundle, compile_bundle},
transitions::*,
validation::{self, Severity},
*,
};
use std::collections::BTreeMap;
struct Side {
root: Id,
elements: Vec<Id>,
exchange: Id,
diagram: Id,
nodes: Vec<Id>,
artifacts: Vec<Id>,
}
struct Fixture {
project: Project,
source: Side,
target: Side,
trace: Id,
trace_artifact: Id,
}
fn artifact(p: &mut Project, owner: Id, label: &str) -> Id {
let id = Id::new_v4();
p.artifact_links.insert(
id,
ArtifactLink {
id,
alias: p.fresh_alias(label, "evidence", id),
name: label.into(),
description: "Independently authored rationale".into(),
owner,
relation: ArtifactRelation::References,
uri: "git:engineering/design.md".into(),
revision: Some("review-17".into()),
fragment: Some("retained-decision".into()),
},
);
id
}
fn side(p: &mut Project, layer: Layer) -> Side {
let (kind, prefix, relation) = match layer {
Layer::Oa => (Kind::Activity, "Operational", RelationKind::Interaction),
Layer::Sa => (
Kind::Function,
"Technical",
RelationKind::FunctionalExchange,
),
};
let root = p
.add_element(kind, layer, &format!("{prefix} design"), None)
.unwrap();
p.elements.get_mut(&root).unwrap().description =
format!("{prefix} independently reviewed intent");
let send = p
.add_element(kind, layer, &format!("{prefix} send"), Some(root))
.unwrap();
let receive = p
.add_element(kind, layer, &format!("{prefix} receive"), Some(root))
.unwrap();
p.elements.get_mut(&send).unwrap().description =
"Preserve the detailed sending responsibility".into();
p.elements.get_mut(&receive).unwrap().description =
"Preserve the detailed receiving responsibility".into();
// A shared, separately owned definition survives deletion of its users.
let item = p
.add_element(
Kind::ExchangeItem,
layer,
&format!("{prefix} information"),
None,
)
.unwrap();
*p.elements.get_mut(&item).unwrap().shared_mut().unwrap() = SharedBody::ExchangeItem {
kind: ExchangeItemKind::Information,
mechanism: ExchangeMechanism::Flow,
supertype: None,
};
p.add_element(
Kind::DataField,
layer,
&format!("{prefix} payload"),
Some(item),
)
.unwrap();
let exchange = p.relate(relation, &format!("{prefix} delivery"), send, receive);
let mut elements = vec![root, send, receive];
match &mut p.relations.get_mut(&exchange).unwrap().body {
RelationBody::Interaction { items, .. } => *items = vec![item],
RelationBody::FunctionalExchange {
items,
source_port,
target_port,
..
} => {
*items = vec![item];
let (output, input) = (*source_port, *target_port);
*p.elements.get_mut(&output).unwrap().shared_mut().unwrap() = SharedBody::Port {
contract: PortContract::FunctionOutput {
outgoing_items: vec![item],
},
legacy: None,
};
*p.elements.get_mut(&input).unwrap().shared_mut().unwrap() = SharedBody::Port {
contract: PortContract::FunctionInput {
incoming_items: vec![item],
},
legacy: None,
};
elements.extend([output, input]);
}
_ => unreachable!(),
}
p.relations.get_mut(&exchange).unwrap().description =
"Retain direction, item contract and relation identity".into();
let diagram = p.ensure_diagram(layer);
p.diagrams.get_mut(&diagram).unwrap().projection = ProjectionKind::Decomposition;
let nodes = [root, send, receive]
.into_iter()
.enumerate()
.map(|(index, element)| {
p.place(
diagram,
element,
Position {
x: 43.25 + index as f32 * 271.,
y: 97.5 + index as f32 * 83.,
},
)
})
.collect::<Vec<_>>();
let root_node = p
.diagrams
.get_mut(&diagram)
.unwrap()
.nodes
.get_mut(&nodes[0])
.unwrap();
root_node.size = Size {
width: 611.,
height: 319.,
};
root_node.collapsed = true;
p.diagrams
.get_mut(&diagram)
.unwrap()
.routes
.push(EdgeRoute {
edge: exchange,
waypoints: vec![Position {
x: 337.5,
y: 423.25,
}],
label: Some(EdgeLabel {
fraction: 0.7,
offset: Position { x: 11., y: -17. },
}),
});
let artifacts = [root, send, exchange, nodes[0]]
.into_iter()
.enumerate()
.map(|(index, owner)| artifact(p, owner, &format!("{prefix} evidence {index}")))
.collect();
artifact(p, item, &format!("{prefix} shared item evidence"));
Side {
root,
elements,
exchange,
diagram,
nodes,
artifacts,
}
}
fn accepted(p: &Project) {
let issues = validation::validate(p);
// This fixture deliberately isolates decomposition and correspondence from
// performer allocation and process/chain/scenario inclusion. Each surviving
// leaf (including a newly transitioned root) must have exactly those two
// incomplete-work warnings; each surviving local exchange lacks a behavior
// context. Check exact record IDs and fields, including duplicate warnings.
// The system of interest and item contracts are complete fixture facts.
assert!(
issues
.iter()
.all(|issue| issue.severity == Severity::Warning),
"{issues:?}"
);
let mut expected = Vec::new();
for element in p.elements.values().filter(|element| {
matches!(element.kind(), Kind::Activity | Kind::Function)
&& !p
.elements
.values()
.any(|child| child.parent == Some(element.id) && child.kind() == element.kind())
}) {
expected.push(("unallocated_leaf", Some(element.id), Some("allocation")));
expected.push(("work_without_behavior_context", Some(element.id), None));
}
for relation in p.relations.values().filter(|relation| {
matches!(
relation.kind(),
RelationKind::Interaction | RelationKind::FunctionalExchange
)
}) {
expected.push(("exchange_without_behavior_context", Some(relation.id), None));
}
let mut actual = issues
.iter()
.map(|issue| (issue.code.as_str(), issue.element, issue.field.as_deref()))
.collect::<Vec<_>>();
expected.sort();
actual.sort();
assert_eq!(
actual, expected,
"Unexpected fixture diagnostics: {issues:?}"
);
}
fn round_trip(p: &Project) -> String {
accepted(p);
let json = serde_json::to_vec_pretty(p).unwrap();
assert_eq!(persistence::decode(&json).unwrap(), *p);
let source = language::format(p);
let compiled = compile_bundle(
&SourceBundle {
entry: "project.arcadia".into(),
files: BTreeMap::from([("project.arcadia".into(), source.clone())]),
},
ProjectLimits::default(),
)
.unwrap();
assert_eq!(compiled.decoded.project, *p);
assert_eq!(language::format(&compiled.decoded.project), source);
source
}
fn explicit(direction: TransitionDirection, source: Id, target: Id) -> TransitionRequest {
let mut request = TransitionRequest::new(direction, vec![source]);
request.mappings.push(TransitionMapping {
source,
target: Some(target),
target_kind: None,
});
request
}
fn fixture(direction: TransitionDirection) -> Fixture {
let mut project = Project::empty();
project.name = "Endpoint lifecycle conservation".into();
project
.add_element(Kind::System, Layer::Sa, "System of interest", None)
.unwrap();
let oa = side(&mut project, Layer::Oa);
let sa = side(&mut project, Layer::Sa);
let (source, target) = match direction {
TransitionDirection::OaToSa => (oa, sa),
TransitionDirection::SaToOa => (sa, oa),
};
accepted(&project);
let before = project.clone();
let mut request = explicit(direction, source.root, target.root);
for field in [TransitionField::Name, TransitionField::Description] {
request.resolutions.push(FieldResolution {
source: source.root,
field,
decision: FieldDecision::KeepTarget,
});
}
let plan = plan_transition(&project, &request).unwrap();
assert!(plan.conflicts.is_empty(), "{:?}", plan.conflicts);
project = apply_transition(&project, &plan).unwrap();
let trace = assert_only_new_trace(&before, &project, direction, source.root, target.root);
let mut without_trace = project.clone();
without_trace.traces.remove(&trace);
assert_eq!(
without_trace, before,
"Initial review must only add correspondence"
);
let trace_artifact = artifact(&mut project, trace, "Correspondence rationale");
round_trip(&project);
Fixture {
project,
source,
target,
trace,
trace_artifact,
}
}
fn assert_only_new_trace(
before: &Project,
after: &Project,
direction: TransitionDirection,
source: Id,
target: Id,
) -> Id {
let added = after
.traces
.values()
.filter(|trace| !before.traces.contains_key(&trace.id))
.collect::<Vec<_>>();
assert_eq!(added.len(), 1);
assert_eq!(after.traces.len(), before.traces.len() + 1);
let trace = added[0];
let (oa, sa) = match direction {
TransitionDirection::OaToSa => (source, target),
TransitionDirection::SaToOa => (target, source),
};
assert_eq!(
(trace.oa, trace.sa, trace.kind),
(oa, sa, TraceKind::Refinement)
);
assert_eq!(
trace.baseline,
Some(TraceBaseline {
oa: after.properties(oa).unwrap(),
sa: after.properties(sa).unwrap(),
semantic: None,
})
);
assert_eq!(trace_drift(after, trace), Drift::Unchanged);
trace.id
}
fn expected_deletion(fixture: &Fixture, side: &Side) -> Project {
let mut expected = fixture.project.clone();
for id in &side.elements {
assert!(expected.elements.remove(id).is_some());
}
assert!(expected.relations.remove(&side.exchange).is_some());
assert!(expected.traces.remove(&fixture.trace).is_some());
for id in side.artifacts.iter().chain([&fixture.trace_artifact]) {
assert!(expected.artifact_links.remove(id).is_some());
}
let diagram = expected.diagrams.get_mut(&side.diagram).unwrap();
for id in &side.nodes {
assert!(diagram.nodes.remove(id).is_some());
}
// This fixture authored exactly one route, for its removed local exchange.
assert_eq!(diagram.routes.len(), 1);
assert_eq!(diagram.routes[0].edge, side.exchange);
diagram.routes.clear();
expected
}
fn unchanged_retry(p: &Project, direction: TransitionDirection, source: Id, target: Id) {
let request = TransitionRequest::new(direction, vec![source]);
let plan = plan_transition(p, &request).unwrap();
assert!(plan.conflicts.is_empty(), "{:?}", plan.conflicts);
assert_eq!(plan.changes.len(), 1);
assert_eq!(
(
plan.changes[0].source,
plan.changes[0].target,
plan.changes[0].action
),
(source, target, TransitionAction::Reuse)
);
assert!(plan.changes[0].fields.is_empty());
assert!(plan.commands.is_empty(), "{:?}", plan.commands);
let mut session = Session::new(p.clone());
let revision = session.revision();
session
.execute(Command::Replace(apply_transition(p, &plan).unwrap()))
.unwrap();
assert_eq!(session.project(), p);
assert_eq!(session.revision(), revision);
assert!(!session.can_undo());
}
fn source_deletion(direction: TransitionDirection) {
let fixture = fixture(direction);
let original_source = round_trip(&fixture.project);
let expected = expected_deletion(&fixture, &fixture.source);
let mut session = Session::new(fixture.project.clone());
session
.execute(Command::Delete(fixture.source.root))
.unwrap();
// Whole-project equality covers every retained target child, typed SA port,
// exchange, placement, route, artifact, shared item and unchanged identity.
assert_eq!(session.project(), &expected);
let deleted_source = round_trip(session.project());
session.undo();
assert_eq!(session.project(), &fixture.project);
assert_eq!(round_trip(session.project()), original_source);
assert!(!session.can_undo(), "Deletion is one history transaction");
session.redo();
assert_eq!(session.project(), &expected);
assert_eq!(round_trip(session.project()), deleted_source);
}
fn implicit_creation(
p: &Project,
direction: TransitionDirection,
source: Id,
forbidden_target: Id,
) -> (Project, Id) {
let request = TransitionRequest::new(direction, vec![source]);
let plan = plan_transition(p, &request).unwrap();
assert!(plan.conflicts.is_empty(), "{:?}", plan.conflicts);
assert_eq!(plan.changes.len(), 1);
let change = &plan.changes[0];
assert_eq!(change.source, source);
assert_eq!(change.action, TransitionAction::Create);
let target = change.target;
assert_ne!(target, forbidden_target);
assert!(!p.contains(target));
let after = apply_transition(p, &plan).unwrap();
let trace = assert_only_new_trace(p, &after, direction, source, target);
let created = &after.elements[&target];
assert_eq!(created.id, target, "Apply preserves the preview identity");
assert_eq!(created.name, p.elements[&source].name);
assert_eq!(created.description, p.elements[&source].description);
assert_eq!(created.parent, None);
assert_eq!(
created.body,
match direction {
TransitionDirection::OaToSa => ElementBody::Sa(SaBody::Function),
TransitionDirection::SaToOa => ElementBody::Oa(OaBody::Activity),
}
);
assert!(!created.alias.is_empty());
assert!(
!p.identities()
.iter()
.any(|(_, alias)| *alias == created.alias)
);
// Remove only the independently expected two additions. Any incidental
// mutation, clone of target children or extra correspondence fails here.
let mut retained = after.clone();
retained.elements.remove(&target);
retained.traces.remove(&trace);
assert_eq!(retained, *p);
round_trip(&after);
// Replaying a creation plan after acceptance violates its snapshot, while
// a fresh unchanged request reuses the accepted counterpart.
assert!(
apply_transition(&after, &plan)
.unwrap_err()
.0
.contains("stale")
);
unchanged_retry(&after, direction, source, target);
(after, target)
}
fn target_deletion(direction: TransitionDirection) {
let fixture = fixture(direction);
let request = TransitionRequest::new(direction, vec![fixture.source.root]);
let old_plan = plan_transition(&fixture.project, &request).unwrap();
assert!(old_plan.conflicts.is_empty());
let expected = expected_deletion(&fixture, &fixture.target);
let mut session = Session::new(fixture.project.clone());
session
.execute(Command::Delete(fixture.target.root))
.unwrap();
assert_eq!(session.project(), &expected);
round_trip(session.project());
let revision = session.revision();
assert!(
apply_transition(session.project(), &old_plan)
.unwrap_err()
.0
.contains("stale")
);
let explicit_plan = plan_transition(
session.project(),
&explicit(direction, fixture.source.root, fixture.target.root),
)
.unwrap();
assert!(
explicit_plan
.conflicts
.iter()
.any(|conflict| conflict.source == Some(fixture.source.root)
&& conflict.target == Some(fixture.target.root)
&& conflict.field.is_none())
);
assert!(apply_transition(session.project(), &explicit_plan).is_err());
assert_eq!(session.project(), &expected);
assert_eq!(session.revision(), revision);
let (created, _) = implicit_creation(
session.project(),
direction,
fixture.source.root,
fixture.target.root,
);
session.execute(Command::Replace(created.clone())).unwrap();
session.undo();
assert_eq!(session.project(), &expected);
session.undo();
assert_eq!(session.project(), &fixture.project);
assert!(!session.can_undo());
session.redo();
session.redo();
assert_eq!(session.project(), &created);
round_trip(session.project());
}
fn source_recreation(direction: TransitionDirection) {
let fixture = fixture(direction);
let mut replacement = fixture.project.elements[&fixture.source.root].clone();
replacement.id = Id::new_v4();
let source = replacement.id;
// Same alias, name and prose, different UUID: name matching must never
// silently relink the retained engineering design.
assert_ne!(source, fixture.source.root);
let deleted = execute_batch(&fixture.project, &[Command::Delete(fixture.source.root)]).unwrap();
assert_eq!(deleted, expected_deletion(&fixture, &fixture.source));
let recreated =
execute_batch(&deleted, &[Command::CreateElement(replacement.clone())]).unwrap();
let mut expected = deleted.clone();
expected.elements.insert(source, replacement);
assert_eq!(recreated, expected);
round_trip(&recreated);
let (implicit, new_target) =
implicit_creation(&recreated, direction, source, fixture.target.root);
assert!(implicit.elements.contains_key(&fixture.target.root));
assert_ne!(new_target, fixture.target.root);
// The explicit branch starts from exactly the same recreated project.
let mut request = explicit(direction, source, fixture.target.root);
let unreviewed = plan_transition(&recreated, &request).unwrap();
for field in [TransitionField::Name, TransitionField::Description] {
assert!(
unreviewed
.conflicts
.iter()
.any(|conflict| conflict.source == Some(source)
&& conflict.target == Some(fixture.target.root)
&& conflict.field == Some(field))
);
}
assert!(apply_transition(&recreated, &unreviewed).is_err());
request.resolutions = vec![
FieldResolution {
source,
field: TransitionField::Name,
decision: FieldDecision::TakeSource,
},
FieldResolution {
source,
field: TransitionField::Description,
decision: FieldDecision::KeepTarget,
},
];
let plan = plan_transition(&recreated, &request).unwrap();
assert!(plan.conflicts.is_empty(), "{:?}", plan.conflicts);
assert_eq!(plan.changes.len(), 1);
assert_eq!(
(plan.changes[0].target, plan.changes[0].action),
(fixture.target.root, TransitionAction::Update)
);
assert_eq!(
plan.changes[0].fields,
vec![FieldChange {
field: TransitionField::Name,
before: recreated.elements[&fixture.target.root].name.clone(),
after: recreated.elements[&source].name.clone(),
}]
);
let relinked = apply_transition(&recreated, &plan).unwrap();
let new_trace = assert_only_new_trace(
&recreated,
&relinked,
direction,
source,
fixture.target.root,
);
assert_ne!(
new_trace, fixture.trace,
"Deleted correspondence is not silently resurrected"
);
let mut expected = recreated.clone();
expected
.elements
.get_mut(&fixture.target.root)
.unwrap()
.name = recreated.elements[&source].name.clone();
let mut without_trace = relinked.clone();
without_trace.traces.remove(&new_trace);
assert_eq!(
without_trace, expected,
"Only the reviewed target name may change; rich target design survives"
);
round_trip(&relinked);
unchanged_retry(&relinked, direction, source, fixture.target.root);
let original_source = round_trip(&recreated);
let mut session = Session::new(recreated.clone());
session.execute(Command::Replace(relinked)).unwrap();
session.undo();
assert_eq!(session.project(), &recreated);
assert_eq!(round_trip(session.project()), original_source);
}
#[test]
fn oa_source_deletion_preserves_rich_sa_design_and_exact_undo() {
source_deletion(TransitionDirection::OaToSa);
}
#[test]
fn sa_source_deletion_preserves_rich_portless_oa_design_and_exact_undo() {
source_deletion(TransitionDirection::SaToOa);
}
#[test]
fn deleted_sa_target_refuses_old_plans_and_implicit_retry_creates_once() {
target_deletion(TransitionDirection::OaToSa);
}
#[test]
fn deleted_oa_target_refuses_old_plans_and_implicit_retry_creates_once() {
target_deletion(TransitionDirection::SaToOa);
}
#[test]
fn recreated_oa_identity_requires_explicit_relink_to_retained_sa_design() {
source_recreation(TransitionDirection::OaToSa);
}
#[test]
fn recreated_sa_identity_requires_explicit_relink_to_retained_oa_design() {
source_recreation(TransitionDirection::SaToOa);
}
@@ -269,9 +269,6 @@ impl App {
.map(|request| request.job);
return task;
}
nav::SymbolEvent::Close => {
return self.show_text_drawer(None);
}
nav::SymbolEvent::Back => {
self.source_symbol_back();
// A successful Back needs no empty results panel. Retain an
@@ -85,6 +85,11 @@ fn symbol(app: &mut App, event: nav::SymbolEvent) {
send(app, message);
}
fn close_drawer(app: &mut App) {
let message = Message::TextWorkspace(TextEvent::HideDrawer(app.text_drawer_context()));
send(app, message);
}
fn choice(app: &App, stamp: query::QueryStamp, reference: bool, index: usize) -> Message {
app.source_symbol_message(nav::SymbolEvent::Choose {
stamp,
@@ -414,7 +419,7 @@ fn close_during_busy_retains_hidden_completion_without_reopening_or_navigation()
let raw = app.source_workspace.bundle();
let cursor = app.source_editor.cursor();
assert!(references_visible(&app));
symbol(&mut app, nav::SymbolEvent::Close);
close_drawer(&mut app);
assert!(app.text_workspace.drawer().is_none());
assert_eq!(app.source_symbols.in_flight, Some(request.job));
assert!(app.source_symbols.request.is_none());
@@ -561,7 +566,7 @@ fn closed_results_stay_closed_on_exact_back_and_reopen_only_for_a_return_notice(
let origin = app.source_editor.cursor();
finish(&mut app, query::Action::Definition);
reveal(&mut app);
symbol(&mut app, nav::SymbolEvent::Close);
close_drawer(&mut app);
assert!(app.text_workspace.drawer().is_none() && app.source_symbols.results.is_some());
if changed {
// Use the workspace edit API to give the origin a new generation.
@@ -1433,7 +1438,7 @@ fn stale_envelope_trial(reset: bool) {
let old_messages = [
app.source_symbol_message(nav::SymbolEvent::Query(query::Action::Definition)),
app.source_symbol_message(nav::SymbolEvent::Back),
app.source_symbol_message(nav::SymbolEvent::Close),
Message::TextWorkspace(TextEvent::HideDrawer(app.text_drawer_context())),
choice(&app, old_results.stamp, false, 0),
];
if reset {
@@ -1494,7 +1499,7 @@ fn stale_envelope_trial(reset: bool) {
// Replay once more with no busy-slot fallback and visible current results.
assert_stale_envelopes_preserve_current(&mut app, &old_messages);
let interaction = app.source_symbols.interaction;
symbol(&mut app, nav::SymbolEvent::Close);
close_drawer(&mut app);
assert_ne!(app.source_symbols.interaction, interaction);
assert!(app.text_workspace.drawer().is_none() && app.source_symbols.results.is_some());
let next = begin(&mut app, query::Action::References);
+5 -1
View File
@@ -78,7 +78,11 @@ impl<Message, R: renderer::Renderer> Widget<Message, Theme, R> for Owner<'_, Mes
}
if state.context.is_none_or(|previous| {
(previous.document, previous.diagram, previous.epoch)
!= (self.context.document, self.context.diagram, self.context.epoch)
!= (
self.context.document,
self.context.diagram,
self.context.epoch,
)
}) || !self.enabled
{
state.focused = false;
@@ -10,7 +10,6 @@ pub enum SymbolEvent {
DiagramUsages,
Query(query::Action),
Back,
Close,
Choose {
stamp: query::QueryStamp,
visit: Id,
+10 -10
View File
@@ -1531,6 +1531,16 @@ fn checks(app: &App) -> View<'_, Message> {
scrollable(out.padding(24)).into()
}
#[cfg(all(test, debug_assertions))]
pub(crate) fn captured_text_drawer_resize(app: &App) -> Message {
text_workspace::capture_drawer_resize(app)
}
#[cfg(all(test, debug_assertions))]
pub(crate) fn captured_source_reference_stale_summary(app: &App) -> bool {
text_workspace::summary_visibility_tests::rendered_stale(app)
}
#[cfg(test)]
mod tests {
use super::short;
@@ -1584,13 +1594,3 @@ mod tests {
assert_eq!(short("e\u{0301}B", 1), "…");
}
}
#[cfg(all(test, debug_assertions))]
pub(crate) fn captured_text_drawer_resize(app: &App) -> Message {
text_workspace::capture_drawer_resize(app)
}
#[cfg(all(test, debug_assertions))]
pub(crate) fn captured_source_reference_stale_summary(app: &App) -> bool {
text_workspace::summary_visibility_tests::rendered_stale(app)
}
+88 -58
View File
@@ -13,6 +13,7 @@ use iced::{
use oa_core::{transitions::*, *};
use std::collections::BTreeSet;
mod communication;
mod counterparts;
pub(crate) mod recovery;
#[cfg(test)]
mod transition_tests;
@@ -34,6 +35,7 @@ pub enum Event {
Scope(String),
Destination(Choice),
MappingTarget(Id, Choice),
MappingQuery(Id, String),
MappingKind(Id, Kind),
CommunicationPort(Id, bool, communication::PortMenuChoice),
OperationOmission(Id, bool),
@@ -274,6 +276,7 @@ pub struct Transition {
pub request: TransitionRequest,
pub plan: TransitionPlan,
communication_ports: std::collections::BTreeMap<Id, communication::PortDraft>,
counterpart_queries: std::collections::BTreeMap<Id, String>,
}
impl Transition {
fn presentation(&self, p: &Project) -> (Project, Vec<Id>) {
@@ -515,6 +518,7 @@ impl Workflow {
request,
plan,
communication_ports: Default::default(),
counterpart_queries: Default::default(),
})))
}
/// Return a complete accepted candidate only on explicit Apply. Intermediate
@@ -619,6 +623,12 @@ impl Workflow {
_ => {}
},
Self::Transition(d) => {
if let Event::MappingQuery(source, query) = event {
d.counterpart_queries.insert(source, query);
// Searching is presentation only: preserve reviewed decisions,
// preview identities and the exact recovery snapshot.
return Ok(None);
}
match &event {
Event::OperationOmission(source, enabled) => {
d.request
@@ -1148,48 +1158,17 @@ impl Workflow {
content=content.push(text("The SA operation remains unchanged. Correlated replies derive this Call's abstraction.").size(11));
}
let mapping = d.request.mappings.iter().find(|m| m.source == source);
let existing = p
.traces
.values()
.filter_map(|t| {
if d.request.direction == TransitionDirection::OaToSa && t.oa == source
{
Some(t.sa)
} else if d.request.direction == TransitionDirection::SaToOa
&& t.sa == source
{
Some(t.oa)
} else {
None
}
})
.collect::<Vec<_>>();
let target = mapping.map(|m| m.target).unwrap_or_else(|| {
if existing.len() == 1 {
Some(existing[0])
} else {
None
}
let target_choice = counterparts::target(p, source, &d.request);
let target = target_choice.flatten();
let query = d
.counterpart_queries
.get(&source)
.map_or("", String::as_str);
let targets = counterparts::choices(p, source, d.request.direction, query);
let no_matches = targets.len() == 1;
let selected = target_choice.map(|target| {
counterparts::selected(p, source, d.request.direction, target)
});
let mut targets = vec![Choice {
id: Id::nil(),
label: "Create new counterpart".into(),
}];
targets.extend(
p.elements
.keys()
.chain(p.relations.keys())
.filter(|id| {
p.layer_of(**id) == Some(d.request.direction.target_layer())
&& p.elements.contains_key(&source)
== p.elements.contains_key(id)
})
.map(|id| choice(p, *id)),
);
let selected = targets
.iter()
.find(|c| Some(c.id) == target.or(Some(Id::nil())))
.cloned();
let mut item = column![
text(format!(
"{} · {}",
@@ -1200,14 +1179,29 @@ impl Workflow {
.unwrap_or("Relationship")
))
.size(14),
text_input("Find counterpart by name or @alias", query)
.on_input(move |query| Message::Workflow(Event::MappingQuery(
source, query
)))
.size(12)
.padding(8),
pick_list(targets, selected, move |v| Message::Workflow(
Event::MappingTarget(source, v)
))
.placeholder("Choose a counterpart")
.text_size(12)
.padding(8)
.width(Fill)
]
.spacing(7);
if no_matches {
let message = if query.trim().is_empty() {
"No compatible existing counterparts. Create a new one."
} else {
"No matching existing counterparts. Clear the search or create a new one."
};
item = item.push(text(message).size(11));
}
if target.is_none()
&& let Some(e) = p.elements.get(&source)
{
@@ -1413,16 +1407,6 @@ impl Workflow {
.color(style::MUTED),
);
}
content = content.push(
text(format!(
"{} changes · {} required boundary choices · {} unresolved conflicts · {} modeling warnings",
d.plan.changes.len(),
d.plan.conflicts.iter().filter(|c| d.boundary_choice(p, c)).count(),
d.plan.conflicts.iter().filter(|c| !d.boundary_choice(p, c)).count(),
d.plan.warnings.len()
))
.size(12),
);
}
}
let enabled = match self {
@@ -1439,12 +1423,50 @@ impl Workflow {
.color(iced::Color::from_rgb8(178, 54, 58)),
);
}
column![
scrollable(content.padding(iced::Padding {
right: 14.,
..Default::default()
}))
.height(480),
let mut footer = column![].spacing(8);
if let Self::Transition(d) = self {
let boundary_choices = d
.plan
.conflicts
.iter()
.filter(|c| d.boundary_choice(p, c))
.count();
footer = footer.push(
text(format!(
"{} reviewed items · {} boundary choices · {} conflicts · {} warnings",
d.plan.changes.len(),
boundary_choices,
d.plan.conflicts.len() - boundary_choices,
d.plan.warnings.len()
))
.size(12),
);
let guidance = if d.destination_error(p).is_some() {
Some("Choose a compatible diagram in Show result in, above the mappings.".into())
} else if boundary_choices > 0 {
Some("Choose the counterpart kind in each mapping that needs a system boundary decision.".into())
} else if let Some(field) = d.plan.conflicts.iter().find_map(|c| c.field) {
let field = match field {
TransitionField::Name => "Name",
TransitionField::Description => "Description",
};
Some(format!(
"Review {field} below the mappings: choose Keep target, Take source, or merged text."
))
} else if !d.plan.conflicts.is_empty() {
Some("Review the conflict messages below the mappings and complete the required decisions.".into())
} else {
None
};
if let Some(guidance) = guidance {
footer = footer.push(
text(guidance)
.size(12)
.color(iced::Color::from_rgb8(178, 54, 58)),
);
}
}
footer = footer.push(
row![
Space::new().width(Fill),
button(text("Cancel").size(12))
@@ -1464,7 +1486,15 @@ impl Workflow {
.padding(10)
.on_press_maybe(enabled.then_some(Message::Workflow(Event::Apply)))
]
.spacing(10)
.spacing(10),
);
column![
scrollable(content.padding(iced::Padding {
right: 14.,
..Default::default()
}))
.height(480),
footer
]
.spacing(18)
.into()
@@ -0,0 +1,315 @@
//! Search is separate from the committed counterpart mapping.
use super::*;
/// Outer None requires a choice between distinct traced targets; inner None
/// means creation (explicit, or no existing correspondence).
pub(super) fn target(p: &Project, source: Id, request: &TransitionRequest) -> Option<Option<Id>> {
if let Some(mapping) = request.mappings.iter().find(|m| m.source == source) {
return Some(mapping.target);
}
let existing = p
.traces
.values()
.filter_map(|trace| match request.direction {
TransitionDirection::OaToSa if trace.oa == source => Some(trace.sa),
TransitionDirection::SaToOa if trace.sa == source => Some(trace.oa),
_ => None,
})
.collect::<BTreeSet<_>>();
match existing.len() {
0 => Some(None),
1 => Some(existing.first().copied()),
_ => None,
}
}
fn new_choice() -> Choice {
Choice {
id: Id::nil(),
label: "Create new counterpart".into(),
}
}
fn identity(p: &Project, id: Id) -> Option<(&str, &str, String)> {
if let Some(e) = p.elements.get(&id) {
Some((&e.name, &e.alias, e.kind().label().into()))
} else {
p.relations
.get(&id)
.map(|r| (r.name.as_str(), r.alias.as_str(), r.kind().to_string()))
}
}
fn label(p: &Project, id: Id) -> Choice {
let label = match identity(p, id) {
Some((name, alias, kind)) => format!("{} · {kind} · @{alias}", short(name, 48)),
None => format!("Missing counterpart · {id}"),
};
Choice { id, label }
}
pub(super) fn selected(
p: &Project,
source: Id,
direction: TransitionDirection,
target: Option<Id>,
) -> Choice {
let Some(target) = target else {
return new_choice();
};
let mut choice = label(p, target);
if p.contains(target) && !counterpart_compatible(p, source, target, direction) {
choice.label.push_str(" (incompatible)");
}
choice
}
pub(super) fn choices(
p: &Project,
source: Id,
direction: TransitionDirection,
query: &str,
) -> Vec<Choice> {
let query = query.trim().to_lowercase();
let mut candidates = p
.elements
.keys()
.chain(p.relations.keys())
.copied()
.filter(|id| counterpart_compatible(p, source, *id, direction))
.filter_map(|id| {
let (name, alias, _) = identity(p, id)?;
let name = name.to_lowercase();
let alias = alias.to_lowercase();
let matches = if let Some(query) = query.strip_prefix('@') {
alias.contains(query)
} else {
name.contains(&query) || alias.contains(&query)
};
matches.then_some((name, alias, id))
})
.collect::<Vec<_>>();
candidates.sort();
std::iter::once(new_choice())
.chain(candidates.into_iter().map(|(_, _, id)| label(p, id)))
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
fn fixture() -> (Project, Id, Id, Id, Id) {
let mut p = Project::empty();
let source = p
.add_element(Kind::Activity, Layer::Oa, "Assess pressure", None)
.unwrap();
let first = p
.add_element(Kind::Function, Layer::Sa, "Assess pressure", None)
.unwrap();
let second = p
.add_element(Kind::Function, Layer::Sa, "Assess pressure", None)
.unwrap();
let incompatible = p
.add_element(Kind::SystemActor, Layer::Sa, "Assess pressure", None)
.unwrap();
p.elements.get_mut(&first).unwrap().alias = "alpha_assess".into();
p.elements.get_mut(&second).unwrap().alias = "beta_assess".into();
(p, source, first, second, incompatible)
}
fn ids(choices: Vec<Choice>) -> Vec<Id> {
choices.into_iter().map(|c| c.id).collect()
}
#[test]
fn search_disambiguates_names_with_aliases_and_retains_creation() {
let (p, source, first, second, _) = fixture();
let all = choices(&p, source, TransitionDirection::OaToSa, "");
assert_eq!(
all.iter().map(|c| c.id).collect::<Vec<_>>(),
vec![Id::nil(), first, second]
);
assert!(all[1].label.contains("@alpha_assess"));
assert!(all[2].label.contains("@beta_assess"));
assert_ne!(all[1].label, all[2].label);
for (query, expected) in [
(" ASSESS PRESSURE ", vec![Id::nil(), first, second]),
(" @BETA_ ", vec![Id::nil(), second]),
("ALPHA", vec![Id::nil(), first]),
("@pressure", vec![Id::nil()]),
("no match", vec![Id::nil()]),
] {
assert_eq!(
ids(choices(&p, source, TransitionDirection::OaToSa, query)),
expected,
"{query}"
);
}
assert_eq!(
ids(choices(&p, first, TransitionDirection::SaToOa, "PRESSURE")),
vec![Id::nil(), source]
);
}
#[test]
fn query_preserves_reviewed_request_plan_ids_and_recovery_exactly() {
let (mut p, source, first, _, _) = fixture();
let second = p
.add_element(Kind::Activity, Layer::Oa, "Second source card", None)
.unwrap();
let before_project = p.clone();
// Exercise both an existing reviewed target and generated preview IDs.
for chosen in [Some(first), None] {
let mut w = Workflow::transition(&p, source).unwrap();
let Workflow::Transition(t) = &mut w else {
panic!()
};
t.request.sources.push(second);
t.plan = plan_transition(&p, &t.request).unwrap();
w.update(
&p,
Event::MappingTarget(
source,
selected(&p, source, TransitionDirection::OaToSa, chosen),
),
)
.unwrap();
w.update(
&p,
Event::Resolve(
source,
TransitionField::Name,
FieldDecision::Value("Reviewed name".into()),
),
)
.unwrap();
let saved = w.recovery_snapshot().unwrap();
let Workflow::Transition(t) = &w else {
panic!()
};
let plan = t.plan.clone();
let request = t.request.clone();
for query in ["assess", "@beta", "no match", ""] {
assert!(
w.update(&p, Event::MappingQuery(source, query.into()))
.unwrap()
.is_none()
);
assert_eq!(w.recovery_snapshot().unwrap(), saved);
let Workflow::Transition(t) = &w else {
panic!()
};
assert_eq!(t.plan, plan);
assert_eq!(t.request, request);
assert_eq!(t.counterpart_queries[&source], query);
assert_eq!(target(&p, source, &t.request), Some(chosen));
assert_eq!(
selected(&p, source, t.request.direction, chosen).id,
chosen.unwrap_or(Id::nil())
);
}
w.update(&p, Event::MappingQuery(second, "other card".into()))
.unwrap();
let Workflow::Transition(t) = &w else {
panic!()
};
assert_eq!(t.counterpart_queries[&source], "");
assert_eq!(t.counterpart_queries[&second], "other card");
assert_eq!(t.plan, plan);
let restored = Workflow::from_recovery(&p, saved.clone()).unwrap();
assert_eq!(restored.recovery_snapshot().unwrap(), saved);
let Workflow::Transition(t) = restored else {
panic!()
};
assert!(t.counterpart_queries.is_empty());
assert_eq!(t.request, request);
}
assert_eq!(p, before_project);
}
#[test]
fn recovered_invalid_or_missing_mapping_stays_visible_and_refuses_apply() {
let (p, source, _, _, incompatible) = fixture();
for chosen in [incompatible, Id::from_u128(987654321)] {
let mut w = Workflow::transition(&p, source).unwrap();
w.update(
&p,
Event::MappingTarget(
source,
Choice {
id: chosen,
label: "Recovered choice".into(),
},
),
)
.unwrap();
let saved = w.recovery_snapshot().unwrap();
let mut restored = Workflow::from_recovery(&p, saved.clone()).unwrap();
restored
.update(&p, Event::MappingQuery(source, "no match".into()))
.unwrap();
assert_eq!(restored.recovery_snapshot().unwrap(), saved);
let Workflow::Transition(t) = &restored else {
panic!()
};
assert_eq!(target(&p, source, &t.request), Some(Some(chosen)));
let display = selected(&p, source, t.request.direction, Some(chosen));
assert_eq!(display.id, chosen);
assert!(display.label.contains(if chosen == incompatible {
"incompatible"
} else {
"Missing counterpart"
}));
assert!(
!choices(&p, source, t.request.direction, "")
.iter()
.any(|c| c.id == chosen)
);
assert!(restored.update(&p, Event::Apply).is_err());
}
}
#[test]
fn trace_inference_counts_distinct_targets_and_explicit_mapping_wins() {
let (mut p, source, first, second, _) = fixture();
let mut request = TransitionRequest::new(TransitionDirection::OaToSa, vec![source]);
assert_eq!(target(&p, source, &request), Some(None));
for (i, counterpart) in [first, first, second].into_iter().enumerate() {
let id = Id::from_u128(100 + i as u128);
p.traces.insert(
id,
Trace {
id,
alias: format!("trace_{i}"),
name: format!("Trace {i}"),
description: String::new(),
oa: source,
sa: counterpart,
kind: TraceKind::Manual,
baseline: None,
},
);
assert_eq!(
target(&p, source, &request),
if i < 2 { Some(Some(first)) } else { None }
);
}
assert_eq!(
target(
&p,
first,
&TransitionRequest::new(TransitionDirection::SaToOa, vec![first])
),
Some(Some(source))
);
for chosen in [Some(second), None] {
request.mappings = vec![TransitionMapping {
source,
target: chosen,
target_kind: None,
}];
assert_eq!(target(&p, source, &request), Some(chosen));
}
}
}
@@ -202,6 +202,7 @@ impl Workflow {
request,
plan,
communication_ports,
counterpart_queries: Default::default(),
};
transition.sync_communication_ports();
transition.plan =
+158
View File
@@ -75,6 +75,164 @@ fn assert_pair(current: &StoredProject) {
assert_eq!(compiled.bundle, current.source_project);
}
#[tokio::test]
async fn explicit_empty_port_anchors_survive_http_move_and_restart_but_export_compactly() {
fn replace_once(source: &mut String, before: &str, after: &str) {
assert_eq!(
source.matches(before).count(),
1,
"ambiguous or absent anchor: {before}"
);
let replacement = source.replacen(before, after, 1);
assert_ne!(*source, replacement, "fixture mutation must change source");
*source = replacement;
}
async fn assert_exports(server: &TestServer, current: &StoredProject, node: Id) -> String {
let response = server
.client
.get(server.url(&format!("/projects/{}/source-project", current.resource_id)))
.send()
.await
.unwrap();
assert_eq!(response.status(), StatusCode::OK);
assert_eq!(response.headers()["etag"], current.etag());
assert_eq!(
response.json::<SourceBundle>().await.unwrap(),
current.source_project
);
let response = server
.client
.get(server.url(&format!("/projects/{}/source", current.resource_id)))
.send()
.await
.unwrap();
assert_eq!(response.status(), StatusCode::OK);
let canonical = response.text().await.unwrap();
assert_eq!(oa_core::language::parse(&canonical).unwrap(), current.model);
assert_eq!(canonical, oa_core::language::format(&current.model));
let exported = compile_bundle(
&SourceBundle {
entry: "project.arcadia".into(),
files: BTreeMap::from([("project.arcadia".into(), canonical.clone())]),
},
ProjectLimits::default(),
)
.unwrap();
assert_eq!(exported.decoded.project, current.model);
assert!(exported.index.declarations.contains_key(&node));
assert!(
!exported
.index
.fields
.contains_key(&(node, "port_anchors".into()))
);
canonical
}
let mut authored = source_bundle();
replace_once(
authored.files.get_mut("views.arcadia").unwrap(),
"diagram @architecture; element @vessel;",
"diagram @architecture; element @vessel;\n position { x 12.5; y -4.0; };\n port_anchors [] ; // Keep explicit automatic placement.\n",
);
let compiled = compile_bundle(&authored, ProjectLimits::default()).unwrap();
assert_eq!(
compiled.bundle, authored,
"the HTTP fixture must already be stable"
);
let diagram = id(&compiled.decoded.project, "architecture");
let node = id(&compiled.decoded.project, "actor_view");
assert!(
compiled
.index
.fields
.contains_key(&(node, "port_anchors".into()))
);
assert_eq!(
compiled.decoded.project.diagrams[&diagram].nodes[&node].position,
oa_core::Position { x: 12.5, y: -4.0 }
);
assert!(
compiled.decoded.project.diagrams[&diagram].nodes[&node]
.port_anchors
.is_empty()
);
// Independent full-model oracle: only this placement coordinate changes.
let mut expected_model = compiled.decoded.project.clone();
expected_model
.diagrams
.get_mut(&diagram)
.unwrap()
.nodes
.get_mut(&node)
.unwrap()
.position
.x = 25.0;
let mut expected_source = authored.clone();
replace_once(
expected_source.files.get_mut("views.arcadia").unwrap(),
"x 12.5;",
"x 25.0;",
);
let directory = tempfile::tempdir().unwrap();
let server = TestServer::start(directory.path(), None).await;
let response = server
.client
.post(server.url("/projects/import-source-project"))
.json(&authored)
.send()
.await
.unwrap();
let status = response.status();
let value: Value = response.json().await.unwrap();
assert_eq!(status, StatusCode::CREATED, "{value}");
let original = server
.get(value["resource_id"].as_str().unwrap().parse().unwrap())
.await;
assert_eq!(original.model, compiled.decoded.project);
assert_eq!(original.source_project, authored);
assert_pair(&original);
assert_exports(&server, &original, node).await;
let response = transact(
&server,
&original,
json!([{"MoveNode": {"diagram": diagram, "node": node,
"position": {"x": 25.0, "y": -4.0}, "with_descendants": false}}]),
false,
)
.await;
let status = response.status();
let value: Value = response.json().await.unwrap();
assert_eq!(status, StatusCode::OK, "{value}");
assert_eq!(value["changed"], true);
let current = server.get(original.resource_id).await;
assert_eq!(current.revision, original.revision + 1);
assert_eq!(current.model, expected_model);
// Exact bundle equality covers the explicit empty field, its rationale,
// sibling placement, and all untouched files and source trivia.
assert_eq!(current.source_project, expected_source);
assert_pair(&current);
let canonical = assert_exports(&server, &current, node).await;
server.stop().await;
let server = TestServer::start(directory.path(), None).await;
let reopened = server.get(current.resource_id).await;
assert_eq!(reopened.model, expected_model);
assert_eq!(reopened.source_project, expected_source);
assert_eq!(
serde_json::to_value(&reopened).unwrap(),
serde_json::to_value(&current).unwrap()
);
assert_pair(&reopened);
assert_eq!(assert_exports(&server, &reopened, node).await, canonical);
server.stop().await;
}
#[tokio::test]
async fn port_anchor_http_commands_and_source_edits_preserve_per_view_state_after_restart() {
let directory = tempfile::tempdir().unwrap();
+107 -16
View File
@@ -2,7 +2,7 @@
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta charset="utf-8" />
<meta name="generator" content="Docutils 0.22.2: https://docutils.sourceforge.io/" />
<meta name="generator" content="Docutils 0.23: https://docutils.sourceforge.io/" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Arcadia Studio OA / SA Language and Editing Contract</title>
<style type="text/css">
@@ -806,11 +806,11 @@ references may be omitted and then mean <span class="docutils literal">none</spa
These omissions are accepted in newly authored source 7, not only in schema-2
compatibility input. Earlier schema admission of constructors and fields is
still governed by its version rules.</p>
<p>Canonical formatting writes the hydrated occurrence references and default
<span class="docutils literal">message_kind</span>, <span class="docutils literal">reply_to</span> and <span class="docutils literal">values</span> explicitly. An absent
<span class="docutils literal">operation</span> remains omitted in canonical output. Lossless no-op updates and
unrelated name edits retain authored omissions; canonical formatting is a
different operation. An admitted incomplete occurrence can still produce model
<p>Canonical formatting omits optional occurrence references equal to <span class="docutils literal">none</span>,
the default Asynchronous <span class="docutils literal">message_kind</span>, empty <span class="docutils literal">values</span>, and absent
<span class="docutils literal">operation</span>. Required fields of a selected constructor remain present.
Lossless updates retain unchanged explicit defaults and authored omissions;
canonical formatting is a different operation. An admitted incomplete occurrence can still produce model
checks requesting its missing work, exchange, lifeline or referenced scenario.</p>
<table>
<caption>Occurrence body constructors</caption>
@@ -1569,29 +1569,40 @@ has no explicit default, and does not convey ownership or deletion of the type.<
<thead>
<tr><th class="head"><p>Constructor</p></th>
<th class="head"><p>Required fields</p></th>
<th class="head"><p>Optional fields (omission value)</p></th>
</tr>
</thead>
<tbody>
<tr><td><p>Boolean</p></td>
<td><p>None</p></td>
<td><p>None</p></td>
</tr>
<tr><td><p>Integer</p></td>
<td><p><span class="docutils literal">minimum</span>, <span class="docutils literal">maximum</span>: optional signed 64-bit integers</p></td>
<td><p>None</p></td>
<td><p><span class="docutils literal">minimum</span>, <span class="docutils literal">maximum</span>: signed 64-bit integers? (none)</p></td>
</tr>
<tr><td><p>Real</p></td>
<td><p><span class="docutils literal">minimum</span>, <span class="docutils literal">maximum</span>: optional finite IEEE-754 binary64 values</p></td>
<td><p>None</p></td>
<td><p><span class="docutils literal">minimum</span>, <span class="docutils literal">maximum</span>: finite IEEE-754 binary64 values? (none)</p></td>
</tr>
<tr><td><p>String</p></td>
<td><p><span class="docutils literal">minimum_length</span>: u32; <span class="docutils literal">maximum_length</span>: optional u32</p></td>
<td><p><span class="docutils literal">minimum_length</span>: u32</p></td>
<td><p><span class="docutils literal">maximum_length</span>: u32? (none)</p></td>
</tr>
<tr><td><p>Enumeration</p></td>
<td><p><span class="docutils literal">literals</span>: unique nonempty, case-sensitive strings</p></td>
<td><p>None</p></td>
</tr>
<tr><td><p>Record</p></td>
<td><p><span class="docutils literal">supertype</span>: same-layer Record ref?</p></td>
<td><p>None</p></td>
<td><p><span class="docutils literal">supertype</span>: same-layer Record ref? (none)</p></td>
</tr>
</tbody>
</table>
<p>Optional fields may be absent or explicitly <span class="docutils literal">none</span>. Selected String and
Enumeration constructors still require <span class="docutils literal">minimum_length</span> and <span class="docutils literal">literals</span>,
respectively, including zero and an empty list. Selecting a constructor does
not merge fields from the default String definition. Unknown fields reject.</p>
<p>Bounds are inclusive and ordered; an absent bound means unbounded within the
represented numeric domain. Integer comparisons MUST NOT convert to binary64.
NaN, infinities, overflow and reversed bounds reject. String length counts Unicode
@@ -2439,10 +2450,27 @@ references, placements, artifacts, trace baselines, and ordered occurrences.
Map order is irrelevant. The writer preserves signed coordinate zero and
emits decimals that recover the original binary32 value.</p>
<p>Canonical output uses LF, four-space indentation, explicit identities,
defaults, and JSON-escaped strings. Elements are emitted in UUID order, then
and JSON-escaped strings. It omits fields equal to the omission defaults of
their actual declaration or selected constructor, as specified in the field
tables. This includes an empty project description. It MUST NOT omit a required
field merely because its value is zero, false, empty, or <span class="docutils literal">none</span>. Elements are emitted in UUID order, then
relations; diagrams and their nodes follow, then artifacts, then traces.
Fields use deterministic name order. Output ends with LF. Alias values come
from the model, never from regenerating display names.</p>
<p>Omission is context-sensitive: Scenario uses the source default MixedLegacy;
BoundaryExchange and CommunicationMean have different orientation defaults;
state declarations determine their state-kind discriminator. A nonempty or
whitespace-only description remains exact. Present-empty optional strings or
collections, unbounded multiplicity versus a finite default, selected operation
intent, and negative coordinate zero remain distinct values. A nondefault
geometry record retains all required coordinates or dimensions.</p>
<p>Only specified optional fields inside selected constructors may be omitted.
For example, Integer bounds and LegacyPortContract.interface may be absent;
String.minimum_length, Signal.items, port-contract payload lists, and historical
snapshot name/description fields remain required. A LegacyPortContract record
still requires direction. Historical semantic baseline payloads are retained
without generic empty/default pruning. Compact output MUST preserve every
persistent model field after parsing, including these distinctions.</p>
<p>Comments and input whitespace are lexical trivia to the canonical writer.
Canonical export does not preserve them. The source-project editor and compiler
preserve authored comments and whitespace on Apply while inserting missing
@@ -2484,11 +2512,19 @@ independent parent and child moves are resolved together. Leading whitespace
adjusts to the destination scope; other token spelling remains authored.</p>
<p>Updates MUST NOT canonicalize surviving declarations in full. Header, field and
nested value positions are derived from the existing syntax parser. Unchanged
fields retain their exact bytes, including field order, omitted defaults, number
fields retain their exact bytes, including field order, explicit and omitted defaults, number
and string spelling, comments, indentation and line endings. Changed literals and
references replace only the required tokens; a reference with comments between
<span class="docutils literal">&#64;</span> and its alias retains those comments. A previously omitted default is
inserted only if its semantic value changes. New fields use the existing record's
<span class="docutils literal">&#64;</span> and its alias retains those comments. A previously omitted field is
inserted only when needed to express the requested value in its new declaration
context. This includes an unchanged value whose omission default changes with
the declaration kind. For example, changing BoundaryExchange to CommunicationMean
while retaining orientation true requires <span class="docutils literal">oriented true;</span> if previously
implicit. Unchanged explicit defaults MUST survive unrelated sibling edits,
including persistence-serializer omissions such as <span class="docutils literal">operation none</span>,
<span class="docutils literal">port_anchors []</span> and <span class="docutils literal">baseline.semantic none</span>. New fields and value fragments
use compact generation while retaining required selected-constructor payloads.
New fields use the existing record's
indentation and line-ending style without reformatting its other fields.</p>
<p>A constructor tag change retains shared nested fields in place. Removed fields,
constructors and declarations remove syntax tokens, retaining their comments
@@ -2618,6 +2654,26 @@ placement move are different transactions. The UI MUST identify which operation
will occur. Reparenting work must not silently change its performer. A computed
parent summary must remain distinguishable from direct allocation. A collapse
operation hides presentation detail without deleting the children.</p>
<p>In an Allocation view, dragging an Activity or Function body MAY combine a
performer change with diagram placement. The preview MUST identify the
destination and preserve readable work and destination identity. Destination
identity is shown outside the canvas, independently of explanatory or refusal
text. The outlined target's hit region remains fixed throughout the gesture;
feedback MUST NOT obscure the work or change its pointer-relative placement.
A clean click selects without applying unrelated drafts. A refused work press
with an unapplied property draft MUST preserve its text, caret and focused
editor. An accepted drag is one atomic model/source/history transaction:
semantic ownership and work identities are
preserved; only leaf direct Allocation relations, their own retired routes and
the affected view geometry change. Existing Allocation identities are retained
on reassignment. Role realizations and assignments MUST NOT be silently changed. Uniform work groups
may move their leaf responsibilities together; ambiguous or distributed groups
and changes requiring collateral record edits MUST be refused with an explanation.
Moving outside performer regions removes direct assignment only when the
effective role responsibility is also absent. Releasing outside the viewport
cancels the gesture. Explicit Arrange next drag applies geometry alone for one
gesture. Refused and snapped no-op drops MUST NOT create history entries or
discard Redo. Undo restores the exact source bundle and identities together.</p>
<p>Applying a reachable source bundle is one model transaction. Graphical edits
update its owning declarations through the source-preserving updater. Both
paths validate their candidate and model/source equality before
@@ -2719,6 +2775,40 @@ leaving dangling references. Deleting a sequence does not delete the semantic
exchanges it references. Cross-layer counterparts are not owned descendants.
Text edits describe a complete candidate and must remove or repair dependencies
explicitly before Apply succeeds.</p>
<section id="focused-diagram-removal">
<h3>8.1. Focused diagram removal</h3>
<p>Delete is scoped to the focused diagram and the exact visible selection.
Text fields retain their own Delete behavior; diagram shortcuts MUST NOT apply
pending property, source or behavior drafts. Keyboard traversal retires diagram
input admission before subsequent keys in the same event batch are processed.
Active placement, creation, connection, reorder and pan gestures do not authorize
removal. A stale document, revision, owner or selection MUST NOT redirect removal.</p>
<p>For an explicit architecture or state placement, Delete removes that placement
only. The semantic element and its placements in other views remain. Shift+Delete
requests confirmed deletion of the semantic element, including when selection
originated from Placement properties. A selected architecture route bend takes
priority: plain Delete removes the bend without removing its edge.</p>
<p>A visible relation with no placement, including a state action or internal
transition row, uses confirmed relation deletion for either key. States and
referenced Activity/Function definitions remain. Hidden or collapsed rows are
not keyboard deletion targets.</p>
<p>Process and scenario projections have no independent occurrence placements.
Delete and Shift+Delete request confirmed removal of the selected occurrence,
participant or maintained link. A link's endpoints and outbound reusable
definitions are preserved. Removing a fragment or branch removes its owned
subtree. Removing a scenario participant preserves its represented element and
retains occurrences with affected participant references cleared; resulting
incomplete references remain visible for repair. The modeled scenario Delete
message is a separate domain event and is not this editing shortcut.</p>
<p>A qualified selection inside an expanded reusable process MUST NOT silently
delete the outer reference or shared definition item. The editor directs the
user to open the definition or explicitly select the outer reference.</p>
<p>Confirmation identifies the captured object and removal effect. Cancel and
Escape preserve model, source and history. A confirmation token belongs to one
dialog; changes to its document, revision, owner or selected identity invalidate
it. Accepted deletion uses one ordinary model/source transaction, and Undo
restores the exact complete accepted source bundle and model.</p>
</section>
</section>
<section id="persistence-and-explicit-migration">
<h2>9. Persistence and explicit migration</h2>
@@ -2793,8 +2883,9 @@ This compatibility applies equally to source and JSON. It adds explicit
defaults without changing any prior identity, name, reference, order, geometry,
artifact or trace field. The original V2 golden source/JSON in
<span class="docutils literal"><span class="pre">crates/oa-core/tests/fixtures</span></span> MUST NOT be regenerated from the newer demo;
they verify that every old persistent field survives. Canonical output writes
the added defaults through explicit migration to the current schema 7.</p>
they verify that every old persistent field survives. Canonical output uses
the current schema 7 and omits values equal to its specified source defaults;
parsing recovers the complete migrated model.</p>
<p>The V2 adapter preserves every original identity, alias, prose, parent,
placement, trace and artifact. Generic ports become Legacy with their original
direction/interface record. Existing FE endpoint IDs remain unchanged. For
+56 -16
View File
@@ -171,11 +171,11 @@ These omissions are accepted in newly authored source 7, not only in schema-2
compatibility input. Earlier schema admission of constructors and fields is
still governed by its version rules.
Canonical formatting writes the hydrated occurrence references and default
``message_kind``, ``reply_to`` and ``values`` explicitly. An absent
``operation`` remains omitted in canonical output. Lossless no-op updates and
unrelated name edits retain authored omissions; canonical formatting is a
different operation. An admitted incomplete occurrence can still produce model
Canonical formatting omits optional occurrence references equal to ``none``,
the default Asynchronous ``message_kind``, empty ``values``, and absent
``operation``. Required fields of a selected constructor remain present.
Lossless updates retain unchanged explicit defaults and authored omissions;
canonical formatting is a different operation. An admitted incomplete occurrence can still produce model
checks requesting its missing work, exchange, lifeline or referenced scenario.
.. list-table:: Occurrence body constructors
@@ -933,18 +933,30 @@ DataTypeDefinition has exactly the following constructors:
* - Constructor
- Required fields
- Optional fields (omission value)
* - Boolean
- None
- None
* - Integer
- ``minimum``, ``maximum``: optional signed 64-bit integers
- None
- ``minimum``, ``maximum``: signed 64-bit integers? (none)
* - Real
- ``minimum``, ``maximum``: optional finite IEEE-754 binary64 values
- None
- ``minimum``, ``maximum``: finite IEEE-754 binary64 values? (none)
* - String
- ``minimum_length``: u32; ``maximum_length``: optional u32
- ``minimum_length``: u32
- ``maximum_length``: u32? (none)
* - Enumeration
- ``literals``: unique nonempty, case-sensitive strings
- None
* - Record
- ``supertype``: same-layer Record ref?
- None
- ``supertype``: same-layer Record ref? (none)
Optional fields may be absent or explicitly ``none``. Selected String and
Enumeration constructors still require ``minimum_length`` and ``literals``,
respectively, including zero and an empty list. Selecting a constructor does
not merge fields from the default String definition. Unknown fields reject.
Bounds are inclusive and ordered; an absent bound means unbounded within the
represented numeric domain. Integer comparisons MUST NOT convert to binary64.
@@ -1840,11 +1852,30 @@ Map order is irrelevant. The writer preserves signed coordinate zero and
emits decimals that recover the original binary32 value.
Canonical output uses LF, four-space indentation, explicit identities,
defaults, and JSON-escaped strings. Elements are emitted in UUID order, then
and JSON-escaped strings. It omits fields equal to the omission defaults of
their actual declaration or selected constructor, as specified in the field
tables. This includes an empty project description. It MUST NOT omit a required
field merely because its value is zero, false, empty, or ``none``. Elements are emitted in UUID order, then
relations; diagrams and their nodes follow, then artifacts, then traces.
Fields use deterministic name order. Output ends with LF. Alias values come
from the model, never from regenerating display names.
Omission is context-sensitive: Scenario uses the source default MixedLegacy;
BoundaryExchange and CommunicationMean have different orientation defaults;
state declarations determine their state-kind discriminator. A nonempty or
whitespace-only description remains exact. Present-empty optional strings or
collections, unbounded multiplicity versus a finite default, selected operation
intent, and negative coordinate zero remain distinct values. A nondefault
geometry record retains all required coordinates or dimensions.
Only specified optional fields inside selected constructors may be omitted.
For example, Integer bounds and LegacyPortContract.interface may be absent;
String.minimum_length, Signal.items, port-contract payload lists, and historical
snapshot name/description fields remain required. A LegacyPortContract record
still requires direction. Historical semantic baseline payloads are retained
without generic empty/default pruning. Compact output MUST preserve every
persistent model field after parsing, including these distinctions.
Comments and input whitespace are lexical trivia to the canonical writer.
Canonical export does not preserve them. The source-project editor and compiler
preserve authored comments and whitespace on Apply while inserting missing
@@ -1890,11 +1921,19 @@ adjusts to the destination scope; other token spelling remains authored.
Updates MUST NOT canonicalize surviving declarations in full. Header, field and
nested value positions are derived from the existing syntax parser. Unchanged
fields retain their exact bytes, including field order, omitted defaults, number
fields retain their exact bytes, including field order, explicit and omitted defaults, number
and string spelling, comments, indentation and line endings. Changed literals and
references replace only the required tokens; a reference with comments between
``@`` and its alias retains those comments. A previously omitted default is
inserted only if its semantic value changes. New fields use the existing record's
``@`` and its alias retains those comments. A previously omitted field is
inserted only when needed to express the requested value in its new declaration
context. This includes an unchanged value whose omission default changes with
the declaration kind. For example, changing BoundaryExchange to CommunicationMean
while retaining orientation true requires ``oriented true;`` if previously
implicit. Unchanged explicit defaults MUST survive unrelated sibling edits,
including persistence-serializer omissions such as ``operation none``,
``port_anchors []`` and ``baseline.semantic none``. New fields and value fragments
use compact generation while retaining required selected-constructor payloads.
New fields use the existing record's
indentation and line-ending style without reformatting its other fields.
A constructor tag change retains shared nested fields in place. Removed fields,
@@ -2173,7 +2212,7 @@ Text edits describe a complete candidate and must remove or repair dependencies
explicitly before Apply succeeds.
8.1. Focused diagram removal
~~~~~~~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Delete is scoped to the focused diagram and the exact visible selection.
Text fields retain their own Delete behavior; diagram shortcuts MUST NOT apply
@@ -2294,8 +2333,9 @@ This compatibility applies equally to source and JSON. It adds explicit
defaults without changing any prior identity, name, reference, order, geometry,
artifact or trace field. The original V2 golden source/JSON in
``crates/oa-core/tests/fixtures`` MUST NOT be regenerated from the newer demo;
they verify that every old persistent field survives. Canonical output writes
the added defaults through explicit migration to the current schema 7.
they verify that every old persistent field survives. Canonical output uses
the current schema 7 and omits values equal to its specified source defaults;
parsing recovers the complete migrated model.
The V2 adapter preserves every original identity, alias, prose, parent,
placement, trace and artifact. Generic ports become Legacy with their original
+2
View File
@@ -49,6 +49,8 @@ P1 extension to frozen relation interface: FunctionalExchange now has `source_po
`transitions::counterpart_kinds(source: Kind, direction: TransitionDirection) -> Vec<Kind>` is the shared choice policy for UI and other clients. It returns supported explicit element counterpart kinds in stable `Kind::ALL` order using the same compatibility predicate as transition validation; an empty list means no automatic element mapping.
`transitions::counterpart_compatible(&Project, source: Id, target: Id, direction: TransitionDirection) -> bool` checks existing-record candidate eligibility: both IDs must exist in the direction's respective layers, belong to the same element/relation category, and have compatible transition kinds. It does not approve properties, dependencies or structural changes; clients still preview and apply a complete plan. Manual trace creation retains its separate policy. The desktop counterpart search uses this predicate, filters by name or `@alias`, and sorts by name then alias and ID. Search is presentation state: it does not replan, change mappings or reviewed field decisions, or enter workflow recovery data. A selected target remains visible when filtered out; invalid or missing recovered targets remain visible with their error and require correction. Multiple traces to the same target count as one inferred counterpart; distinct traced targets require an explicit choice.
`TransitionRequest {direction:OaToSa|SaToOa, sources:Vec<Id>, scope:SelectedOnly|Required|Subtree, mappings:Vec<TransitionMapping>, fields:Vec<TransitionField>, resolutions:Vec<FieldResolution>, destination_diagram:Option<Id>}`. `TransitionRequest::new(direction,sources)` defaults to selected-only and name/description fields. `TransitionMapping {source,target:Option<Id>,target_kind:Option<Kind>}` selects reuse or explicit creation kind. An explicit mapping with target=None means create a new counterpart even if another trace exists. Without mapping, one existing trace is reused; multiple traces require selection.
`FieldResolution {source,field:Name|Description,decision:TakeSource|KeepTarget|Value(String)}`. `TransitionPlan {base_fingerprint,request,changes,conflicts,warnings,commands}`. Changes carry source/target IDs, Create/Update/Reuse and before/after field changes. Conflicts have optional source/target/field and message. Conflicts prevent apply. The deterministic fingerprint guards stale plans, is not a security hash, and does not introduce persistent project revision fields. Planned IDs and aliases remain unchanged during apply. Server owns request idempotency/revision storage. Apply is one candidate transaction; Session adoption can use Command::Replace(candidate) for one Undo.
@@ -1,43 +1,34 @@
oa_activity oa_restore_work "Restore safe pressure" id "1b91f8a7-0bdb-514e-b1cd-955658e09927" {
description "";
parent none;
}
oa_activity oa_assess "Assess pressure alarm" id "61f7bbfb-74a4-5540-9182-2583dfb27a49" {
description "";
parent @oa_restore_work;
}
oa_activity oa_confirm "Confirm safe pressure" id "727c0b5c-8db6-5eb7-83d6-735519c5949f" {
description "";
parent @oa_restore_work;
}
oa_activity oa_repair "Repair valve in the field" id "a21465af-05f3-5fb5-8983-de26932422fb" {
description "";
parent @oa_restore_work;
}
oa_activity oa_reset "Reset valve remotely" id "d0c8be87-d238-503a-a549-7558a210507f" {
description "";
parent @oa_restore_work;
}
capability_involvement oa_assessment_support "Capability involvement: Assess an alarm" id "ffdbfcd0-d423-5707-a880-0dfbe1400f72" {
description "";
source @oa_assess;
target @oa_triage;
}
interaction oa_repair_request "Request field repair" id "cf423ea2-8b2e-5a27-93d4-5e8542d9db8d" {
description "";
items [@oa_repair_operation];
source @oa_assess;
target @oa_repair;
}
interaction oa_repair_report "Report repair result" id "b436d0b3-c824-5a96-af6b-2c269816d1cc" {
description "";
items [@oa_repair_operation];
source @oa_repair;
target @oa_confirm;
@@ -1,6 +1,4 @@
oa_process oa_assessment_procedure "Assess an alarm procedure" id "022d3cfa-bbaf-5e13-924a-8e55099b8745" {
description "";
parent none;
steps [@oa_assessment_action];
}
@@ -8,17 +6,12 @@ oa_occurrence oa_assessment_action "Assess pressure alarm" id "942975b8-0bef-5ae
body {
Action {
action @oa_assess;
lifeline none;
};
};
description "";
parent @oa_assessment_procedure;
postcondition "";
precondition "";
}
chain_involvement oa_assessment_delivery "Chain involvement: Assess an alarm" id "628cb4fc-fe63-5f6b-b364-6268f26b5740" {
description "";
source @oa_assessment_procedure;
target @oa_triage;
}
@@ -1,6 +1,4 @@
oa_process oa_field_procedure "Field repair procedure" id "5d3af417-244c-54bb-9588-16daedda4e1f" {
description "";
parent none;
steps [@oa_request_step, @oa_repair_step, @oa_report_step];
}
@@ -8,59 +6,35 @@ oa_occurrence oa_repair_step "Repair the valve" id "c59432fe-6092-5ee0-908d-9dbf
body {
Action {
action @oa_repair;
lifeline none;
};
};
description "";
parent @oa_field_procedure;
postcondition "";
precondition "";
}
oa_occurrence oa_report_step "Report the measured result" id "df2fd3f8-6bc2-5380-a13f-36643ed35ac7" {
body {
Exchange {
exchange @oa_repair_report;
message_kind Asynchronous;
receiver none;
reply_to none;
sender none;
values [];
};
};
description "";
parent @oa_field_procedure;
postcondition "";
precondition "";
}
oa_occurrence oa_request_step "Request the field repair" id "5ba8079c-2dbe-5e98-ae82-2b60c6020a02" {
body {
Exchange {
exchange @oa_repair_request;
message_kind Asynchronous;
receiver none;
reply_to none;
sender none;
values [];
};
};
description "";
parent @oa_field_procedure;
postcondition "";
precondition "";
}
sequence_link oa_repair_then_report "Sequence link: Report the measured result" id "c8196d35-2168-5fef-8b03-ba0d139bc00f" {
description "";
guard "";
source @oa_repair_step;
target @oa_report_step;
}
sequence_link oa_request_then_repair "Sequence link: Repair the valve" id "3f54d890-4ca5-5e36-873b-abb5c92d578c" {
description "";
guard "";
source @oa_request_step;
target @oa_repair_step;
}
@@ -1,6 +1,4 @@
oa_process oa_response "Handle a pressure alarm" id "58bae2b0-f560-50f2-818d-19091d22d18a" {
description "";
parent none;
steps [@oa_assess_step, @oa_decision, @oa_reset_step, @oa_use_field, @oa_merge, @oa_confirm_step];
}
@@ -10,23 +8,16 @@ oa_occurrence oa_assess_step "Assess the alarm" id "0b07b9ed-492d-57f6-b0b1-ce8a
sequence @oa_assessment_procedure;
};
};
description "";
parent @oa_response;
postcondition "";
precondition "";
}
oa_occurrence oa_confirm_step "Confirm safe pressure" id "e00594a3-6122-5a6b-b3c2-ecb5506c741d" {
body {
Action {
action @oa_confirm;
lifeline none;
};
};
description "";
parent @oa_response;
postcondition "";
precondition "";
}
oa_occurrence oa_decision "Can a remote reset safely recover pressure?" id "b280c6ca-e5f9-5424-a5c1-5676e01c2371" {
@@ -35,10 +26,7 @@ oa_occurrence oa_decision "Can a remote reset safely recover pressure?" id "b280
kind Choice;
};
};
description "";
parent @oa_response;
postcondition "";
precondition "";
}
oa_occurrence oa_merge "Recovery path completed" id "6b945a6d-6aa0-5dd3-96e6-2e8d757d167d" {
@@ -47,23 +35,16 @@ oa_occurrence oa_merge "Recovery path completed" id "6b945a6d-6aa0-5dd3-96e6-2e8
kind Merge;
};
};
description "";
parent @oa_response;
postcondition "";
precondition "";
}
oa_occurrence oa_reset_step "Try the authorized remote reset" id "d2026fc2-74a8-5e22-9942-ff3eb68a3677" {
body {
Action {
action @oa_reset;
lifeline none;
};
};
description "";
parent @oa_response;
postcondition "";
precondition "";
}
oa_occurrence oa_use_field "Use the field repair procedure" id "fc6763bf-a534-54bf-afcd-57ab285ff07d" {
@@ -72,56 +53,42 @@ oa_occurrence oa_use_field "Use the field repair procedure" id "fc6763bf-a534-54
sequence @oa_field_procedure;
};
};
description "";
parent @oa_response;
postcondition "";
precondition "";
}
sequence_link oa_assess_then_decide "Continue" id "109e2d6b-7e86-5934-93a5-fab56eced84e" {
description "";
guard "";
source @oa_assess_step;
target @oa_decision;
}
sequence_link oa_field_path "else" id "c1adbbd0-a762-5ee3-8b9a-5c18af377771" {
description "";
guard "else";
source @oa_decision;
target @oa_use_field;
}
sequence_link oa_remote_path "remote reset is authorized" id "76218d89-68fd-531d-b35b-90c5b769a5ef" {
description "";
guard "remote reset is authorized";
source @oa_decision;
target @oa_reset_step;
}
sequence_link oa_confirm_path "Continue" id "229ee94f-d147-50f6-9769-b5bd0c03a084" {
description "";
guard "";
source @oa_merge;
target @oa_confirm_step;
}
sequence_link oa_remote_merge "Continue" id "bac43d3b-910b-5bdc-be9b-f78cf973e1ed" {
description "";
guard "";
source @oa_reset_step;
target @oa_merge;
}
chain_involvement oa_response_delivery "Chain involvement: Restore a safe supply" id "c7c248f8-28b8-55be-9023-0991d44021e3" {
description "";
source @oa_response;
target @oa_restore;
}
sequence_link oa_field_merge "Continue" id "c8593670-8906-530f-b6a2-3ada77a9c219" {
description "";
guard "";
source @oa_use_field;
target @oa_merge;
}
@@ -1,7 +1,5 @@
oa_scenario oa_conversation "Repair request and measured reply" id "20472233-5ce6-5ffe-b4ad-0bdbccf3e423" {
context @oa_restore;
description "";
parent none;
postcondition "The controller has received the measured pressure after repair";
precondition "A pressure alarm has been assessed";
steps [@oa_call, @oa_reply];
@@ -14,23 +12,17 @@ oa_occurrence oa_call "Request valve repair" id "b97437c6-cd7a-5a05-b537-b30a17d
exchange @oa_contact;
message_kind Call;
receiver @oa_responder;
reply_to none;
sender @oa_caller;
values [{
expression "420.0";
field @oa_pressure_parameter;
path [];
}];
};
};
description "";
parent @oa_conversation;
postcondition "";
precondition "";
}
oa_lifeline oa_caller "Controller" id "41859a65-ded2-547b-ab03-14e2fa740cf9" {
description "";
parent @oa_conversation;
represented @oa_control;
}
@@ -46,18 +38,13 @@ oa_occurrence oa_reply "Return pressure after repair" id "cc2d497a-fd15-5455-9ee
values [{
expression "320.0";
field @oa_pressure_parameter;
path [];
}];
};
};
description "";
parent @oa_conversation;
postcondition "";
precondition "";
}
oa_lifeline oa_responder "Technician" id "fc81c5f0-0442-5b88-992b-e5b10f351c59" {
description "";
parent @oa_conversation;
represented @oa_technician;
}
@@ -1,7 +1,5 @@
oa_scenario oa_full_scenario "Restore supply through field repair" id "d6f881a4-95da-54e9-ad97-1b8965532629" {
context @oa_restore;
description "";
parent none;
postcondition "The controller has received the measured pressure after repair";
precondition "A pressure alarm has been assessed";
steps [@oa_use_conversation];
@@ -9,13 +7,11 @@ oa_scenario oa_full_scenario "Restore supply through field repair" id "d6f881a4-
}
oa_lifeline oa_outer_controller "Controller" id "2011b3ac-614b-5a47-8891-7ff81965f425" {
description "";
parent @oa_full_scenario;
represented @oa_control;
}
oa_lifeline oa_outer_technician "Technician" id "fffff882-78fd-5415-928d-c1ef7fac6576" {
description "";
parent @oa_full_scenario;
represented @oa_technician;
}
@@ -33,9 +29,6 @@ oa_occurrence oa_use_conversation "Use the reviewed repair conversation" id "9ff
scenario @oa_conversation;
};
};
description "";
parent @oa_full_scenario;
postcondition "";
precondition "";
}
@@ -1,80 +1,43 @@
oa_state_machine oa_monitor "Pressure supervision" id "1970608f-85d1-5234-ac86-5e2b0df27dc3" {
description "";
parent @oa_control;
}
oa_region oa_monitor_region "Supervision cycle" id "8412e9c6-96fe-5ed6-a903-1d37ec3c3bf7" {
description "";
parent @oa_monitor;
}
oa_initial oa_monitor_initial "Start" id "a7ffb869-9dde-5e76-b276-c719c5c143a9" {
description "";
do_activity "";
entry "";
exit "";
kind Initial;
parent @oa_monitor_region;
}
oa_state oa_monitoring "Monitoring" id "6cbde0d8-9f5f-510a-a8c1-e29cb99ad4ae" {
description "";
do_activity "";
entry "";
exit "";
kind State;
parent @oa_monitor_region;
}
oa_state oa_waiting "Awaiting repair" id "58c15833-6e75-523a-b57f-a88f19f6d672" {
description "";
do_activity "";
entry "";
exit "";
kind State;
parent @oa_monitor_region;
}
state_transition oa_start_monitoring "State transition: Monitoring" id "2b00270c-79a5-52a5-af96-33346bea531b" {
description "";
effect "";
effects [];
guard "";
source @oa_monitor_initial;
target @oa_monitoring;
transition_kind External;
trigger "";
triggers [];
}
state_transition oa_alarm_transition "State transition: Awaiting repair" id "fbde9d5e-c7e9-55db-a2be-497345c491b3" {
description "";
effect "";
effects [];
guard "";
source @oa_monitoring;
target @oa_waiting;
transition_kind External;
trigger "";
triggers [@oa_high_pressure];
}
state_action oa_assess_on_entry "Assess when repair is needed" id "7fa37e63-db65-5aa7-88f2-31ff4347a787" {
description "";
phase Entry;
source @oa_waiting;
target @oa_assess;
}
state_transition oa_normal_transition "State transition: Monitoring" id "21fe5e8e-cbc2-5dad-b3e8-1df46e167d97" {
description "";
effect "";
effects [];
guard "repair confirmed and pressure is safe";
source @oa_waiting;
target @oa_monitoring;
transition_kind External;
trigger "";
triggers [];
}
@@ -1,26 +1,18 @@
oa_mission oa_mission "Maintain safe water supply" id "c9c61f50-06e0-5d17-973d-dcc57cd5cc90" {
description "";
parent none;
}
oa_capability oa_restore "Restore a safe supply" id "3dfee23a-148b-5cec-ad2d-b774e84ea5bb" {
description "";
parent none;
}
oa_capability oa_triage "Assess an alarm" id "6a30b763-cc51-546d-907f-5c63657cfd03" {
description "";
parent none;
}
mission_capability oa_mission_delivery "Mission capability: Restore a safe supply" id "1f8b1898-65f6-5360-99cf-b78a71bd50ec" {
description "";
source @oa_mission;
target @oa_restore;
}
capability_include oa_include_triage "Capability include: Assess an alarm" id "e21d9a54-ae8e-5618-a4f8-67ee22b92638" {
description "";
source @oa_restore;
target @oa_triage;
}
-7
View File
@@ -1,8 +1,6 @@
oa_exchange_category oa_contact_category "Field contact" id "19bd3a3a-4d0c-56d5-bf50-a6b43fd81771" {
description "";
exchanges [@oa_contact];
family "Participant";
parent none;
}
oa_event oa_high_pressure "Pressure alarm detected" id "65d6b8d6-271a-5c84-8b42-abbe310c955e" {
@@ -11,14 +9,9 @@ oa_event oa_high_pressure "Pressure alarm detected" id "65d6b8d6-271a-5c84-8b42-
expression "measured_pressure > operating_limit";
};
};
description "";
parent none;
}
oa_exchange_category oa_repair_traffic "Repair work exchanges" id "9349b353-53f2-56f8-b571-a05b09335cdf" {
description "";
exchanges [@oa_repair_request, @oa_repair_report];
family "Work";
parent none;
}
-13
View File
@@ -1,59 +1,46 @@
oa_entity oa_control "Control room" id "32aa9940-df6e-5a12-b9bd-6337e2d2ed22" {
description "";
parent none;
}
oa_actor oa_technician "Field technician" id "e07c3952-4a1a-5cc7-b7d5-03a01101ce49" {
description "";
parent none;
}
allocation oa_assess_allocation "Allocation: Assess pressure alarm" id "594c61cd-94e0-5c07-890d-edfd520d7df9" {
description "";
source @oa_control;
target @oa_assess;
}
allocation oa_confirm_allocation "Allocation: Confirm safe pressure" id "bfd4326e-ea65-5269-b5e6-e063a355d1e9" {
description "";
source @oa_control;
target @oa_confirm;
}
communication_mean oa_contact "Technician service conversation" id "27b47887-baf0-518b-bf87-f5acbf7d0b93" {
description "";
items [@oa_repair_operation];
oriented false;
source @oa_control;
target @oa_technician;
}
exchange_allocation oa_report_carriage "Exchange allocation: Report repair result" id "4549d3fa-5ab9-52a9-b4f9-3a78fc9d625a" {
description "";
source @oa_contact;
target @oa_repair_report;
}
exchange_allocation oa_request_carriage "Exchange allocation: Request field repair" id "094b888c-450c-5c4f-971a-7d75db059574" {
description "";
source @oa_contact;
target @oa_repair_request;
}
mission_involvement oa_mission_participant "Mission involvement: Maintain safe water supply" id "0c4c9e58-ff32-5c00-b82a-d408a4be0183" {
description "";
source @oa_control;
target @oa_mission;
}
allocation oa_reset_allocation "Allocation: Reset valve remotely" id "1853df1f-30f4-5b1a-baac-2dcaf73c7b3a" {
description "";
source @oa_control;
target @oa_reset;
}
allocation oa_repair_allocation "Allocation: Repair valve in the field" id "ae53f363-0dec-5a7c-9afd-8fcd1b778ab2" {
description "";
source @oa_technician;
target @oa_repair;
}
-13
View File
@@ -1,35 +1,22 @@
oa_data_type oa_pressure "Measured pressure" id "e7824764-5db9-5384-8198-2fbaea2b10b2" {
default none;
definition {
Real {
maximum 600.0;
minimum 0.0;
};
};
description "";
parent none;
unit "kPa";
}
oa_exchange_item oa_repair_operation "Valve repair" id "d736c7d6-d369-550a-8ba4-dae223e40bb3" {
description "";
kind Information;
mechanism Operation;
parent none;
supertype none;
}
oa_data_field oa_pressure_parameter "Pressure before / after repair" id "8bc6d0a4-3e87-559b-8bd7-1b679a2db5b7" {
default none;
description "";
direction InOut;
maximum 1;
minimum 1;
parent @oa_repair_operation;
storage Value;
type_ref {
Named @oa_pressure;
};
unit "";
}
@@ -1,6 +1,4 @@
sa_functional_chain sa_assessment_procedure "Assess an alarm procedure" id "acd5a018-f559-5298-a40d-1cf6e1353e74" {
description "";
parent none;
steps [@sa_assessment_action];
}
@@ -8,17 +6,12 @@ sa_occurrence sa_assessment_action "Assess pressure alarm" id "bae5b192-5b8e-51c
body {
Action {
action @sa_assess;
lifeline none;
};
};
description "";
parent @sa_assessment_procedure;
postcondition "";
precondition "";
}
chain_involvement sa_assessment_delivery "Chain involvement: Assess an alarm" id "a6d5a6be-fa6e-5fa0-a309-08e87ba40c57" {
description "";
source @sa_assessment_procedure;
target @sa_triage;
}
@@ -1,6 +1,4 @@
sa_functional_chain sa_field_procedure "Field repair procedure" id "93eb6479-54e9-59cb-ac7b-16912347bfc0" {
description "";
parent none;
steps [@sa_request_step, @sa_repair_step, @sa_report_step];
}
@@ -8,59 +6,35 @@ sa_occurrence sa_repair_step "Repair the valve" id "0f195fea-3493-5ebd-966d-da9e
body {
Action {
action @sa_repair;
lifeline none;
};
};
description "";
parent @sa_field_procedure;
postcondition "";
precondition "";
}
sa_occurrence sa_report_step "Report the measured result" id "ea32e4ec-8bdc-57ea-8a90-7b09ba4f0eac" {
body {
Exchange {
exchange @sa_repair_report;
message_kind Asynchronous;
receiver none;
reply_to none;
sender none;
values [];
};
};
description "";
parent @sa_field_procedure;
postcondition "";
precondition "";
}
sa_occurrence sa_request_step "Request the field repair" id "40d94322-baa2-5c81-b261-ff47cdc33782" {
body {
Exchange {
exchange @sa_repair_request;
message_kind Asynchronous;
receiver none;
reply_to none;
sender none;
values [];
};
};
description "";
parent @sa_field_procedure;
postcondition "";
precondition "";
}
sequence_link sa_repair_then_report "Sequence link: Report the measured result" id "7fb30b1d-195c-5f0e-a19c-a536f519a899" {
description "";
guard "";
source @sa_repair_step;
target @sa_report_step;
}
sequence_link sa_request_then_repair "Sequence link: Repair the valve" id "6a6d968f-26d6-5dbb-b721-5a27d362779a" {
description "";
guard "";
source @sa_request_step;
target @sa_repair_step;
}
@@ -1,6 +1,4 @@
sa_functional_chain sa_response "Handle a pressure alarm" id "5139ce73-56fd-5875-ba3a-e569fc890231" {
description "";
parent none;
steps [@sa_assess_step, @sa_decision, @sa_reset_step, @sa_use_field, @sa_merge, @sa_confirm_step];
}
@@ -10,23 +8,16 @@ sa_occurrence sa_assess_step "Assess the alarm" id "9024243c-ccc1-5edb-8477-2aa1
sequence @sa_assessment_procedure;
};
};
description "";
parent @sa_response;
postcondition "";
precondition "";
}
sa_occurrence sa_confirm_step "Confirm safe pressure" id "47bec4f9-d6d0-5ed1-adca-fa0d960dc416" {
body {
Action {
action @sa_confirm;
lifeline none;
};
};
description "";
parent @sa_response;
postcondition "";
precondition "";
}
sa_occurrence sa_decision "Can a remote reset safely recover pressure?" id "30c8fad9-a49b-5692-9542-3148e76afd7b" {
@@ -35,10 +26,7 @@ sa_occurrence sa_decision "Can a remote reset safely recover pressure?" id "30c8
kind Choice;
};
};
description "";
parent @sa_response;
postcondition "";
precondition "";
}
sa_occurrence sa_merge "Recovery path completed" id "4ed0dd9d-c9e0-5652-aedf-5f0ab4328a07" {
@@ -47,23 +35,16 @@ sa_occurrence sa_merge "Recovery path completed" id "4ed0dd9d-c9e0-5652-aedf-5f0
kind Merge;
};
};
description "";
parent @sa_response;
postcondition "";
precondition "";
}
sa_occurrence sa_reset_step "Try the authorized remote reset" id "bfa2ef0b-ce68-55c2-a9e5-31ea8a74fce6" {
body {
Action {
action @sa_reset;
lifeline none;
};
};
description "";
parent @sa_response;
postcondition "";
precondition "";
}
sa_occurrence sa_use_field "Use the field repair procedure" id "d5e61c5d-510b-5091-a878-da46f3a9e152" {
@@ -72,56 +53,42 @@ sa_occurrence sa_use_field "Use the field repair procedure" id "d5e61c5d-510b-50
sequence @sa_field_procedure;
};
};
description "";
parent @sa_response;
postcondition "";
precondition "";
}
sequence_link sa_assess_then_decide "Continue" id "9d4a131f-36ad-5125-b21f-c3dd033b8c82" {
description "";
guard "";
source @sa_assess_step;
target @sa_decision;
}
sequence_link sa_field_path "else" id "1bbd5957-1173-51d8-97e3-ba4f084227ae" {
description "";
guard "else";
source @sa_decision;
target @sa_use_field;
}
sequence_link sa_remote_path "remote reset is authorized" id "d8b6710a-e713-5979-934f-e5ca7d155bb4" {
description "";
guard "remote reset is authorized";
source @sa_decision;
target @sa_reset_step;
}
sequence_link sa_confirm_path "Continue" id "1e738f27-a3a1-526d-b330-6384088b9689" {
description "";
guard "";
source @sa_merge;
target @sa_confirm_step;
}
sequence_link sa_remote_merge "Continue" id "e5279265-8eac-52e8-98dc-571b20475e1f" {
description "";
guard "";
source @sa_reset_step;
target @sa_merge;
}
chain_involvement sa_response_delivery "Chain involvement: Restore a safe supply" id "66efc484-53c7-51a3-89e9-422429344b38" {
description "";
source @sa_response;
target @sa_restore;
}
sequence_link sa_field_merge "Continue" id "0f6ac3fb-4ad6-581e-ad14-336472072d5e" {
description "";
guard "";
source @sa_use_field;
target @sa_merge;
}
@@ -1,7 +1,5 @@
sa_scenario sa_conversation "Repair request and measured reply" id "730b27ca-b039-5a3b-8dc6-03418f9061b3" {
context @sa_restore;
description "";
parent none;
postcondition "The controller has received the measured pressure after repair";
precondition "A pressure alarm has been assessed";
steps [@sa_call, @sa_reply];
@@ -14,23 +12,17 @@ sa_occurrence sa_call "Request valve repair" id "8bab4783-a098-5984-9e36-493957c
exchange @sa_contact;
message_kind Call;
receiver @sa_responder;
reply_to none;
sender @sa_caller;
values [{
expression "420.0";
field @sa_pressure_parameter;
path [];
}];
};
};
description "";
parent @sa_conversation;
postcondition "";
precondition "";
}
sa_lifeline sa_caller "Controller" id "98c61788-2fee-51f4-8cf9-de48e8c70e9e" {
description "";
parent @sa_conversation;
represented @sa_control;
}
@@ -46,18 +38,13 @@ sa_occurrence sa_reply "Return pressure after repair" id "4e1c0a56-ce80-5f13-a93
values [{
expression "320.0";
field @sa_pressure_parameter;
path [];
}];
};
};
description "";
parent @sa_conversation;
postcondition "";
precondition "";
}
sa_lifeline sa_responder "Technician" id "6249b54a-e396-5338-82b9-89266c55670d" {
description "";
parent @sa_conversation;
represented @sa_technician;
}
@@ -1,7 +1,5 @@
sa_scenario sa_full_scenario "Restore supply through field repair" id "f746a1cd-ff9f-55c0-b325-01c03d2f4fae" {
context @sa_restore;
description "";
parent none;
postcondition "The controller has received the measured pressure after repair";
precondition "A pressure alarm has been assessed";
steps [@sa_use_conversation];
@@ -9,13 +7,11 @@ sa_scenario sa_full_scenario "Restore supply through field repair" id "f746a1cd-
}
sa_lifeline sa_outer_controller "Controller" id "1edfcf9e-9bfa-51f1-80d5-62b8fd8bb023" {
description "";
parent @sa_full_scenario;
represented @sa_control;
}
sa_lifeline sa_outer_technician "Technician" id "155e4fe4-9e3b-5f4e-b886-28abcc3f086b" {
description "";
parent @sa_full_scenario;
represented @sa_technician;
}
@@ -33,9 +29,6 @@ sa_occurrence sa_use_conversation "Use the reviewed repair conversation" id "1c9
scenario @sa_conversation;
};
};
description "";
parent @sa_full_scenario;
postcondition "";
precondition "";
}
@@ -1,80 +1,43 @@
sa_state_machine sa_monitor "Pressure supervision" id "132b3f28-3f88-5327-9868-8c8b645ed412" {
description "";
parent @sa_control;
}
sa_region sa_monitor_region "Supervision cycle" id "3ab11e24-4140-58fc-b3fa-da5f51c7fe2c" {
description "";
parent @sa_monitor;
}
sa_initial sa_monitor_initial "Start" id "57eb5348-14d8-560e-996a-8dbf993243e2" {
description "";
do_activity "";
entry "";
exit "";
kind Initial;
parent @sa_monitor_region;
}
sa_state sa_monitoring "Monitoring" id "8af21f9b-c21e-5aae-af0d-0958840f6655" {
description "";
do_activity "";
entry "";
exit "";
kind State;
parent @sa_monitor_region;
}
sa_state sa_waiting "Awaiting repair" id "a6bb10b1-f1e5-5b31-8afd-5bad5a7b6c37" {
description "";
do_activity "";
entry "";
exit "";
kind State;
parent @sa_monitor_region;
}
state_transition sa_start_monitoring "State transition: Monitoring" id "47730532-0372-5cc5-9d6d-0b8953aebdd8" {
description "";
effect "";
effects [];
guard "";
source @sa_monitor_initial;
target @sa_monitoring;
transition_kind External;
trigger "";
triggers [];
}
state_transition sa_alarm_transition "State transition: Awaiting repair" id "13c3bfa4-9254-5ab2-b96f-f65f402adf81" {
description "";
effect "";
effects [];
guard "";
source @sa_monitoring;
target @sa_waiting;
transition_kind External;
trigger "";
triggers [@sa_high_pressure];
}
state_action sa_assess_on_entry "Assess when repair is needed" id "a887f96a-37cb-5891-8070-011fa276e5b5" {
description "";
phase Entry;
source @sa_waiting;
target @sa_assess;
}
state_transition sa_normal_transition "State transition: Monitoring" id "6e2c1b47-764c-577a-b325-ff66dd2a2768" {
description "";
effect "";
effects [];
guard "repair confirmed and pressure is safe";
source @sa_waiting;
target @sa_monitoring;
transition_kind External;
trigger "";
triggers [];
}
@@ -1,26 +1,18 @@
sa_mission sa_mission "Maintain safe water supply" id "5ad034b1-084d-5273-9e70-971671766132" {
description "";
parent none;
}
sa_capability sa_restore "Restore a safe supply" id "81259051-370a-512d-a3b5-6420a6f933b6" {
description "";
parent none;
}
sa_capability sa_triage "Assess an alarm" id "bad8fa7c-f634-55fd-98d3-e1eb2da05d8f" {
description "";
parent none;
}
mission_capability sa_mission_delivery "Mission capability: Restore a safe supply" id "a0902e0f-3d18-576a-9aca-975a136dfdd7" {
description "";
source @sa_mission;
target @sa_restore;
}
capability_include sa_include_triage "Capability include: Assess an alarm" id "f3fe7690-734a-5877-8b57-a6599a4033c4" {
description "";
source @sa_restore;
target @sa_triage;
}
@@ -1,5 +1,4 @@
sa_function sa_audit_thresholds "Audit pressure thresholds" id "4149a872-3440-59de-9879-8981e78d27ab" {
description "Deliberately unfinished: assigned to the service, but no behavior context yet. Model checks should expose this one work_without_behavior_context warning.";
parent none;
}
@@ -1,10 +1,7 @@
sa_function sa_restore_work "Restore safe pressure" id "58867e7d-916e-5ebc-9e4c-8dbed1314be3" {
description "";
parent none;
}
sa_function sa_assess "Assess pressure alarm" id "dddae020-a444-5d48-acc8-139d2696a672" {
description "";
parent @sa_restore_work;
}
@@ -14,13 +11,10 @@ sa_port sa_repair_request_out "Request field repair output" id "24a6850e-b819-57
outgoing_items [@sa_repair_operation];
};
};
description "";
legacy none;
parent @sa_assess;
}
sa_function sa_confirm "Confirm safe pressure" id "d75eb3fa-a27d-52d5-8782-8b342b3865f6" {
description "";
parent @sa_restore_work;
}
@@ -30,13 +24,10 @@ sa_port sa_repair_report_in "Report repair result input" id "391af7c4-b07d-53a7-
incoming_items [@sa_repair_operation];
};
};
description "";
legacy none;
parent @sa_confirm;
}
sa_function sa_repair "Repair valve in the field" id "9497e10f-04c4-5586-8738-2464a6f00bd4" {
description "";
parent @sa_restore_work;
}
@@ -46,8 +37,6 @@ sa_port sa_repair_report_out "Report repair result output" id "994faed7-dbc8-5b9
outgoing_items [@sa_repair_operation];
};
};
description "";
legacy none;
parent @sa_repair;
}
@@ -57,24 +46,19 @@ sa_port sa_repair_request_in "Request field repair input" id "d2113fb0-a531-54ef
incoming_items [@sa_repair_operation];
};
};
description "";
legacy none;
parent @sa_repair;
}
sa_function sa_reset "Reset valve remotely" id "09342ccb-044b-5483-ac58-87dc3cf7831c" {
description "";
parent @sa_restore_work;
}
capability_involvement sa_assessment_support "Capability involvement: Assess an alarm" id "93eaad8d-83b7-5980-b9bb-86c9e3f31da4" {
description "";
source @sa_assess;
target @sa_triage;
}
functional_exchange sa_repair_request "Request field repair" id "93474111-f23e-56c1-b61b-b7c2396b91bd" {
description "";
items [@sa_repair_operation];
source @sa_assess;
source_port @sa_repair_request_out;
@@ -83,7 +67,6 @@ functional_exchange sa_repair_request "Request field repair" id "93474111-f23e-5
}
functional_exchange sa_repair_report "Report repair result" id "684eadc3-e670-5779-a60f-bbc9a5bfb062" {
description "";
items [@sa_repair_operation];
source @sa_repair;
source_port @sa_repair_report_out;
-7
View File
@@ -1,8 +1,6 @@
sa_exchange_category sa_contact_category "Field contact" id "781cbe75-410f-5cc5-aeb8-a43491f77b99" {
description "";
exchanges [@sa_contact];
family "Participant";
parent none;
}
sa_event sa_high_pressure "Pressure alarm detected" id "01490fa8-2f01-5ac8-9722-d55d45414379" {
@@ -11,14 +9,9 @@ sa_event sa_high_pressure "Pressure alarm detected" id "01490fa8-2f01-5ac8-9722-
expression "measured_pressure > operating_limit";
};
};
description "";
parent none;
}
sa_exchange_category sa_repair_traffic "Repair work exchanges" id "a9a47e48-100c-57a8-95f0-7fe24da94f94" {
description "";
exchanges [@sa_repair_request, @sa_repair_report];
family "Work";
parent none;
}
-21
View File
@@ -1,6 +1,4 @@
sa_system sa_control "Pressure coordination service" id "435bf87c-73fd-5e4d-978a-4c471bc9eaf0" {
description "";
parent none;
}
sa_port sa_control_port "Repair service client" id "98d411e7-7c65-5c3f-b543-8bf0f2522ea2" {
@@ -11,13 +9,10 @@ sa_port sa_control_port "Repair service client" id "98d411e7-7c65-5c3f-b543-8bf0
};
};
description "Explicitly chosen Standard component port for the repair service.";
legacy none;
parent @sa_control;
}
sa_actor sa_technician "Field technician" id "c05b9422-b9da-5de5-b21d-07ad23bd2b8a" {
description "";
parent none;
}
sa_port sa_technician_port "Repair service provider" id "e6d69cbd-99d7-596b-bbe3-dada6118a600" {
@@ -28,36 +23,30 @@ sa_port sa_technician_port "Repair service provider" id "e6d69cbd-99d7-596b-bbe3
};
};
description "Explicitly chosen Standard component port for the repair service.";
legacy none;
parent @sa_technician;
}
allocation sa_assess_allocation "Allocation: Assess pressure alarm" id "9b9356d7-2db2-5a94-a47e-ea892cf02afe" {
description "";
source @sa_control;
target @sa_assess;
}
allocation sa_audit_allocation "Allocation: Audit pressure thresholds" id "1b245065-7242-5d81-8472-aaeb932054c9" {
description "";
source @sa_control;
target @sa_audit_thresholds;
}
allocation sa_confirm_allocation "Allocation: Confirm safe pressure" id "b76bf8de-2fa4-563f-a540-dd30f30e6c49" {
description "";
source @sa_control;
target @sa_confirm;
}
interface_requirement sa_client_requires "Interface requirement: Valve repair service" id "4f85337b-f6eb-533c-a45a-9a5d1d9567ca" {
description "";
source @sa_control_port;
target @sa_repair_interface;
}
boundary_exchange sa_contact "Technician service conversation" id "63009b5f-539e-5914-b630-ec6b7fefb4d5" {
description "";
items [@sa_repair_operation];
oriented false;
source @sa_control_port;
@@ -65,61 +54,51 @@ boundary_exchange sa_contact "Technician service conversation" id "63009b5f-539e
}
exchange_allocation sa_report_carriage "Exchange allocation: Report repair result" id "ff9897b3-a23a-5994-a5b3-99a5b4d34ceb" {
description "";
source @sa_contact;
target @sa_repair_report;
}
exchange_allocation sa_request_carriage "Exchange allocation: Request field repair" id "4c4c4851-5a3d-5bb9-94df-b19f9cf39819" {
description "";
source @sa_contact;
target @sa_repair_request;
}
port_allocation sa_port_report_in "Port allocation: Report repair result input" id "568c5c01-de21-52a2-83bd-afdddc158085" {
description "";
source @sa_control_port;
target @sa_repair_report_in;
}
port_allocation sa_port_request_out "Port allocation: Request field repair output" id "55ec4dbf-bd17-5b0c-b3a3-95b2194aa8b9" {
description "";
source @sa_control_port;
target @sa_repair_request_out;
}
mission_involvement sa_mission_participant "Mission involvement: Maintain safe water supply" id "1c1e5634-ba8c-591f-b143-90a27cdc6cd2" {
description "";
source @sa_control;
target @sa_mission;
}
allocation sa_reset_allocation "Allocation: Reset valve remotely" id "3cc9ab52-2b00-57fd-8709-aabc75e5d3e3" {
description "";
source @sa_control;
target @sa_reset;
}
allocation sa_repair_allocation "Allocation: Repair valve in the field" id "725f2e1b-aa2c-5615-9b1f-28da178168fa" {
description "";
source @sa_technician;
target @sa_repair;
}
port_allocation sa_port_report_out "Port allocation: Report repair result output" id "e53b7a40-e8a3-5b3f-826d-c2b5c164a84a" {
description "";
source @sa_technician_port;
target @sa_repair_report_out;
}
port_allocation sa_port_request_in "Port allocation: Request field repair input" id "09eaa992-4cdf-5ea3-b748-f10fd74b8cba" {
description "";
source @sa_technician_port;
target @sa_repair_request_in;
}
interface_provision sa_provider_provides "Interface provision: Valve repair service" id "55f89fbf-aa3c-5ae1-95ef-ac676f9c1ada" {
description "";
source @sa_technician_port;
target @sa_repair_interface;
}
-18
View File
@@ -1,48 +1,30 @@
sa_data_type sa_pressure "Measured pressure" id "87d0a95b-af1b-5352-b753-e4c6533671a5" {
default none;
definition {
Real {
maximum 600.0;
minimum 0.0;
};
};
description "";
parent none;
unit "kPa";
}
sa_interface sa_repair_interface "Valve repair service" id "a4b8942e-a975-5f88-ad14-8f38c345baac" {
description "";
parent none;
supertype none;
}
sa_exchange_item sa_repair_operation "Valve repair" id "a962fc5e-3ab0-5296-8b0d-fd84633f9218" {
description "";
kind Information;
mechanism Operation;
parent none;
supertype none;
}
sa_data_field sa_pressure_parameter "Pressure before / after repair" id "bb12ba5d-aab5-5a1b-99de-c767f5365b57" {
default none;
description "";
direction InOut;
maximum 1;
minimum 1;
parent @sa_repair_operation;
storage Value;
type_ref {
Named @sa_pressure;
};
unit "";
}
interface_item_allocation sa_repair_interface_item "Repair operation contract" id "f074e041-23cf-54ad-af9c-f171e30621e7" {
acquisition Accept;
description "";
order 0;
source @sa_repair_interface;
target @sa_repair_operation;
transmission Synchronous;
-73
View File
@@ -9,7 +9,6 @@ trace trace_oa_alarm_transition "State transition: Awaiting repair ↔ State tra
name "State transition: Awaiting repair";
};
};
description "";
kind Manual;
oa @oa_alarm_transition;
sa @sa_alarm_transition;
@@ -26,8 +25,6 @@ trace trace_oa_assess "Assess pressure alarm ↔ Assess pressure alarm" id "5309
name "Assess pressure alarm";
};
};
description "";
kind Refinement;
oa @oa_assess;
sa @sa_assess;
}
@@ -43,7 +40,6 @@ trace trace_oa_assess_allocation "Allocation: Assess pressure alarm ↔ Allocati
name "Allocation: Assess pressure alarm";
};
};
description "";
kind Manual;
oa @oa_assess_allocation;
sa @sa_assess_allocation;
@@ -60,7 +56,6 @@ trace trace_oa_assess_on_entry "Assess when repair is needed ↔ Assess when rep
name "Assess when repair is needed";
};
};
description "";
kind Manual;
oa @oa_assess_on_entry;
sa @sa_assess_on_entry;
@@ -77,7 +72,6 @@ trace trace_oa_assess_step "Assess the alarm ↔ Assess the alarm" id "e848da41-
name "Assess the alarm";
};
};
description "";
kind Sequence;
oa @oa_assess_step;
sa @sa_assess_step;
@@ -94,7 +88,6 @@ trace trace_oa_assess_then_decide "Continue ↔ Continue" id "a5c1eb01-a95f-5f7a
name "Continue";
};
};
description "";
kind Manual;
oa @oa_assess_then_decide;
sa @sa_assess_then_decide;
@@ -111,7 +104,6 @@ trace trace_oa_assessment_action "Assess pressure alarm ↔ Assess pressure alar
name "Assess pressure alarm";
};
};
description "";
kind Sequence;
oa @oa_assessment_action;
sa @sa_assessment_action;
@@ -128,7 +120,6 @@ trace trace_oa_assessment_delivery "Chain involvement: Assess an alarm ↔ Chain
name "Chain involvement: Assess an alarm";
};
};
description "";
kind Manual;
oa @oa_assessment_delivery;
sa @sa_assessment_delivery;
@@ -145,7 +136,6 @@ trace trace_oa_assessment_procedure "Assess an alarm procedure ↔ Assess an ala
name "Assess an alarm procedure";
};
};
description "";
kind Sequence;
oa @oa_assessment_procedure;
sa @sa_assessment_procedure;
@@ -162,7 +152,6 @@ trace trace_oa_assessment_support "Capability involvement: Assess an alarm ↔ C
name "Capability involvement: Assess an alarm";
};
};
description "";
kind Manual;
oa @oa_assessment_support;
sa @sa_assessment_support;
@@ -179,7 +168,6 @@ trace trace_oa_call "Request valve repair ↔ Request valve repair" id "f0cc86e5
name "Request valve repair";
};
};
description "";
kind Scenario;
oa @oa_call;
sa @sa_call;
@@ -196,7 +184,6 @@ trace trace_oa_caller "Controller ↔ Controller" id "241dfdac-2450-5704-a4cb-9f
name "Controller";
};
};
description "";
kind Scenario;
oa @oa_caller;
sa @sa_caller;
@@ -213,8 +200,6 @@ trace trace_oa_confirm "Confirm safe pressure ↔ Confirm safe pressure" id "d77
name "Confirm safe pressure";
};
};
description "";
kind Refinement;
oa @oa_confirm;
sa @sa_confirm;
}
@@ -230,7 +215,6 @@ trace trace_oa_confirm_allocation "Allocation: Confirm safe pressure ↔ Allocat
name "Allocation: Confirm safe pressure";
};
};
description "";
kind Manual;
oa @oa_confirm_allocation;
sa @sa_confirm_allocation;
@@ -247,7 +231,6 @@ trace trace_oa_confirm_path "Continue ↔ Continue" id "a106d183-c239-5c9f-b1f1-
name "Continue";
};
};
description "";
kind Manual;
oa @oa_confirm_path;
sa @sa_confirm_path;
@@ -264,7 +247,6 @@ trace trace_oa_confirm_step "Confirm safe pressure ↔ Confirm safe pressure" id
name "Confirm safe pressure";
};
};
description "";
kind Sequence;
oa @oa_confirm_step;
sa @sa_confirm_step;
@@ -281,7 +263,6 @@ trace trace_oa_contact "Technician service conversation ↔ Technician service c
name "Technician service conversation";
};
};
description "";
kind Exchange;
oa @oa_contact;
sa @sa_contact;
@@ -298,7 +279,6 @@ trace trace_oa_contact_category "Field contact ↔ Field contact" id "e96ff119-b
name "Field contact";
};
};
description "";
kind Manual;
oa @oa_contact_category;
sa @sa_contact_category;
@@ -315,7 +295,6 @@ trace trace_oa_control "Control room ↔ Pressure coordination service" id "aba5
name "Pressure coordination service";
};
};
description "";
kind Participant;
oa @oa_control;
sa @sa_control;
@@ -332,7 +311,6 @@ trace trace_oa_conversation "Repair request and measured reply ↔ Repair reques
name "Repair request and measured reply";
};
};
description "";
kind Scenario;
oa @oa_conversation;
sa @sa_conversation;
@@ -349,7 +327,6 @@ trace trace_oa_decision "Can a remote reset safely recover pressure? ↔ Can a r
name "Can a remote reset safely recover pressure?";
};
};
description "";
kind Sequence;
oa @oa_decision;
sa @sa_decision;
@@ -366,7 +343,6 @@ trace trace_oa_field_merge "Continue ↔ Continue" id "d32dd433-9c9e-5b57-9355-2
name "Continue";
};
};
description "";
kind Manual;
oa @oa_field_merge;
sa @sa_field_merge;
@@ -383,7 +359,6 @@ trace trace_oa_field_path "else ↔ else" id "2e849e31-35aa-5aef-a989-26948038e5
name "else";
};
};
description "";
kind Manual;
oa @oa_field_path;
sa @sa_field_path;
@@ -400,7 +375,6 @@ trace trace_oa_field_procedure "Field repair procedure ↔ Field repair procedur
name "Field repair procedure";
};
};
description "";
kind Sequence;
oa @oa_field_procedure;
sa @sa_field_procedure;
@@ -417,7 +391,6 @@ trace trace_oa_full_scenario "Restore supply through field repair ↔ Restore su
name "Restore supply through field repair";
};
};
description "";
kind Scenario;
oa @oa_full_scenario;
sa @sa_full_scenario;
@@ -434,7 +407,6 @@ trace trace_oa_high_pressure "Pressure alarm detected ↔ Pressure alarm detecte
name "Pressure alarm detected";
};
};
description "";
kind State;
oa @oa_high_pressure;
sa @sa_high_pressure;
@@ -451,7 +423,6 @@ trace trace_oa_include_triage "Capability include: Assess an alarm ↔ Capabilit
name "Capability include: Assess an alarm";
};
};
description "";
kind Manual;
oa @oa_include_triage;
sa @sa_include_triage;
@@ -468,7 +439,6 @@ trace trace_oa_merge "Recovery path completed ↔ Recovery path completed" id "0
name "Recovery path completed";
};
};
description "";
kind Sequence;
oa @oa_merge;
sa @sa_merge;
@@ -485,7 +455,6 @@ trace trace_oa_mission "Maintain safe water supply ↔ Maintain safe water suppl
name "Maintain safe water supply";
};
};
description "";
kind Mission;
oa @oa_mission;
sa @sa_mission;
@@ -502,7 +471,6 @@ trace trace_oa_mission_delivery "Mission capability: Restore a safe supply ↔ M
name "Mission capability: Restore a safe supply";
};
};
description "";
kind Manual;
oa @oa_mission_delivery;
sa @sa_mission_delivery;
@@ -519,7 +487,6 @@ trace trace_oa_mission_participant "Mission involvement: Maintain safe water sup
name "Mission involvement: Maintain safe water supply";
};
};
description "";
kind Manual;
oa @oa_mission_participant;
sa @sa_mission_participant;
@@ -536,7 +503,6 @@ trace trace_oa_monitor "Pressure supervision ↔ Pressure supervision" id "b5386
name "Pressure supervision";
};
};
description "";
kind State;
oa @oa_monitor;
sa @sa_monitor;
@@ -553,7 +519,6 @@ trace trace_oa_monitor_initial "Start ↔ Start" id "48d243f0-553f-507e-acb0-947
name "Start";
};
};
description "";
kind State;
oa @oa_monitor_initial;
sa @sa_monitor_initial;
@@ -570,7 +535,6 @@ trace trace_oa_monitor_region "Supervision cycle ↔ Supervision cycle" id "2087
name "Supervision cycle";
};
};
description "";
kind State;
oa @oa_monitor_region;
sa @sa_monitor_region;
@@ -587,7 +551,6 @@ trace trace_oa_monitoring "Monitoring ↔ Monitoring" id "12ec320f-2e14-5a5a-8c6
name "Monitoring";
};
};
description "";
kind State;
oa @oa_monitoring;
sa @sa_monitoring;
@@ -604,7 +567,6 @@ trace trace_oa_normal_transition "State transition: Monitoring ↔ State transit
name "State transition: Monitoring";
};
};
description "";
kind Manual;
oa @oa_normal_transition;
sa @sa_normal_transition;
@@ -621,7 +583,6 @@ trace trace_oa_outer_controller "Controller ↔ Controller" id "fd195498-dbba-52
name "Controller";
};
};
description "";
kind Scenario;
oa @oa_outer_controller;
sa @sa_outer_controller;
@@ -638,7 +599,6 @@ trace trace_oa_outer_technician "Technician ↔ Technician" id "c132a0df-1270-55
name "Technician";
};
};
description "";
kind Scenario;
oa @oa_outer_technician;
sa @sa_outer_technician;
@@ -655,7 +615,6 @@ trace trace_oa_pressure "Measured pressure ↔ Measured pressure" id "d9d47371-e
name "Measured pressure";
};
};
description "";
kind Item;
oa @oa_pressure;
sa @sa_pressure;
@@ -672,7 +631,6 @@ trace trace_oa_pressure_parameter "Pressure before / after repair ↔ Pressure b
name "Pressure before / after repair";
};
};
description "";
kind Item;
oa @oa_pressure_parameter;
sa @sa_pressure_parameter;
@@ -689,7 +647,6 @@ trace trace_oa_remote_merge "Continue ↔ Continue" id "1b788e74-36b2-5c17-a936-
name "Continue";
};
};
description "";
kind Manual;
oa @oa_remote_merge;
sa @sa_remote_merge;
@@ -706,7 +663,6 @@ trace trace_oa_remote_path "remote reset is authorized ↔ remote reset is autho
name "remote reset is authorized";
};
};
description "";
kind Manual;
oa @oa_remote_path;
sa @sa_remote_path;
@@ -723,8 +679,6 @@ trace trace_oa_repair "Repair valve in the field ↔ Repair valve in the field"
name "Repair valve in the field";
};
};
description "";
kind Refinement;
oa @oa_repair;
sa @sa_repair;
}
@@ -740,7 +694,6 @@ trace trace_oa_repair_allocation "Allocation: Repair valve in the field ↔ Allo
name "Allocation: Repair valve in the field";
};
};
description "";
kind Manual;
oa @oa_repair_allocation;
sa @sa_repair_allocation;
@@ -757,7 +710,6 @@ trace trace_oa_repair_operation "Valve repair ↔ Valve repair" id "cf92cedf-ea1
name "Valve repair";
};
};
description "";
kind Item;
oa @oa_repair_operation;
sa @sa_repair_operation;
@@ -774,7 +726,6 @@ trace trace_oa_repair_report "Report repair result ↔ Report repair result" id
name "Report repair result";
};
};
description "";
kind Exchange;
oa @oa_repair_report;
sa @sa_repair_report;
@@ -791,7 +742,6 @@ trace trace_oa_repair_request "Request field repair ↔ Request field repair" id
name "Request field repair";
};
};
description "";
kind Exchange;
oa @oa_repair_request;
sa @sa_repair_request;
@@ -808,7 +758,6 @@ trace trace_oa_repair_step "Repair the valve ↔ Repair the valve" id "ac3aa6b0-
name "Repair the valve";
};
};
description "";
kind Sequence;
oa @oa_repair_step;
sa @sa_repair_step;
@@ -825,7 +774,6 @@ trace trace_oa_repair_then_report "Sequence link: Report the measured result ↔
name "Sequence link: Report the measured result";
};
};
description "";
kind Manual;
oa @oa_repair_then_report;
sa @sa_repair_then_report;
@@ -842,7 +790,6 @@ trace trace_oa_repair_traffic "Repair work exchanges ↔ Repair work exchanges"
name "Repair work exchanges";
};
};
description "";
kind Manual;
oa @oa_repair_traffic;
sa @sa_repair_traffic;
@@ -859,7 +806,6 @@ trace trace_oa_reply "Return pressure after repair ↔ Return pressure after rep
name "Return pressure after repair";
};
};
description "";
kind Scenario;
oa @oa_reply;
sa @sa_reply;
@@ -876,7 +822,6 @@ trace trace_oa_report_step "Report the measured result ↔ Report the measured r
name "Report the measured result";
};
};
description "";
kind Sequence;
oa @oa_report_step;
sa @sa_report_step;
@@ -893,7 +838,6 @@ trace trace_oa_request_step "Request the field repair ↔ Request the field repa
name "Request the field repair";
};
};
description "";
kind Sequence;
oa @oa_request_step;
sa @sa_request_step;
@@ -910,7 +854,6 @@ trace trace_oa_request_then_repair "Sequence link: Repair the valve ↔ Sequence
name "Sequence link: Repair the valve";
};
};
description "";
kind Manual;
oa @oa_request_then_repair;
sa @sa_request_then_repair;
@@ -927,8 +870,6 @@ trace trace_oa_reset "Reset valve remotely ↔ Reset valve remotely" id "82a5be1
name "Reset valve remotely";
};
};
description "";
kind Refinement;
oa @oa_reset;
sa @sa_reset;
}
@@ -944,7 +885,6 @@ trace trace_oa_reset_allocation "Allocation: Reset valve remotely ↔ Allocation
name "Allocation: Reset valve remotely";
};
};
description "";
kind Manual;
oa @oa_reset_allocation;
sa @sa_reset_allocation;
@@ -961,7 +901,6 @@ trace trace_oa_reset_step "Try the authorized remote reset ↔ Try the authorize
name "Try the authorized remote reset";
};
};
description "";
kind Sequence;
oa @oa_reset_step;
sa @sa_reset_step;
@@ -978,7 +917,6 @@ trace trace_oa_responder "Technician ↔ Technician" id "c5051628-b7af-5fb1-a014
name "Technician";
};
};
description "";
kind Scenario;
oa @oa_responder;
sa @sa_responder;
@@ -995,7 +933,6 @@ trace trace_oa_response "Handle a pressure alarm ↔ Handle a pressure alarm" id
name "Handle a pressure alarm";
};
};
description "";
kind Sequence;
oa @oa_response;
sa @sa_response;
@@ -1012,7 +949,6 @@ trace trace_oa_response_delivery "Chain involvement: Restore a safe supply ↔ C
name "Chain involvement: Restore a safe supply";
};
};
description "";
kind Manual;
oa @oa_response_delivery;
sa @sa_response_delivery;
@@ -1029,7 +965,6 @@ trace trace_oa_restore "Restore a safe supply ↔ Restore a safe supply" id "0fb
name "Restore a safe supply";
};
};
description "";
kind Capability;
oa @oa_restore;
sa @sa_restore;
@@ -1046,8 +981,6 @@ trace trace_oa_restore_work "Restore safe pressure ↔ Restore safe pressure" id
name "Restore safe pressure";
};
};
description "";
kind Refinement;
oa @oa_restore_work;
sa @sa_restore_work;
}
@@ -1063,7 +996,6 @@ trace trace_oa_start_monitoring "State transition: Monitoring ↔ State transiti
name "State transition: Monitoring";
};
};
description "";
kind Manual;
oa @oa_start_monitoring;
sa @sa_start_monitoring;
@@ -1080,7 +1012,6 @@ trace trace_oa_technician "Field technician ↔ Field technician" id "d47d3c36-5
name "Field technician";
};
};
description "";
kind Participant;
oa @oa_technician;
sa @sa_technician;
@@ -1097,7 +1028,6 @@ trace trace_oa_triage "Assess an alarm ↔ Assess an alarm" id "6acd072d-431e-58
name "Assess an alarm";
};
};
description "";
kind Capability;
oa @oa_triage;
sa @sa_triage;
@@ -1114,7 +1044,6 @@ trace trace_oa_use_conversation "Use the reviewed repair conversation ↔ Use th
name "Use the reviewed repair conversation";
};
};
description "";
kind Scenario;
oa @oa_use_conversation;
sa @sa_use_conversation;
@@ -1131,7 +1060,6 @@ trace trace_oa_use_field "Use the field repair procedure ↔ Use the field repai
name "Use the field repair procedure";
};
};
description "";
kind Sequence;
oa @oa_use_field;
sa @sa_use_field;
@@ -1148,7 +1076,6 @@ trace trace_oa_waiting "Awaiting repair ↔ Awaiting repair" id "0754be23-e4b8-5
name "Awaiting repair";
};
};
description "";
kind State;
oa @oa_waiting;
sa @sa_waiting;
@@ -1,21 +1,9 @@
diagram oa_allocation_view "1 · OA responsibility and repair traffic" id "103ea62c-ac56-5827-94e7-cf09bca96ccc" {
description "Teaching view: positions and filters do not change ownership or exchange identities.";
layer Oa;
projection Allocation;
routes [];
view_options {
coverage "All";
exchange_labels "Name";
hidden_edges [];
participant_categories "All";
relation_kinds "All";
work_categories "All";
work_contexts "All";
};
}
node oa_allocation_view_oa_assess id "5ae1c46d-59a3-50fd-8cc3-b292df21f951" {
collapsed false;
diagram @oa_allocation_view;
element @oa_assess;
position {
@@ -29,7 +17,6 @@ node oa_allocation_view_oa_assess id "5ae1c46d-59a3-50fd-8cc3-b292df21f951" {
}
node oa_allocation_view_oa_confirm id "edd74060-ac61-5450-a57c-a6577daa5d50" {
collapsed false;
diagram @oa_allocation_view;
element @oa_confirm;
position {
@@ -43,7 +30,6 @@ node oa_allocation_view_oa_confirm id "edd74060-ac61-5450-a57c-a6577daa5d50" {
}
node oa_allocation_view_oa_control id "3191e05c-1c7f-5f0e-beb0-5f3bded54c48" {
collapsed false;
diagram @oa_allocation_view;
element @oa_control;
position {
@@ -57,7 +43,6 @@ node oa_allocation_view_oa_control id "3191e05c-1c7f-5f0e-beb0-5f3bded54c48" {
}
node oa_allocation_view_oa_repair id "202ebe37-ddb4-5099-8ce9-ec3e895ab9e9" {
collapsed false;
diagram @oa_allocation_view;
element @oa_repair;
position {
@@ -71,7 +56,6 @@ node oa_allocation_view_oa_repair id "202ebe37-ddb4-5099-8ce9-ec3e895ab9e9" {
}
node oa_allocation_view_oa_reset id "7066f78c-9736-5349-b94a-0587331b980e" {
collapsed false;
diagram @oa_allocation_view;
element @oa_reset;
position {
@@ -85,7 +69,6 @@ node oa_allocation_view_oa_reset id "7066f78c-9736-5349-b94a-0587331b980e" {
}
node oa_allocation_view_oa_technician id "d27f28ef-f10a-5355-9a05-6f2fd39b7de6" {
collapsed false;
diagram @oa_allocation_view;
element @oa_technician;
position {
@@ -2,20 +2,9 @@ diagram sa_allocation_view "2 · SA service boundary and functions" id "35d3c24b
description "Teaching view: positions and filters do not change ownership or exchange identities.";
layer Sa;
projection Allocation;
routes [];
view_options {
coverage "All";
exchange_labels "Name";
hidden_edges [];
participant_categories "All";
relation_kinds "All";
work_categories "All";
work_contexts "All";
};
}
node sa_allocation_view_sa_assess id "60f6ad74-b69e-5aef-b832-f1bd60b903ae" {
collapsed false;
diagram @sa_allocation_view;
element @sa_assess;
position {
@@ -29,7 +18,6 @@ node sa_allocation_view_sa_assess id "60f6ad74-b69e-5aef-b832-f1bd60b903ae" {
}
node sa_allocation_view_sa_confirm id "7e3ba36f-bc68-5520-bdb9-77083bb7bba2" {
collapsed false;
diagram @sa_allocation_view;
element @sa_confirm;
position {
@@ -43,7 +31,6 @@ node sa_allocation_view_sa_confirm id "7e3ba36f-bc68-5520-bdb9-77083bb7bba2" {
}
node sa_allocation_view_sa_control id "8345c76a-8d85-5c31-902e-ec1cd33ed4a3" {
collapsed false;
diagram @sa_allocation_view;
element @sa_control;
position {
@@ -57,7 +44,6 @@ node sa_allocation_view_sa_control id "8345c76a-8d85-5c31-902e-ec1cd33ed4a3" {
}
node sa_allocation_view_sa_repair id "34630a18-41b0-5fdd-bff4-ee86e93e8651" {
collapsed false;
diagram @sa_allocation_view;
element @sa_repair;
position {
@@ -71,7 +57,6 @@ node sa_allocation_view_sa_repair id "34630a18-41b0-5fdd-bff4-ee86e93e8651" {
}
node sa_allocation_view_sa_reset id "73406244-ebd7-5995-86c7-e17c73c50b32" {
collapsed false;
diagram @sa_allocation_view;
element @sa_reset;
position {
@@ -85,7 +70,6 @@ node sa_allocation_view_sa_reset id "73406244-ebd7-5995-86c7-e17c73c50b32" {
}
node sa_allocation_view_sa_technician id "f7225d1c-e506-505d-bb20-916950269d4b" {
collapsed false;
diagram @sa_allocation_view;
element @sa_technician;
position {
@@ -2,20 +2,9 @@ diagram sa_capability_view "4 · SA mission and included capability" id "3e6ad19
description "Teaching view: positions and filters do not change ownership or exchange identities.";
layer Sa;
projection Capabilities;
routes [];
view_options {
coverage "All";
exchange_labels "Name";
hidden_edges [];
participant_categories "All";
relation_kinds "All";
work_categories "All";
work_contexts "All";
};
}
node sa_capability_view_sa_control id "41ddb68b-bd2b-5f91-82eb-efde01157828" {
collapsed false;
diagram @sa_capability_view;
element @sa_control;
position {
@@ -29,7 +18,6 @@ node sa_capability_view_sa_control id "41ddb68b-bd2b-5f91-82eb-efde01157828" {
}
node sa_capability_view_sa_mission id "4b814335-bf98-5b68-85d5-ea549766517a" {
collapsed false;
diagram @sa_capability_view;
element @sa_mission;
position {
@@ -43,7 +31,6 @@ node sa_capability_view_sa_mission id "4b814335-bf98-5b68-85d5-ea549766517a" {
}
node sa_capability_view_sa_restore id "d3bc13f9-7726-5280-83fe-63f391c45afb" {
collapsed false;
diagram @sa_capability_view;
element @sa_restore;
position {
@@ -57,7 +44,6 @@ node sa_capability_view_sa_restore id "d3bc13f9-7726-5280-83fe-63f391c45afb" {
}
node sa_capability_view_sa_triage id "069a3fb3-077c-59d8-91da-27b0665143c7" {
collapsed false;
diagram @sa_capability_view;
element @sa_triage;
position {
@@ -2,24 +2,18 @@ diagram sa_service_view "3 · SA service conversation only" id "50af4c9b-93ce-5f
description "Teaching view: positions and filters do not change ownership or exchange identities.";
layer Sa;
projection Allocation;
routes [];
view_options {
coverage "All";
exchange_labels "NameAndItems";
hidden_edges [];
participant_categories {
Any [@sa_contact_category];
};
relation_kinds "All";
work_categories {
Any [];
};
work_contexts "All";
};
}
node sa_service_view_sa_assess id "439f1e45-e5ed-567c-978b-4c93ef267db1" {
collapsed false;
diagram @sa_service_view;
element @sa_assess;
position {
@@ -33,7 +27,6 @@ node sa_service_view_sa_assess id "439f1e45-e5ed-567c-978b-4c93ef267db1" {
}
node sa_service_view_sa_confirm id "6f138672-149b-5f88-ab07-7fc413a42262" {
collapsed false;
diagram @sa_service_view;
element @sa_confirm;
position {
@@ -47,7 +40,6 @@ node sa_service_view_sa_confirm id "6f138672-149b-5f88-ab07-7fc413a42262" {
}
node sa_service_view_sa_control id "f53d4352-b042-56a6-8b3e-c51cd27254f9" {
collapsed false;
diagram @sa_service_view;
element @sa_control;
position {
@@ -61,7 +53,6 @@ node sa_service_view_sa_control id "f53d4352-b042-56a6-8b3e-c51cd27254f9" {
}
node sa_service_view_sa_repair id "ae36489d-6ec8-5f1c-b603-8b497227efba" {
collapsed false;
diagram @sa_service_view;
element @sa_repair;
position {
@@ -75,7 +66,6 @@ node sa_service_view_sa_repair id "ae36489d-6ec8-5f1c-b603-8b497227efba" {
}
node sa_service_view_sa_reset id "d52da4e6-40b7-5dbc-84fb-80c443711c69" {
collapsed false;
diagram @sa_service_view;
element @sa_reset;
position {
@@ -89,7 +79,6 @@ node sa_service_view_sa_reset id "d52da4e6-40b7-5dbc-84fb-80c443711c69" {
}
node sa_service_view_sa_technician id "40210589-69f7-5209-b329-10bb83cf615c" {
collapsed false;
diagram @sa_service_view;
element @sa_technician;
position {
@@ -6,20 +6,9 @@ diagram sa_state_view "5 · SA pressure supervision" id "30d5baf9-6581-5cb6-846a
machine @sa_monitor;
};
};
routes [];
view_options {
coverage "All";
exchange_labels "Name";
hidden_edges [];
participant_categories "All";
relation_kinds "All";
work_categories "All";
work_contexts "All";
};
}
node sa_state_view_sa_monitor id "a676fbb9-0833-59a8-98cf-a10d8f4b7434" {
collapsed false;
diagram @sa_state_view;
element @sa_monitor;
position {
@@ -33,7 +22,6 @@ node sa_state_view_sa_monitor id "a676fbb9-0833-59a8-98cf-a10d8f4b7434" {
}
node sa_state_view_sa_monitor_initial id "5e2326a0-0f6c-5f8d-8a21-fdded18a9281" {
collapsed false;
diagram @sa_state_view;
element @sa_monitor_initial;
position {
@@ -47,7 +35,6 @@ node sa_state_view_sa_monitor_initial id "5e2326a0-0f6c-5f8d-8a21-fdded18a9281"
}
node sa_state_view_sa_monitor_region id "55a74f91-2025-5001-8db8-9566b1dbe783" {
collapsed false;
diagram @sa_state_view;
element @sa_monitor_region;
position {
@@ -61,7 +48,6 @@ node sa_state_view_sa_monitor_region id "55a74f91-2025-5001-8db8-9566b1dbe783" {
}
node sa_state_view_sa_monitoring id "b3af7b04-5a45-5b82-ab69-7eada4b876f0" {
collapsed false;
diagram @sa_state_view;
element @sa_monitoring;
position {
@@ -75,7 +61,6 @@ node sa_state_view_sa_monitoring id "b3af7b04-5a45-5b82-ab69-7eada4b876f0" {
}
node sa_state_view_sa_waiting id "57acbb71-ca86-57fc-ae90-42dad195f0e0" {
collapsed false;
diagram @sa_state_view;
element @sa_waiting;
position {
@@ -0,0 +1,66 @@
# Ordinary-text union: subsequent gates
Closed automated-gates checkpoint. Preserve the separately published initial evidence package
at `../ordinary-text-integration-20260913/`.
Run `python verify.py` with Python 3.10 or newer. Verification uses only these
five files and the standard library: no archived code execution, extraction,
network, Git, Cargo, binaries or original-machine paths. All outer text files
are UTF-8 with LF before hashing. Raw ZIP contents retain original bytes.
`files.json` pins the archive, manifest, verifier and README; the manifest pins
each member and maps each capture to exact source inventory/archive objects.
Only byte-identical objects are deduplicated, including ZIP timestamps.
| Capture | Outcome |
| --- | --- |
| desktop-list-1 | 1,185 actual listed test names |
| core-2 | 537 passed, 0 failed, 1 ignored |
| desktop-1 | 1,179 passed, 0 failed, 6 ignored |
| clippy-1 | Failed items_after_test_module lint; raw logs retained |
| clippy-2 | Failed unused SymbolEvent::Close lint; raw logs retained |
| clippy-3 | Passed strict workspace/all-target lint |
| desktop-affected-2 | 83 passed, 0 failed, 1 known ignored, 1,101 filtered |
| windows-build-1 | Workspace binaries built successfully; 3 binary artifact receipts |
The full suites use the e624d5b9c4f848eff3fba2ed83f9decf959e14d8 input
state. The final lint inputs include the reviewed four-file fix committed as
f714a25bbcd435941404104ad0cbb0fea3b89149. That change moves test-only
helpers, removes the unconstructed Close variant and its handler, and changes
existing tests to send the captured HideDrawer event already used by production
UI. `fullSuiteToFinalInputChanges` records the exact four file/digest pairs.
Per-run source inventories are authoritative; receipt HEADs can predate dirty
changes. Full-suite outcomes are scoped to their recorded inputs; they are not
a full-suite rerun of the later fix. The final affected-test recheck uses the
same 707 input hashes as clippy-3. Its 84 names equal the OR-filter selection
from the prior 1,185-name listing: `source_symbols::tests`, `text_workspace::`,
`views::tests`, `palette_union`. All 83 enabled selected tests passed; the
known ignored case is the source-symbol 100-module performance probe. This
check introduces no test names and leaves 1,101 previous tests unexecuted on
the final source state.
The final Windows build uses those same 707 input hashes. Its three binary
artifact records are preserved; the binaries themselves are not in this package.
Verification checks archive membership/hashes, nested source manifests and all
707 inputs including 71 vendored iced_widget files, receipt relationships,
helper identity, commands, outcomes, exact test summary rows, and artifact
receipt records. It independently recomputes actual desktop names/outcomes,
listing/execution equality, all 150 required matches (144 passed, 6 ignored),
removed/retained names, and 24 original checklist Git blob bindings from archived
source bytes. It checks the full-suite-to-final four-file input delta. The
checklist's original pending-listing wording is preserved as raw historical
input; desktop-admission.json records the completed admission.
`final-input-commit-readback.json` is a secondary parent-produced summary of
comparison between all 707 final lint inputs and f714a25 committed blobs. Its
helper and Git stream were not archived. Verification checks its relationship
to the captured input manifest; it does not independently reproduce the Git
comparison or authenticate that commit.
Integrity and internal consistency do not establish independent reproduction or
authenticated provenance. Expected summary rows are frozen from raw logs.
Ignored probes did not execute. Test membership/pass outcomes do not establish
complete requirement coverage or native GUI acceptance. Compiler artifact hashes
are retained as observations; binaries are intentionally absent and cannot be
verified here. Source archives are not a hermetic compiler/dependency bundle.
This package makes no native interaction, release publication or packaged-build
claim. No credentials, dependency caches or whole user profile are included.
@@ -0,0 +1,18 @@
{
"evidence.zip": {
"bytes": 8624040,
"sha256": "b0da6611dd86e83c8f779c025786e473834298f9b9990b19a76b250b61137780"
},
"manifest.json": {
"bytes": 19056,
"sha256": "752d4c8e703cdbac361f959716a8a752096280727f9f0c966854d479aa68750d"
},
"verify.py": {
"bytes": 12815,
"sha256": "5157b5cadf38e59bd59693fe1d14235d2007788bb6c8bd9cbac938cb048b14f4"
},
"README.md": {
"bytes": 4177,
"sha256": "9bfafd6506d1eb322e6efb8070a1599766d6d09532a678251a8b2414387edc6e"
}
}
@@ -0,0 +1,819 @@
{
"schema": 1,
"fullSuiteInputsCommit": "e624d5b9c4f848eff3fba2ed83f9decf959e14d8",
"reviewedFixCommit": "f714a25bbcd435941404104ad0cbb0fea3b89149",
"runs": {
"desktop-list-1": {
"kind": "cargo",
"inputs": "inputs/bbe8290d9cb11a7ee5dba250350451c5b7ec88069aa65f6be786a67922408c48.json",
"archive": "inputs/86ac50e9cec8b39db91bbc98e5388e8c0f8466e6c217cf2f4eca5f1db087dde1.zip",
"command": [
"cargo",
"test",
"--locked",
"--offline",
"-p",
"oa-desktop",
"--bin",
"arcadia-studio",
"--message-format=json",
"--",
"--list"
],
"head": "e624d5b9c4f848eff3fba2ed83f9decf959e14d8",
"exitCode": 0,
"inputCount": 707,
"testRows": [],
"testTotals": [
0,
0,
0,
0,
0
]
},
"core-2": {
"kind": "cargo",
"inputs": "inputs/bbe8290d9cb11a7ee5dba250350451c5b7ec88069aa65f6be786a67922408c48.json",
"archive": "inputs/86ac50e9cec8b39db91bbc98e5388e8c0f8466e6c217cf2f4eca5f1db087dde1.zip",
"command": [
"cargo",
"test",
"--locked",
"--offline",
"-p",
"oa-core",
"--no-fail-fast",
"--message-format=json",
"--",
"--nocapture"
],
"head": "cb941367d7748f7bfe7dda53641d395ad60a499f",
"exitCode": 0,
"inputCount": 707,
"testRows": [
[
102,
0,
0,
0,
0
],
[
3,
0,
0,
0,
0
],
[
9,
0,
0,
0,
0
],
[
6,
0,
0,
0,
0
],
[
10,
0,
0,
0,
0
],
[
9,
0,
0,
0,
0
],
[
6,
0,
0,
0,
0
],
[
11,
0,
0,
0,
0
],
[
6,
0,
0,
0,
0
],
[
16,
0,
0,
0,
0
],
[
13,
0,
0,
0,
0
],
[
8,
0,
0,
0,
0
],
[
3,
0,
0,
0,
0
],
[
7,
0,
0,
0,
0
],
[
1,
0,
0,
0,
0
],
[
8,
0,
0,
0,
0
],
[
5,
0,
0,
0,
0
],
[
35,
0,
0,
0,
0
],
[
7,
0,
0,
0,
0
],
[
15,
0,
1,
0,
0
],
[
7,
0,
0,
0,
0
],
[
5,
0,
0,
0,
0
],
[
4,
0,
0,
0,
0
],
[
7,
0,
0,
0,
0
],
[
4,
0,
0,
0,
0
],
[
4,
0,
0,
0,
0
],
[
13,
0,
0,
0,
0
],
[
13,
0,
0,
0,
0
],
[
14,
0,
0,
0,
0
],
[
11,
0,
0,
0,
0
],
[
6,
0,
0,
0,
0
],
[
15,
0,
0,
0,
0
],
[
5,
0,
0,
0,
0
],
[
18,
0,
0,
0,
0
],
[
2,
0,
0,
0,
0
],
[
2,
0,
0,
0,
0
],
[
4,
0,
0,
0,
0
],
[
8,
0,
0,
0,
0
],
[
16,
0,
0,
0,
0
],
[
10,
0,
0,
0,
0
],
[
3,
0,
0,
0,
0
],
[
12,
0,
0,
0,
0
],
[
33,
0,
0,
0,
0
],
[
13,
0,
0,
0,
0
],
[
28,
0,
0,
0,
0
],
[
0,
0,
0,
0,
0
]
],
"testTotals": [
537,
0,
1,
0,
0
]
},
"desktop-1": {
"kind": "cargo",
"inputs": "inputs/bbe8290d9cb11a7ee5dba250350451c5b7ec88069aa65f6be786a67922408c48.json",
"archive": "inputs/86ac50e9cec8b39db91bbc98e5388e8c0f8466e6c217cf2f4eca5f1db087dde1.zip",
"command": [
"cargo",
"test",
"--locked",
"--offline",
"-p",
"oa-desktop",
"--bin",
"arcadia-studio",
"--no-fail-fast",
"--message-format=json",
"--",
"--nocapture"
],
"head": "cb941367d7748f7bfe7dda53641d395ad60a499f",
"exitCode": 0,
"inputCount": 707,
"testRows": [
[
1179,
0,
6,
0,
0
]
],
"testTotals": [
1179,
0,
6,
0,
0
]
},
"clippy-1": {
"kind": "cargo",
"inputs": "inputs/bbe8290d9cb11a7ee5dba250350451c5b7ec88069aa65f6be786a67922408c48.json",
"archive": "inputs/86ac50e9cec8b39db91bbc98e5388e8c0f8466e6c217cf2f4eca5f1db087dde1.zip",
"command": [
"cargo",
"clippy",
"--locked",
"--offline",
"--workspace",
"--all-targets",
"--message-format=json",
"--",
"-D",
"warnings"
],
"head": "cb941367d7748f7bfe7dda53641d395ad60a499f",
"exitCode": 101,
"inputCount": 707,
"testRows": [],
"testTotals": [
0,
0,
0,
0,
0
]
},
"clippy-2": {
"kind": "cargo",
"inputs": "inputs/6b2c2925c732b79150b1d3c2f9dc215f9221b89796bb1d8668d3ecc138095f82.json",
"archive": "inputs/c5080fa78cb5cac2c6bdbf53f00d9d19313fa08de351153b9c5babd3a55b2f7d.zip",
"command": [
"cargo",
"clippy",
"--locked",
"--offline",
"--workspace",
"--all-targets",
"--message-format=json",
"--",
"-D",
"warnings"
],
"head": "cb941367d7748f7bfe7dda53641d395ad60a499f",
"exitCode": 101,
"inputCount": 707,
"testRows": [],
"testTotals": [
0,
0,
0,
0,
0
]
},
"clippy-3": {
"kind": "cargo",
"inputs": "inputs/a36e2cbc29eb7a9217774464cda9da8aa1d3c39caae9b9aacd6cdb28df5bad95.json",
"archive": "inputs/9346fba1be9bf4dd2225fe9965fd76a30429f086fd6a101f92d0963d3822f71c.zip",
"command": [
"cargo",
"clippy",
"--locked",
"--offline",
"--workspace",
"--all-targets",
"--message-format=json",
"--",
"-D",
"warnings"
],
"head": "cb941367d7748f7bfe7dda53641d395ad60a499f",
"exitCode": 0,
"inputCount": 707,
"testRows": [],
"testTotals": [
0,
0,
0,
0,
0
]
},
"desktop-affected-2": {
"kind": "cargo",
"inputs": "inputs/a36e2cbc29eb7a9217774464cda9da8aa1d3c39caae9b9aacd6cdb28df5bad95.json",
"archive": "inputs/9346fba1be9bf4dd2225fe9965fd76a30429f086fd6a101f92d0963d3822f71c.zip",
"command": [
"cargo",
"test",
"--locked",
"--offline",
"-p",
"oa-desktop",
"--bin",
"arcadia-studio",
"--message-format=json",
"--",
"source_symbols::tests",
"text_workspace::",
"views::tests",
"palette_union",
"--nocapture"
],
"head": "f714a25bbcd435941404104ad0cbb0fea3b89149",
"exitCode": 0,
"inputCount": 707,
"testRows": [
[
83,
0,
1,
0,
1101
]
],
"testTotals": [
83,
0,
1,
0,
1101
]
},
"windows-build-1": {
"kind": "cargo",
"inputs": "inputs/a36e2cbc29eb7a9217774464cda9da8aa1d3c39caae9b9aacd6cdb28df5bad95.json",
"archive": "inputs/9346fba1be9bf4dd2225fe9965fd76a30429f086fd6a101f92d0963d3822f71c.zip",
"command": [
"cargo",
"build",
"--locked",
"--offline",
"--workspace",
"--bins",
"--message-format=json"
],
"head": "f714a25bbcd435941404104ad0cbb0fea3b89149",
"exitCode": 0,
"inputCount": 707,
"testRows": [],
"testTotals": [
0,
0,
0,
0,
0
]
}
},
"members": {
"helpers/desktop-admission.json": {
"bytes": 289720,
"sha256": "af2d601032f1f98217d841d181255dda5e123f48078f8d09815b498ee8975eb9"
},
"helpers/final-input-commit-readback.json": {
"bytes": 260,
"sha256": "9670f7400e358c5033b09ae4388cf577fb3db3f288a51d7ffe7b47c87bc377c0"
},
"helpers/gate-union.py": {
"bytes": 3495,
"sha256": "9cca2646b1bcf09961a9ad1a276d633d8e4296ff2c6f2f356f028afaf789f503"
},
"helpers/required-desktop-tests.json": {
"bytes": 125261,
"sha256": "6672e3f7ccfb92b7710310836af6933a8e58ef113353505ec4fc0c3d6908c136"
},
"inputs/6b2c2925c732b79150b1d3c2f9dc215f9221b89796bb1d8668d3ecc138095f82.json": {
"bytes": 118403,
"sha256": "6b2c2925c732b79150b1d3c2f9dc215f9221b89796bb1d8668d3ecc138095f82"
},
"inputs/86ac50e9cec8b39db91bbc98e5388e8c0f8466e6c217cf2f4eca5f1db087dde1.zip": {
"bytes": 2831327,
"sha256": "86ac50e9cec8b39db91bbc98e5388e8c0f8466e6c217cf2f4eca5f1db087dde1"
},
"inputs/9346fba1be9bf4dd2225fe9965fd76a30429f086fd6a101f92d0963d3822f71c.zip": {
"bytes": 2831326,
"sha256": "9346fba1be9bf4dd2225fe9965fd76a30429f086fd6a101f92d0963d3822f71c"
},
"inputs/a36e2cbc29eb7a9217774464cda9da8aa1d3c39caae9b9aacd6cdb28df5bad95.json": {
"bytes": 118403,
"sha256": "a36e2cbc29eb7a9217774464cda9da8aa1d3c39caae9b9aacd6cdb28df5bad95"
},
"inputs/bbe8290d9cb11a7ee5dba250350451c5b7ec88069aa65f6be786a67922408c48.json": {
"bytes": 118403,
"sha256": "bbe8290d9cb11a7ee5dba250350451c5b7ec88069aa65f6be786a67922408c48"
},
"inputs/c5080fa78cb5cac2c6bdbf53f00d9d19313fa08de351153b9c5babd3a55b2f7d.zip": {
"bytes": 2831320,
"sha256": "c5080fa78cb5cac2c6bdbf53f00d9d19313fa08de351153b9c5babd3a55b2f7d"
},
"runs/clippy-1/process.json": {
"bytes": 199,
"sha256": "1af72ef6a92af65144a1faea8cae4f5c868799894bf539385b8f40912b0b50ae"
},
"runs/clippy-1/result.json": {
"bytes": 1799,
"sha256": "355929311a614bfd682e9dc47fa1afef98e14b22f76c856771564b9eb282af57"
},
"runs/clippy-1/started.json": {
"bytes": 1400,
"sha256": "4671c110e5e26a34e48470b0002322fc450b3844ee849f90ae6523091528ece1"
},
"runs/clippy-1/stderr.log": {
"bytes": 1368,
"sha256": "741e0cd2a2418cdb748703cf02cc72064e5b88b123a6fdacb5c6f87a2a6959c6"
},
"runs/clippy-1/stdout.log": {
"bytes": 272957,
"sha256": "37c21ea32cba74fbb54bc9e9f962fc7dfd5c018ac7f3750c2ae905e7cb51a542"
},
"runs/clippy-2/process.json": {
"bytes": 199,
"sha256": "6d11bf41b9461e19f6a06b64841ef1fa30c5129de4a4dad215866fc14c8f29d1"
},
"runs/clippy-2/result.json": {
"bytes": 1797,
"sha256": "0583fa653d2b2be928250b1845d2eec36886a5ea1dc12013ea853b68a79fe4be"
},
"runs/clippy-2/started.json": {
"bytes": 1400,
"sha256": "9d290135f4125ac119601b2704f1ac9b03ec5800a80624155ead21a35e7a4ec6"
},
"runs/clippy-2/stderr.log": {
"bytes": 155,
"sha256": "93a6965e0904eb30abe0657f4a9c30b5ba9e148c9d5274b3c3fb5e78d898c562"
},
"runs/clippy-2/stdout.log": {
"bytes": 270250,
"sha256": "bdc2338aab9fc18fb4e29fbb9e9192ccf9b67e1e9fb2cca828ec50acc90c1437"
},
"runs/clippy-3/process.json": {
"bytes": 199,
"sha256": "0d3a92e71ae9b33b703479a54fd1ca2491b67c1374b1a1e259816eebea9bb3b0"
},
"runs/clippy-3/result.json": {
"bytes": 1796,
"sha256": "cf49e16fa5cbecacf24edbf054036b1190a02771c61f1178c12c6f3d7d9477b0"
},
"runs/clippy-3/started.json": {
"bytes": 1400,
"sha256": "5499895565380e2efb1ccf6a9acc825318dfe09135815adf9512bf50530524a5"
},
"runs/clippy-3/stderr.log": {
"bytes": 325,
"sha256": "d2811192fb769c1a0f2e4258dd31c76f6a475e43bc6214e49db7c0757e7dad00"
},
"runs/clippy-3/stdout.log": {
"bytes": 307989,
"sha256": "319cad11af00c742a1436734b5daee273a112859c7775b06fada1948d54e93d6"
},
"runs/core-2/process.json": {
"bytes": 197,
"sha256": "50d3c62701417b7cf49de72139a71d5b26ede0ebec36eac6ecc254cf45f1594a"
},
"runs/core-2/result.json": {
"bytes": 67027,
"sha256": "2eb69042c0c6eecb89b11e739b9598005b8a26479564d299275817310011b5bb"
},
"runs/core-2/started.json": {
"bytes": 1398,
"sha256": "eadc9d64a5fedc1bea614c2ccae4d19616e2f7b0538bc5e08e84e9ee92f532e0"
},
"runs/core-2/stderr.log": {
"bytes": 8930,
"sha256": "0d5a5d2a4e64bd38c89fffafd8089de7b74a52f1dbbccb8cc59dae640bebba3b"
},
"runs/core-2/stdout.log": {
"bytes": 131042,
"sha256": "4185a25e52e52f3c6ee1dcd942a45e1a3ef57b3961ec7e7c4ca0aacc86967cfc"
},
"runs/desktop-1/process.json": {
"bytes": 227,
"sha256": "38ff4960fbcdf8282c8fc323a337b25f19b1ab424d7ed590917e6765f8a643c1"
},
"runs/desktop-1/result.json": {
"bytes": 3154,
"sha256": "f254ec40b83763f1398ab96fff16d96c9a0ff8a3113c2d16556c973eb5eaf93e"
},
"runs/desktop-1/started.json": {
"bytes": 1438,
"sha256": "ba43fbd949da1d6e453217c2f534d661b81dbe4b42fc1fe198c811234dc48aaf"
},
"runs/desktop-1/stderr.log": {
"bytes": 552,
"sha256": "c975782e7df7ce13b9f298d164fd4cc8e8161b6da7fd77c4607920fd8b3bf900"
},
"runs/desktop-1/stdout.log": {
"bytes": 430091,
"sha256": "1bfcc88362ef725cb3e4b9af7967d4bd18e65db3eccd342fe3264ea6ef95ec19"
},
"runs/desktop-affected-2/process.json": {
"bytes": 287,
"sha256": "b72fb9ac1c7400ef7af2ed80f26a1e13fc718342779c868e4cbad4a5152b131f"
},
"runs/desktop-affected-2/result.json": {
"bytes": 3228,
"sha256": "cfb3f9563f35977efe26d69d6a12040278f725bf09fb30ec812a984cab72dc02"
},
"runs/desktop-affected-2/started.json": {
"bytes": 1513,
"sha256": "cca30f43f1d79c10a996422f9a506a08fc91eba7fc15a02615105f7fed89a20a"
},
"runs/desktop-affected-2/stderr.log": {
"bytes": 281,
"sha256": "90504f37b7256fd61af78a6c636560b3646ec9bdb2bc954c6e5bc77f69f21306"
},
"runs/desktop-affected-2/stdout.log": {
"bytes": 304036,
"sha256": "adf50044a9f6947e7384dbd0193fe069276c2e78da1d6c45a55150d2b1defa26"
},
"runs/desktop-list-1/process.json": {
"bytes": 204,
"sha256": "139e8f5d11f45dd2b6d2151e0b0628d892f3ad228134032ab8c9bfb1989059db"
},
"runs/desktop-list-1/result.json": {
"bytes": 3128,
"sha256": "24eacbb644a6b82be7439e4f8895562356eb63485e82c30292b2951cb090d29d"
},
"runs/desktop-list-1/started.json": {
"bytes": 1410,
"sha256": "33adc56c5fad5bd53dd425f5c6ccf8faf550bd382867b848d04ebaafcb3d5c66"
},
"runs/desktop-list-1/stderr.log": {
"bytes": 1427,
"sha256": "97885faf304979ca43898f88d3ca7118100fd1c2358f9d4729984f7369788704"
},
"runs/desktop-list-1/stdout.log": {
"bytes": 421428,
"sha256": "32f0025d9205288e92c4031dc1b53298b8e79f1ccb8ce990a40d7c3d66f4305f"
},
"runs/windows-build-1/process.json": {
"bytes": 167,
"sha256": "b400c1f8076bcddb583424fe34d155655a41d25b5ccf0db8af26bda6b9b2685e"
},
"runs/windows-build-1/result.json": {
"bytes": 5532,
"sha256": "5e9e227c66a4b71ade734b8ebeb12070b8a5c64a9a6cf9d8d0b6d70379d4ffc2"
},
"runs/windows-build-1/started.json": {
"bytes": 1353,
"sha256": "15c54be09a881e6860735395569b22a7a9114789a1f8276cf79bc6fb095c8163"
},
"runs/windows-build-1/stderr.log": {
"bytes": 261,
"sha256": "480dff999944c756fff581db0f3fac088717e0d73cae20af9a28829446df746f"
},
"runs/windows-build-1/stdout.log": {
"bytes": 295092,
"sha256": "5a824ca91877328acd332585b39194b27fd2cb167279b99c4c78a89bf13bd19b"
}
},
"fullSuiteToFinalInputChanges": {
"crates/oa-desktop/src/app/source_symbols.rs": {
"before": {
"bytes": 26476,
"sha256": "230b2d19b0aeb767a311bdc0eddfd69986ebc5f65b8892382cd033a69f86cb2d"
},
"after": {
"bytes": 26369,
"sha256": "9dbcb70a7eaa0b2f7ec011da1fcc808ba1e53ec1c29a3c9a09cf7517e058c7b7"
}
},
"crates/oa-desktop/src/app/source_symbols/tests.rs": {
"before": {
"bytes": 85389,
"sha256": "0e3ae6861ded52e7d137736f4ffe6bf55a00291bf685d3bbe6141174210eee9c"
},
"after": {
"bytes": 85506,
"sha256": "aebd05f3fa9f1e9c2e744948d4478d9f267365d063f01f8b8f4e67560bc23f32"
}
},
"crates/oa-desktop/src/source_navigation/controller.rs": {
"before": {
"bytes": 3691,
"sha256": "2042d608a4e9698c614c0d308d02896983480087908ecc353369b424eca3d124"
},
"after": {
"bytes": 3680,
"sha256": "938b8264c2d5166bd979439723d6fbec34c4cc5ed7f872f58c2cb9ba06a4a93d"
}
},
"crates/oa-desktop/src/views.rs": {
"before": {
"bytes": 58364,
"sha256": "46bb9410567d47e3043723fc3b8c7342c04e85b1b4a4c06a84e8497757cc04dc"
},
"after": {
"bytes": 58364,
"sha256": "e74da124914a54a3b757eb4728a5093157f172cd7f5e90b7744e526e3ec3f8d2"
}
}
}
}
@@ -0,0 +1,169 @@
"""Read-only, Python-standard-library verification. Never executes archived code."""
from pathlib import Path, PurePosixPath
from datetime import datetime
import hashlib
import io
import json
import re
import zipfile
ROOT = Path(__file__).resolve().parent
def require(ok, message):
if not ok:
raise ValueError(message)
def digest(data):
return {'bytes': len(data), 'sha256': hashlib.sha256(data).hexdigest()}
def members(z, expected):
names = z.namelist()
require(len(names) == len(set(names)), 'duplicate ZIP members')
require(set(names) == set(expected), 'archive membership mismatch')
require(all(not PurePosixPath(n).is_absolute() and '..' not in PurePosixPath(n).parts
and '\\' not in n and ':' not in n for n in names), 'unsafe member')
def main():
inventory = json.loads((ROOT / 'files.json').read_text())
for name, expected in inventory.items():
require(digest((ROOT / name).read_bytes()) == expected, 'external file hash: ' + name)
manifest = json.loads((ROOT / 'manifest.json').read_text())
with zipfile.ZipFile(ROOT / 'evidence.zip') as z:
members(z, manifest['members'])
for name, expected in manifest['members'].items():
require(digest(z.read(name)) == expected, 'member hash: ' + name)
def raw(name): return z.read(name)
def obj(name): return json.loads(raw(name))
checked = set()
for run, spec in manifest['runs'].items():
if spec['kind'] != 'cargo':
continue
prefix = 'runs/' + run + '/'
start, result, process = (obj(prefix + n + '.json') for n in ('started', 'result', 'process'))
require(all(result[k] == v for k, v in start.items()), run + ': start/result relationship')
require(result['command'] == spec['command'] == process['command'], run + ': command')
require(result['head'] == spec['head'], run + ': HEAD')
require(result['exitCode'] == spec['exitCode'] and result['inputsUnchanged'] is True, run + ': outcome')
require(datetime.fromisoformat(result['finish']) >= datetime.fromisoformat(start['start']), run + ': times')
require(result['seconds'] >= 0, run + ': duration')
require(result['helper'] == digest(raw('helpers/gate-union.py')), run + ': helper')
for field, file in [('inputs', spec['inputs']), ('archive', spec['archive']), ('stdout', prefix+'stdout.log'), ('stderr', prefix+'stderr.log')]:
require(result[field] == digest(raw(file)), run + ': receipt ' + field)
inputs = obj(spec['inputs'])
require(len(inputs) == result['inputCount'] == spec['inputCount'] == 707, run + ': input count')
require(sum(p.startswith('crates/vendor/iced_widget/') for p in inputs) == 71, run + ': vendor count')
pair = (spec['inputs'], spec['archive'])
if pair not in checked:
with zipfile.ZipFile(io.BytesIO(raw(spec['archive']))) as source:
members(source, inputs)
for name, expected in inputs.items():
require(digest(source.read(name)) == expected, run + ': source hash ' + name)
checked.add(pair)
stdout = raw(prefix+'stdout.log').decode('utf-8', errors='replace')
rows = [[int(x) for x in row] for row in re.findall(r'test result: (?:ok|FAILED)\. (\d+) passed; (\d+) failed; (\d+) ignored; (\d+) measured; (\d+) filtered out;', stdout)]
require(rows == spec['testRows'], run + ': exact test summaries')
require([sum(row[i] for row in rows) for i in range(5)] == spec['testTotals'], run + ': test totals')
artifacts = []
for line in stdout.splitlines():
try: item = json.loads(line)
except json.JSONDecodeError: continue
if isinstance(item, dict) and item.get('reason') == 'compiler-artifact' and item.get('executable'):
artifacts.append(item)
require(artifacts == [a['cargo'] for a in result['artifacts']], run + ': artifact receipt relationship')
print(run, 'exit', result['exitCode'], 'test totals', spec['testTotals'])
admission(z, manifest)
print('PASS:', len(manifest['runs']), 'closed captures;', len(manifest['members']), 'members;', len(checked), 'input manifest/archive pairs')
print('Integrity and recorded outcomes only; no rerun, binary verification, or native GUI claim.')
def admission(z, manifest):
def obj(p): return json.loads(z.read(p))
report = obj('helpers/desktop-admission.json')
checklist = obj('helpers/required-desktop-tests.json')
require(report['status'] == 'PASS', 'admission status')
require({k:report['checklist'][k] for k in ('bytes','sha256')} == digest(z.read('helpers/required-desktop-tests.json')), 'checklist receipt')
require(report['checklist']['sourceCommit'] == checklist['sourceCommit'], 'checklist source pin')
for run, receipt in report['runs'].items():
result = obj('runs/'+run+'/result.json')
for field in ('head','command','exitCode','inputsUnchanged','inputCount'):
require(receipt[field] == result[field], 'admission run relationship '+field)
require(receipt['inputManifest'] == result['inputs'] and receipt['stdout'] == result['stdout'], 'admission inputs/log')
require(receipt['resultReceipt'] == digest(z.read('runs/'+run+'/result.json')), 'admission result hash')
listing = z.read('runs/desktop-list-1/stdout.log').decode('utf-8')
execution = z.read('runs/desktop-1/stdout.log').decode('utf-8')
names = re.findall(r'^([A-Za-z0-9_:]+): test$', listing, re.M)
outcomes = re.findall(r'^test ([A-Za-z0-9_:]+) \.\.\. (ok|FAILED|ignored)(?:, (.*))?$', execution, re.M)
require(len(names) == len(set(names)) == 1185, 'listing names/count/uniqueness')
require(len(outcomes) == len({r[0] for r in outcomes}) == 1185, 'execution names/count/uniqueness')
require(set(names) == {r[0] for r in outcomes}, 'listing/execution set equality')
actual = {n:(status, reason or None) for n,status,reason in outcomes}
require(actual == {r['name']:(r['status'],r['reason']) for r in report['allActualNamesAndOutcomes']}, 'admission raw outcomes')
require(len(report['allActualNamesAndOutcomes']) == len(actual), 'admission duplicate outcomes')
required = []
for spec in checklist['required']:
matches = [n for n in names if n.endswith(spec['match']['suffix'])]
require(len(matches) == spec['match']['expectedOccurrences'] == 1, 'required unique match '+spec['name'])
n = matches[0]
require(actual[n][0] == ('ignored' if spec['expectedIgnored'] else 'ok'), 'required outcome '+n)
required.append({'sourcePath':spec['sourcePath'],'expectedBasename':spec['name'],'actualName':n,'expectedIgnored':spec['expectedIgnored'],'status':actual[n][0],'reason':actual[n][1]})
require(required == report['requiredOutcomes'], 'required admission relationships')
removed=[]
for spec in checklist['removedOrReplaced']:
require(not any(n.endswith('::'+spec['name']) for n in names), 'removed test present')
replacements=[n for replacement in spec.get('replacementNames',[]) for n in names if n.endswith('::'+replacement)]
removed.append({'name':spec['name'],'absent':True,'replacementActualNames':replacements})
require(removed == report['removedOrReplaced'], 'removed admission relationship')
retained=[]
for spec in checklist['duplicateDeparture']:
require(actual[spec['requiredFullName']][0] == 'ok' and spec['removedFullName'] not in names, 'retained duplicate admission')
require(sum(n.endswith('::'+spec['name']) for n in names) == spec['expectedTotalOccurrences'] == 1, 'retained duplicate count')
retained.append({'name':spec['name'],'retainedName':spec['requiredFullName'],'retainedStatus':'ok','removedParentNameAbsent':True})
require(retained == report['retainedDepartureCases'], 'retained admission relationship')
before = obj(manifest['runs']['desktop-1']['inputs'])
require(before == obj(manifest['runs']['desktop-list-1']['inputs']), 'listing/execution source equality')
require(before == obj(manifest['runs']['core-2']['inputs']), 'core/desktop full-suite source equality')
with zipfile.ZipFile(io.BytesIO(z.read(manifest['runs']['desktop-1']['archive']))) as source:
bindings=[]
for spec in checklist['sourceBindings']:
b=source.read(spec['path'])
blob=hashlib.sha1(b'blob '+str(len(b)).encode()+b'\0'+b).hexdigest()
require(blob == spec['gitBlob'], 'original checklist source blob '+spec['path'])
bindings.append({**spec,'capturedAndCurrentDigest':digest(b),'matchesOriginalChecklistBlob':True})
require(bindings == report['sourceBindings'], 'source binding admission')
summary={'listed':len(names),'executed':len(actual),'exactNameSetEquality':True,'duplicateListedNames':0,'duplicateExecutedNames':0,
'passed':sum(v[0]=='ok' for v in actual.values()),'failed':sum(v[0]=='FAILED' for v in actual.values()),'ignored':sum(v[0]=='ignored' for v in actual.values()),
'required':len(required),'requiredPassed':sum(r['status']=='ok' for r in required),'requiredIgnored':sum(r['status']=='ignored' for r in required),
'removedNamesAbsent':len(checklist['removedOrReplaced']),'retainedDuplicateCasesPassExactlyOnce':len(checklist['duplicateDeparture']),
'originalChecklistSourceFilesVerified':len(checklist['sourceBindings']),'listingAndExecutionInputManifestsEqual':True}
require(summary == report['summary'], 'admission summary')
after = obj(manifest['runs']['clippy-3']['inputs'])
changed={p:{'before':before.get(p),'after':after.get(p)} for p in sorted(set(before)|set(after)) if before.get(p)!=after.get(p)}
require(changed == manifest['fullSuiteToFinalInputChanges'] and len(changed)==4, 'four-file source delta')
require(set(changed) == {'crates/oa-desktop/src/source_navigation/controller.rs','crates/oa-desktop/src/app/source_symbols.rs','crates/oa-desktop/src/app/source_symbols/tests.rs','crates/oa-desktop/src/views.rs'}, 'reviewed four-file scope')
readback=obj('helpers/final-input-commit-readback.json')
require(readback['status']=='PASS' and readback['commit']==manifest['reviewedFixCommit'], 'secondary readback summary')
require(readback['inputCount']==len(after) and readback['inputManifestSha256']==digest(z.read(manifest['runs']['clippy-3']['inputs']))['sha256'], 'secondary readback input relationship')
for run in ('desktop-affected-2','windows-build-1'):
if run in manifest['runs']:
require(obj(manifest['runs'][run]['inputs']) == after, run+': final source equality')
if 'windows-build-1' in manifest['runs']:
result=obj('runs/windows-build-1/result.json')
require(result['exitCode']==0 and len(result['artifacts'])==3, 'final build outcome/artifacts')
require({a['cargo']['target']['name'] for a in result['artifacts']}=={'arcadia','arcadia-server','arcadia-studio'}, 'final build target membership')
if 'desktop-affected-2' in manifest['runs']:
spec=manifest['runs']['desktop-affected-2']
args=spec['command'][spec['command'].index('--')+1:]
filters=[arg for arg in args if not arg.startswith('--')]
require(filters==['source_symbols::tests','text_workspace::','views::tests','palette_union'], 'affected OR filters')
selected={n for n in names if any(f in n for f in filters)}
log=z.read('runs/desktop-affected-2/stdout.log').decode('utf-8')
affected=re.findall(r'^test ([A-Za-z0-9_:]+) \.\.\. (ok|FAILED|ignored)(?:, (.*))?$',log,re.M)
require(len(affected)==len({r[0] for r in affected})==len(selected)==84, 'affected count/unique names')
require({n for n,s,r in affected}==selected, 'affected exact OR-filter selection from prior listing')
require(sum(s=='ok' for n,s,r in affected)==83 and sum(s=='FAILED' for n,s,r in affected)==0, 'affected enabled passes')
require([n for n,s,r in affected if s=='ignored']==['app::source_symbols::tests::source_symbols_100_modules_1000_definitions_10000_uses'], 'affected known ignored probe')
require(spec['testTotals']==[83,0,1,0,len(names)-len(selected)], 'affected summary and filtered count')
print('Affected recheck: exact prior-list OR selection; 83 passed, 1 known ignored, 1101 filtered')
print('Desktop admission:', summary['passed'], 'passed,', summary['ignored'], 'ignored;',len(required),'required; four final changed input files')
if __name__ == '__main__':
main()
@@ -0,0 +1,52 @@
# Ordinary-text union: closed-run checkpoint
Run `python verify.py` with Python 3.10 or newer in this directory. The verifier
uses only the standard library and these five files. It reads archives in memory,
does not extract or execute source/binaries, and does not consult Git, Cargo,
network services, or paths recorded from the original machine.
`files.json` hashes the ZIP, manifest, verifier and this README. `manifest.json`
lists every ZIP member and maps each run to its exact input manifest and ZIP.
Only byte-identical input files are deduplicated; ZIPs with different timestamps
remain separate even when their source inventories match. Receipts and raw logs
are retained verbatim, including failures and original local path strings.
The production composition is d7c065991d3f63cc41341d23a546739de8837ac3.
The checkpoint is e624d5b9c4f848eff3fba2ed83f9decf959e14d8, including generated
teaching fixtures and normative language documentation. Individual receipt HEADs
and input inventories are authoritative for each run; a HEAD alone does not
describe dirty fixture/document changes during capture.
| Closed capture | Recorded outcome |
| --- | --- |
| core-consumers-1 | Offline dependency resolution failed: num-bigint missing |
| windows-dependencies-1 | Dependency preparation completed |
| core-1 | 536 passed, 1 failed, 1 ignored; stale generated teaching fixture failure preserved |
| teaching-generate-1 | Teaching generator completed; raw generated-release1 outputs included |
| teaching-check-2 | 4 passed after fixture regeneration |
| language-doc-1 | Warning log from failed strict render preserved |
| language-doc-2 | Strict docutils 0.23 render succeeded; empty warning log |
| consumers-1 | 63 passed |
The verifier checks exact archive membership, hashes and lengths, nested input
archive membership/content, input counts (including 71 vendored iced_widget
files), start/result/process command relationships, unchanged-input assertions,
helper hashes, exit codes, exact test summary rows and aggregate counts,
compiler-artifact receipt relationships, composition patch hash, generated output
inventory, and successful document source/output/warning receipts.
Limitations: integrity and internal consistency are not independent reproduction
or authenticated provenance. Test expectations in the manifest are frozen from
the raw capture. No runtime binaries, dependency cache, credentials or user
profile are packaged. Recorded artifact readback hashes cannot be independently
checked without the excluded binaries. Input archives contain project sources,
not a hermetic toolchain or complete dependency distribution. The gate helper is
receipt-pinned; the included render helper is its current successful-render
version, with no historical helper hash receipt. language-doc-1 contains only
warnings.log: its original helper, exact failed input and process/exit receipt
were not captured. The successful render inputs and outputs are hash-pinned by
its result receipt. Generated outputs are archived as observed, without a
separate generation-output hash receipt. This package does not claim a full
core rerun after regeneration, complete desktop/GUI validation, lint success,
release packaging, or publication. Active desktop-list-1 and any later runs are
excluded from this initial checkpoint.
@@ -0,0 +1,18 @@
{
"evidence.zip": {
"bytes": 8584848,
"sha256": "f220433f8f6fe99cea762aca98ee471c14411b5f6762745cf91432e6bfaca440"
},
"manifest.json": {
"bytes": 24274,
"sha256": "ad731f45fda8eb8678160315de00a4c77037d49ee40f0774496e7a2a970342aa"
},
"README.md": {
"bytes": 3328,
"sha256": "98c900e5c83e13da6c6edc11521df8edb87f4e1ebebb004fd1d6cc8acc91683b"
},
"verify.py": {
"bytes": 5526,
"sha256": "65136bbf0074c63a1478a56e0a66523d785081c368a50cd698d1231f6b0618ba"
}
}
@@ -0,0 +1,901 @@
{
"schema": 1,
"productionCommit": "d7c065991d3f63cc41341d23a546739de8837ac3",
"checkpointCommit": "e624d5b9c4f848eff3fba2ed83f9decf959e14d8",
"runs": {
"core-consumers-1": {
"kind": "cargo",
"inputs": "inputs/e3d2d504b18bb1f7c4c3fa1f26abff425b957f18b418d8556888151723ba67b4.json",
"archive": "inputs/12ce1ca9c1185d7a91a1dea322cdc0c7fc4372db5d879386595acdbfd412d880.zip",
"command": [
"cargo",
"test",
"--locked",
"--offline",
"-p",
"oa-core",
"-p",
"oa-server",
"--test",
"http",
"-p",
"oa-cli",
"--test",
"source_cli",
"--no-fail-fast",
"--message-format=json",
"--",
"--nocapture"
],
"head": "d7c065991d3f63cc41341d23a546739de8837ac3",
"exitCode": 101,
"inputCount": 707,
"testRows": [],
"testTotals": [
0,
0,
0,
0,
0
]
},
"windows-dependencies-1": {
"kind": "cargo",
"inputs": "inputs/e3d2d504b18bb1f7c4c3fa1f26abff425b957f18b418d8556888151723ba67b4.json",
"archive": "inputs/12ce1ca9c1185d7a91a1dea322cdc0c7fc4372db5d879386595acdbfd412d880.zip",
"command": [
"cargo",
"fetch",
"--locked",
"--target",
"x86_64-pc-windows-gnullvm"
],
"head": "d7c065991d3f63cc41341d23a546739de8837ac3",
"exitCode": 0,
"inputCount": 707,
"testRows": [],
"testTotals": [
0,
0,
0,
0,
0
]
},
"core-1": {
"kind": "cargo",
"inputs": "inputs/e3d2d504b18bb1f7c4c3fa1f26abff425b957f18b418d8556888151723ba67b4.json",
"archive": "inputs/12ce1ca9c1185d7a91a1dea322cdc0c7fc4372db5d879386595acdbfd412d880.zip",
"command": [
"cargo",
"test",
"--locked",
"--offline",
"-p",
"oa-core",
"--no-fail-fast",
"--message-format=json",
"--",
"--nocapture"
],
"head": "d7c065991d3f63cc41341d23a546739de8837ac3",
"exitCode": 101,
"inputCount": 707,
"testRows": [
[
102,
0,
0,
0,
0
],
[
3,
0,
0,
0,
0
],
[
9,
0,
0,
0,
0
],
[
6,
0,
0,
0,
0
],
[
10,
0,
0,
0,
0
],
[
9,
0,
0,
0,
0
],
[
6,
0,
0,
0,
0
],
[
11,
0,
0,
0,
0
],
[
6,
0,
0,
0,
0
],
[
16,
0,
0,
0,
0
],
[
13,
0,
0,
0,
0
],
[
8,
0,
0,
0,
0
],
[
3,
0,
0,
0,
0
],
[
7,
0,
0,
0,
0
],
[
1,
0,
0,
0,
0
],
[
8,
0,
0,
0,
0
],
[
5,
0,
0,
0,
0
],
[
35,
0,
0,
0,
0
],
[
7,
0,
0,
0,
0
],
[
15,
0,
1,
0,
0
],
[
7,
0,
0,
0,
0
],
[
5,
0,
0,
0,
0
],
[
3,
1,
0,
0,
0
],
[
7,
0,
0,
0,
0
],
[
4,
0,
0,
0,
0
],
[
4,
0,
0,
0,
0
],
[
13,
0,
0,
0,
0
],
[
13,
0,
0,
0,
0
],
[
14,
0,
0,
0,
0
],
[
11,
0,
0,
0,
0
],
[
6,
0,
0,
0,
0
],
[
15,
0,
0,
0,
0
],
[
5,
0,
0,
0,
0
],
[
18,
0,
0,
0,
0
],
[
2,
0,
0,
0,
0
],
[
2,
0,
0,
0,
0
],
[
4,
0,
0,
0,
0
],
[
8,
0,
0,
0,
0
],
[
16,
0,
0,
0,
0
],
[
10,
0,
0,
0,
0
],
[
3,
0,
0,
0,
0
],
[
12,
0,
0,
0,
0
],
[
33,
0,
0,
0,
0
],
[
13,
0,
0,
0,
0
],
[
28,
0,
0,
0,
0
],
[
0,
0,
0,
0,
0
]
],
"testTotals": [
536,
1,
1,
0,
0
]
},
"teaching-generate-1": {
"kind": "cargo",
"inputs": "inputs/e3d2d504b18bb1f7c4c3fa1f26abff425b957f18b418d8556888151723ba67b4.json",
"archive": "inputs/12ce1ca9c1185d7a91a1dea322cdc0c7fc4372db5d879386595acdbfd412d880.zip",
"command": [
"cargo",
"run",
"--locked",
"--offline",
"-p",
"oa-core",
"--example",
"generate_release1",
"--message-format=json",
"--",
".local/ordinary-text-integration/generated-release1"
],
"head": "d7c065991d3f63cc41341d23a546739de8837ac3",
"exitCode": 0,
"inputCount": 707,
"testRows": [],
"testTotals": [
0,
0,
0,
0,
0
]
},
"teaching-check-2": {
"kind": "cargo",
"inputs": "inputs/0fa1da63d71c984cd99b079de661e88154b5a31818b384f557deefd40b8ead3a.json",
"archive": "inputs/81bd006f10034bc0c203187af179548a549fda6e58bc7cde0702cc26994fd28d.zip",
"command": [
"cargo",
"test",
"--locked",
"--offline",
"-p",
"oa-core",
"--test",
"release1_teaching",
"--message-format=json",
"--",
"--nocapture"
],
"head": "d7c065991d3f63cc41341d23a546739de8837ac3",
"exitCode": 0,
"inputCount": 707,
"testRows": [
[
4,
0,
0,
0,
0
]
],
"testTotals": [
4,
0,
0,
0,
0
]
},
"language-doc-1": {
"kind": "render"
},
"language-doc-2": {
"kind": "render"
},
"consumers-1": {
"kind": "cargo",
"inputs": "inputs/bbe8290d9cb11a7ee5dba250350451c5b7ec88069aa65f6be786a67922408c48.json",
"archive": "inputs/86ac50e9cec8b39db91bbc98e5388e8c0f8466e6c217cf2f4eca5f1db087dde1.zip",
"command": [
"cargo",
"test",
"--locked",
"--offline",
"-p",
"oa-server",
"--test",
"http",
"-p",
"oa-cli",
"--test",
"source_cli",
"--no-fail-fast",
"--message-format=json",
"--",
"--nocapture"
],
"head": "d7c065991d3f63cc41341d23a546739de8837ac3",
"exitCode": 0,
"inputCount": 707,
"testRows": [
[
12,
0,
0,
0,
0
],
[
51,
0,
0,
0,
0
]
],
"testTotals": [
63,
0,
0,
0,
0
]
}
},
"members": {
"generated-release1/expected-diagnostics.json": {
"bytes": 288,
"sha256": "3700393d76b940cfc076bf52861bd7b1f43370f8eabce3594531bd0bf1fc851e"
},
"generated-release1/model.json": {
"bytes": 134520,
"sha256": "ef0ef4006c3e09d5db6e1b621256b3f7d6642f3a7b09cc34607cfbcca830c727"
},
"generated-release1/oa/activities/activity_oa_restore_work.arcadia": {
"bytes": 1130,
"sha256": "21cbd7176af8fad68453306f2371ff03e127a994b8796cdf044567332de9686f"
},
"generated-release1/oa/behavior/process_oa_assessment_procedure.arcadia": {
"bytes": 550,
"sha256": "23cabc213c264a16c8595414db6acb09248c1cef9cd699f1b0316e5bb82c062a"
},
"generated-release1/oa/behavior/process_oa_field_procedure.arcadia": {
"bytes": 1178,
"sha256": "9c52086759b7344b4809ceb8d372f65647b16fc24153a03b887faa378c37fe96"
},
"generated-release1/oa/behavior/process_oa_response.arcadia": {
"bytes": 2560,
"sha256": "675798bd19e6b40d03e7076b0e59b110cdaaab7b6d16c47c3ac80fa960072e84"
},
"generated-release1/oa/behavior/scenario_oa_conversation.arcadia": {
"bytes": 1515,
"sha256": "e217e5394a0771dc16206ed9437a231da27905d2f19be64b3f48281c0a32fb6b"
},
"generated-release1/oa/behavior/scenario_oa_full_scenario.arcadia": {
"bytes": 1121,
"sha256": "c5a285f2d7c61bd2a136107f267fbf9cddb1cba3f63ff36a6f53f7ab98f33df5"
},
"generated-release1/oa/behavior/state_machine_oa_monitor.arcadia": {
"bytes": 1358,
"sha256": "e7349e1e5336282eed3f5a103129db33d3ae4ed500788ee7129e8fc193ac57f6"
},
"generated-release1/oa/capabilities.arcadia": {
"bytes": 630,
"sha256": "26a3d9b615a3251ff010e0b47127f2d16bc2dcd45ed95967208b0076d725843a"
},
"generated-release1/oa/model.arcadia": {
"bytes": 531,
"sha256": "069a5eaaf525089935cc9d73ae7b916866ed78c6534705d75739650ecdea7ada"
},
"generated-release1/oa/participants.arcadia": {
"bytes": 1580,
"sha256": "591a75a9ecad280302be4d4ae0171ad616377ed11ead5e233848ca2dc0a71819"
},
"generated-release1/oa/types.arcadia": {
"bytes": 554,
"sha256": "f02c391d584674c879f662a418abba3a220f1fee88471ca49322ac9873de628d"
},
"generated-release1/project.arcadia": {
"bytes": 1958,
"sha256": "b2674e6bf77cfe0f6f0ba79c81d2e96d92f39c02ca2c6266d80e91723bfb9fda"
},
"generated-release1/sa/behavior/process_sa_assessment_procedure.arcadia": {
"bytes": 559,
"sha256": "5c414fbdaabf9f9e402b0441fcacf1cacf4dbbc6c822162f028de35abf59be2d"
},
"generated-release1/sa/behavior/process_sa_field_procedure.arcadia": {
"bytes": 1187,
"sha256": "ece846fdf93857e71a9ab0efcbdf64a5c41c71180f62afa3f79077fffb53b8f2"
},
"generated-release1/sa/behavior/process_sa_response.arcadia": {
"bytes": 2569,
"sha256": "af7fdda280a917cf626f6e077162d481f9aaad280f8e81330c07d7fe384b1c52"
},
"generated-release1/sa/behavior/scenario_sa_conversation.arcadia": {
"bytes": 1847,
"sha256": "01339699ec3ee409418d9bb232542f2bcd9419f1e67b183e77700c56bb9f71d2"
},
"generated-release1/sa/behavior/scenario_sa_full_scenario.arcadia": {
"bytes": 1116,
"sha256": "5dce6b00a3a441162e8dee3e23d9ca5ab7bed4a24de669b1fba03664a3086dda"
},
"generated-release1/sa/behavior/state_machine_sa_monitor.arcadia": {
"bytes": 1358,
"sha256": "17dbcfa5bbfd10fc8e23a325fc950ce853c219b4aa89c74f96278cbb3f34acc8"
},
"generated-release1/sa/capabilities.arcadia": {
"bytes": 630,
"sha256": "81706a0ca9d7db2aa0350888b9c00d853885185db2e6b90557d3d51a11c3d645"
},
"generated-release1/sa/functions/function_sa_audit_thresholds.arcadia": {
"bytes": 280,
"sha256": "27f798f78f36e72b38edab3725af2feb0eaa010fd52a60b5c6b6bef57ad3b686"
},
"generated-release1/sa/functions/function_sa_restore_work.arcadia": {
"bytes": 2251,
"sha256": "357c6acb80f86b4e8d8f5c98f9f79d140f85fbe09f3406b41b7ac6eba464ec8c"
},
"generated-release1/sa/model.arcadia": {
"bytes": 531,
"sha256": "805b90262e27090ae7da81a51bcefa49c97fb0b3d3220b4a72323b4c4351c395"
},
"generated-release1/sa/participants.arcadia": {
"bytes": 3604,
"sha256": "ca144d6a8f45fe720b5d18c7b59c7671444baa8c37e5ce9117bcba34b3a6aa84"
},
"generated-release1/sa/types.arcadia": {
"bytes": 903,
"sha256": "540ddbc2bcd0ccf45298e3ea5660637341aff63ff65fd87ac2f26469d1adc064"
},
"generated-release1/traceability.arcadia": {
"bytes": 28041,
"sha256": "474bd28d955ff7af7722f6e9a23220d816879e3f46202e79a233544b9c593116"
},
"generated-release1/transition-before.json": {
"bytes": 35801,
"sha256": "2253e6434f741687e22327c37d520ed15c043bc36025498650bc2366bb6a874d"
},
"generated-release1/transition-request.json": {
"bytes": 4276,
"sha256": "43b9a22e5f95ad3bc0ebd2478ead0e14406854abc989ae94ded9367b013d812a"
},
"generated-release1/views/diagram_oa_allocation_view.arcadia": {
"bytes": 1786,
"sha256": "c45d7467839219d894804c1ef2d22ff4d4a5a84a6a273c526bafd4c1facde624"
},
"generated-release1/views/diagram_sa_allocation_view.arcadia": {
"bytes": 1797,
"sha256": "352a3ca745517592b5c9b1239b5770f62f0466a329a91fd03457ef2b53c06ed6"
},
"generated-release1/views/diagram_sa_capability_view.arcadia": {
"bytes": 1286,
"sha256": "8f18fcccbcddcf3c1ea0ffe727b5b23b77fc148c5da4fde953abe681e4c20cd2"
},
"generated-release1/views/diagram_sa_service_view.arcadia": {
"bytes": 1960,
"sha256": "4c72cb7593d9c4be8d2600b0656a7a3a6ce84c45bb2f3e58a4ee5ae3bf91913c"
},
"generated-release1/views/diagram_sa_state_view.arcadia": {
"bytes": 1576,
"sha256": "dd1bea81ac7dc549a53f9b190be6f13052e00ede345bd180444cc31edb892230"
},
"helpers/composition.json": {
"bytes": 1190,
"sha256": "78a98f3a034cda4d0cc1abfe33efb785e847bb6278b49c2071d70ee5f1dc23f3"
},
"helpers/gate-union.py": {
"bytes": 3495,
"sha256": "9cca2646b1bcf09961a9ad1a276d633d8e4296ff2c6f2f356f028afaf789f503"
},
"helpers/ordinary-contract.patch": {
"bytes": 7301,
"sha256": "931a5822dbfa5ad34a0dc961035e060ccdc7aed15712c4a9b5dc15a852675f71"
},
"helpers/ordinary.patch": {
"bytes": 145928,
"sha256": "e319a5151b7e6713880eaaab632f3f1c01aee6df3cd534d85a1c099fb501dcb7"
},
"helpers/render-language.py": {
"bytes": 1009,
"sha256": "82909cf11b0bdb0594b424cba51e71ec0abdead78d878f101b7ae8ff6c4281f8"
},
"helpers/required-desktop-tests.json": {
"bytes": 125261,
"sha256": "6672e3f7ccfb92b7710310836af6933a8e58ef113353505ec4fc0c3d6908c136"
},
"inputs/0fa1da63d71c984cd99b079de661e88154b5a31818b384f557deefd40b8ead3a.json": {
"bytes": 118403,
"sha256": "0fa1da63d71c984cd99b079de661e88154b5a31818b384f557deefd40b8ead3a"
},
"inputs/12ce1ca9c1185d7a91a1dea322cdc0c7fc4372db5d879386595acdbfd412d880.zip": {
"bytes": 2830239,
"sha256": "12ce1ca9c1185d7a91a1dea322cdc0c7fc4372db5d879386595acdbfd412d880"
},
"inputs/81bd006f10034bc0c203187af179548a549fda6e58bc7cde0702cc26994fd28d.zip": {
"bytes": 2828619,
"sha256": "81bd006f10034bc0c203187af179548a549fda6e58bc7cde0702cc26994fd28d"
},
"inputs/86ac50e9cec8b39db91bbc98e5388e8c0f8466e6c217cf2f4eca5f1db087dde1.zip": {
"bytes": 2831327,
"sha256": "86ac50e9cec8b39db91bbc98e5388e8c0f8466e6c217cf2f4eca5f1db087dde1"
},
"inputs/bbe8290d9cb11a7ee5dba250350451c5b7ec88069aa65f6be786a67922408c48.json": {
"bytes": 118403,
"sha256": "bbe8290d9cb11a7ee5dba250350451c5b7ec88069aa65f6be786a67922408c48"
},
"inputs/e3d2d504b18bb1f7c4c3fa1f26abff425b957f18b418d8556888151723ba67b4.json": {
"bytes": 118404,
"sha256": "e3d2d504b18bb1f7c4c3fa1f26abff425b957f18b418d8556888151723ba67b4"
},
"render/language.html": {
"bytes": 188141,
"sha256": "57e71090f56ae27aa50f757886d6cadf63d49f5641fe4a365547cbd9e59685b6"
},
"render/language.rst": {
"bytes": 142931,
"sha256": "5deafe3f75a503ab5fb3d05c5ad8f126d82668f89bef02067ce5d56c3045a2bb"
},
"runs/consumers-1/process.json": {
"bytes": 257,
"sha256": "85b18634abd4ccf90144b44b01cbcade2521ef6706b2e46abc2e1d60a4dfc346"
},
"runs/consumers-1/result.json": {
"bytes": 6986,
"sha256": "5501663ed1f845b8d18487c90fb827faab2a1c2319cf8ea8c127324fdd9add15"
},
"runs/consumers-1/started.json": {
"bytes": 1488,
"sha256": "908dda6bce0013b3c5db9bc5cf678a9df3b221845e9c29d29409bf440bab52d3"
},
"runs/consumers-1/stderr.log": {
"bytes": 652,
"sha256": "6dca8394973a95c080a0b8858926efe7779512adf0fd5e64b47d1239f9446618"
},
"runs/consumers-1/stdout.log": {
"bytes": 144498,
"sha256": "f1ff82c06e0e1b0f0c3eadb5533402bfea42983c89fa3581340768f3d953de3e"
},
"runs/core-1/process.json": {
"bytes": 197,
"sha256": "1bf0f358d94b6e95eab5cf3823a4230a72f0363b77c75b8222c73f1122739f21"
},
"runs/core-1/result.json": {
"bytes": 67077,
"sha256": "a9f111627d5a2a7fa33e525cbd43150b803505e3e201b8465f94d2060fce143e"
},
"runs/core-1/started.json": {
"bytes": 1398,
"sha256": "56fb8a8824c031ed8f831561241a3bf321158f75cc0a2acdca6b0a77d9b648f6"
},
"runs/core-1/stderr.log": {
"bytes": 12121,
"sha256": "ca7ae2dfd0b6eecec811fce5831ea0dc7fb3b6209ffcad88d45eb6c5afd65103"
},
"runs/core-1/stdout.log": {
"bytes": 131218,
"sha256": "0d683632d1046f102e6b70af56d67ea6841134d99c923f0648261b4832a860fa"
},
"runs/core-consumers-1/process.json": {
"bytes": 274,
"sha256": "16bba7d4e4e3eb39c0ae89ecdbbfe38818c2ed329a32ac52a8db3cea69ed7fd4"
},
"runs/core-consumers-1/result.json": {
"bytes": 1907,
"sha256": "4bb8e79cd647aacebe2c4790a18ac37d07bbc29b0f7c8eca1933f1c5020474f8"
},
"runs/core-consumers-1/started.json": {
"bytes": 1515,
"sha256": "2ff77ca7601800340bf458d0707aaeef742a010ec7f6a55cf1577a1c33f5c543"
},
"runs/core-consumers-1/stderr.log": {
"bytes": 327,
"sha256": "5ded46d388f7ecc597c5e468621e84a297b1231f4eab8c2aa413697dd005e286"
},
"runs/core-consumers-1/stdout.log": {
"bytes": 0,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
"runs/language-doc-1/warnings.log": {
"bytes": 144,
"sha256": "82cf5ddae4d21aa3730437a7962e6582507aa8032cc75fb8164ff7cf9003b7ae"
},
"runs/language-doc-2/result.json": {
"bytes": 415,
"sha256": "620a449b2995fbbb27aa769cadf603dd01d9f7036c595d012e66b74469102bde"
},
"runs/language-doc-2/warnings.log": {
"bytes": 0,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
"runs/teaching-check-2/process.json": {
"bytes": 210,
"sha256": "18e9d3ee3e9c0d2d21c196c811ae543c899cc4580ad3288eca3a544f20f7a9cc"
},
"runs/teaching-check-2/result.json": {
"bytes": 3147,
"sha256": "cfc8dad31b1d8ba45face31f7986a30066c3bc464d19bd3b8495c9a013b45c0e"
},
"runs/teaching-check-2/started.json": {
"bytes": 1416,
"sha256": "259944f97150c87b2c95e9f53ae5cbb09264b03284510c9ac44272a70fdc9c87"
},
"runs/teaching-check-2/stderr.log": {
"bytes": 218,
"sha256": "9c90adb28961f087f33b905352c50c5d2aaa46785f47f3061dbe7dc161bc95d9"
},
"runs/teaching-check-2/stdout.log": {
"bytes": 42153,
"sha256": "d53a7d2d3d726302111aab71208814f487489080d250c1f555ad18312e80d86c"
},
"runs/teaching-generate-1/process.json": {
"bytes": 252,
"sha256": "c97f65ec53807bca963754b767ab21a88c3476c3639f0b54b38880281aff8a22"
},
"runs/teaching-generate-1/result.json": {
"bytes": 3171,
"sha256": "bd0095f78b4ff3cbaf7c1c3183bc280dbb1eec1cb68a528507af27ac35f34f83"
},
"runs/teaching-generate-1/started.json": {
"bytes": 1458,
"sha256": "e0526c09c8baa67bdb753ed1b0f15e237d3db2b1c60f097a145a141b9d174169"
},
"runs/teaching-generate-1/stderr.log": {
"bytes": 229,
"sha256": "464f44f1eaa5cabe25b7f66bcd689912977482c3eaad0e65328e0210749491fa"
},
"runs/teaching-generate-1/stdout.log": {
"bytes": 41964,
"sha256": "c7b195917f9b5b15aaf654703349b713b788f4cfbf00dce966539ff0764f892b"
},
"runs/windows-dependencies-1/process.json": {
"bytes": 146,
"sha256": "3901471653ef98f1abe326244dc639c6a2f0f53d3fff72db82acf068d8afabf9"
},
"runs/windows-dependencies-1/result.json": {
"bytes": 1712,
"sha256": "f1aa540938e09c5f4b92d13b293861a9e2cd08681e3f6b285b9e85f848a44935"
},
"runs/windows-dependencies-1/started.json": {
"bytes": 1321,
"sha256": "a54a7f78b455414d592b7ca3daf3d824487583a844097db76265e564bfb531d7"
},
"runs/windows-dependencies-1/stderr.log": {
"bytes": 117,
"sha256": "a82eeeb2d2fffd4d9e5d6e20ef76ed29e9e365923e2cc33bac0ab8f21a7a88d8"
},
"runs/windows-dependencies-1/stdout.log": {
"bytes": 0,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
}
},
"generatedFiles": [
"generated-release1/expected-diagnostics.json",
"generated-release1/model.json",
"generated-release1/oa/activities/activity_oa_restore_work.arcadia",
"generated-release1/oa/behavior/process_oa_assessment_procedure.arcadia",
"generated-release1/oa/behavior/process_oa_field_procedure.arcadia",
"generated-release1/oa/behavior/process_oa_response.arcadia",
"generated-release1/oa/behavior/scenario_oa_conversation.arcadia",
"generated-release1/oa/behavior/scenario_oa_full_scenario.arcadia",
"generated-release1/oa/behavior/state_machine_oa_monitor.arcadia",
"generated-release1/oa/capabilities.arcadia",
"generated-release1/oa/model.arcadia",
"generated-release1/oa/participants.arcadia",
"generated-release1/oa/types.arcadia",
"generated-release1/project.arcadia",
"generated-release1/sa/behavior/process_sa_assessment_procedure.arcadia",
"generated-release1/sa/behavior/process_sa_field_procedure.arcadia",
"generated-release1/sa/behavior/process_sa_response.arcadia",
"generated-release1/sa/behavior/scenario_sa_conversation.arcadia",
"generated-release1/sa/behavior/scenario_sa_full_scenario.arcadia",
"generated-release1/sa/behavior/state_machine_sa_monitor.arcadia",
"generated-release1/sa/capabilities.arcadia",
"generated-release1/sa/functions/function_sa_audit_thresholds.arcadia",
"generated-release1/sa/functions/function_sa_restore_work.arcadia",
"generated-release1/sa/model.arcadia",
"generated-release1/sa/participants.arcadia",
"generated-release1/sa/types.arcadia",
"generated-release1/traceability.arcadia",
"generated-release1/transition-before.json",
"generated-release1/transition-request.json",
"generated-release1/views/diagram_oa_allocation_view.arcadia",
"generated-release1/views/diagram_sa_allocation_view.arcadia",
"generated-release1/views/diagram_sa_capability_view.arcadia",
"generated-release1/views/diagram_sa_service_view.arcadia",
"generated-release1/views/diagram_sa_state_view.arcadia"
],
"generatedCount": 34
}
@@ -0,0 +1,89 @@
"""Read-only, Python-standard-library verification. Never executes archived code."""
from pathlib import Path, PurePosixPath
from datetime import datetime
import hashlib
import io
import json
import re
import zipfile
ROOT = Path(__file__).resolve().parent
def require(ok, message):
if not ok:
raise ValueError(message)
def digest(data):
return {'bytes': len(data), 'sha256': hashlib.sha256(data).hexdigest()}
def members(z, expected):
names = z.namelist()
require(len(names) == len(set(names)), 'duplicate ZIP members')
require(set(names) == set(expected), 'archive membership mismatch')
require(all(not PurePosixPath(n).is_absolute() and '..' not in PurePosixPath(n).parts
and '\\' not in n and ':' not in n for n in names), 'unsafe member')
def main():
inventory = json.loads((ROOT / 'files.json').read_text())
for name, expected in inventory.items():
require(digest((ROOT / name).read_bytes()) == expected, 'external file hash: ' + name)
manifest = json.loads((ROOT / 'manifest.json').read_text())
with zipfile.ZipFile(ROOT / 'evidence.zip') as z:
members(z, manifest['members'])
for name, expected in manifest['members'].items():
require(digest(z.read(name)) == expected, 'member hash: ' + name)
def raw(name): return z.read(name)
def obj(name): return json.loads(raw(name))
checked = set()
for run, spec in manifest['runs'].items():
if spec['kind'] != 'cargo':
continue
prefix = 'runs/' + run + '/'
start, result, process = (obj(prefix + n + '.json') for n in ('started', 'result', 'process'))
require(all(result[k] == v for k, v in start.items()), run + ': start/result relationship')
require(result['command'] == spec['command'] == process['command'], run + ': command')
require(result['head'] == spec['head'], run + ': HEAD')
require(result['exitCode'] == spec['exitCode'] and result['inputsUnchanged'] is True, run + ': outcome')
require(datetime.fromisoformat(result['finish']) >= datetime.fromisoformat(start['start']), run + ': times')
require(result['seconds'] >= 0, run + ': duration')
require(result['helper'] == digest(raw('helpers/gate-union.py')), run + ': helper')
for field, file in [('inputs', spec['inputs']), ('archive', spec['archive']), ('stdout', prefix+'stdout.log'), ('stderr', prefix+'stderr.log')]:
require(result[field] == digest(raw(file)), run + ': receipt ' + field)
inputs = obj(spec['inputs'])
require(len(inputs) == result['inputCount'] == spec['inputCount'], run + ': input count')
require(sum(p.startswith('crates/vendor/iced_widget/') for p in inputs) == 71, run + ': vendor count')
pair = (spec['inputs'], spec['archive'])
if pair not in checked:
with zipfile.ZipFile(io.BytesIO(raw(spec['archive']))) as source:
members(source, inputs)
for name, expected in inputs.items():
require(digest(source.read(name)) == expected, run + ': source hash ' + name)
checked.add(pair)
stdout = raw(prefix+'stdout.log').decode('utf-8', errors='replace')
rows = [[int(x) for x in row] for row in re.findall(r'test result: (?:ok|FAILED)\. (\d+) passed; (\d+) failed; (\d+) ignored; (\d+) measured; (\d+) filtered out;', stdout)]
require(rows == spec['testRows'], run + ': exact test summaries')
require([sum(row[i] for row in rows) for i in range(5)] == spec['testTotals'], run + ': test totals')
artifacts = []
for line in stdout.splitlines():
try: item = json.loads(line)
except json.JSONDecodeError: continue
if isinstance(item, dict) and item.get('reason') == 'compiler-artifact' and item.get('executable'):
artifacts.append(item)
require(artifacts == [a['cargo'] for a in result['artifacts']], run + ': artifact receipt relationship')
print(run, 'exit', result['exitCode'], 'test totals', spec['testTotals'])
composition = obj('helpers/composition.json')
require(composition['patch'] == digest(raw('helpers/ordinary.patch')), 'composition patch')
doc = obj('runs/language-doc-2/result.json')
require(doc['strict'] is True and doc['docutils'] == '0.23', 'render settings')
for field, path in [('source','render/language.rst'), ('html','render/language.html'), ('warnings','runs/language-doc-2/warnings.log')]:
require(doc[field] == digest(raw(path)), 'render receipt ' + field)
require(raw('runs/language-doc-2/warnings.log') == b'', 'successful render warnings')
require(bool(raw('runs/language-doc-1/warnings.log')), 'failed render warning missing')
for path in manifest['generatedFiles']:
require(path.startswith('generated-release1/') and path in manifest['members'], 'generated mapping')
require(len(manifest['generatedFiles']) == manifest['generatedCount'], 'generated count')
print('PASS:', len(manifest['runs']), 'closed captures;', len(manifest['members']), 'members;', len(checked), 'input manifest/archive pairs')
print('Integrity and receipt consistency only; no rerun, signature, binary verification, or GUI claim.')
if __name__ == '__main__':
main()
@@ -0,0 +1,45 @@
# Ordinary-text union: native trial evidence
Run `python verify.py` with Python 3.10 or newer. These five files form a portable
read-only package. The verifier uses only the Python standard library, reads
ZIPs without extraction, and executes neither archived helpers nor binaries.
It requires no Git checkout, original machine, network, UI or dependency cache.
Outer files are UTF-8 LF before hash pins; all archived raw bytes are unchanged.
The complete closed trial-1 capture set is preserved, including small auxiliary
picker frames, extra captures, resumed-state capture, and repeated save-path
attempts. Filename numbering labels recorded observations; it is not a complete
action log and does not assert an unrecorded intermediate transition. Screenshots
and accessibility snapshots support human review; their interpretation is not
automatically proven by this verifier.
`native/independent-review.md` preserves the separate review of screenshots,
source/CLI evidence and claim boundaries. Its judgments are human-readable review
evidence, distinct from machine-verified byte and receipt relationships. Dialog
states are archived; operator/tool calls are not a complete archived action log.
Archived material includes original input, both saved bundles, all trial receipts,
logs and frames, capture/launch helpers, saved-bundle check helper and raw results.
The exact successful windows-build-1 receipts, logs, gate helper, input manifest
and 707-file input archive are included for a self-contained receipt chain.
Those build captures also appear in the gates package committed at
7836a93edb98d2f9dcbf9c0290352e38f9acbb39. The production build source is
f714a25bbcd435941404104ad0cbb0fea3b89149. Binaries are intentionally excluded;
hashes establish receipt consistency, not independent binary reproduction.
Verification checks every archive member/hash, nested source membership and
content, build and native start/result/helper relationships, GUI/CLI binary
receipt links, original input archive, capture timestamps/image references,
normal GUI exit and empty logs. It recomputes exact saved-source membership and
bytes: compact added activity and include, stable recorded IDs, and only the
intended existing diagram x coordinate changing from 120 to 190 between saved
bundles. Both CLI captures report checked status, four warnings, no writes or
changes, and zero identities to stabilize.
These are saved-byte and recorded CLI parsing/validation oracles, not a comparison
of the complete parsed models. Undo/Redo, reopening, diagnostic freshness and
navigation observations require inspecting the actual frames and review. Empty
logs and exit zero alone do not establish usability or complete acceptance.
The hashes are integrity checks, not authenticated provenance. Raw captures
retain original local path strings and timestamps; no credentials, user profile,
runtime binaries or dependency cache are packaged.
@@ -0,0 +1,18 @@
{
"evidence.zip": {
"bytes": 5704887,
"sha256": "b827d0bdaec76b7b86d8a169530dcf98767c9883187ecdda02b4ea87171a42b5"
},
"manifest.json": {
"bytes": 24911,
"sha256": "49d0bf1c4c4732c9db8a44ae525fb4516cca4883e1a209aa736b913550396037"
},
"README.md": {
"bytes": 2931,
"sha256": "2422dd76bfcd3e81d57d1db48db27b36e7505ff6b17a68c3b393518f623de256"
},
"verify.py": {
"bytes": 9043,
"sha256": "7242e9ecc68f21349f0073379391d7890367e0b4f5f32697af306cc2346e50d0"
}
}
@@ -0,0 +1,624 @@
{
"schema": 1,
"productionCommit": "f714a25bbcd435941404104ad0cbb0fea3b89149",
"relatedGatesCommit": "7836a93edb98d2f9dcbf9c0290352e38f9acbb39",
"captures": [
"native/trial-1/01-start.json",
"native/trial-1/02-open-dialog.json",
"native/trial-1/03-open-dialog-ready.json",
"native/trial-1/04-open-picker.json",
"native/trial-1/05-open-filename-focus.json",
"native/trial-1/06-open-fixture-path.json",
"native/trial-1/07-opened-fixture.json",
"native/trial-1/08-model-explorer.json",
"native/trial-1/09-created-activity.json",
"native/trial-1/10-text-created-source.json",
"native/trial-1/11-source-folder.json",
"native/trial-1/12-activity-folder.json",
"native/trial-1/13-compact-activity.json",
"native/trial-1/14-save-before-dialog.json",
"native/trial-1/15-save-before-picker.json",
"native/trial-1/16-save-before-focus.json",
"native/trial-1/17-save-before-selected.json",
"native/trial-1/18-save-before-selection-confirmed.json",
"native/trial-1/19-save-before-path.json",
"native/trial-1/20-baseline-saved.json",
"native/trial-1/21-before-drag.json",
"native/trial-1/22-after-drag.json",
"native/trial-1/23-text-after-drag.json",
"native/trial-1/24-root-after-drag.json",
"native/trial-1/25-text-actions.json",
"native/trial-1/25a-resumed-state.json",
"native/trial-1/26-model-undo.json",
"native/trial-1/27-redo-menu.json",
"native/trial-1/28-model-redo.json",
"native/trial-1/29-save-after-pending.json",
"native/trial-1/30-save-after-dialog.json",
"native/trial-1/31a-save-after-field.json",
"native/trial-1/32-save-after-path.json",
"native/trial-1/33-save-after-path-ready.json",
"native/trial-1/34-save-after-path.json",
"native/trial-1/35-saved-after.json",
"native/trial-1/36-new-cleared.json",
"native/trial-1/37-home-reopen.json",
"native/trial-1/38-reopened-after.json",
"native/trial-1/39-reopened-source.json",
"native/trial-1/40-problems-menu.json",
"native/trial-1/41-fresh-diagnostics.json",
"native/trial-1/42-fresh-warning-navigation.json",
"native/trial-1/43-comment-insertion-point.json",
"native/trial-1/44-edited-diagnostics-stale.json",
"native/trial-1/45-refreshed-diagnostics.json",
"native/trial-1/46-refreshed-warning-navigation.json",
"native/trial-1/47-restore-menu.json",
"native/trial-1/48-diagnostic-edit-undone.json"
],
"captureCount": 49,
"imageCount": 62,
"members": {
"build/gate-union.py": {
"bytes": 3495,
"sha256": "9cca2646b1bcf09961a9ad1a276d633d8e4296ff2c6f2f356f028afaf789f503"
},
"build/inputs.json": {
"bytes": 118403,
"sha256": "a36e2cbc29eb7a9217774464cda9da8aa1d3c39caae9b9aacd6cdb28df5bad95"
},
"build/inputs.zip": {
"bytes": 2831326,
"sha256": "9346fba1be9bf4dd2225fe9965fd76a30429f086fd6a101f92d0963d3822f71c"
},
"build/process.json": {
"bytes": 167,
"sha256": "b400c1f8076bcddb583424fe34d155655a41d25b5ccf0db8af26bda6b9b2685e"
},
"build/result.json": {
"bytes": 5532,
"sha256": "5e9e227c66a4b71ade734b8ebeb12070b8a5c64a9a6cf9d8d0b6d70379d4ffc2"
},
"build/started.json": {
"bytes": 1353,
"sha256": "15c54be09a881e6860735395569b22a7a9114789a1f8276cf79bc6fb095c8163"
},
"build/stderr.log": {
"bytes": 261,
"sha256": "480dff999944c756fff581db0f3fac088717e0d73cae20af9a28829446df746f"
},
"build/stdout.log": {
"bytes": 295092,
"sha256": "5a824ca91877328acd332585b39194b27fd2cb167279b99c4c78a89bf13bd19b"
},
"native/bundle-checks/saved-after.result.json": {
"bytes": 1930,
"sha256": "34e2e8754026f35f4d57b24f8bde39f5b354aea703cd9c6650c673f674c2d167"
},
"native/bundle-checks/saved-after.stderr.log": {
"bytes": 0,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
"native/bundle-checks/saved-after.stdout.json": {
"bytes": 2592,
"sha256": "095f83e38f6c59b69fd72d0d7d0825b7849d34b963ff35e7f9a4ab7434bae5f2"
},
"native/bundle-checks/saved-before.result.json": {
"bytes": 1931,
"sha256": "52d3f54ce37412fe6f0bf23484d7ada549ed26f8b16501e0ddff1261df673f6c"
},
"native/bundle-checks/saved-before.stderr.log": {
"bytes": 0,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
"native/bundle-checks/saved-before.stdout.json": {
"bytes": 2593,
"sha256": "966ee3fac987156a8b9e5e1ebf9d10dd7f7a89c84d0f42645874d153a404517e"
},
"native/bundle-checks/summary.json": {
"bytes": 484,
"sha256": "7f9f6ddd98b303caf0edebef19aa507257c176168f94218ac86f3ac180d13631"
},
"native/capture-snippet.js": {
"bytes": 1579,
"sha256": "55279ed080289ccf5ba482cb24b5a807669a9360012e2d87f794d4465227cb2c"
},
"native/check-saved-bundles.py": {
"bytes": 4216,
"sha256": "beaf19c8d07e63fbc462c555f5e486fc6d035f0175ae9567ac1217414a63038c"
},
"native/independent-review.md": {
"bytes": 5534,
"sha256": "7e952270478fb0f57b0ed44d54bba6c2b09b5c315cebe11ac7ab613155ed299f"
},
"native/input/project.arcadia": {
"bytes": 451,
"sha256": "375c80aaf2b26bb7a638e79d76e4374454e0a86b9698b888d39dc5cad0206818"
},
"native/run-native.py": {
"bytes": 2609,
"sha256": "2d2a63bcfccad32c2b5db09cd723dcff9877d3cbe813853e278cb3df49ed7b88"
},
"native/saved-after/oa/activities/activity_new_activity.arcadia": {
"bytes": 89,
"sha256": "7d2a6a27fecb94eaf42a13b330fed5a269d0e80cad75688a9f26d4e08db0dfe7"
},
"native/saved-after/project.arcadia": {
"bytes": 511,
"sha256": "c93bd35eaaf3a19bc07b5c6a4ee5839ab565e9fd90a2605261b7618fc8a7ecef"
},
"native/saved-before/oa/activities/activity_new_activity.arcadia": {
"bytes": 89,
"sha256": "7d2a6a27fecb94eaf42a13b330fed5a269d0e80cad75688a9f26d4e08db0dfe7"
},
"native/saved-before/project.arcadia": {
"bytes": 511,
"sha256": "dad153f45038ed9ae79ea7263364e6cdedc40737fe7f63fdad7fb198ee7f65c8"
},
"native/trial-1/01-start-0.jpg": {
"bytes": 117627,
"sha256": "0778c21336a401d30316ed3ef5c93abe9ed349082a64cf78e8643edc0b29ee9e"
},
"native/trial-1/01-start.json": {
"bytes": 919,
"sha256": "986aab266f45d9871bd5b134d318a38ce7d8a7c992e2a78a38c54dcfa4a86baa"
},
"native/trial-1/02-open-dialog-0.jpg": {
"bytes": 116197,
"sha256": "e6248082f0dbe81d8d387906686538061ce1cf0f1be4b68aa70156238ea991c0"
},
"native/trial-1/02-open-dialog.json": {
"bytes": 925,
"sha256": "e9852679994314e8ede462a414787841ed8254b8ea660f83543b1bf4e4ab3979"
},
"native/trial-1/03-open-dialog-ready-0.jpg": {
"bytes": 90928,
"sha256": "2a931629a94f94660af8b189f919ab3e41400ca860ae8eac486f4b90184083ce"
},
"native/trial-1/03-open-dialog-ready.json": {
"bytes": 735,
"sha256": "5aea33223bfdb19456c182f9c7cd55eaedaa8c62a6af63c33b5d334d6b7a4af6"
},
"native/trial-1/04-open-picker-0.jpg": {
"bytes": 674,
"sha256": "5a2387a1a593a32ea169b56b9a3e59333936ee1da95164564e4d9be61bf5a24b"
},
"native/trial-1/04-open-picker-1.jpg": {
"bytes": 53413,
"sha256": "61176947f83f35fe13aa1e31b0aa61ea478d0537b9a08fbf45ab6442ab4e2296"
},
"native/trial-1/04-open-picker.json": {
"bytes": 4951,
"sha256": "c8f356251dafcf9465555c629d32e68d6d905d5c221e9580853f6f378123dbe4"
},
"native/trial-1/05-open-filename-focus-0.jpg": {
"bytes": 674,
"sha256": "5a2387a1a593a32ea169b56b9a3e59333936ee1da95164564e4d9be61bf5a24b"
},
"native/trial-1/05-open-filename-focus-1.jpg": {
"bytes": 53809,
"sha256": "fc29b6c470522c8d8c024a9ce43477b507525b4c5ad6482f812f69b526a321eb"
},
"native/trial-1/05-open-filename-focus.json": {
"bytes": 4967,
"sha256": "4ad084f9d123899129c9c7cb4faa53f4727b14bc901fc1cc1dce45e0a15702b4"
},
"native/trial-1/06-open-fixture-path-0.jpg": {
"bytes": 674,
"sha256": "5a2387a1a593a32ea169b56b9a3e59333936ee1da95164564e4d9be61bf5a24b"
},
"native/trial-1/06-open-fixture-path-1.jpg": {
"bytes": 56049,
"sha256": "fc7d2cd6b12111e9bca60cc786b82f1f8f42ee37654a29bd058345ce10b77eaf"
},
"native/trial-1/06-open-fixture-path.json": {
"bytes": 4963,
"sha256": "1eb51f68dc204ad419d54cc3d7bf1e5335b41bd65a083d23bed53dfc90f9a374"
},
"native/trial-1/07-opened-fixture-0.jpg": {
"bytes": 98357,
"sha256": "eddb3b2e85487bc4e53da399a2062c6bd138b6022216dc0b01c3cffc811c2c99"
},
"native/trial-1/07-opened-fixture.json": {
"bytes": 956,
"sha256": "d384ab6efa1fbf49e1b2afd93f933c64d865aa2b6baf6c7a69e2ac372da50449"
},
"native/trial-1/08-model-explorer-0.jpg": {
"bytes": 90072,
"sha256": "65a6e4f0545f6a5e104b9a2896e9b1f82851528a9e91651a31915d9a4f7a0058"
},
"native/trial-1/08-model-explorer.json": {
"bytes": 963,
"sha256": "3a772406fe219f21d954590f6c96f47adc9901d130c2d7bb087acc086cf6079b"
},
"native/trial-1/09-created-activity-0.jpg": {
"bytes": 100572,
"sha256": "115443b3ac12e32fc5a9d0736c9a475a431ed7423a84275a17fa1b4b10496185"
},
"native/trial-1/09-created-activity.json": {
"bytes": 975,
"sha256": "49a134749f520039a5390fc4b4110e5411822c6fb7573e31e1ffb1bff428a4d0"
},
"native/trial-1/10-text-created-source-0.jpg": {
"bytes": 103031,
"sha256": "6999c782d6b38719757898cd7d1575e71239fab882b040ce3125e823e734d995"
},
"native/trial-1/10-text-created-source.json": {
"bytes": 978,
"sha256": "56af19e09aa7d9bd4ef315a80d5060bfd52bd8e08d111c3647ead2bfff9f8b12"
},
"native/trial-1/11-source-folder-0.jpg": {
"bytes": 103956,
"sha256": "72656e598bfef0ad4355ca53d85b88a0d62ad186b3189b0a3e3f5900461e16b1"
},
"native/trial-1/11-source-folder.json": {
"bytes": 972,
"sha256": "e59af1c50e4f374cd840bd62fd0b8676e18e47a45ad820dd8b6a8dd835315c27"
},
"native/trial-1/12-activity-folder-0.jpg": {
"bytes": 105653,
"sha256": "2aff6c99a3845c334b94ba3e7b27fbdb3c31056784ce3ec4e76ef45ebb32911f"
},
"native/trial-1/12-activity-folder.json": {
"bytes": 974,
"sha256": "383be400c6317b571546e51d0c40128e7381b5ce1686ab3aaa20ece1a5e801ee"
},
"native/trial-1/13-compact-activity-0.jpg": {
"bytes": 88781,
"sha256": "87b367c8fe3017d221228ec63aa5192aaee19b4f80db6a07007b32f10f41d242"
},
"native/trial-1/13-compact-activity.json": {
"bytes": 975,
"sha256": "ba4daa10aeb0d9cd555d03cfbcb6bf5a7c6777d9247a46859f35d6fe740fa347"
},
"native/trial-1/14-save-before-dialog-0.jpg": {
"bytes": 83376,
"sha256": "9fd9bd5067f57dc0f297f5d02442e41e3395bd3f6964d726e852746c54a0cff7"
},
"native/trial-1/14-save-before-dialog.json": {
"bytes": 977,
"sha256": "91884e38ade4bd4fa93474a1076d84a4228b4d65a3d4c70afe296166150335b5"
},
"native/trial-1/15-save-before-picker-0.jpg": {
"bytes": 674,
"sha256": "5a2387a1a593a32ea169b56b9a3e59333936ee1da95164564e4d9be61bf5a24b"
},
"native/trial-1/15-save-before-picker-1.jpg": {
"bytes": 55054,
"sha256": "3529090d92bf9ea0375a8e95cc6f1b67d9159872367c89e3bf308e3d024a7ff7"
},
"native/trial-1/15-save-before-picker.json": {
"bytes": 4764,
"sha256": "febecff84014510968d802845126b9935a1c5b40a92dc2795d7bd05d4b11ecbf"
},
"native/trial-1/16-save-before-focus-0.jpg": {
"bytes": 674,
"sha256": "5a2387a1a593a32ea169b56b9a3e59333936ee1da95164564e4d9be61bf5a24b"
},
"native/trial-1/16-save-before-focus-1.jpg": {
"bytes": 55425,
"sha256": "2544f1ccc8ed5a21e682f6e3c836daa684a60f5ee0e4eb39de8ec4f05aca5daa"
},
"native/trial-1/16-save-before-focus.json": {
"bytes": 4762,
"sha256": "e07187e7179555f84a8b3ad7c87e707ba686945bf60dc89b2bfe86ce34d4fb42"
},
"native/trial-1/17-save-before-selected-0.jpg": {
"bytes": 674,
"sha256": "5a2387a1a593a32ea169b56b9a3e59333936ee1da95164564e4d9be61bf5a24b"
},
"native/trial-1/17-save-before-selected-1.jpg": {
"bytes": 55437,
"sha256": "7e12139b558f7427de23cf4a611c6a0a8b20be25e6a0b0799832ddc715ba1fd5"
},
"native/trial-1/17-save-before-selected.json": {
"bytes": 4768,
"sha256": "96d7d8468d87e24808617aea0cd42b52acdf4126c601b10e6f98b614beaf5f75"
},
"native/trial-1/18-save-before-selection-confirmed-0.jpg": {
"bytes": 674,
"sha256": "5a2387a1a593a32ea169b56b9a3e59333936ee1da95164564e4d9be61bf5a24b"
},
"native/trial-1/18-save-before-selection-confirmed-1.jpg": {
"bytes": 54676,
"sha256": "4cc51ff7fa2af6325879b442aea5eee1992f0fd41c4b6455fab3540601691a49"
},
"native/trial-1/18-save-before-selection-confirmed.json": {
"bytes": 4790,
"sha256": "a24fb0576c3339fa281c96d82055428d1e947ea8e0fbda3d9304fbec2410ea62"
},
"native/trial-1/19-save-before-path-0.jpg": {
"bytes": 674,
"sha256": "5a2387a1a593a32ea169b56b9a3e59333936ee1da95164564e4d9be61bf5a24b"
},
"native/trial-1/19-save-before-path-1.jpg": {
"bytes": 57797,
"sha256": "3e5f67d8125353318557c1fffc3aa3184e7870b79f57fb2b272f784a55438854"
},
"native/trial-1/19-save-before-path.json": {
"bytes": 4760,
"sha256": "0af8bed5fa5a3d93b59d046b48623992321d2436275d835ba39550df0edf3a89"
},
"native/trial-1/20-baseline-saved-0.jpg": {
"bytes": 85738,
"sha256": "2a1cb7c65ce2ae68c6c8e1b60c052fec4ccad5574da8e8900d452b41324150c0"
},
"native/trial-1/20-baseline-saved.json": {
"bytes": 965,
"sha256": "5990919d8cdb16e9f5445bb858b0d014015ef02d9157cbc2dc84f75a74f02b00"
},
"native/trial-1/21-before-drag-0.jpg": {
"bytes": 101486,
"sha256": "81f4bb4b0cb7314011b8acd829e9905993b596175139c482cef6030ec542e078"
},
"native/trial-1/21-before-drag.json": {
"bytes": 960,
"sha256": "e35a0357f5b334c8e519cb83116b2d516e9f19bb1ac794cfde0aaf3f6c98d62d"
},
"native/trial-1/22-after-drag-0.jpg": {
"bytes": 103060,
"sha256": "6a8f8c1bd011bd6224050236736e90a982c2102ec2a4700d09e7fc0ecd6ee9a6"
},
"native/trial-1/22-after-drag.json": {
"bytes": 967,
"sha256": "68e018c9d5b9e28b9a138f8085539bc9d87b0e0e48761049fd57d67334313715"
},
"native/trial-1/23-text-after-drag-0.jpg": {
"bytes": 83460,
"sha256": "dfb676eca325444b0383f47d056621d62c9cba3a522739a0015eb1d4a04a36f3"
},
"native/trial-1/23-text-after-drag.json": {
"bytes": 974,
"sha256": "100687443402acff33f2c9eba03a57d1146475e2225e6e90baa9d36624fec96f"
},
"native/trial-1/24-root-after-drag-0.jpg": {
"bytes": 104490,
"sha256": "25a2302b69bd367ea04c4b1ca28030c34e4a5b32a7c88c7c21c5a721fa69c60d"
},
"native/trial-1/24-root-after-drag.json": {
"bytes": 974,
"sha256": "ebbd8e200f431ad99760ed94895a74bc0b01d33f1fb67a6ab1464a1cf008c40a"
},
"native/trial-1/25-text-actions-0.jpg": {
"bytes": 117335,
"sha256": "4627be733b9c84798ff8324fe8bb8bba98dd7c8f3c7be5563d1a24c99e66b59c"
},
"native/trial-1/25-text-actions.json": {
"bytes": 971,
"sha256": "4d0a137244adf721a04dc17af1ae0ef380db737c3ab7478fbf6661bab24ce835"
},
"native/trial-1/25a-resumed-state-0.jpg": {
"bytes": 117221,
"sha256": "f509ed4e37b675d4cbcf1c075a983654b016d5459905a68b45514e24891ced3f"
},
"native/trial-1/25a-resumed-state.json": {
"bytes": 973,
"sha256": "304de4fa36d0b97b891455b26a14850b43bfd0811eb7e7be22bdcae9814da703"
},
"native/trial-1/26-model-undo-0.jpg": {
"bytes": 103052,
"sha256": "a8966f1244e9bbe9be4ad75278f3a492e748029c3fff3174ce38250997250a11"
},
"native/trial-1/26-model-undo.json": {
"bytes": 959,
"sha256": "9cd25b0f2d954f7772ae139c8d5afcb04ab1bf4c965aae6c6e22af37c352618b"
},
"native/trial-1/27-redo-menu-0.jpg": {
"bytes": 117521,
"sha256": "13634900648a485f38fd5183aa097ae544688b5a6112b821362b7128709d06c1"
},
"native/trial-1/27-redo-menu.json": {
"bytes": 958,
"sha256": "4cb0672342dd468ce4a403993e22613abe7498cdceb6111eb39747e7a24a8c86"
},
"native/trial-1/28-model-redo-0.jpg": {
"bytes": 103295,
"sha256": "8202074761aca9c067f5851a17c0586cc6698426473c1621defd3739cbbb817b"
},
"native/trial-1/28-model-redo.json": {
"bytes": 969,
"sha256": "252817aafe641c994ddba7fa244435371b4e75f6b9bbd16d355f02e1b35f0d9d"
},
"native/trial-1/29-save-after-pending-0.jpg": {
"bytes": 102718,
"sha256": "57a731a2b96cd9685ed0dc206a49b7804231d83d763503308fe73e6e326e725d"
},
"native/trial-1/29-save-after-pending.json": {
"bytes": 977,
"sha256": "32490225d11e793b31168cbd00ce184d38190c6e7c4420dba0e4c20424b19533"
},
"native/trial-1/30-save-after-dialog-0.jpg": {
"bytes": 674,
"sha256": "5a2387a1a593a32ea169b56b9a3e59333936ee1da95164564e4d9be61bf5a24b"
},
"native/trial-1/30-save-after-dialog-1.jpg": {
"bytes": 56737,
"sha256": "58dab12435a2f7b258e0c83ab0edd4a95379b0d6ec95f16184d63f8d76b94daf"
},
"native/trial-1/30-save-after-dialog.json": {
"bytes": 5057,
"sha256": "c9515daa0a573ee5510a421a205d1697835dfeaa3d2d69a64d93e4d66c94310e"
},
"native/trial-1/31a-save-after-field-0.jpg": {
"bytes": 674,
"sha256": "5a2387a1a593a32ea169b56b9a3e59333936ee1da95164564e4d9be61bf5a24b"
},
"native/trial-1/31a-save-after-field-1.jpg": {
"bytes": 56737,
"sha256": "58dab12435a2f7b258e0c83ab0edd4a95379b0d6ec95f16184d63f8d76b94daf"
},
"native/trial-1/31a-save-after-field.json": {
"bytes": 5057,
"sha256": "39e59a6120ad31528e55d379eeb2a463af05b5bea8710020c659687b414208fc"
},
"native/trial-1/32-save-after-path-0.jpg": {
"bytes": 674,
"sha256": "5a2387a1a593a32ea169b56b9a3e59333936ee1da95164564e4d9be61bf5a24b"
},
"native/trial-1/32-save-after-path-1.jpg": {
"bytes": 55986,
"sha256": "cb789f536317fff99d6f55133297a8d8511ef280a6b82c5e6e2960da6d0ceafe"
},
"native/trial-1/32-save-after-path.json": {
"bytes": 5053,
"sha256": "9167fd1352cfe5bb1b70631d1413cb7dac3a49311f459c39df374cdfe2d453d8"
},
"native/trial-1/33-save-after-path-ready-0.jpg": {
"bytes": 674,
"sha256": "5a2387a1a593a32ea169b56b9a3e59333936ee1da95164564e4d9be61bf5a24b"
},
"native/trial-1/33-save-after-path-ready-1.jpg": {
"bytes": 56034,
"sha256": "b41240172f775f45e4f19c1240784595004134baace690ccc5388097c9776104"
},
"native/trial-1/33-save-after-path-ready.json": {
"bytes": 5065,
"sha256": "00f3dc075eb6e446a6e7b85790a6d7e913151de53ff8041d9a63c6acd2597c47"
},
"native/trial-1/34-save-after-path-0.jpg": {
"bytes": 674,
"sha256": "5a2387a1a593a32ea169b56b9a3e59333936ee1da95164564e4d9be61bf5a24b"
},
"native/trial-1/34-save-after-path-1.jpg": {
"bytes": 59217,
"sha256": "182c41675727c07555b126f0e7876b32e7c8f141b0d7254e1511299318e08c9d"
},
"native/trial-1/34-save-after-path.json": {
"bytes": 5053,
"sha256": "2c356eeff45b49e9ce17919042929382027445ee07917a80eb8432581ede66e3"
},
"native/trial-1/35-saved-after-0.jpg": {
"bytes": 102789,
"sha256": "cb90a19d855faf3bab39cdb9468c788d72cf840366ef41b98e99d2cd4d9160fc"
},
"native/trial-1/35-saved-after.json": {
"bytes": 962,
"sha256": "f96d3e0c7d0c83603ae14ec4231250e7be9f12113b465050c101852de1755548"
},
"native/trial-1/36-new-cleared-0.jpg": {
"bytes": 90719,
"sha256": "dfc505fa7c2ce48fea5a04aa90b2745d11b144704c16cec0b2431a92291a8966"
},
"native/trial-1/36-new-cleared.json": {
"bytes": 925,
"sha256": "04c4e7a5de7af7974b5ecff290582854cb07979ffca6ca3e468eda09af403b09"
},
"native/trial-1/37-home-reopen-0.jpg": {
"bytes": 115448,
"sha256": "79680f4fbe8ea41a8318d175959d9bf80b8f28ae161b81818f6daa1a97ff294f"
},
"native/trial-1/37-home-reopen.json": {
"bytes": 925,
"sha256": "93b4c5dedb87e64bfd4ebead90b9fb6e6cc89a96983312b70260d8b1cd2ea430"
},
"native/trial-1/38-reopened-after-0.jpg": {
"bytes": 98471,
"sha256": "1a463750e7eeca04b36db7d1ebd3e093f44c92cff40bc68ce603e2e788c23fe7"
},
"native/trial-1/38-reopened-after.json": {
"bytes": 956,
"sha256": "154ba579c620a7d4adaf6c303e045b73223e69996ebaa5841d751dc9cf3f090f"
},
"native/trial-1/39-reopened-source-0.jpg": {
"bytes": 103219,
"sha256": "2bf7bc9dc32c265c303453c054a032af780cb091fb4cc9e8830b0c8d1019c4cd"
},
"native/trial-1/39-reopened-source.json": {
"bytes": 964,
"sha256": "5faf1b0d7810c67d17c77d914824b111b945915a1cf97020e544d3df5c2378d8"
},
"native/trial-1/40-problems-menu-0.jpg": {
"bytes": 117324,
"sha256": "ffdbb858d382d5eb7f5c02769fb6ca0fea64a223280f27366c95087ee5a1f618"
},
"native/trial-1/40-problems-menu.json": {
"bytes": 962,
"sha256": "a2f4a64ce2619952ce1983af22400c13f28f496ba69cc2c9e09eb3b19d0d045b"
},
"native/trial-1/41-fresh-diagnostics-0.jpg": {
"bytes": 124773,
"sha256": "25f26374a247b7188a8458950ded42eeb1635919a54b4bfd33cedc17446f7506"
},
"native/trial-1/41-fresh-diagnostics.json": {
"bytes": 966,
"sha256": "3c227b0e93aa63082536324498c01a47df905fe7677bda072bd8309dc369e78f"
},
"native/trial-1/42-fresh-warning-navigation-0.jpg": {
"bytes": 107742,
"sha256": "2d5d6b4dc722d63313f8aaa2bb6943d3bec11a1bcf9087cdc56ad287e5b5936d"
},
"native/trial-1/42-fresh-warning-navigation.json": {
"bytes": 973,
"sha256": "0a3fd08f2e994530dd41b6fdc6d28bcb0d665e5d9c16a2e89f8bd54dcb797048"
},
"native/trial-1/43-comment-insertion-point-0.jpg": {
"bytes": 108879,
"sha256": "4267f2294d6ceb9ffdb1ea5e72cfbd9aa17ef14df72ea410c0e80128ef854520"
},
"native/trial-1/43-comment-insertion-point.json": {
"bytes": 972,
"sha256": "182b10cdcaf379defeb0fc448cf1e933aab4f783aa383a97bdbf73a3338af69d"
},
"native/trial-1/44-edited-diagnostics-stale-0.jpg": {
"bytes": 91431,
"sha256": "206a1ea8360ca766a17d12594265edc5c54abeda976456259315569879aa4165"
},
"native/trial-1/44-edited-diagnostics-stale.json": {
"bytes": 983,
"sha256": "222c7915eb406f618e8e10caf8b02bb57545a3668fe32f20ef4381eb3780d67a"
},
"native/trial-1/45-refreshed-diagnostics-0.jpg": {
"bytes": 107187,
"sha256": "0c9309378f1bac39941836312b926eddf52a78c164329955f677df1000c8c4c1"
},
"native/trial-1/45-refreshed-diagnostics.json": {
"bytes": 980,
"sha256": "42a51a6eccce6b983368981c194a54c2e3172cee61302bc75b9ee555ace9c367"
},
"native/trial-1/46-refreshed-warning-navigation-0.jpg": {
"bytes": 109007,
"sha256": "e2e23d83f8e5ad9ddbc98f53858276990e27e59550de94dcf6e96054ee0f6a00"
},
"native/trial-1/46-refreshed-warning-navigation.json": {
"bytes": 987,
"sha256": "39d8e7cc945cb444b5f664c243e9ad373ccc1d18b6c04e8cc6dd37a8ce46c643"
},
"native/trial-1/47-restore-menu-0.jpg": {
"bytes": 120506,
"sha256": "a5f11f64e091b20caa628c3253ad92b8b98b2647c19e798f150f3a2d7a3f87e8"
},
"native/trial-1/47-restore-menu.json": {
"bytes": 971,
"sha256": "4a7e3bc9c23f95df538a488d0186f37dc54a5bb1dd184d06d2d6268bf1ccf256"
},
"native/trial-1/48-diagnostic-edit-undone-0.jpg": {
"bytes": 105205,
"sha256": "cf550b51b74b6fef8e21cd2d50f41f1797e22b4b7f38214d8311062bee3e0483"
},
"native/trial-1/48-diagnostic-edit-undone.json": {
"bytes": 971,
"sha256": "43c970d94c53c31a570ab92c6855566a6e2ce690768f6ed7836d64c5a6120f81"
},
"native/trial-1/input.zip": {
"bytes": 343,
"sha256": "f73fb93e2ee2eae45d1df2f63cdbb838a60cdda3f09b8977af6ded6e4ab424f0"
},
"native/trial-1/process.json": {
"bytes": 61,
"sha256": "8a60a20728ea02517d7c22812d27f9814bc3aa98fee597820e8351076f32e7c2"
},
"native/trial-1/result.json": {
"bytes": 1665,
"sha256": "6e615456856f30b8c25dedcd881d7dff6918535916a62b39b4de15c591960dd2"
},
"native/trial-1/started.json": {
"bytes": 1198,
"sha256": "39425278d31c29db4f293b4be7f3e9b2616a85e09c2f6a124dbab2b7ea0ac48c"
},
"native/trial-1/stderr.log": {
"bytes": 0,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
"native/trial-1/stdout.log": {
"bytes": 0,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
}
}
}
@@ -0,0 +1,100 @@
"""Portable read-only evidence verification; no archived helper or binary runs."""
from pathlib import Path, PurePosixPath
from datetime import datetime
import hashlib, io, json, zipfile
ROOT=Path(__file__).resolve().parent
def require(ok,message):
if not ok: raise ValueError(message)
def pin(b): return {'bytes':len(b),'sha256':hashlib.sha256(b).hexdigest()}
def membership(z,expected):
names=z.namelist()
require(len(names)==len(set(names)) and set(names)==set(expected),'ZIP membership')
require(all(not PurePosixPath(n).is_absolute() and '..' not in PurePosixPath(n).parts and '\\' not in n and ':' not in n for n in names),'unsafe ZIP path')
def main():
files=json.loads((ROOT/'files.json').read_bytes())
for name,p in files.items(): require(pin((ROOT/name).read_bytes())==p,'outer hash '+name)
manifest=json.loads((ROOT/'manifest.json').read_bytes())
with zipfile.ZipFile(ROOT/'evidence.zip') as z:
membership(z,manifest['members'])
for name,p in manifest['members'].items():require(pin(z.read(name))==p,'member hash '+name)
def raw(p):return z.read(p)
def obj(p):return json.loads(raw(p))
build=obj('build/result.json'); start=obj('build/started.json')
require(all(build[k]==v for k,v in start.items()),'build start/result')
require(build['exitCode']==0 and build['inputsUnchanged'] is True,'build success')
require(build['head']==manifest['productionCommit'],'build commit')
require(datetime.fromisoformat(build['start'])<=datetime.fromisoformat(build['finish']),'build time order')
require(build['command']==obj('build/process.json')['command'],'build process command')
for field,path in [('inputs','build/inputs.json'),('archive','build/inputs.zip'),('helper','build/gate-union.py'),('stdout','build/stdout.log'),('stderr','build/stderr.log')]:
require(build[field]==pin(raw(path)),'build receipt '+field)
inputs=obj('build/inputs.json')
require(len(inputs)==build['inputCount']==707,'707 build inputs')
require(sum(p.startswith('crates/vendor/iced_widget/') for p in inputs)==71,'vendor inventory')
with zipfile.ZipFile(io.BytesIO(raw('build/inputs.zip'))) as source:
membership(source,inputs)
for name,p in inputs.items():require(pin(source.read(name))==p,'source input '+name)
artifacts=[]
for line in raw('build/stdout.log').decode('utf-8').splitlines():
try:a=json.loads(line)
except json.JSONDecodeError:continue
if a.get('reason')=='compiler-artifact' and a.get('executable'):artifacts.append(a)
require(artifacts==[a['cargo'] for a in build['artifacts']],'build artifact receipts')
binaries={a['cargo']['target']['name']:a['readback'] for a in build['artifacts']}
executables={a['cargo']['target']['name']:a['cargo']['executable'] for a in build['artifacts']}
require(set(binaries)=={'arcadia','arcadia-server','arcadia-studio'},'build artifact targets')
result=obj('native/trial-1/result.json');start=obj('native/trial-1/started.json')
require(all(result[k]==v for k,v in start.items()),'native start/result')
require(result['exitCode']==0 and result['inputUnchanged'] is True,'native closure/input')
require(result['command']==[executables['arcadia-studio']],'native exact GUI command')
process=obj('native/trial-1/process.json')
require(isinstance(process['pid'],int) and process['pid']>0,'native positive process ID')
require(datetime.fromisoformat(result['start'])<=datetime.fromisoformat(process['observed'])<=datetime.fromisoformat(result['finish']),'native process observation time')
require(result['binary']==result['binaryAfter']==binaries['arcadia-studio'],'GUI binary receipt chain')
require(result['buildReceipt']==pin(raw('build/result.json')) and result['buildInputs']==pin(raw('build/inputs.json')) and result['buildHead']==build['head'],'native build receipt chain')
for field,path in [('helper','native/run-native.py'),('captureHelper','native/capture-snippet.js'),('inputArchive','native/trial-1/input.zip'),('stdout','native/trial-1/stdout.log'),('stderr','native/trial-1/stderr.log')]:
require(result[field]==pin(raw(path)),'native receipt '+field)
require(not raw('native/trial-1/stdout.log') and not raw('native/trial-1/stderr.log'),'native empty streams')
with zipfile.ZipFile(io.BytesIO(raw('native/trial-1/input.zip'))) as fixture:
membership(fixture,result['input'])
for name,p in result['input'].items():require(pin(fixture.read(name))==p and fixture.read(name)==raw('native/input/'+name),'fixture bytes')
captures=manifest['captures']; seen=[]
for name in captures:
capture=obj(name)
require(datetime.fromisoformat(result['start'])<=datetime.fromisoformat(capture['time'])<=datetime.fromisoformat(result['finish']),'capture timestamp')
for image in capture['images']:
path='native/trial-1/'+image['file']
require(path in manifest['members'] and image['width']>0 and image['height']>0,'capture image reference')
require(raw(path).startswith(b'\xff\xd8'),'JPEG signature')
seen.append(path)
images=[n for n in manifest['members'] if n.startswith('native/trial-1/') and n.endswith('.jpg')]
require(len(seen)==len(set(seen)) and set(seen)==set(images),'all raw frames referenced exactly once')
require(len(captures)==manifest['captureCount'] and len(images)==manifest['imageCount'],'capture counts')
original=raw('native/input/project.arcadia')
expected={'project.arcadia':original[:-2]+b'\n include "oa/activities/activity_new_activity.arcadia";\n}\n',
'oa/activities/activity_new_activity.arcadia':b'\noa_activity new_activity "New activity" id "d2b6d784-dea5-433d-aea9-214c604744e5" {\n}\n\n\n'}
require(original.endswith(b'}\n') and expected['project.arcadia'].count(b'position { x 120.0; y 80.0; };')==1,'fixture oracle preconditions')
for bundle in ['saved-before','saved-after']:
if bundle=='saved-after':expected['project.arcadia']=expected['project.arcadia'].replace(b'position { x 120.0; y 80.0; };',b'position { x 190.0; y 80.0; };',1)
prefix='native/'+bundle+'/'
actual={n[len(prefix):]:raw(n) for n in manifest['members'] if n.startswith(prefix)}
require(actual==expected,bundle+' exact source-byte oracle')
receipt=obj('native/bundle-checks/'+bundle+'.result.json')
report=obj('native/bundle-checks/'+bundle+'.stdout.json')
expected_entry=result['cwd'].replace('\\','/').rstrip('/')+'/.local/union-native/'+bundle+'/project.arcadia'
require(len(receipt['command'])==4 and receipt['command'][0]==executables['arcadia'] and receipt['command'][1]=='check' and receipt['command'][2].replace('\\','/')==expected_entry and receipt['command'][3]=='--json',bundle+' exact CLI command')
require(datetime.fromisoformat(receipt['start'])<=datetime.fromisoformat(receipt['finish']),bundle+' CLI time order')
require(receipt['files']=={n:pin(b) for n,b in actual.items()} and receipt['filesUnchanged'] is True,bundle+' file receipts')
require(receipt['binary']==binaries['arcadia'] and receipt['buildReceipt']==pin(raw('build/result.json')),bundle+' CLI build receipt')
require(receipt['producer']==pin(raw('native/check-saved-bundles.py')),bundle+' producer')
require(receipt['exitCode']==0 and report['status']=='checked',bundle+' CLI status')
for field,suffix in [('stdout','.stdout.json'),('stderr','.stderr.log')]:require(receipt[field]==pin(raw('native/bundle-checks/'+bundle+suffix)),bundle+' '+field)
require(not raw('native/bundle-checks/'+bundle+'.stderr.log'),bundle+' empty stderr')
require(len(report['diagnostics'])==4 and all(d['severity']=='warning' for d in report['diagnostics']),bundle+' warnings')
require(receipt['diagnostics']==[{k:d[k] for k in ('severity','code','object_id')} for d in report['diagnostics']],bundle+' diagnostic receipt')
require(not report['changes'] and not report['written'] and report['identities_to_stabilize']==0,bundle+' nonmutating stable-ID check')
summary=obj('native/bundle-checks/summary.json')
require(summary['status']=='PASS' and summary['runs']==['saved-before','saved-after'] and summary['producer']==pin(raw('native/check-saved-bundles.py')),'bundle summary')
print('PASS:',len(manifest['members']),'members;',len(captures),'captures;',len(images),'unaltered frames; 707 build inputs; native closure and both saved-byte/CLI receipt oracles.')
print('No archived code executed. Screenshot interpretation and full parsed-model equality are not machine-verified.')
if __name__=='__main__':main()
@@ -0,0 +1,51 @@
# Transition lifecycle: closed-gate evidence
Closed Cargo-gate checkpoint with eight runs. Native work remains separate.
Run `python verify.py` using
Python 3.10 or newer. The five package files are sufficient; no archived code,
binary, Git command or network operation is executed by verification.
Raw logs/receipts preserve the initial lifecycle-1 fixture admission failure as
a distinct input state. Successful core-transition-2, desktop-workflow-1 and
core-workflows-1 inputs are bound to
acbfb6cc929cb08b599b2e8ccf4d6752e63b84a8 by an independent local comparison of
all 710 captured input hashes with exact committed bytes using Git cat-file.
The readback summary records Git blob identities; its raw Git stream is not
archived. Offline verification checks those blob hashes and input relationships,
not authenticated membership of a remote commit. Receipt HEADs may precede dirty
changes, so per-run input manifests are authoritative for tested source bytes.
Strict all-target workspace Clippy passed on those tested inputs. The first
format check failed only on an inherited tuple layout in diagram/focus.rs;
the raw failure is retained. Commit d46854c2dc719bb8b44a0978102de44ecc3a6b39
reformats that tuple and the second format check passes. Its separate 710-input
inventory is independently compared with that commit; per-run inputCommit
bindings and formattingInputChanges distinguish these source states. Earlier
execution and lint results are not represented as reruns on the formatted
commit. No application behavior change is claimed from formatting evidence.
The Windows workspace build completed on d46854c with the same 710 unchanged
inputs as format-2 and three executable artifact receipts. Those recorded
artifacts identify CLI, server and desktop binaries; binaries are not packaged.
| Gate | Recorded outcome |
| --- | --- |
| lifecycle-1 | Initial fixture admission failed: 6 failures |
| core-transition-2 | 10 passed |
| desktop-workflow-1 | 25 passed, 1,164 filtered out |
| core-workflows-1 | 28 passed |
| clippy-1 | Strict workspace/all-target lint passed |
| format-1 | Initial tuple-format check failed |
| format-2 | Format check passed with empty streams |
| windows-build-1 | Build passed; three executable artifact records |
The manifest maps each run to exact manifests and archives; only byte-identical
input objects are deduplicated. Verification checks safe exact ZIP membership,
hashes/lengths, source inventory and contents, 71 vendored iced_widget files,
helper/start/process/result relationships, time order, commands, exit outcomes,
unchanged input assertions, exact test summary rows and artifact receipt records.
Outer text uses UTF-8 LF before pinning; inner bytes remain raw.
This is process evidence and internal consistency, not a test rerun, proof of
complete coverage, native acceptance or release readiness. Binaries, dependency
caches and credentials are excluded; artifact readback hashes are historical
observations, not independently verified binaries.
@@ -0,0 +1,18 @@
{
"evidence.zip": {
"bytes": 8496343,
"sha256": "5f44ad5d83024a04a873142e77c5b8961c278a19d76492618c3ef2db63801070"
},
"manifest.json": {
"bytes": 14323,
"sha256": "7d48cb7e0788fb409b196f8be94c5947b30c698219bd016141fec136dbce3148"
},
"README.md": {
"bytes": 3017,
"sha256": "7946dd1317e326a6319c8e6399a6177ce251c6c8bc6c6c9cc7eac1517130d2db"
},
"verify.py": {
"bytes": 8239,
"sha256": "13cd285e8c5331bc9b1b7ada1ee7d3ebce4135570e45c047fb56c2f66b558700"
}
}
@@ -0,0 +1,477 @@
{
"schema": 1,
"testedSourceCommit": "acbfb6cc929cb08b599b2e8ccf4d6752e63b84a8",
"formattedSourceCommit": "d46854c2dc719bb8b44a0978102de44ecc3a6b39",
"runs": {
"lifecycle-1": {
"command": [
"cargo",
"test",
"--locked",
"--offline",
"-p",
"oa-core",
"--test",
"transition_lifecycle",
"--message-format=json",
"--",
"--nocapture"
],
"head": "3f9e6a18af96b5cd10940da4093be8da28b9c835",
"exitCode": 101,
"inputCount": 708,
"kind": "cargo",
"inputs": "inputs/c637fea08bdfad17a86c335b58f79666a22b677174a84e817300451a11260a7a.json",
"archive": "inputs/6f54f538a71dfcd81cfe1309e6fa338f27f6e844f2f315983ef3556134e3eaf6.zip",
"testRows": [
[
0,
6,
0,
0,
0
]
],
"testTotals": [
0,
6,
0,
0,
0
]
},
"core-transition-2": {
"command": [
"cargo",
"test",
"--locked",
"--offline",
"-p",
"oa-core",
"--test",
"transition_lifecycle",
"--test",
"transition_counterpart_compatibility",
"--message-format=json",
"--",
"--nocapture"
],
"head": "3f9e6a18af96b5cd10940da4093be8da28b9c835",
"exitCode": 0,
"inputCount": 710,
"kind": "cargo",
"inputs": "inputs/65138ba4ac5843e411a068a8309dfe62c0c4186579aec9e755f6decbed8ecc26.json",
"archive": "inputs/e68321a18e2c1951263744ac9a4e505b18d4bdd520992360f652937fd0abf88b.zip",
"inputCommit": "acbfb6cc929cb08b599b2e8ccf4d6752e63b84a8",
"testRows": [
[
4,
0,
0,
0,
0
],
[
6,
0,
0,
0,
0
]
],
"testTotals": [
10,
0,
0,
0,
0
]
},
"desktop-workflow-1": {
"command": [
"cargo",
"test",
"--locked",
"--offline",
"-p",
"oa-desktop",
"--bin",
"arcadia-studio",
"--message-format=json",
"workflow::",
"--",
"--nocapture"
],
"head": "3f9e6a18af96b5cd10940da4093be8da28b9c835",
"exitCode": 0,
"inputCount": 710,
"kind": "cargo",
"inputs": "inputs/65138ba4ac5843e411a068a8309dfe62c0c4186579aec9e755f6decbed8ecc26.json",
"archive": "inputs/e68321a18e2c1951263744ac9a4e505b18d4bdd520992360f652937fd0abf88b.zip",
"inputCommit": "acbfb6cc929cb08b599b2e8ccf4d6752e63b84a8",
"testRows": [
[
25,
0,
0,
0,
1164
]
],
"testTotals": [
25,
0,
0,
0,
1164
]
},
"core-workflows-1": {
"command": [
"cargo",
"test",
"--locked",
"--offline",
"-p",
"oa-core",
"--test",
"workflows",
"--message-format=json",
"--",
"--nocapture"
],
"head": "3f9e6a18af96b5cd10940da4093be8da28b9c835",
"exitCode": 0,
"inputCount": 710,
"kind": "cargo",
"inputs": "inputs/65138ba4ac5843e411a068a8309dfe62c0c4186579aec9e755f6decbed8ecc26.json",
"archive": "inputs/e68321a18e2c1951263744ac9a4e505b18d4bdd520992360f652937fd0abf88b.zip",
"inputCommit": "acbfb6cc929cb08b599b2e8ccf4d6752e63b84a8",
"testRows": [
[
28,
0,
0,
0,
0
]
],
"testTotals": [
28,
0,
0,
0,
0
]
},
"clippy-1": {
"command": [
"cargo",
"clippy",
"--locked",
"--offline",
"--workspace",
"--all-targets",
"--message-format=json",
"--",
"-D",
"warnings"
],
"head": "acbfb6cc929cb08b599b2e8ccf4d6752e63b84a8",
"exitCode": 0,
"inputCount": 710,
"kind": "cargo",
"inputs": "inputs/65138ba4ac5843e411a068a8309dfe62c0c4186579aec9e755f6decbed8ecc26.json",
"archive": "inputs/e68321a18e2c1951263744ac9a4e505b18d4bdd520992360f652937fd0abf88b.zip",
"inputCommit": "acbfb6cc929cb08b599b2e8ccf4d6752e63b84a8",
"testRows": [],
"testTotals": [
0,
0,
0,
0,
0
]
},
"format-1": {
"command": [
"cargo",
"fmt",
"--all",
"--",
"--check"
],
"head": "acbfb6cc929cb08b599b2e8ccf4d6752e63b84a8",
"exitCode": 1,
"inputCount": 710,
"kind": "cargo",
"inputs": "inputs/65138ba4ac5843e411a068a8309dfe62c0c4186579aec9e755f6decbed8ecc26.json",
"archive": "inputs/e68321a18e2c1951263744ac9a4e505b18d4bdd520992360f652937fd0abf88b.zip",
"inputCommit": "acbfb6cc929cb08b599b2e8ccf4d6752e63b84a8",
"testRows": [],
"testTotals": [
0,
0,
0,
0,
0
]
},
"format-2": {
"command": [
"cargo",
"fmt",
"--all",
"--",
"--check"
],
"head": "d46854c2dc719bb8b44a0978102de44ecc3a6b39",
"exitCode": 0,
"inputCount": 710,
"kind": "cargo",
"inputs": "inputs/feb3e7ff11a74571fcfe290016131165d7be82e83b8e4a4daae1667e8a9feb3c.json",
"archive": "inputs/00a630ff5d3aa838a3d6ba582f3e54ce12fe45ab52569f4da2cc583be1b1f802.zip",
"inputCommit": "d46854c2dc719bb8b44a0978102de44ecc3a6b39",
"testRows": [],
"testTotals": [
0,
0,
0,
0,
0
]
},
"windows-build-1": {
"command": [
"cargo",
"build",
"--locked",
"--offline",
"--workspace",
"--bins",
"--message-format=json"
],
"head": "d46854c2dc719bb8b44a0978102de44ecc3a6b39",
"exitCode": 0,
"inputCount": 710,
"kind": "cargo",
"inputs": "inputs/feb3e7ff11a74571fcfe290016131165d7be82e83b8e4a4daae1667e8a9feb3c.json",
"archive": "inputs/00a630ff5d3aa838a3d6ba582f3e54ce12fe45ab52569f4da2cc583be1b1f802.zip",
"inputCommit": "d46854c2dc719bb8b44a0978102de44ecc3a6b39",
"testRows": [],
"testTotals": [
0,
0,
0,
0,
0
]
}
},
"members": {
"helpers/commit-readbacks.json": {
"bytes": 149015,
"sha256": "d78b2c4182f2b1db7413cc3eaa36614ade32686e520d6e565bab959b36067838"
},
"helpers/gate-transition.py": {
"bytes": 3500,
"sha256": "065a752452e297ba8a000ee69cfae3996b4e6df9b4edbe002f233a66c32e1c04"
},
"inputs/00a630ff5d3aa838a3d6ba582f3e54ce12fe45ab52569f4da2cc583be1b1f802.zip": {
"bytes": 2843714,
"sha256": "00a630ff5d3aa838a3d6ba582f3e54ce12fe45ab52569f4da2cc583be1b1f802"
},
"inputs/65138ba4ac5843e411a068a8309dfe62c0c4186579aec9e755f6decbed8ecc26.json": {
"bytes": 118907,
"sha256": "65138ba4ac5843e411a068a8309dfe62c0c4186579aec9e755f6decbed8ecc26"
},
"inputs/6f54f538a71dfcd81cfe1309e6fa338f27f6e844f2f315983ef3556134e3eaf6.zip": {
"bytes": 2836501,
"sha256": "6f54f538a71dfcd81cfe1309e6fa338f27f6e844f2f315983ef3556134e3eaf6"
},
"inputs/c637fea08bdfad17a86c335b58f79666a22b677174a84e817300451a11260a7a.json": {
"bytes": 118565,
"sha256": "c637fea08bdfad17a86c335b58f79666a22b677174a84e817300451a11260a7a"
},
"inputs/e68321a18e2c1951263744ac9a4e505b18d4bdd520992360f652937fd0abf88b.zip": {
"bytes": 2843705,
"sha256": "e68321a18e2c1951263744ac9a4e505b18d4bdd520992360f652937fd0abf88b"
},
"inputs/feb3e7ff11a74571fcfe290016131165d7be82e83b8e4a4daae1667e8a9feb3c.json": {
"bytes": 118907,
"sha256": "feb3e7ff11a74571fcfe290016131165d7be82e83b8e4a4daae1667e8a9feb3c"
},
"runs/clippy-1/process.json": {
"bytes": 199,
"sha256": "e8aa78d9ea3dd2b623d990a9481e70354707e6aa5a591cb7b93dba5180a90aa9"
},
"runs/clippy-1/result.json": {
"bytes": 1801,
"sha256": "b6b63d0069a0194d4a1fa91f17d2f6a69555087ec6a9224c322891a3b3047db2"
},
"runs/clippy-1/started.json": {
"bytes": 1405,
"sha256": "165e5bb3b433703336dff190ad594ff3438da491d3396398e616dc5bc700e376"
},
"runs/clippy-1/stderr.log": {
"bytes": 456,
"sha256": "e3de1e791b11f30e85b682df843625d4ca6556f7eb298d6a1bf310aa5b38aa30"
},
"runs/clippy-1/stdout.log": {
"bytes": 310455,
"sha256": "e2692121e9c3536fe9f673de7388c393f23e6fe22dc08bb191a37610392dc0f9"
},
"runs/core-transition-2/process.json": {
"bytes": 263,
"sha256": "39b8ea38cfb392a251a3279cb7d4904545702ef10a1fb870b5e866af843b8b3b"
},
"runs/core-transition-2/result.json": {
"bytes": 4667,
"sha256": "3ed748bc46d4e7ebf21881ab6ffea85da6f94a36dde0778068b7a0282212a49d"
},
"runs/core-transition-2/started.json": {
"bytes": 1484,
"sha256": "3cca432872797dddf8de33e0cf9f931274851acdc8002d5d3599a4fd43ca853d"
},
"runs/core-transition-2/stderr.log": {
"bytes": 489,
"sha256": "92fd897c8f9608b06cdca0033a5008a6f00c5c28c4a88b3cba4bcbb3aeb4f986"
},
"runs/core-transition-2/stdout.log": {
"bytes": 43653,
"sha256": "1c35f25e869d6151cbb6de224282a05395caa41d27cd8d2452872c651ba08dbf"
},
"runs/core-workflows-1/process.json": {
"bytes": 202,
"sha256": "b6a5f0a3c4887888198f10d8e93b2422a1559c4b1214162f6e0954a8f0826828"
},
"runs/core-workflows-1/result.json": {
"bytes": 3128,
"sha256": "89dccdd6655ffb2cd792d25ce76e7da258efbd884dc4ab47d0ba48f8aa23ec6e"
},
"runs/core-workflows-1/started.json": {
"bytes": 1413,
"sha256": "a33e662e9743d650f57401b7276b696f97d97b8c5a916c0c783465b02eb3fc3c"
},
"runs/core-workflows-1/stderr.log": {
"bytes": 271,
"sha256": "bb006c1bb74a6ce4d938f3eb0db36bb9179395fbc3a1b42a7dfb3edad691ca46"
},
"runs/core-workflows-1/stdout.log": {
"bytes": 43869,
"sha256": "a51bf33e8e8767c73efcdc176f615801c63ff3ec9e369586f640fc634d60c528"
},
"runs/desktop-workflow-1/process.json": {
"bytes": 223,
"sha256": "3360e8316f62834c74da7c08742e9f2e62856b7a43974f3fd311d95074bfe4cd"
},
"runs/desktop-workflow-1/result.json": {
"bytes": 3170,
"sha256": "a9626e778ea9626fe76080c95faeac312455c7fd6b7511f1b5887da07bb6676a"
},
"runs/desktop-workflow-1/started.json": {
"bytes": 1439,
"sha256": "cf9cee18e2a6cd06d1ed12306eecb79a056f2a85ca3913557a219302f9b650c5"
},
"runs/desktop-workflow-1/stderr.log": {
"bytes": 539,
"sha256": "84734875bfd88145ebb582986937ccaf5fad3fc32ca0b50d03f57554195742e5"
},
"runs/desktop-workflow-1/stdout.log": {
"bytes": 296022,
"sha256": "0ec72412bc1b3976e0c7470853a06efdf99f051aace8dad540fb3afc3a99bae2"
},
"runs/format-1/process.json": {
"bytes": 116,
"sha256": "3ed4ed5a510a0b116a3c0fd2baf3e4db7ff7de66b7df0a0464cc098841fff115"
},
"runs/format-1/result.json": {
"bytes": 1688,
"sha256": "1d5eac33f2174bcb464d89bc858483affe3c73d5016d19db7d0a32412d9ea475"
},
"runs/format-1/started.json": {
"bytes": 1297,
"sha256": "60fcb6b008530a620534fba89c0cb8e143d423fab2dd2fe08a19a72d21eab904"
},
"runs/format-1/stderr.log": {
"bytes": 0,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
"runs/format-1/stdout.log": {
"bytes": 538,
"sha256": "e6eac9f78ecb50fdaa288974ac26df928f6aa437d293d5aaf9ac60190394b88f"
},
"runs/format-2/process.json": {
"bytes": 116,
"sha256": "20c7c2177f85bb157b2cfff08b26c447bebed42eb11e0e7f6b43c6664c4a494d"
},
"runs/format-2/result.json": {
"bytes": 1686,
"sha256": "b220a0401f478cde47f2e1386a311de58e11bf7937a722490ac630ab223ab279"
},
"runs/format-2/started.json": {
"bytes": 1297,
"sha256": "6d87fb899116fed854f2c2f2c358e0810103f462aa2f3f225feaf89b0855af6d"
},
"runs/format-2/stderr.log": {
"bytes": 0,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
"runs/format-2/stdout.log": {
"bytes": 0,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
"runs/lifecycle-1/process.json": {
"bytes": 213,
"sha256": "ef6976e24b19e881d5d435f5dd2d7a54fd228f8013be6cd4a97110a5078f4494"
},
"runs/lifecycle-1/result.json": {
"bytes": 3188,
"sha256": "149a0f3c8747b3c5227916aa88e4b94b77415b0c8d765445e92bfdd201069fb0"
},
"runs/lifecycle-1/started.json": {
"bytes": 1425,
"sha256": "3e3458c4227da8bfbc4d4b6244fe0510687b8c98c2167a4802b2b691431565fa"
},
"runs/lifecycle-1/stderr.log": {
"bytes": 21458,
"sha256": "2e13355db62b04032baa7007f7d7d691eadaad974f4fde8e0bf0ddd4a8b12027"
},
"runs/lifecycle-1/stdout.log": {
"bytes": 42779,
"sha256": "9d545eaba4c279233a344b1e8bc845d0df4dbce5de5c4af9de14d288d3d8d7d8"
},
"runs/windows-build-1/process.json": {
"bytes": 167,
"sha256": "5292a76450540df9e145cd21cc20118b310ed306126da85e893d0c9c221a8564"
},
"runs/windows-build-1/result.json": {
"bytes": 5583,
"sha256": "91b38045319adc1fb9aa0e51d45d79a4be99d2de9aec08a47f73915e3ac9dd79"
},
"runs/windows-build-1/started.json": {
"bytes": 1358,
"sha256": "826453a44cd915065caf1db9c0cc547dca0fa5a2b56594e4a72146b19ffe9e8c"
},
"runs/windows-build-1/stderr.log": {
"bytes": 276,
"sha256": "0a035e700f490f73889da441e363052b34f33b3de5690c30eb1e6c2666c75bc6"
},
"runs/windows-build-1/stdout.log": {
"bytes": 295257,
"sha256": "6ecb53aef4a76dda4174213dd5ba9b03aabc1434577c3141c4ed3e71adf068fa"
}
},
"formattingInputChanges": {
"crates/oa-desktop/src/diagram/focus.rs": {
"before": {
"bytes": 9197,
"sha256": "b88a7c43123b08cf6228d8e32257f6ed7fe361f7278f427ec138e233e1ea6ccf"
},
"after": {
"bytes": 9276,
"sha256": "44e7c13f8f56deb21c62cb7757a412ec8b790344387cebdee983c3a28b6b0160"
}
}
}
}
@@ -0,0 +1,112 @@
"""Read-only, Python-standard-library verification. Never executes archived code."""
from pathlib import Path, PurePosixPath
from datetime import datetime
import hashlib
import io
import json
import re
import zipfile
ROOT = Path(__file__).resolve().parent
def require(ok, message):
if not ok:
raise ValueError(message)
def digest(data):
return {'bytes': len(data), 'sha256': hashlib.sha256(data).hexdigest()}
def members(z, expected):
names = z.namelist()
require(len(names) == len(set(names)), 'duplicate ZIP members')
require(set(names) == set(expected), 'archive membership mismatch')
require(all(not PurePosixPath(n).is_absolute() and '..' not in PurePosixPath(n).parts
and '\\' not in n and ':' not in n for n in names), 'unsafe member')
def main():
inventory = json.loads((ROOT / 'files.json').read_text())
for name, expected in inventory.items():
require(digest((ROOT / name).read_bytes()) == expected, 'external file hash: ' + name)
manifest = json.loads((ROOT / 'manifest.json').read_text())
with zipfile.ZipFile(ROOT / 'evidence.zip') as z:
members(z, manifest['members'])
for name, expected in manifest['members'].items():
require(digest(z.read(name)) == expected, 'member hash: ' + name)
def raw(name): return z.read(name)
def obj(name): return json.loads(raw(name))
checked = set()
for run, spec in manifest['runs'].items():
if spec['kind'] != 'cargo':
continue
prefix = 'runs/' + run + '/'
start, result, process = (obj(prefix + n + '.json') for n in ('started', 'result', 'process'))
require(all(result[k] == v for k, v in start.items()), run + ': start/result relationship')
require(result['command'] == spec['command'] == process['command'], run + ': command')
require(result['head'] == spec['head'], run + ': HEAD')
require(result['exitCode'] == spec['exitCode'] and result['inputsUnchanged'] is True, run + ': outcome')
require(datetime.fromisoformat(result['finish']) >= datetime.fromisoformat(start['start']), run + ': times')
require(result['seconds'] >= 0, run + ': duration')
require(process['pid']>0 and datetime.fromisoformat(start['start'])<=datetime.fromisoformat(process['observed'])<=datetime.fromisoformat(result['finish']), run+': process observation')
require(result['helper'] == digest(raw('helpers/gate-transition.py')), run + ': helper')
for field, file in [('inputs', spec['inputs']), ('archive', spec['archive']), ('stdout', prefix+'stdout.log'), ('stderr', prefix+'stderr.log')]:
require(result[field] == digest(raw(file)), run + ': receipt ' + field)
inputs = obj(spec['inputs'])
require(len(inputs) == result['inputCount'] == spec['inputCount'], run + ': input count')
require(sum(p.startswith('crates/vendor/iced_widget/') for p in inputs) == 71, run + ': vendor count')
pair = (spec['inputs'], spec['archive'])
if pair not in checked:
with zipfile.ZipFile(io.BytesIO(raw(spec['archive']))) as source:
members(source, inputs)
for name, expected in inputs.items():
require(digest(source.read(name)) == expected, run + ': source hash ' + name)
checked.add(pair)
stdout = raw(prefix+'stdout.log').decode('utf-8', errors='replace')
rows = [[int(x) for x in row] for row in re.findall(r'test result: (?:ok|FAILED)\. (\d+) passed; (\d+) failed; (\d+) ignored; (\d+) measured; (\d+) filtered out;', stdout)]
require(rows == spec['testRows'], run + ': exact test summaries')
require([sum(row[i] for row in rows) for i in range(5)] == spec['testTotals'], run + ': test totals')
artifacts = []
for line in stdout.splitlines():
try: item = json.loads(line)
except json.JSONDecodeError: continue
if isinstance(item, dict) and item.get('reason') == 'compiler-artifact' and item.get('executable'):
artifacts.append(item)
require(artifacts == [a['cargo'] for a in result['artifacts']], run + ': artifact receipt relationship')
print(run, 'exit', result['exitCode'], 'test totals', spec['testTotals'])
readbacks=obj('helpers/commit-readbacks.json')
require(set(readbacks)=={spec['inputCommit'] for spec in manifest['runs'].values() if 'inputCommit' in spec}, 'readback commit coverage')
base_inputs=obj(manifest['runs']['core-transition-2']['inputs'])
for commit,readback in readbacks.items():
require(readback['status']=='PASS' and readback['commit']==commit, 'commit readback status')
specs=[spec for spec in manifest['runs'].values() if spec.get('inputCommit')==commit]
success=specs[0];inputs=obj(success['inputs'])
require(all(obj(spec['inputs'])==inputs for spec in specs), 'per-commit input equality')
require(readback['inputs']==digest(raw(success['inputs'])) and len(inputs)==readback['inputCount']==710, '710 readback')
require(set(readback['gitBlobs'])==set(inputs), 'readback blob inventory')
with zipfile.ZipFile(io.BytesIO(raw(success['archive']))) as source:
for name,oid in readback['gitBlobs'].items():
b=source.read(name)
require(hashlib.sha1(b'blob '+str(len(b)).encode()+b'\0'+b).hexdigest()==oid, 'readback blob '+name)
if commit==manifest['formattedSourceCommit']:
changed={p:{'before':base_inputs.get(p),'after':inputs.get(p)} for p in sorted(set(base_inputs)|set(inputs)) if base_inputs.get(p)!=inputs.get(p)}
require(changed==manifest['formattingInputChanges'] and set(changed)=={'crates/oa-desktop/src/diagram/focus.rs'}, 'one-file formatting input difference')
with zipfile.ZipFile(io.BytesIO(raw(manifest['runs']['core-transition-2']['archive']))) as old, zipfile.ZipFile(io.BytesIO(raw(success['archive']))) as new:
path='crates/oa-desktop/src/diagram/focus.rs'
before=old.read(path);after=new.read(path)
compact=b' != (self.context.document, self.context.diagram, self.context.epoch)'
expanded=b' != (\n self.context.document,\n self.context.diagram,\n self.context.epoch,\n )'
require(before.count(compact)==1 and before.replace(compact,expanded,1)==after, 'exact tuple-format-only delta')
for run in ('core-transition-2','desktop-workflow-1','core-workflows-1','clippy-1','format-1'):
if run in manifest['runs']:require(manifest['runs'][run]['inputCommit']==manifest['testedSourceCommit'], 'earlier execution source scope '+run)
for run in ('format-2','windows-build-1'):
if run in manifest['runs']:require(manifest['runs'][run]['inputCommit']==manifest['formattedSourceCommit'], 'formatted source scope '+run)
if 'format-2' in manifest['runs']:
require(not raw('runs/format-2/stdout.log') and not raw('runs/format-2/stderr.log'), 'format2 empty success streams')
if 'windows-build-1' in manifest['runs']:
build=obj('runs/windows-build-1/result.json')
require(build['exitCode']==0 and len(build['artifacts'])==3, 'Windows build outcome')
require({a['cargo']['target']['name'] for a in build['artifacts']}=={'arcadia','arcadia-server','arcadia-studio'}, 'Windows build executable membership')
require(obj(manifest['runs']['lifecycle-1']['inputs'])!=base_inputs, 'initial failure remains distinct')
print('PASS:',len(manifest['runs']),'closed runs;',len(manifest['members']),'members;',len(checked),'input pairs;710 successful input blob bindings.')
print('No archived code executed. Local commit-readback summary is not authenticated commit membership.')
if __name__=='__main__':main()
@@ -0,0 +1,55 @@
# Closed lifecycle checker and footer gates
Run `python verify.py` with Python 3.10 or newer. Verification uses only these
package files and the standard library; it does not execute archived source,
binaries, Cargo, Git or network requests. `prepare.py [CHECKOUT]` reproducibly
packages the explicitly named raw captures if those local originals are present.
The input capsules are source snapshots, not a hermetic toolchain distribution.
Ten closed Cargo runs are preserved: footer-format-1, footer-windows-build-1,
footer-windows-build-2, footer-clippy-1, lifecycle-producer-build-1,
lifecycle-producer-generate-1, lifecycle-producer-build-2, footer-windows-build-3,
footer-format-2 and lifecycle-producer-format-1. Each remains bound
to its original command, source inventory, helper, logs and receipts. The first
footer build has no JSON executable artifact records; the second captures them.
Recorded HEAD d0b7c28 does not identify uncommitted footer/producer source bytes;
the archived per-run input manifests do.
The three final gates use the same 715-file manifest, SHA-256
`b5a62817a1b5df936b9d759241bdfc4e8283901c1d4b540b9e494995e1544d8d`.
Compared with producer build2's captured inputs, only the footer literal changes
from "changes" to "reviewed items". The verifier checks that exact byte delta.
The final GUI build and both format checks pass. Earlier lint and oracle captures
retain their original input scope; they are not presented as reruns of the final
wording. The checker source is unchanged by that label refinement.
Producer build1 and its generation succeeded, but that older checker incorrectly
rejected expected engineering warnings during saved-file admission. Build2
contains the corrected admission logic. The 40 PASS falsifier trials are bound
to build2's exact checker artifact receipt and producer source, not to build1.
Build success alone does not demonstrate checker correctness. The earlier native
admission failure is outside these selected captures, not fabricated as a Cargo
failure or silently converted into successful native evidence.
The oracle archive retains all original fixtures, repeated generation outputs,
mutated specimens, identity receipts, copied build evidence, runner/source,
stdout/stderr and per-trial results. The 40 expected outcomes include accepted
original/deleted/recreated/relinked states and deliberate rejection cases for
retained prose/geometry corruption, source/trace identity drift, missing persisted
IDs, absent identity receipts and receipt overwrite. These are synthetic checker
tests, not native interaction. A PASS summary includes expected negative exits.
`manifest.json` maps every logical raw path to a stored member. Only byte-identical
inputs.json/inputs.zip files are deduplicated; exact run mapping is retained.
`files.json` pins outer files including the reproducible packager. Outer text is
UTF-8 LF; all raw archived bytes are preserved. The verifier checks exact safe ZIP
membership, hashes, nested input contents/counts, helpers, timestamps, receipt and
binary-pin relationships, repeated fixture equality, watched specimen identities,
all 40 outcomes and their recorded rejection text. It rereads evidence rather
than rerunning the model checker, and does not authenticate historical execution.
Native trial1 lacks a closing exit receipt and is excluded. No successful native
deletion, Undo, relinking, save/reopen, or full lifecycle acceptance is claimed.
No runtime binaries, credentials or whole user profile are included. Recorded
absolute paths are historical labels; verification maps them to archive members
without accessing the original machine. Binary hashes remain receipt observations.
@@ -0,0 +1,22 @@
{
"evidence.zip": {
"bytes": 14254708,
"sha256": "663bf83db4ead276063b321fd5e9857e29d2305e6046c2e87dbbac87a366b19c"
},
"manifest.json": {
"bytes": 79071,
"sha256": "5d2be3d68206871a560dd1a83064ff779e8aab6e11302d09aaf1a655502dd38e"
},
"README.md": {
"bytes": 3696,
"sha256": "0e33fe1a85a1ee17ebde57ed7994280dcb95d3a7c4338b78c1f7e0ae9fdcbb7d"
},
"verify.py": {
"bytes": 10908,
"sha256": "1e8ba1cc2759fbe76e14636a37925c9dfce9a6f14bf63569acb76498bd8380ad"
},
"prepare.py": {
"bytes": 3572,
"sha256": "147a63f26b7131076df52057f1cc263dfefdf99d937b2c2a819217a46c56105f"
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,56 @@
"""Repackage the explicitly closed captures; reads raw evidence, never runs Cargo."""
from pathlib import Path
import hashlib, json, sys, zipfile
OUT=Path(__file__).resolve().parent
ROOT=Path(sys.argv[1]).resolve() if len(sys.argv)>1 else OUT.parents[3]
GATES=ROOT/'.local/transition-gates'
ORACLE=ROOT/'.local/transition-native/oracle-falsifiers-1'
RUNS=['footer-format-1','footer-windows-build-1','footer-windows-build-2','footer-clippy-1',
'lifecycle-producer-build-1','lifecycle-producer-generate-1','lifecycle-producer-build-2',
'footer-windows-build-3','footer-format-2','lifecycle-producer-format-1']
def pin(b):return {'bytes':len(b),'sha256':hashlib.sha256(b).hexdigest()}
def main():
payload={};logical={};run_specs={}
def add(name,b):
# Deduplicate only byte-identical input manifests and source archives.
stored='capsules/'+pin(b)['sha256']+Path(name).suffix if Path(name).name in ('inputs.json','inputs.zip') else name
if stored in payload:assert payload[stored]==b
payload[stored]=b;logical[name]=stored
for run in RUNS:
result=json.loads((GATES/run/'result.json').read_bytes())
assert 'finish' in result and result['inputsUnchanged'] is True,run
for f in sorted((GATES/run).iterdir()):
assert f.is_file(),f
add('cargo/'+run+'/'+f.name,f.read_bytes())
helpers=[f for f in GATES.glob('*.py') if pin(f.read_bytes())==result['helper']]
assert helpers,run+' helper unavailable'
helper='helpers/'+result['helper']['sha256']+'.py'
add(helper,helpers[0].read_bytes())
run_specs[run]={k:result[k] for k in ('command','head','exitCode','inputCount')}
run_specs[run]['helper']=helper
for f in sorted(ORACLE.rglob('*')):
if f.is_file():add('oracle/'+f.relative_to(ORACLE).as_posix(),f.read_bytes())
summary=json.loads((ORACLE/'summary.json').read_bytes())
assert summary['status']=='PASS' and summary['trialCount']==40
manifest={'schema':1,'captureCheckout':'d0b7c285018a516c6dc34c196d1359a861ddaeb3',
'cargo':run_specs,'oracleExpectedTrials':summary['trials'],
'oracleOriginalDirectory':str(ORACLE).replace('\\','/'),
'logicalFiles':dict(sorted(logical.items())),
'members':{n:pin(b) for n,b in sorted(payload.items())}}
with zipfile.ZipFile(OUT/'evidence.zip','w',compression=zipfile.ZIP_DEFLATED,compresslevel=9) as z:
for name,b in sorted(payload.items()):
info=zipfile.ZipInfo(name,date_time=(2026,9,13,0,0,0));info.compress_type=zipfile.ZIP_DEFLATED
z.writestr(info,b)
(OUT/'manifest.json').write_bytes((json.dumps(manifest,indent=2)+'\n').encode())
for name in ('README.md','verify.py','prepare.py'):
(OUT/name).write_bytes((OUT/name).read_bytes().replace(b'\r\n',b'\n'))
files={name:pin((OUT/name).read_bytes()) for name in ('evidence.zip','manifest.json','README.md','verify.py','prepare.py')}
(OUT/'files.json').write_bytes((json.dumps(files,indent=2)+'\n').encode())
with zipfile.ZipFile(OUT/'evidence.zip') as z:
assert all(z.read(logical[n])==b for n,b in (
[('cargo/'+r+'/'+f.name,f.read_bytes()) for r in RUNS for f in sorted((GATES/r).iterdir())]+
[('oracle/'+f.relative_to(ORACLE).as_posix(),f.read_bytes()) for f in sorted(ORACLE.rglob('*')) if f.is_file()]))
print(json.dumps(files,indent=2))
print('Exact raw fidelity:',len(logical),'logical files;',len(payload),'stored members')
if __name__=='__main__':main()
@@ -0,0 +1,132 @@
"""Read-only standard-library verification; no archived source/binary execution."""
from pathlib import Path, PurePosixPath
from datetime import datetime
import hashlib, io, json, zipfile
ROOT=Path(__file__).resolve().parent
def pin(b):return {'bytes':len(b),'sha256':hashlib.sha256(b).hexdigest()}
def need(ok,msg):
if not ok:raise ValueError(msg)
def safe(n):return not PurePosixPath(n).is_absolute() and '..' not in PurePosixPath(n).parts and '\\' not in n and ':' not in n
def members(z,expected):
names=z.namelist()
need(len(names)==len(set(names)) and set(names)==set(expected),'archive membership')
need(all(safe(n) for n in names),'unsafe archive member')
def time_order(*values):return list(map(datetime.fromisoformat,values))==sorted(map(datetime.fromisoformat,values))
def main():
files=json.loads((ROOT/'files.json').read_bytes())
need(set(files)=={'evidence.zip','manifest.json','README.md','verify.py','prepare.py'},'outer inventory')
for n,p in files.items():need(pin((ROOT/n).read_bytes())==p,'outer hash '+n)
m=json.loads((ROOT/'manifest.json').read_bytes())
with zipfile.ZipFile(ROOT/'evidence.zip') as z:
members(z,m['members'])
for n,p in m['members'].items():need(pin(z.read(n))==p,'member hash '+n)
logical=m['logicalFiles']
need(all(safe(n) for n in logical) and set(logical.values())==set(m['members']),'logical mapping')
def raw(n):return z.read(logical[n])
def obj(n):return json.loads(raw(n))
def directory(prefix):return {n[len(prefix):]:pin(raw(n)) for n in logical if n.startswith(prefix)}
pairs=set()
for run,spec in m['cargo'].items():
p='cargo/'+run+'/'
start=obj(p+'started.json');result=obj(p+'result.json');process=obj(p+'process.json')
need(all(result[k]==v for k,v in start.items()),run+' start/result')
need(result['command']==process['command']==spec['command'],run+' commands')
need(result['head']==spec['head'] and result['exitCode']==spec['exitCode'],run+' recorded outcome')
need(result['inputsUnchanged'] is True and result['seconds']>=0,run+' source/duration')
need(process['pid']>0 and time_order(start['start'],process['observed'],result['finish']),run+' process times')
for field,n in [('inputs','inputs.json'),('archive','inputs.zip'),('stdout','stdout.log'),('stderr','stderr.log')]:need(result[field]==pin(raw(p+n)),run+' '+field)
need(result['helper']==pin(raw(spec['helper'])),run+' helper')
inputs=obj(p+'inputs.json')
need(len(inputs)==result['inputCount']==spec['inputCount'],run+' input count')
need(sum(n.startswith('crates/vendor/iced_widget/') for n in inputs)==71,run+' vendor inventory')
pair=(logical[p+'inputs.json'],logical[p+'inputs.zip'])
if pair not in pairs:
with zipfile.ZipFile(io.BytesIO(raw(p+'inputs.zip'))) as source:
members(source,inputs)
for n,expected in inputs.items():need(pin(source.read(n))==expected,run+' source '+n)
pairs.add(pair)
artifacts=[]
for line in raw(p+'stdout.log').decode('utf-8',errors='replace').splitlines():
try:item=json.loads(line)
except json.JSONDecodeError:continue
if isinstance(item,dict) and item.get('reason')=='compiler-artifact' and item.get('executable'):artifacts.append(item)
need(artifacts==[a['cargo'] for a in result['artifacts']],run+' artifact relationships')
print(run,'exit',result['exitCode'],'inputs',len(inputs),'artifact records',len(artifacts))
# Successful build1/generation are not the corrected checker admission.
older=obj('cargo/lifecycle-producer-build-1/inputs.json')
newer=obj('cargo/lifecycle-producer-build-2/inputs.json')
need(older==obj('cargo/lifecycle-producer-generate-1/inputs.json'),'generation uses older producer inputs')
source_path='harness/experiments/transition-native-lifecycle-20260913/producer/src/main.rs'
need(older[source_path]!=newer[source_path],'distinct producer revisions')
final_runs=('footer-windows-build-3','footer-format-2','lifecycle-producer-format-1')
final_inputs=obj('cargo/footer-windows-build-3/inputs.json')
final_pin='b5a62817a1b5df936b9d759241bdfc4e8283901c1d4b540b9e494995e1544d8d'
for run in final_runs:
p='cargo/'+run+'/'
need(pin(raw(p+'inputs.json'))['sha256']==final_pin and obj(p+'inputs.json')==final_inputs,run+' final source manifest')
need(obj(p+'result.json')['exitCode']==0 and len(final_inputs)==715,run+' final successful gate')
for run in final_runs[1:]:
need(not raw('cargo/'+run+'/stdout.log') and not raw('cargo/'+run+'/stderr.log'),run+' empty format streams')
final_build=obj('cargo/footer-windows-build-3/result.json')
need(len(final_build['artifacts'])==1 and final_build['artifacts'][0]['cargo']['target']['name']=='arcadia-studio','final GUI artifact')
changed={n for n in set(newer)|set(final_inputs) if newer.get(n)!=final_inputs.get(n)}
need(changed=={'crates/oa-desktop/src/workflow.rs'},'final wording-only file scope')
with zipfile.ZipFile(io.BytesIO(raw('cargo/lifecycle-producer-build-2/inputs.zip'))) as before, zipfile.ZipFile(io.BytesIO(raw('cargo/footer-windows-build-3/inputs.zip'))) as after:
path='crates/oa-desktop/src/workflow.rs';a=before.read(path);b=after.read(path)
old='{} changes · {} boundary choices · {} conflicts · {} warnings'.encode()
new='{} reviewed items · {} boundary choices · {} conflicts · {} warnings'.encode()
need(a.count(old)==1 and a.replace(old,new,1)==b,'exact final footer label delta')
build=obj('cargo/lifecycle-producer-build-2/result.json')
artifact=[a for a in build['artifacts'] if a['cargo']['target']['name']=='transition-native-lifecycle']
need(len(artifact)==1,'corrected checker artifact')
# Oracle copied build capsule must match the independently included gate.
for name in ('result.json','started.json','process.json','inputs.json','inputs.zip','stdout.log','stderr.log'):
need(raw('oracle/build-capture/'+name)==raw('cargo/lifecycle-producer-build-2/'+name),'oracle build capsule '+name)
need(pin(raw('oracle/build-capture/build-helper.py'))==build['helper'],'oracle build helper')
start=obj('oracle/started.json');summary=obj('oracle/summary.json')
need(summary['status']=='PASS' and summary['trialCount']==40,'oracle completion')
need(all(summary['context'][k]==v for k,v in start.items() if k!='start'),'oracle context')
need(start['checker']==artifact[0]['readback'] and start['buildReceipt']==pin(raw('cargo/lifecycle-producer-build-2/result.json')),'oracle checker chain')
need(start['runner']==pin(raw('oracle/oracle-falsifiers.py')),'oracle runner pin')
for path,p in directory('oracle/producer/').items():need(p==newer['harness/experiments/transition-native-lifecycle-20260913/producer/'+path],'oracle producer source '+path)
fixtures=directory('oracle/fixtures/')
need(fixtures==start['fixtureInput']==directory('oracle/repeat-generation-a/')==directory('oracle/repeat-generation-b/'),'deterministic repeated fixtures')
need(directory('oracle/specimens/')==summary['specimens'],'mutation inventory')
rows=summary['trials']
need(rows==m['oracleExpectedTrials'] and len(rows)==len({r['label'] for r in rows})==40,'forty distinct trials')
actual_labels={n.split('/')[2] for n in logical if n.startswith('oracle/trials/')}
need(actual_labels=={r['label'] for r in rows},'exact trial membership')
# Original absolute names are mapped to archive members, never opened.
original=m['oracleOriginalDirectory'].rstrip('/')+'/'
def mapped(path):
norm=path.replace('\\','/')
need(norm.startswith(original),'unmapped trial input path')
rel=norm[len(original):];need(safe(rel),'unsafe trial input mapping')
return 'oracle/'+rel
for row in rows:
label=row['label'];p='oracle/trials/'+label+'/'
r=obj(p+'result.json')
need(r['label']==label and r['exitCode']==r['expectedExitCode']==row['exitCode']==row['expectedExitCode'],label+' outcome')
need(time_order(start['start'],r['start'],r['finish'],summary['finish']),label+' time order')
need(r['checkerBefore']==r['checkerAfter']==start['checker'] and r['runner']==start['runner'] and r['buildReceipt']==start['buildReceipt'],label+' provenance')
for stream in ('stdout','stderr'):need(r[stream]==pin(raw(p+stream+'.log')),label+' '+stream)
need(r['watchedBefore']==r['watchedAfter'],label+' unchanged inputs')
for path,expected in r['watchedBefore'].items():need(pin(raw(mapped(path)))==expected,label+' watched input')
cmd=r['command'];need(cmd[0]==artifact[0]['cargo']['executable'],label+' executable command')
if cmd[1]=='check':
need(mapped(cmd[3]) in logical,label+' check entry')
opts=cmd[4:];need(len(opts)%2==0,label+' option arity')
for flag,path in zip(opts[::2],opts[1::2]):
need(flag in ('--identities','--write-identities') and mapped(path) in logical,label+' identity option')
if r['exitCode']==0:
report=obj(p+'stdout.log')
need(report['status']=='PASS' and report['comparison']=='complete Project equality' and report['state']==cmd[2] and report['entry']==cmd[3],label+' positive report')
if cmd[2].endswith(('-reopened','-noop')):need(report['trace_identity_pinned'] is True and '--identities' in opts,label+' continuity receipt')
if '--write-identities' in opts:need(obj(p+'written-identities.json')==report['new_ids']==obj(mapped(opts[opts.index('--write-identities')+1])),label+' identity write')
else:need(cmd[1]=='generate' and len(cmd)==3,label+' generation command')
stderr=raw(p+'stderr.log').decode('utf-8',errors='replace')
if r['expectedStderrFragment']:need(r['expectedStderrFragment'] in stderr,label+' rejection reason')
elif r['exitCode']==0:need(not stderr,label+' successful stderr')
print('PASS:',len(m['cargo']),'closed Cargo runs; 40 recorded oracle trials;',len(logical),'logical raw files;',len(m['members']),'stored members;',len(pairs),'source capsule pairs.')
print('Offline consistency only: no binaries/source executed; no native lifecycle completion claim.')
if __name__=='__main__':main()
@@ -0,0 +1,45 @@
# Closed native transition lifecycle captures
Keep this directory beside `../checks/`. Run `python verify.py` with Python 3.10
or newer. Verification requires the exact pinned `../checks/evidence.zip` and
these native package files, uses only the standard library, and executes no
archived helper, application, checker, Cargo command or network request.
`prepare.py [CHECKOUT]` rebuilds this archive from the named closed originals.
Trials lifecycle-2/3 capture OA deletion, recreation, first relink, a separate
reopening and no-op check. Trials lifecycle-4/5 capture reverse SA deletion,
Undo/Redo, recreation, first relink, reopening and no-op. All four GUI receipts
close successfully with empty stdout/stderr and unchanged fixture/binary pins.
Trial1 is excluded entirely because it lacked a closing process receipt. This
package does not turn that interrupted history into successful evidence.
Trials 2/3 use footer-windows-build-2; trials 4/5 use footer-windows-build-3 with
the refined "reviewed items" footer. Snapshot checks use the corrected producer
build2. Exact build receipts, helpers, artifact records and complete source
capsules are read from the pinned sibling checks archive; source proof is not
self-contained in the native archive alone. Runtime binaries are excluded.
Every original frame, adjacent metadata, trial file, accepted identity receipt,
saved source snapshot and helper is retained without editing. The launch-pinned
captureHelper refers to the corresponding capture-lifecycle-N.js source. Each
trial's separately recorded capture-function.js is the serialized function used
in the persistent UI session; it is different bytes and was not that launch pin.
The files document the distinction rather than asserting byte equivalence or
authenticated execution. Frame labels/timestamps/application paths record states,
not a complete action invocation log or independent PID attestation. Image/window
dimensions vary; these captures are not all minimum-window checks.
The verifier checks exact safe archive membership, hashes, linked build/source
capsules, trial/helper/stream relationships, frame membership and timestamps,
snapshot helper and corrected-checker provenance, and the recorded complete
Project comparison results. Accepted first-relink IDs match later reopen/no-op
receipts in each direction, and both later saved source bundles are byte-identical
to their first-relink bundle. The producer's full-model result is recorded evidence;
the portable verifier does not rerun its Rust comparisons or derive model equality
from screenshots. Snapshot receipts record start time but no completion timestamp;
only their start can be located in the GUI process interval.
These finite workflows do not establish exhaustive usability/accessibility,
performance, deployment readiness, or behavior on another platform. No credentials,
whole user profile, or runtime binaries are packaged. Outer text is UTF-8 LF;
archived raw bytes and historical local-path strings remain unchanged.
@@ -0,0 +1,22 @@
{
"evidence.zip": {
"bytes": 6888298,
"sha256": "b53253fbe9b98db6a0028ed3629426460e7086c8943be0d13ab5ec540e7e4b68"
},
"manifest.json": {
"bytes": 56348,
"sha256": "f6bd0bb5e3d29d5e47f1dfb8f1cb7747a3c2e12aa62b6e4e81b68e68ce5ccf95"
},
"README.md": {
"bytes": 3019,
"sha256": "01aed34547156bede9c2fea75162c8fecda18203fa575c4cf81e28e440130a95"
},
"verify.py": {
"bytes": 10474,
"sha256": "5f93ff8b266c0c7c3e64630f64a522ff5f8f8bd41d7d0a14cc4856c16a529306"
},
"prepare.py": {
"bytes": 2861,
"sha256": "c1884b10724cc618b58abd84b93c03623ea9e80dbda4e5a8b866fff499e93fca"
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,37 @@
"""Package only closed lifecycle trials 2-5; never runs a GUI or checker."""
from pathlib import Path
import hashlib,json,sys,zipfile
OUT=Path(__file__).resolve().parent
ROOT=Path(sys.argv[1]).resolve() if len(sys.argv)>1 else OUT.parents[3]
SRC=ROOT/'.local/transition-native'
def pin(b):return {'bytes':len(b),'sha256':hashlib.sha256(b).hexdigest()}
def main():
payload={};runs={}
for number in (2,3,4,5):
run=f'lifecycle-{number}';folder=SRC/run
result=json.loads((folder/'result.json').read_bytes())
assert result['exitCode']==0 and 'finish' in result,run+' not closed successfully'
for f in sorted(folder.rglob('*')):
if f.is_file():payload['native/'+f.relative_to(SRC).as_posix()]=f.read_bytes()
helpers={}
for field,name in [('runner',f'run-lifecycle-{number}.py'),('launchCapture',f'capture-lifecycle-{number}.js'),('snapshot',f'check-lifecycle-snapshot-{number}.py')]:
helpers[field]='helpers/'+name;payload[helpers[field]]=(SRC/name).read_bytes()
captures=[n for n,b in payload.items() if n.startswith('native/'+run+'/') and n.endswith('.json') and isinstance(json.loads(b),dict) and 'images' in json.loads(b)]
snapshots={f.name:json.loads((f/'result.json').read_bytes())['command'][2] for f in sorted((folder/'snapshots').iterdir()) if f.is_dir()}
runs[run]={'helpers':helpers,'guiBuild':'footer-windows-build-2' if number<4 else 'footer-windows-build-3','captures':captures,'snapshots':snapshots}
for f in sorted((SRC/'lifecycle-input-1').rglob('*')):
if f.is_file():payload['fixtures/'+f.relative_to(SRC/'lifecycle-input-1').as_posix()]=f.read_bytes()
checks=OUT.parent/'checks'
payload['references/checks-manifest.json']=(checks/'manifest.json').read_bytes()
manifest={'schema':1,'checksEvidence':pin((checks/'evidence.zip').read_bytes()),
'checksRelativePath':'../checks/evidence.zip','originalNativeDirectory':str(SRC).replace('\\','/'),
'runs':runs,'members':{n:pin(b) for n,b in sorted(payload.items())}}
with zipfile.ZipFile(OUT/'evidence.zip','w',compression=zipfile.ZIP_DEFLATED,compresslevel=9) as z:
for n,b in sorted(payload.items()):
info=zipfile.ZipInfo(n,date_time=(2026,9,22,0,0,0));info.compress_type=zipfile.ZIP_DEFLATED;z.writestr(info,b)
(OUT/'manifest.json').write_bytes((json.dumps(manifest,indent=2)+'\n').encode())
for n in ('README.md','verify.py','prepare.py'):(OUT/n).write_bytes((OUT/n).read_bytes().replace(b'\r\n',b'\n'))
files={n:pin((OUT/n).read_bytes()) for n in ('evidence.zip','manifest.json','README.md','verify.py','prepare.py')}
(OUT/'files.json').write_bytes((json.dumps(files,indent=2)+'\n').encode())
print(json.dumps(files,indent=2));print('Native raw members:',len(payload))
if __name__=='__main__':main()
@@ -0,0 +1,115 @@
"""Read-only offline verification. Requires the pinned sibling checks/evidence.zip."""
from pathlib import Path,PurePosixPath
from datetime import datetime
import hashlib,io,json,zipfile
ROOT=Path(__file__).resolve().parent
def pin(b):return {'bytes':len(b),'sha256':hashlib.sha256(b).hexdigest()}
def need(ok,msg):
if not ok:raise ValueError(msg)
def safe(n):return not PurePosixPath(n).is_absolute() and '..' not in PurePosixPath(n).parts and '\\' not in n and ':' not in n
def members(z,expected):
names=z.namelist();need(len(names)==len(set(names)) and set(names)==set(expected),'exact ZIP membership');need(all(safe(n) for n in names),'safe ZIP members')
def ordered(*times):return list(map(datetime.fromisoformat,times))==sorted(map(datetime.fromisoformat,times))
def main():
files=json.loads((ROOT/'files.json').read_bytes())
need(set(files)=={'evidence.zip','manifest.json','README.md','verify.py','prepare.py'},'outer inventory')
for n,p in files.items():need(pin((ROOT/n).read_bytes())==p,'outer hash '+n)
m=json.loads((ROOT/'manifest.json').read_bytes())
need(m['checksRelativePath']=='../checks/evidence.zip','fixed sibling evidence location')
checks_bytes=(ROOT/'../checks/evidence.zip').read_bytes()
need(pin(checks_bytes)==m['checksEvidence'],'pinned sibling checks archive')
with zipfile.ZipFile(ROOT/'evidence.zip') as z,zipfile.ZipFile(io.BytesIO(checks_bytes)) as checks:
members(z,m['members'])
for n,p in m['members'].items():need(pin(z.read(n))==p,'native member hash '+n)
def raw(n):return z.read(n)
def obj(n):return json.loads(raw(n))
def directory(prefix):return {n[len(prefix):]:pin(raw(n)) for n in m['members'] if n.startswith(prefix)}
cm=obj('references/checks-manifest.json');members(checks,cm['members'])
def craw(logical):
n=cm['logicalFiles'][logical];b=checks.read(n);need(pin(b)==cm['members'][n],'checks member '+n);return b
def cobj(n):return json.loads(craw(n))
# Source capsules and receipt helper chains are independently checked here.
builds={};artifacts={};pairs=set()
for run in ('footer-windows-build-2','footer-windows-build-3','lifecycle-producer-build-2'):
p='cargo/'+run+'/';r=cobj(p+'result.json');start=cobj(p+'started.json');process=cobj(p+'process.json')
need(all(r[k]==v for k,v in start.items()) and r['exitCode']==0 and r['inputsUnchanged'] is True,run+' build closure')
need(r['command']==process['command'] and ordered(r['start'],process['observed'],r['finish']),run+' build process')
for field,n in [('inputs','inputs.json'),('archive','inputs.zip'),('stdout','stdout.log'),('stderr','stderr.log')]:need(r[field]==pin(craw(p+n)),run+' receipt '+field)
need(r['helper']==pin(craw(cm['cargo'][run]['helper'])),run+' build helper')
inputs=cobj(p+'inputs.json');need(len(inputs)==r['inputCount'] and sum(n.startswith('crates/vendor/iced_widget/') for n in inputs)==71,run+' input inventory')
pair=(r['inputs']['sha256'],r['archive']['sha256'])
if pair not in pairs:
with zipfile.ZipFile(io.BytesIO(craw(p+'inputs.zip'))) as source:
members(source,inputs)
for n,expected in inputs.items():need(pin(source.read(n))==expected,run+' source '+n)
pairs.add(pair)
records=[]
for line in craw(p+'stdout.log').decode('utf-8',errors='replace').splitlines():
try:item=json.loads(line)
except json.JSONDecodeError:continue
if isinstance(item,dict) and item.get('reason')=='compiler-artifact' and item.get('executable'):records.append(item)
need(records==[a['cargo'] for a in r['artifacts']],run+' artifact records')
expected='transition-native-lifecycle' if run.startswith('lifecycle') else 'arcadia-studio'
selected=[a for a in r['artifacts'] if a['cargo']['target']['name']==expected];need(len(selected)==1,run+' unique artifact')
builds[run]=r;artifacts[run]=selected[0]
original=m['originalNativeDirectory'].rstrip('/')+'/'
def mapped(path):
norm=path.replace('\\','/');need(norm.startswith(original),'snapshot path outside captured native root')
rel=norm[len(original):];need(safe(rel),'unsafe snapshot path');return 'native/'+rel
fixtures=directory('fixtures/');snapshots={};reports={};capture_count=image_count=0
need(set(m['runs'])=={'lifecycle-2','lifecycle-3','lifecycle-4','lifecycle-5'},'closed trial membership')
for run,spec in m['runs'].items():
p='native/'+run+'/';r=obj(p+'result.json');start=obj(p+'started.json');process=obj(p+'process.json')
build=builds[spec['guiBuild']];artifact=artifacts[spec['guiBuild']]
need(all(r[k]==v for k,v in start.items()),run+' start/result relationship')
need(r['exitCode']==0 and r['inputUnchanged'] is True and r['binary']==r['binaryAfter']==artifact['readback'],run+' native closure and binary pin')
need(r['buildReceipt']==pin(craw('cargo/'+spec['guiBuild']+'/result.json')) and r['buildHead']==build['head'],run+' GUI build relationship')
need(r['command']==[artifact['cargo']['executable']] and process['pid']>0,run+' native command/PID')
need(ordered(build['finish'],r['start'],process['observed'],r['finish']),run+' native times')
need(r['helper']==pin(raw(spec['helpers']['runner'])) and r['captureHelper']==pin(raw(spec['helpers']['launchCapture'])),run+' launch helper pins')
need(p+'capture-function.js' in m['members'],run+' actual capture-function record')
need(r['inputs']==pin(raw(p+'inputs.json')) and obj(p+'inputs.json')==fixtures,run+' unchanged fixture bytes')
for stream in ('stdout','stderr'):need(not raw(p+stream+'.log') and r[stream]==pin(raw(p+stream+'.log')),run+' empty '+stream)
images=[]
actual_captures=[n for n in m['members'] if n.startswith(p) and n.endswith('.json') and isinstance(obj(n),dict) and 'images' in obj(n)]
need(set(actual_captures)==set(spec['captures']) and len(actual_captures)==len(spec['captures']),run+' exact metadata coverage')
for n in spec['captures']:
frame=obj(n);need(ordered(r['start'],frame['time'],r['finish']),n+' frame timestamp')
need(frame['window']['app'].replace('\\','/')=='process:'+r['command'][0].replace('\\','/'),n+' window executable path')
for image in frame['images']:
name=p+image['file'];b=raw(name)
need(image['width']>0 and image['height']>0 and (b.startswith(b'\xff\xd8') or b.startswith(b'\x89PNG')),n+' image record')
images.append(name)
actual_images={n for n in m['members'] if n.startswith(p) and n.endswith(('.jpg','.png'))}
need(len(images)==len(set(images)) and set(images)==actual_images,run+' all original images')
capture_count+=len(spec['captures']);image_count+=len(images)
actual_snapshots={n.split('/')[3] for n in m['members'] if n.startswith(p+'snapshots/')}
need(actual_snapshots==set(spec['snapshots']),run+' snapshot inventory')
for label,state in spec['snapshots'].items():
q=p+'snapshots/'+label+'/';s=obj(q+'result.json');ss=obj(q+'started.json');report=obj(q+'stdout.log')
checker=artifacts['lifecycle-producer-build-2']
need(all(s[k]==v for k,v in ss.items()) and s['exitCode']==0 and s['binaryUnchanged'] is True and s['workingSourceUnchanged'] is True,label+' snapshot closure')
need(ordered(r['start'],s['time'],r['finish']),label+' snapshot start during trial')
need(s['binary']==checker['readback'] and s['buildReceipt']==pin(craw('cargo/lifecycle-producer-build-2/result.json')),label+' corrected checker build')
need(s['helper']==pin(raw(spec['helpers']['snapshot'])),label+' snapshot helper pin')
need(s['source']==directory(q+'source/'),label+' exact saved source bytes')
for stream in ('stdout','stderr'):need(s[stream]==pin(raw(q+stream+'.log')),label+' '+stream)
need(not raw(q+'stderr.log'),label+' empty snapshot stderr')
cmd=s['command'];need(cmd[:3]==[checker['cargo']['executable'],'check',state] and mapped(cmd[3])==q+'source/original.arcadia',label+' checker command')
need(report['status']=='PASS' and report['comparison']=='complete Project equality' and report['state']==state and report['entry']==cmd[3],label+' accepted model oracle')
opts=cmd[4:];need(len(opts)%2==0,label+' option pairs')
for flag,path in zip(opts[::2],opts[1::2]):
need(flag in ('--identities','--write-identities'),label+' identity-only options')
need(obj(mapped(path))==report['new_ids'],label+' exact identity receipt')
if state.endswith(('-reopened','-noop')):need('--identities' in opts and report['trace_identity_pinned'] is True,label+' continuity identity pin')
if state.endswith('-relinked'):need('--write-identities' in opts and mapped(report['identity_receipt'])==mapped(opts[opts.index('--write-identities')+1]),label+' initial accepted identities')
snapshots[(run,label)]=s['source'];reports[(run,label)]=report
print(run,'captures',len(spec['captures']),'images',len(images),'accepted snapshots',len(spec['snapshots']))
for side,first,later in [('oa','lifecycle-2','lifecycle-3'),('sa','lifecycle-4','lifecycle-5')]:
base=snapshots[(first,side+'-relinked')];ids=reports[(first,side+'-relinked')]['new_ids']
for phase in ('reopened','noop'):
key=(later,side+'-'+phase)
need(snapshots[key]==base and reports[key]['new_ids']==ids,side+' saved bytes and identity continuity '+phase)
print('PASS: 4 closed trials;',len(snapshots),'accepted full-model receipts;',capture_count,'capture records;',image_count,'original images;',len(pairs),'linked source capsules.')
print('No UI history authentication or checker re-execution; snapshot receipts lack completion timestamps. Frame dimensions vary.')
if __name__=='__main__':main()
@@ -0,0 +1,434 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "autocfg"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
[[package]]
name = "bitflags"
version = "2.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da"
[[package]]
name = "bumpalo"
version = "3.20.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
[[package]]
name = "cfg-if"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "errno"
version = "0.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
dependencies = [
"libc",
"windows-sys",
]
[[package]]
name = "fastrand"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223"
[[package]]
name = "futures-core"
version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e"
[[package]]
name = "futures-task"
version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd"
[[package]]
name = "futures-util"
version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc"
dependencies = [
"futures-core",
"futures-task",
"pin-project-lite",
"slab",
]
[[package]]
name = "getrandom"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099"
dependencies = [
"cfg-if",
"libc",
"r-efi",
]
[[package]]
name = "itoa"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
[[package]]
name = "js-sys"
version = "0.3.104"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e0c1080212aad755ea003d18543e8768dd432c48819efd73a7bf1e39b7a5a3a"
dependencies = [
"cfg-if",
"futures-util",
"wasm-bindgen",
]
[[package]]
name = "libc"
version = "0.2.189"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
[[package]]
name = "linux-raw-sys"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
[[package]]
name = "memchr"
version = "2.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
[[package]]
name = "num-bigint"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93e7820bc0a80a0238e650327316f929ba18d5be054b647490a3a6a339f3e7c0"
dependencies = [
"num-integer",
"num-traits",
]
[[package]]
name = "num-integer"
version = "0.1.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ce2d95d4b3734dc35aa2f45e1aa22cd416814592a4f9d9205e11affd5b8e10b"
dependencies = [
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
]
[[package]]
name = "oa-core"
version = "0.2.0"
dependencies = [
"libc",
"num-bigint",
"num-integer",
"num-traits",
"same-file",
"serde",
"serde_json",
"tempfile",
"thiserror",
"uuid",
]
[[package]]
name = "once_cell"
version = "1.21.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
[[package]]
name = "pin-project-lite"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
[[package]]
name = "proc-macro2"
version = "1.0.107"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
dependencies = [
"proc-macro2",
]
[[package]]
name = "r-efi"
version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
[[package]]
name = "rustix"
version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
dependencies = [
"bitflags",
"errno",
"libc",
"linux-raw-sys",
"windows-sys",
]
[[package]]
name = "rustversion"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
[[package]]
name = "same-file"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
dependencies = [
"winapi-util",
]
[[package]]
name = "serde"
version = "1.0.229"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba"
dependencies = [
"serde_core",
"serde_derive",
]
[[package]]
name = "serde_core"
version = "1.0.229"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.229"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
dependencies = [
"proc-macro2",
"quote",
"syn 3.0.5",
]
[[package]]
name = "serde_json"
version = "1.0.151"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14"
dependencies = [
"itoa",
"memchr",
"serde",
"serde_core",
"zmij",
]
[[package]]
name = "sha1_smol"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d"
[[package]]
name = "slab"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
[[package]]
name = "syn"
version = "2.0.119"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "syn"
version = "3.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12df2e0110f65b775f769bb17ef989067a1d931b2eb822bd4346631eeada89f9"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "tempfile"
version = "3.27.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
dependencies = [
"fastrand",
"getrandom",
"once_cell",
"rustix",
"windows-sys",
]
[[package]]
name = "thiserror"
version = "2.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "2.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af"
dependencies = [
"proc-macro2",
"quote",
"syn 3.0.5",
]
[[package]]
name = "transition-native-lifecycle"
version = "0.1.0"
dependencies = [
"oa-core",
"serde",
"serde_json",
]
[[package]]
name = "unicode-ident"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[[package]]
name = "uuid"
version = "1.26.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5772d71c9be8a8a6ac2117d949c5b224c1b72241bb611d9a3012edcf8af7812"
dependencies = [
"getrandom",
"js-sys",
"serde_core",
"sha1_smol",
"wasm-bindgen",
]
[[package]]
name = "wasm-bindgen"
version = "0.2.127"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b70935747edd64d89de3efa29d73789b806c15798f8e7dca4d8ac356b50ce70"
dependencies = [
"cfg-if",
"once_cell",
"rustversion",
"wasm-bindgen-macro",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.127"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77775f8f3f7217702089053b94958f8f54061a3f663417df76e19cbdcca29bc1"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.127"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e11d33f857dc2fb11b8bc75aee111aa9cbeb12cd9f25efd3d4c2a3dd4e235284"
dependencies = [
"bumpalo",
"proc-macro2",
"quote",
"syn 2.0.119",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.127"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ef64dbcc55df09c7e5a46182d181c2cfa3e925f3da937ea764728b4bbb9dcbf"
dependencies = [
"unicode-ident",
]
[[package]]
name = "winapi-util"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
dependencies = [
"windows-sys",
]
[[package]]
name = "windows-link"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
name = "windows-sys"
version = "0.61.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
dependencies = [
"windows-link",
]
[[package]]
name = "zmij"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
@@ -0,0 +1,12 @@
[package]
name = "transition-native-lifecycle"
version = "0.1.0"
edition = "2024"
publish = false
[workspace]
[dependencies]
oa-core = { path = "../../../../crates/oa-core" }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
@@ -0,0 +1,154 @@
# Native transition lifecycle fixture and oracle
This isolated producer/checker prepares native OA↔SA acceptance work. It does
not automate the application. The project owns no production code. It has its
own Cargo workspace and depends on this checkout's `oa-core` by relative path.
## Commands
From the repository root, with the configured Rust toolchain and compiler gate:
```powershell
cargo run --manifest-path harness/experiments/transition-native-lifecycle-20260913/producer/Cargo.toml -- generate .local/transition-native/lifecycle-input-1
cargo run --manifest-path harness/experiments/transition-native-lifecycle-20260913/producer/Cargo.toml -- check original .local/transition-native/lifecycle-input-1/original.arcadia
```
The parent owns compilation, execution and native verification. These commands
are instructions, not evidence that they have run. Generation refuses to
overwrite an existing deliverable; use a new output directory for a new run.
All fixture identities are reproducible UUIDv5 values of fixed aliases in one
fixed namespace. Generating into a second directory must reproduce file bytes.
`generate OUTDIR` validates and round-trips each expected project, then emits:
- `original.arcadia`, `original.json`, and `manifest.json` with owned removal IDs.
- `oa-deleted.arcadia` / `.json` and `sa-deleted.arcadia` / `.json`.
- `oa-recreated.arcadia` / `.json` and `sa-recreated.arcadia` / `.json`.
- `oa-recreate-root.snippet` and `sa-recreate-root.snippet`.
The deleted/recreated files are independent expected artifacts. Opening them
does not establish that the native deletion, Undo or editing operation works.
Each snippet contains only its replacement root declaration, with the original
alias, name and description, a deterministic new UUID, no children, and no
placement. Generation verifies that inserting it immediately before the deleted
project's final closing brace compiles to the exact expected recreated Project.
`check STATE SAVED_ENTRY [--placements PATH] [--identities PATH]
[--write-identities PATH]` loads the actual saved entry and its reachable
includes through the public disk loader/compiler. It rejects any source bundle
changed by compilation, including declarations with missing UUIDs. An identity
inserted only into the compiler's memory cannot prove a saved identity. States:
| State | Required saved model |
| --- | --- |
| `original` | Entire original Project, including every original UUID |
| `oa-deleted`, `sa-deleted` | Explicit source-owned removals; opposite design and shared definitions retained exactly |
| `oa-recreated`, `sa-recreated` | Deleted state plus only the original source root under its pinned replacement UUID |
| `oa-relinked`, `sa-relinked` | First relink: recreated state plus exactly one new refinement trace, with both existing prose values acknowledged |
| `oa-reopened`, `sa-reopened` | Same relinked model; requires `--identities` with its previously accepted trace UUID |
| `oa-noop`, `sa-noop` | Same relinked model after unchanged retry; requires `--identities` with its previously accepted trace UUID |
The checker compares every Project field, including IDs, aliases, prose, typed
bodies, ownership, item/port contracts, relations, trace baselines, diagram
geometry/routes and artifacts. A mismatch reports its first differing JSON path.
No deletion impact helper, transition planner, or observed post-deletion state
constructs the expected removal set. Relinking expectations construct a new
Trace directly. The source UUID is always the deterministic snippet UUID unless
an explicit `--identities` file supplies another source UUID. Only the first
`*-relinked` check can observe a fresh trace UUID. Use `--write-identities PATH`
on that successful check to write a receipt, then pass that exact receipt using
`--identities PATH` for reopening and no-op checks. Those stages refuse to run
without a pinned trace UUID and reject any replacement of source, trace or
declared placement identities. Receipt writes refuse overwrite and happen only
after complete-model comparison succeeds.
An identity receipt is a JSON object with `source` and, after relinking, `trace`
UUID strings. Optional declared placements add `placement:ALIAS` UUID entries.
An explicitly authored source-only identity file can establish a different
GUI-created root UUID for initial recreation/relinking. Do not re-extract it
from later saves. The checker rejects unused identity entries and reports whether
trace identity was pinned; the first relink establishes identity evidence but
does not itself prove preservation across save/reopen.
Example using the already compiled producer executable:
```powershell
transition-native-lifecycle check oa-relinked saved-first-relink.arcadia --write-identities first-relink.identities.json
transition-native-lifecycle check oa-reopened saved-after-reopen.arcadia --identities first-relink.identities.json
transition-native-lifecycle check oa-noop saved-after-noop.arcadia --identities first-relink.identities.json
```
## Native workflow
| Layer | Root to select | Alias | Diagrams |
| --- | --- | --- | --- |
| OA | Coordinate distress response | `oa_response` | `oa_work`, `oa_overview` |
| SA | Deliver coordinated response | `sa_response` | `sa_work`, `sa_overview` |
Both roots own two work children with an internal report exchange. The SA
children own typed report input/output ports. Shared report definitions have
Flow mechanisms and String payload fields. OA intentionally has no ports.
Both layers have an expanded decomposition and overview diagram, authored
placements, exchange routing/labels, and artifacts on roots, children,
exchanges, placements and shared items. The initial correspondence and its
artifact must disappear when either root is deleted. Shared item definitions,
their fields/evidence, diagrams and the independent System remain.
1. Open `original.arcadia`. Save a native snapshot and check `original`.
2. Graphically delete **Coordinate distress response** (`oa_response`). Save a
snapshot and check `oa-deleted`. Inspect the complete retained SA design.
3. Native Undo, save another snapshot and check `original`. This checks the
actual saved result, including original IDs and all owned records.
4. Delete OA again. In the native Textual model editor, insert the inspected
`oa-recreate-root.snippet` immediately before the project's final `}`. Apply,
save, and check `oa-recreated`. Do not open the generated recreated fixture
as a substitute for this editing step.
5. Select the newly recreated `oa_response` and start OA→SA transition. Choose
existing `sa_response` explicitly. Select **Keep target** for both Name and
Description; do not request child/required dependency creation. The existing
target already has placements. Apply, save, and check `oa-relinked` with
`--write-identities first-relink.identities.json`.
6. Reopen the saved result and save a fresh snapshot; check `oa-reopened` with
`--identities first-relink.identities.json`.
Re-preview unchanged and inspect reuse/no changes. Applying any no-op and
saving must pass `oa-noop` with the same original identity receipt. Do not
substitute another unpinned `oa-relinked` observation for these later checks.
7. For the reverse workflow, start again from `original.arcadia`, delete
`sa_response`, use the SA snippet, and explicitly relink to retained
`oa_response`, using the corresponding `sa-*` checker states.
These instructions separate source recreation in the text editor from relinking
through the graphical transition dialog, avoiding unpredictable canvas placement
side effects. The newly recreated root deliberately has no children or placement.
## Optional explicitly authored source placement
If a separate GUI recreation task intentionally creates a placement, pass
`--placements` with an expectation file shaped like `oa-placement.example.json`. Set its diagram, alias,
position, size and collapsed state to the intended interaction inputs. Only the
new placement UUID is observed during the first check; all its other fields are
compared with the authored expectation. It must reference the new source root
and use the existing source-layer work/overview diagram. Undeclared placements,
changed target geometry and extra artifacts fail comparison. The snippet
workflow requires no placement expectation file. Later checks must supply the
same placement specifications and the saved identity receipt; the placement UUID
is then pinned rather than re-learned from the new saved model.
## Explicit incompleteness and limits
The architecture fixture deliberately omits performer allocations and
process/chain/scenario contexts. Admission requires exactly `unallocated_leaf`
(`allocation`) and `work_without_behavior_context` (no field) on each surviving
leaf work UUID, and `exchange_without_behavior_context` (no field) on each
surviving work exchange UUID. Errors, extra warnings, wrong warning owners and
duplicate warnings fail. System and data contracts are modeled explicitly.
The disk loader and source compiler may report these same engineering warnings.
Their diagnostic multisets must exactly match the validated model warnings by
severity, code, object UUID and field. Additional loading warnings, missing or
duplicate warnings, and all source-loading errors remain failures.
Whole-model comparison and source/JSON round trips do not prove usability,
authored comment/whitespace preservation, native Undo without saving, or HTTP
retry behavior. The parent must record actual native interactions, executable
provenance, saved snapshots and checker results. This tool does not perform
native interaction or publish an acceptance result itself.
@@ -0,0 +1,9 @@
[
{
"diagram": "oa_work",
"alias": "view_coordinate_distress_response",
"position": { "x": 60.0, "y": 60.0 },
"size": { "width": 224.0, "height": 94.0 },
"collapsed": false
}
]
@@ -0,0 +1,914 @@
use oa_core::{
language, source_files,
source_project::{ProjectLimits, SourceBundle, compile_bundle},
validation::{self, Severity},
*,
};
use serde::{Deserialize, Serialize};
use std::{collections::BTreeMap, fs, io::Write, path::Path};
type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
fn id(alias: &str) -> Id {
Id::new_v5(
&Id::from_u128(0xc1192026_0913_5000_8000_000000000028),
alias.as_bytes(),
)
}
#[derive(Serialize)]
struct Side {
prefix: String,
root: Id,
elements: Vec<Id>,
exchange: Id,
placements: Vec<(Id, Id)>,
artifacts: Vec<Id>,
}
struct Fixture {
project: Project,
oa: Side,
sa: Side,
}
fn add(
p: &mut Project,
kind: Kind,
layer: Layer,
alias: &str,
name: &str,
parent: Option<Id>,
) -> Id {
let key = id(alias);
assert!(
p.elements
.insert(
key,
Element {
id: key,
alias: alias.into(),
name: name.into(),
description: String::new(),
parent,
body: ElementBody::new(kind, layer).unwrap(),
}
)
.is_none()
);
key
}
fn artifact(p: &mut Project, owner: Id, alias: &str, name: &str) -> Id {
let key = id(alias);
p.artifact_links.insert(
key,
ArtifactLink {
id: key,
alias: alias.into(),
name: name.into(),
description: "Reviewed evidence must survive opposite-layer deletion.".into(),
owner,
relation: ArtifactRelation::References,
uri: "git:response/design.md".into(),
revision: Some("review-17".into()),
fragment: Some("response-handling".into()),
},
);
key
}
fn port(p: &mut Project, alias: &str, owner: Id, item: Id, output: bool) -> Id {
let key = id(alias);
p.elements.insert(
key,
Element {
id: key,
alias: alias.into(),
name: if output {
"Report output"
} else {
"Report input"
}
.into(),
description: "Retain the typed report contract.".into(),
parent: Some(owner),
body: ElementBody::shared_in(
Layer::Sa,
SharedBody::Port {
contract: if output {
PortContract::FunctionOutput {
outgoing_items: vec![item],
}
} else {
PortContract::FunctionInput {
incoming_items: vec![item],
}
},
legacy: None,
},
),
},
);
key
}
fn side(p: &mut Project, layer: Layer) -> Side {
let (prefix, kind, root_name, send_name, receive_name, description) = match layer {
Layer::Oa => (
"oa",
Kind::Activity,
"Coordinate distress response",
"Report distress",
"Assess incident",
"Coordinate responders while preserving operational responsibility.",
),
Layer::Sa => (
"sa",
Kind::Function,
"Deliver coordinated response",
"Transmit report",
"Classify incident",
"Technical design: preserve independent processing and report contracts.",
),
};
let root = add(
p,
kind,
layer,
&format!("{prefix}_response"),
root_name,
None,
);
p.elements.get_mut(&root).unwrap().description = description.into();
let send = add(
p,
kind,
layer,
&format!("{prefix}_send"),
send_name,
Some(root),
);
let receive = add(
p,
kind,
layer,
&format!("{prefix}_receive"),
receive_name,
Some(root),
);
p.elements.get_mut(&send).unwrap().description =
"Keep the detailed sending responsibility.".into();
p.elements.get_mut(&receive).unwrap().description =
"Keep the detailed receiving responsibility.".into();
let item = add(
p,
Kind::ExchangeItem,
layer,
&format!("{prefix}_report"),
"Incident report",
None,
);
*p.elements.get_mut(&item).unwrap().shared_mut().unwrap() = SharedBody::ExchangeItem {
kind: ExchangeItemKind::Information,
mechanism: ExchangeMechanism::Flow,
supertype: None,
};
add(
p,
Kind::DataField,
layer,
&format!("{prefix}_report_text"),
"Report text",
Some(item),
);
let mut elements = vec![root, send, receive];
let body = if layer == Layer::Sa {
let output = port(p, "sa_send_output", send, item, true);
let input = port(p, "sa_receive_input", receive, item, false);
elements.extend([output, input]);
RelationBody::FunctionalExchange {
source: send,
target: receive,
source_port: output,
target_port: input,
items: vec![item],
}
} else {
RelationBody::Interaction {
source: send,
target: receive,
items: vec![item],
}
};
let exchange_alias = format!("{prefix}_report_delivery");
let exchange = id(&exchange_alias);
p.relations.insert(
exchange,
Relation {
id: exchange,
alias: exchange_alias,
name: "Deliver incident report".into(),
description: "Preserve direction, item identity and endpoint contracts.".into(),
body,
},
);
let mut placements = vec![];
let mut artifacts = vec![
artifact(
p,
root,
&format!("{prefix}_root_evidence"),
"Response rationale",
),
artifact(
p,
send,
&format!("{prefix}_send_evidence"),
"Sending requirement",
),
artifact(
p,
exchange,
&format!("{prefix}_exchange_evidence"),
"Report contract evidence",
),
];
for (suffix, title, projection) in [
(
"work",
"Response decomposition",
ProjectionKind::Decomposition,
),
("overview", "Response overview", ProjectionKind::Overview),
] {
let alias = format!("{prefix}_{suffix}");
let diagram = id(&alias);
let mut nodes = BTreeMap::new();
for (index, (element, short)) in [(root, "root"), (send, "send"), (receive, "receive")]
.into_iter()
.enumerate()
{
let node_alias = format!("{prefix}_{suffix}_{short}");
let node_id = id(&node_alias);
nodes.insert(
node_id,
NodePlacement {
id: node_id,
alias: node_alias,
element,
position: match index {
0 => Position { x: 40., y: 40. },
1 => Position { x: 80., y: 120. },
_ => Position { x: 400., y: 120. },
},
size: if index == 0 {
Size {
width: 660.,
height: 240.,
}
} else {
Size {
width: 224.,
height: 94.,
}
},
collapsed: false,
port_anchors: vec![],
},
);
placements.push((diagram, node_id));
if index == 0 {
artifacts.push(artifact(
p,
node_id,
&format!("{prefix}_{suffix}_layout_evidence"),
"Reviewed layout",
));
}
}
p.diagrams.insert(
diagram,
Diagram {
id: diagram,
alias,
name: format!("{} {title}", layer),
description: "Inspect the response root, its children and their original exchange."
.into(),
layer,
projection,
view_options: Default::default(),
nodes,
routes: vec![EdgeRoute {
edge: exchange,
waypoints: vec![Position { x: 360., y: 167. }],
label: Some(EdgeLabel {
fraction: 0.55,
offset: Position { x: 0., y: -18. },
}),
}],
},
);
}
artifact(
p,
item,
&format!("{prefix}_item_evidence"),
"Shared report definition",
);
Side {
prefix: prefix.into(),
root,
elements,
exchange,
placements,
artifacts,
}
}
fn fixture() -> Fixture {
let mut project = Project::empty();
project.id = id("response_lifecycle");
project.alias = "response_lifecycle".into();
project.name = "Distress response: transition lifecycle".into();
project.description =
"Native acceptance fixture for preserving opposite-layer engineering work.".into();
add(
&mut project,
Kind::System,
Layer::Sa,
"response_system",
"Response support system",
None,
);
let oa = side(&mut project, Layer::Oa);
let sa = side(&mut project, Layer::Sa);
let key = id("response_refinement");
project.traces.insert(
key,
Trace {
id: key,
alias: "response_refinement".into(),
name: "Reviewed response refinement".into(),
description: "Operational and technical prose are intentionally different.".into(),
oa: oa.root,
sa: sa.root,
kind: TraceKind::Refinement,
baseline: Some(TraceBaseline {
oa: project.properties(oa.root).unwrap(),
sa: project.properties(sa.root).unwrap(),
semantic: None,
}),
},
);
artifact(
&mut project,
key,
"response_trace_evidence",
"Review decision",
);
Fixture { project, oa, sa }
}
// The removal oracle enumerates fixture-owned records. No deletion command,
// impact helper, transition planner or observed post-deletion model is used.
fn deleted(f: &Fixture, side: &Side) -> Project {
let mut p = f.project.clone();
for key in &side.elements {
assert!(p.elements.remove(key).is_some());
}
assert!(p.relations.remove(&side.exchange).is_some());
assert!(p.traces.remove(&id("response_refinement")).is_some());
for key in side
.artifacts
.iter()
.copied()
.chain([id("response_trace_evidence")])
{
assert!(p.artifact_links.remove(&key).is_some());
}
for (diagram, node) in &side.placements {
assert!(
p.diagrams
.get_mut(diagram)
.unwrap()
.nodes
.remove(node)
.is_some()
);
}
for suffix in ["work", "overview"] {
let diagram = p
.diagrams
.get_mut(&id(&format!("{}_{suffix}", side.prefix)))
.unwrap();
assert_eq!(diagram.routes.len(), 1);
assert_eq!(diagram.routes[0].edge, side.exchange);
diagram.routes.clear();
}
p
}
fn accepted(p: &Project) -> Result<Vec<validation::Issue>> {
// Intentional isolated architecture: no allocations, processes or scenarios.
// Require the exact warning multiset by record UUID, code and field.
let issues = validation::validate(p);
if issues.iter().any(|i| i.severity != Severity::Warning) {
return Err(format!("Structural validation failed: {issues:?}").into());
}
let mut expected = vec![];
for e in p.elements.values().filter(|e| {
matches!(e.kind(), Kind::Activity | Kind::Function)
&& !p
.elements
.values()
.any(|child| child.parent == Some(e.id) && child.kind() == e.kind())
}) {
expected.push(("unallocated_leaf", Some(e.id), Some("allocation")));
expected.push(("work_without_behavior_context", Some(e.id), None));
}
for r in p.relations.values().filter(|r| {
matches!(
r.kind(),
RelationKind::Interaction | RelationKind::FunctionalExchange
)
}) {
expected.push(("exchange_without_behavior_context", Some(r.id), None));
}
let mut actual = issues
.iter()
.map(|i| (i.code.as_str(), i.element, i.field.as_deref()))
.collect::<Vec<_>>();
expected.sort();
actual.sort();
if expected != actual {
return Err(
format!("Unexpected warnings: expected {expected:?}; actual {actual:?}").into(),
);
}
Ok(issues)
}
fn compile(source: String) -> Result<Project> {
let bundle = SourceBundle {
entry: "project.arcadia".into(),
files: BTreeMap::from([("project.arcadia".into(), source)]),
};
let result = compile_bundle(&bundle, ProjectLimits::default())
.map_err(|e| format!("Source compile: {e:?}"))?;
Ok(result.decoded.project)
}
fn round_trip(p: &Project) -> Result<String> {
accepted(p)?;
let bytes = serde_json::to_vec_pretty(p)?;
compare(p, &persistence::decode(&bytes)?)?;
let source = language::format(p);
compare(p, &compile(source.clone())?)?;
Ok(source)
}
fn recreated(f: &Fixture, side: &Side, new_id: Id) -> Project {
assert!(!f.project.contains(new_id));
let mut p = deleted(f, side);
let mut root = f.project.elements[&side.root].clone();
root.id = new_id;
p.elements.insert(new_id, root);
p
}
fn snippet(f: &Fixture, side: &Side) -> String {
let e = &f.project.elements[&side.root];
let keyword = if side.prefix == "oa" {
"oa_activity"
} else {
"sa_function"
};
format!(
" {keyword} {} {} id \"{}\" {{\n description {};\n }}\n",
e.alias,
serde_json::to_string(&e.name).unwrap(),
id(&format!("{}_recreated_response", side.prefix)),
serde_json::to_string(&e.description).unwrap()
)
}
fn generate(out: &Path) -> Result<()> {
let f = fixture();
let mut files: Vec<(String, Vec<u8>)> = vec![
(
"original.arcadia".into(),
round_trip(&f.project)?.into_bytes(),
),
(
"original.json".into(),
serde_json::to_vec_pretty(&f.project)?,
),
(
"manifest.json".into(),
serde_json::to_vec_pretty(&serde_json::json!({
"fixture": "transition-native-lifecycle-20260913", "oa": &f.oa, "sa": &f.sa,
"trace": id("response_refinement"), "trace_artifact": id("response_trace_evidence"),
"recreated_oa_id": id("oa_recreated_response"), "recreated_sa_id": id("sa_recreated_response")
}))?,
),
];
for side in [&f.oa, &f.sa] {
let removal = deleted(&f, side);
let removal_source = round_trip(&removal)?;
let replacement = recreated(&f, side, id(&format!("{}_recreated_response", side.prefix)));
let replacement_source = round_trip(&replacement)?;
let fragment = snippet(&f, side);
let (body, tail) = removal_source
.rsplit_once('}')
.ok_or("Missing project closing brace")?;
compare(&replacement, &compile(format!("{body}{fragment}}}{tail}"))?)?;
files.extend([
(
format!("{}-deleted.json", side.prefix),
serde_json::to_vec_pretty(&removal)?,
),
(
format!("{}-deleted.arcadia", side.prefix),
removal_source.into_bytes(),
),
(
format!("{}-recreated.json", side.prefix),
serde_json::to_vec_pretty(&replacement)?,
),
(
format!("{}-recreated.arcadia", side.prefix),
replacement_source.into_bytes(),
),
(
format!("{}-recreate-root.snippet", side.prefix),
fragment.into_bytes(),
),
]);
}
if files.iter().any(|(name, _)| out.join(name).exists()) {
return Err(
"Generation refuses to overwrite fixture files; use a fresh output directory".into(),
);
}
fs::create_dir_all(out)?;
for (name, bytes) in files {
fs::OpenOptions::new()
.write(true)
.create_new(true)
.open(out.join(name))?
.write_all(&bytes)?;
}
println!(
"Generated and round-trip verified both directions in {}",
out.display()
);
Ok(())
}
#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
struct PlacementExpectation {
diagram: String,
alias: String,
position: Position,
size: Size,
collapsed: bool,
}
fn first_difference(
expected: &serde_json::Value,
actual: &serde_json::Value,
path: &str,
) -> Option<String> {
use serde_json::Value;
if expected == actual {
return None;
}
match (expected, actual) {
(Value::Object(a), Value::Object(b)) => {
for key in a.keys().chain(b.keys()) {
match (a.get(key), b.get(key)) {
(Some(x), Some(y)) => {
if let Some(diff) = first_difference(x, y, &format!("{path}.{key}")) {
return Some(diff);
}
}
_ => {
return Some(format!(
"{path}.{key}: expected presence {}, actual presence {}",
a.contains_key(key),
b.contains_key(key)
));
}
}
}
}
(Value::Array(a), Value::Array(b)) if a.len() == b.len() => {
for (index, (x, y)) in a.iter().zip(b).enumerate() {
if let Some(diff) = first_difference(x, y, &format!("{path}[{index}]")) {
return Some(diff);
}
}
}
_ => return Some(format!("{path}: expected {expected}, actual {actual}")),
}
Some(format!("{path}: values differ"))
}
fn compare(expected: &Project, actual: &Project) -> Result<()> {
if expected != actual {
return Err(first_difference(
&serde_json::to_value(expected)?,
&serde_json::to_value(actual)?,
"Project",
)
.unwrap_or_else(|| "Project differs".into())
.into());
}
Ok(())
}
fn check(
state: &str,
entry: &Path,
placement_path: Option<&Path>,
identity_path: Option<&Path>,
receipt_path: Option<&Path>,
) -> Result<()> {
let f = fixture();
let identity_expectations: Option<BTreeMap<String, Id>> = identity_path
.map(|path| -> Result<_> { Ok(serde_json::from_slice(&fs::read(path)?)?) })
.transpose()?;
let disk = source_files::load_project(entry, ProjectLimits::default())?;
if disk
.diagnostics
.iter()
.any(|located| located.diagnostic.severity != Severity::Warning)
{
return Err(format!("Source loading errors: {:?}", disk.diagnostics).into());
}
let disk_bundle = disk.reachable_bundle();
let compiled = compile_bundle(&disk_bundle, ProjectLimits::default())
.map_err(|e| format!("Saved source compile: {e:?}"))?;
if compiled.bundle != disk_bundle {
return Err("Saved source is not identity-stable: compilation inserted UUIDs or changed source; save explicit identities before checking".into());
}
let actual = &compiled.decoded.project;
let validated_warnings = accepted(actual)?;
let mut expected_diagnostics = validated_warnings
.iter()
.map(|issue| {
(
issue.severity == Severity::Warning,
issue.code.as_str(),
issue.element,
issue.field.as_deref(),
)
})
.collect::<Vec<_>>();
expected_diagnostics.sort();
// Loading/compilation may return the fixture's intentional engineering
// warnings. Admit exactly the independently validated warning multiset,
// retaining owner, field and severity checks; no extra discovery warning or
// error is silently discarded. Source location spelling is not semantic.
for (stage, diagnostics) in [
("loading", &disk.diagnostics),
("compilation", &compiled.diagnostics),
] {
let mut actual_diagnostics = diagnostics
.iter()
.map(|located| {
let diagnostic = &located.diagnostic;
(
diagnostic.severity == Severity::Warning,
diagnostic.code.as_str(),
diagnostic.object_id,
diagnostic.field.as_deref(),
)
})
.collect::<Vec<_>>();
actual_diagnostics.sort();
if actual_diagnostics != expected_diagnostics {
return Err(format!("Unexpected source {stage} diagnostics: expected {expected_diagnostics:?}; actual {actual_diagnostics:?}").into());
}
}
let mut generated_ids = BTreeMap::<String, Id>::new();
let expected = if state == "original" {
if placement_path.is_some() || identity_path.is_some() || receipt_path.is_some() {
return Err("original does not accept placement/identity options".into());
}
f.project.clone()
} else {
let (prefix, phase) = state
.split_once('-')
.ok_or("State must be original or oa-/sa-deleted/recreated/relinked/reopened/noop")?;
let side = match prefix {
"oa" => &f.oa,
"sa" => &f.sa,
_ => return Err("Unknown source layer".into()),
};
if phase == "deleted" {
if placement_path.is_some() || identity_path.is_some() || receipt_path.is_some() {
return Err("deleted does not accept placement/identity options".into());
}
deleted(&f, side)
} else if matches!(phase, "recreated" | "relinked" | "reopened" | "noop") {
if matches!(phase, "reopened" | "noop")
&& identity_expectations
.as_ref()
.and_then(|ids| ids.get("trace"))
.is_none()
{
return Err("Reopened/noop checks require --identities with the first accepted relink trace UUID".into());
}
let original_root = &f.project.elements[&side.root];
let new_source = match &identity_expectations {
Some(ids) => *ids
.get("source")
.ok_or("Identity expectations must explicitly include source")?,
None => id(&format!("{prefix}_recreated_response")),
};
let roots = actual
.elements
.values()
.filter(|e| e.alias == original_root.alias)
.collect::<Vec<_>>();
if roots.len() != 1 || roots[0].id != new_source || f.project.contains(new_source) {
return Err(
"Expected exactly one recreated root with the original alias and the pinned new UUID (snippet UUID unless --identities supplied)"
.into(),
);
}
generated_ids.insert("source".into(), new_source);
let mut candidate = recreated(&f, side, new_source);
if let Some(path) = placement_path {
let specs: Vec<PlacementExpectation> = serde_json::from_slice(&fs::read(path)?)?;
for spec in specs {
if ![format!("{prefix}_work"), format!("{prefix}_overview")]
.contains(&spec.diagram)
{
return Err("A new source placement must use its existing source-layer work/overview diagram".into());
}
let diagram = id(&spec.diagram);
let nodes = actual
.diagrams
.get(&diagram)
.ok_or("Expected diagram is missing")?
.nodes
.values()
.filter(|node| node.alias == spec.alias)
.collect::<Vec<_>>();
if nodes.len() != 1 || f.project.contains(nodes[0].id) {
return Err(
"Expected one newly identified declared source placement".into()
);
}
let receipt_key = format!("placement:{}", spec.alias);
let key = match &identity_expectations {
Some(ids) => *ids.get(&receipt_key).ok_or(
"Identity receipt lacks an explicitly declared placement UUID",
)?,
None => nodes[0].id,
};
if nodes[0].id != key {
return Err(
"Source placement UUID changed from its identity expectation".into(),
);
}
if candidate
.diagrams
.get_mut(&diagram)
.unwrap()
.nodes
.insert(
key,
NodePlacement {
id: key,
alias: spec.alias.clone(),
element: new_source,
position: spec.position,
size: spec.size,
collapsed: spec.collapsed,
port_anchors: vec![],
},
)
.is_some()
{
return Err("Duplicate placement expectation".into());
}
generated_ids.insert(receipt_key, key);
}
}
if phase != "recreated" {
if actual.traces.len() != 1 {
return Err("Relinking must create exactly one trace".into());
}
let actual_key = actual.traces.values().next().unwrap().id;
let key = identity_expectations
.as_ref()
.and_then(|ids| ids.get("trace"))
.copied()
.unwrap_or(actual_key);
if actual_key != key {
return Err(
"Refinement trace UUID changed from its accepted identity receipt".into(),
);
}
if f.project.contains(key) || candidate.contains(key) {
return Err("Expected a fresh trace UUID".into());
}
let (oa, sa) = if prefix == "oa" {
(new_source, f.sa.root)
} else {
(f.oa.root, new_source)
};
candidate.traces.insert(
key,
Trace {
id: key,
alias: "trace_oa_response_sa_response".into(),
name: format!(
"{} ↔ {}",
candidate.elements[&oa].name, candidate.elements[&sa].name
),
description: String::new(),
oa,
sa,
kind: TraceKind::Refinement,
baseline: Some(TraceBaseline {
oa: candidate.properties(oa).unwrap(),
sa: candidate.properties(sa).unwrap(),
semantic: None,
}),
},
);
generated_ids.insert("trace".into(), key);
}
candidate
} else {
return Err("Unknown lifecycle phase".into());
}
};
// No actual body, prose, ownership, geometry, alias or baseline is copied
// into the expected model: only explicitly permitted fresh identities.
compare(&expected, actual)?;
round_trip(&expected)?;
if let Some(ids) = &identity_expectations {
for (label, expected_id) in ids {
if generated_ids.get(label) != Some(expected_id) {
return Err(format!("Unused or mismatching identity expectation: {label}").into());
}
}
}
if let Some(path) = receipt_path {
// Publish a receipt only after complete-model validation succeeds. Never
// replace earlier accepted identity evidence with a new observation.
fs::OpenOptions::new()
.write(true)
.create_new(true)
.open(path)?
.write_all(&serde_json::to_vec_pretty(&generated_ids)?)?;
}
println!(
"{}",
serde_json::to_string_pretty(&serde_json::json!({
"status": "PASS", "state": state, "entry": entry,
"comparison": "complete Project equality", "new_ids": generated_ids,
"trace_identity_pinned": identity_expectations.as_ref().is_some_and(|ids| ids.contains_key("trace")),
"identity_receipt": receipt_path,
"recreated_id_matches_snippet": generated_ids.get("source").map(|key|
*key == id(if state.starts_with("oa-") { "oa_recreated_response" } else { "sa_recreated_response" }))
}))?
);
Ok(())
}
fn run() -> Result<()> {
let args = std::env::args().skip(1).collect::<Vec<_>>();
match args.as_slice() {
[command, out] if command == "generate" => generate(Path::new(out)),
[command, state, entry, options @ ..] if command == "check" => {
if options.len() % 2 != 0 { return Err("Every check option requires a path".into()); }
let (mut placements, mut identities, mut receipt) = (None, None, None);
for pair in options.chunks_exact(2) {
let slot = match pair[0].as_str() {
"--placements" => &mut placements,
"--identities" => &mut identities,
"--write-identities" => &mut receipt,
_ => return Err(format!("Unknown check option: {}", pair[0]).into()),
};
if slot.replace(Path::new(&pair[1])).is_some() { return Err("Duplicate check option".into()); }
}
check(state, Path::new(entry), placements, identities, receipt)
},
_ => Err("Usage: transition-native-lifecycle generate OUTDIR | check STATE SAVED_ENTRY [--placements PATH] [--identities PATH] [--write-identities PATH]".into()),
}
}
fn main() {
if let Err(error) = run() {
eprintln!("FAIL: {error}");
std::process::exit(1);
}
}
@@ -0,0 +1,102 @@
# Independent bounded transition review
Reviewer: `transition_picker_review`, 2026-09-22. This review inspected code,
original native screenshots and closed-run/snapshot receipts. The reviewer did
not operate the application, build it, rerun the checker, or alter the specimens.
Only this review note was written. The parent owns execution and publication.
The reviewed production baseline is `d0b7c285018a516c6dc34c196d1359a861ddaeb3`
plus the captured, initially uncommitted footer changes in
`crates/oa-desktop/src/workflow.rs`. HEAD alone does not identify those changes.
Lifecycle trials 2 and 3 identify GUI SHA-256
`3813e79d84cf4a83f2290639b93c3daaa8de427b0268b288f87e4f535fa1aea2`;
trials 4 and 5 identify final-footer GUI SHA-256
`f0bb021e350bcc55435d29e3daf6a4b3dd7389620df99f5d169f9cb669fd53d0`.
Exact build/source inventories belong to the accompanying evidence packages.
## Code and evaluator findings
The picker shares the core layer/category/kind predicate, preserves selected
identity independently of search results, and keeps query state outside the
request and recovery schema. Query events return before replanning. Compatibility
does not imply that dependencies or the complete transition plan are valid.
Distinct traced IDs, rather than trace-record count, determine inferred reuse;
ambiguous traces remain an explicit decision. Manual tracing remains separate.
The desktop tests compare the whole request, plan and recovery snapshot during
search, including generated preview identities. The initial second-query test
used a target ID rather than a second rendered source card; native corrected
picker frame 17 separately demonstrates two visible source cards with independent
queries. These checks do not prove keyboard access or every focus transition.
The core lifecycle tests and [native producer/checker](producer/src/main.rs)
construct expected deletions from fixture-owned identities, not a deletion
impact helper or observed deletion result. Expected recreation adds only the
replacement root. Expected relinking adds a directly constructed refinement
trace and reviewed baseline while preserving target prose, bodies, ownership,
geometry, routes and artifacts. Whole-Project equality detects incidental changes.
First-relink admission may observe the fresh trace UUID; reopen/no-op admission
requires the previously written identity receipt and rejects replacement IDs.
Source loading rejects compiler-inserted identities. Round trips compare the
decoded model; they do not promise original comments or formatting preservation.
No blocking conservation-oracle defect was found in the inspected paths.
## Native observations
Trial/frame labels below refer to original `*-0.jpg` files and matching JSON
under `.local/transition-native/` at capture time. Receipt evidence is distinct
from what screenshots visibly establish. Capture helpers and checksums provide
provenance context, not authenticated historical action logs.
| Originals inspected | Visible observation |
| --- | --- |
| `picker-baseline-1` 10–11 | Activity menu includes incompatible ports; selecting one yields a kind conflict. |
| `picker-corrected-2` 06–07, 10–12, 15, 17–19, 22–23 | Six compatible Functions are sorted; alias filtering preserves current selection until explicit selection; zero results preserve the selected target; two cards retain independent queries; field review remains required; clearing search retains Keep target; Cancel returns to the saved-status view. |
| `lifecycle-2` 05, 11–12 | OA deletion is visible; a replacement root declaration is entered and applied through Textual model. |
| `lifecycle-2` 17, 21, 27–30 | Recreation initially offers Create rather than silently relinking. Selecting the retained SA target requires Name and Description decisions. Sticky guidance advances from Name/two conflicts to Description/one, then clears after both Keep target decisions. Seven warnings do not block Apply. |
| `lifecycle-2` 31–32 | Apply displays the retained SA root, children and exchange; subsequent frame reports all changes saved. |
| `lifecycle-3` 02, 06–08 | A fresh process opens the saved model; unchanged transition infers reuse without field conflicts; Apply and save return to the retained SA design. |
| `lifecycle-4` 12, 14, 16, 22–23 | SA deletion, Undo restoration, Redo deletion and replacement-root entry/application are visible. |
| `lifecycle-4` 28, 30–38 | Reverse transition defaults to Create; alias search offers the retained OA Activity; explicit selection requires both prose decisions. Persistent guidance follows two/one/zero conflicts. Apply displays the retained OA decomposition and save reports completion. The final `reviewed items` wording is visible. |
| `lifecycle-5` 02, 07–09 | A fresh process opens the retained OA design; unchanged SA-to-OA review infers the accepted counterpart without conflicts; Apply and save retain the visible OA design. |
Closed GUI receipts for lifecycle trials 2–5 record exit 0, empty stdout/stderr,
and unchanged executable hashes. Snapshot receipts record exit 0 for OA deletion,
recreation and relink; OA reopen/no-op; and SA deletion, original-state Undo,
deletion-state Redo, recreation, relink and subsequent reopen/no-op. Checks operate on saved source copies
and report the working source unchanged. SA Redo's source hash equals deletion's.
OA relink, reopen and no-op snapshots have identical hashes for both
`original.arcadia` and `traceability.arcadia`. Reopen/no-op use source
`8eb83649-2810-58e1-94cd-71e25422658e` and accepted trace
`856eb758-33fc-4ebf-9a87-22ad40a89952`. SA first relink establishes source
`074eab66-7092-5e7e-8b83-b31b95afdbcb` and trace
`acc7969b-08c3-43f4-a31d-9140ca873ffe`. Trial 5 passes that exact first-relink
receipt to both saved-source checks. Its reopen and no-op source hashes match the
first relink for both files (`original.arcadia`: `e75b4cb55e5b205bd5784a1f35573931f1e64491b646f4ffb241f90288447b5d`;
`traceability.arcadia`: `5046e2d2731c5d8fcbcb7fe2deede0677ec61de03b85890852fa20f778c57552`).
This supports conservation and persisted identity across these finite reopen and
unchanged-review workflows in both directions. No new blocking finding arose
from the reviewed lifecycle evidence.
## Feedback correction and remaining limits
Corrected picker frames 12/15 initially left Apply disabled while its explanation
was below the fold. The reviewed footer change moves counts outside the scrolling
body and derives guidance from the same conflicts and destination validity used
by Apply. It adds no persisted state and weakens no validation. Generic guidance
was corrected from “adjust mapping or scope” because operation-omission rationale
and component-port decisions require other controls. Final native trial 4 shows
the Name/Description guidance and `reviewed items` count. Destination, boundary
and general-conflict guidance were checked against code, not exercised in these
native lifecycle frames.
All inspected originals are 1442×932. Resize attempts did not establish a smaller
viewport, regardless of frame filenames; minimum-window behavior is unverified.
The inherited picker is pointer-dependent. This review does not establish full
keyboard/screen-reader accessibility, packaged launch, other platforms, arbitrary
models, implicit creation applied natively, or relink Undo/Redo. Recreated roots
were authored through Textual model and intentionally have no new placement;
the empty source canvas is consistent with that fixture. Screenshots alone do
not establish complete model conservation: the independent saved-source comparisons
provide that bounded evidence.
@@ -0,0 +1,41 @@
# Transition picker: native comparison evidence
Run `python verify.py` using Python 3.10 or newer. This five-file package supports
read-only verification using the standard library. It does not execute archived
helpers, binaries, Git commands or network calls. Outer text is UTF-8 LF before
hashing; archived files retain exact original bytes.
All three trials, launch helpers, copied release1 inputs and original frames are
preserved. Baseline picker-baseline-1 used the f714a25 GUI and closed with exit 0.
Corrected picker-corrected-1 exited immediately with 3221225781 and empty logs;
the operator attributes this to omitted runtime environment setup. That cause
is not independently proven by empty logs. Corrected picker-corrected-2 used
the d46854c GUI, ran from 15:22:22 to 15:27:13 UTC, and closed with exit 0 and
empty logs. Its frame 02 is a loading state, not proof that loading finished.
The capture-snippet.js source was retained from a previous persistent session.
It was not pinned in these trials' launch receipts. Archiving it now does not
establish the exact capture function executed during the trials. Frame JSON
records timestamps, a window/application path and image references; it is not
an action invocation log or independent PID attestation. No frame is fabricated,
renumbered, cropped or omitted, including auxiliary dialog images.
Both exact GUI build result receipts are included. Full build source evidence
remains external: the sibling transition-lifecycle-20260913 Cargo package at
commit fe43730bec196cc6ebbc06dcf827169f04c120f0 covers the corrected d46854c
build, and the ordinary-text-native-20260913 package at
commit 3f9e6a18af96b5cd10940da4093be8da28b9c835 includes the baseline f714a25
build/source evidence. The manifest pins those related evidence ZIPs. This is
not a self-contained source or runtime distribution; the verifier does not
require those external packages for its local receipt/frame checks.
Verification checks exact safe ZIP membership/hashes, launch helper pins,
start/process/result timing, expected exit outcomes, empty streams, GUI binary
and build receipt relationships, identical copied input membership/bytes across
all trials, and complete frame/image references within process intervals. Binary
hashes are receipt observations; binaries are excluded. Screenshots require
separate interpretation and do not prove action execution, complete model
equality, persistence or full usability. The included independent-review.md is
a parent-consolidated record of separate human-readable review observations.
No credentials or whole user profile are
packaged; raw receipts retain original runtime path strings.
@@ -0,0 +1,18 @@
{
"evidence.zip": {
"bytes": 3412321,
"sha256": "c801eda9e33cd796a3594e73c34d6d29691597552a68307d2dc5a5e113c1e470"
},
"manifest.json": {
"bytes": 26082,
"sha256": "f80092a483a00e17c3ffcdbf2dd71480ed6ead4d003248de2f9ef708104cfba1"
},
"README.md": {
"bytes": 2673,
"sha256": "86251ca15d4950cd0b037ba05b63433877608c1d0d6424fedef8ff4e34340141"
},
"verify.py": {
"bytes": 5143,
"sha256": "23c5040f91f29e243e65ad31b4f8f82f7ad1d692efd862b1bc87450c31e5ed5f"
}
}
@@ -0,0 +1,614 @@
{
"schema": 1,
"runs": {
"picker-baseline-1": {
"helper": "native/run-picker-baseline.py",
"build": "build/baseline-result.json",
"exitCode": 0,
"captures": [
"native/picker-baseline-1/01-start.json",
"native/picker-baseline-1/02-open-pending.json",
"native/picker-baseline-1/03-open-dialog.json",
"native/picker-baseline-1/04-teaching-path.json",
"native/picker-baseline-1/05-teaching-opened.json",
"native/picker-baseline-1/06-teaching-ready.json",
"native/picker-baseline-1/07-source-selected.json",
"native/picker-baseline-1/08-source-actions.json",
"native/picker-baseline-1/09-transition-dialog.json",
"native/picker-baseline-1/10-unfiltered-counterparts.json",
"native/picker-baseline-1/11-incompatible-selection.json",
"native/picker-baseline-1/12-cancel-preserved.json"
]
},
"picker-corrected-1": {
"helper": "native/run-picker-corrected.py",
"build": "build/corrected-result.json",
"exitCode": 3221225781,
"captures": []
},
"picker-corrected-2": {
"helper": "native/run-picker-corrected-2.py",
"build": "build/corrected-result.json",
"exitCode": 0,
"captures": [
"native/picker-corrected-2/01-opened.json",
"native/picker-corrected-2/02-teaching-reopened.json",
"native/picker-corrected-2/03-loaded.json",
"native/picker-corrected-2/04-source-selected.json",
"native/picker-corrected-2/05-actions.json",
"native/picker-corrected-2/06-transition-dialog.json",
"native/picker-corrected-2/07-compatible-menu.json",
"native/picker-corrected-2/08-menu-dismissed.json",
"native/picker-corrected-2/09-search-focus.json",
"native/picker-corrected-2/10-alias-query.json",
"native/picker-corrected-2/11-filtered-menu.json",
"native/picker-corrected-2/12-existing-selection.json",
"native/picker-corrected-2/13-refocus-search.json",
"native/picker-corrected-2/14-query-selected.json",
"native/picker-corrected-2/15-no-results-selection-retained.json",
"native/picker-corrected-2/16-parent-search-focus.json",
"native/picker-corrected-2/17-independent-card-queries.json",
"native/picker-corrected-2/18-field-review.json",
"native/picker-corrected-2/19-keep-target-reviewed.json",
"native/picker-corrected-2/20-reviewed-search-focus.json",
"native/picker-corrected-2/21-reviewed-query-selected.json",
"native/picker-corrected-2/22-cleared-search-review-retained.json",
"native/picker-corrected-2/23-cancel-restores-saved.json"
]
}
},
"members": {
"build/baseline-result.json": {
"bytes": 5532,
"sha256": "5e9e227c66a4b71ade734b8ebeb12070b8a5c64a9a6cf9d8d0b6d70379d4ffc2"
},
"build/corrected-result.json": {
"bytes": 5583,
"sha256": "91b38045319adc1fb9aa0e51d45d79a4be99d2de9aec08a47f73915e3ac9dd79"
},
"native/capture-snippet.js": {
"bytes": 1579,
"sha256": "55279ed080289ccf5ba482cb24b5a807669a9360012e2d87f794d4465227cb2c"
},
"native/independent-review.md": {
"bytes": 1898,
"sha256": "c59d2ff78635f94f3f225c27999b69d24c11977c89026fc8a7331ddbd160c8c6"
},
"native/input/release1/README.md": {
"bytes": 11137,
"sha256": "113cfb1cf38a98b51bff60442d86a35d636a3d80ef206bece7d53d0aae67f2d5"
},
"native/input/release1/evidence/repair-check.md": {
"bytes": 1419,
"sha256": "f4db17bdca4ecbb1bf0caef01b2da5f1f7f798ab3152b648ce6dd8696a6a68e5"
},
"native/input/release1/expected-diagnostics.json": {
"bytes": 288,
"sha256": "3700393d76b940cfc076bf52861bd7b1f43370f8eabce3594531bd0bf1fc851e"
},
"native/input/release1/model.json": {
"bytes": 134520,
"sha256": "ef0ef4006c3e09d5db6e1b621256b3f7d6642f3a7b09cc34607cfbcca830c727"
},
"native/input/release1/model.rs": {
"bytes": 28762,
"sha256": "15f30f6bde655808fe754665c26d560e36f2a9bad340f346b09137d0d05045f5"
},
"native/input/release1/oa/activities/activity_oa_restore_work.arcadia": {
"bytes": 1130,
"sha256": "21cbd7176af8fad68453306f2371ff03e127a994b8796cdf044567332de9686f"
},
"native/input/release1/oa/behavior/process_oa_assessment_procedure.arcadia": {
"bytes": 550,
"sha256": "23cabc213c264a16c8595414db6acb09248c1cef9cd699f1b0316e5bb82c062a"
},
"native/input/release1/oa/behavior/process_oa_field_procedure.arcadia": {
"bytes": 1178,
"sha256": "9c52086759b7344b4809ceb8d372f65647b16fc24153a03b887faa378c37fe96"
},
"native/input/release1/oa/behavior/process_oa_response.arcadia": {
"bytes": 2560,
"sha256": "675798bd19e6b40d03e7076b0e59b110cdaaab7b6d16c47c3ac80fa960072e84"
},
"native/input/release1/oa/behavior/scenario_oa_conversation.arcadia": {
"bytes": 1515,
"sha256": "e217e5394a0771dc16206ed9437a231da27905d2f19be64b3f48281c0a32fb6b"
},
"native/input/release1/oa/behavior/scenario_oa_full_scenario.arcadia": {
"bytes": 1121,
"sha256": "c5a285f2d7c61bd2a136107f267fbf9cddb1cba3f63ff36a6f53f7ab98f33df5"
},
"native/input/release1/oa/behavior/state_machine_oa_monitor.arcadia": {
"bytes": 1358,
"sha256": "e7349e1e5336282eed3f5a103129db33d3ae4ed500788ee7129e8fc193ac57f6"
},
"native/input/release1/oa/capabilities.arcadia": {
"bytes": 630,
"sha256": "26a3d9b615a3251ff010e0b47127f2d16bc2dcd45ed95967208b0076d725843a"
},
"native/input/release1/oa/model.arcadia": {
"bytes": 531,
"sha256": "069a5eaaf525089935cc9d73ae7b916866ed78c6534705d75739650ecdea7ada"
},
"native/input/release1/oa/participants.arcadia": {
"bytes": 1580,
"sha256": "591a75a9ecad280302be4d4ae0171ad616377ed11ead5e233848ca2dc0a71819"
},
"native/input/release1/oa/types.arcadia": {
"bytes": 554,
"sha256": "f02c391d584674c879f662a418abba3a220f1fee88471ca49322ac9873de628d"
},
"native/input/release1/project.arcadia": {
"bytes": 1958,
"sha256": "b2674e6bf77cfe0f6f0ba79c81d2e96d92f39c02ca2c6266d80e91723bfb9fda"
},
"native/input/release1/sa/behavior/process_sa_assessment_procedure.arcadia": {
"bytes": 559,
"sha256": "5c414fbdaabf9f9e402b0441fcacf1cacf4dbbc6c822162f028de35abf59be2d"
},
"native/input/release1/sa/behavior/process_sa_field_procedure.arcadia": {
"bytes": 1187,
"sha256": "ece846fdf93857e71a9ab0efcbdf64a5c41c71180f62afa3f79077fffb53b8f2"
},
"native/input/release1/sa/behavior/process_sa_response.arcadia": {
"bytes": 2569,
"sha256": "af7fdda280a917cf626f6e077162d481f9aaad280f8e81330c07d7fe384b1c52"
},
"native/input/release1/sa/behavior/scenario_sa_conversation.arcadia": {
"bytes": 1847,
"sha256": "01339699ec3ee409418d9bb232542f2bcd9419f1e67b183e77700c56bb9f71d2"
},
"native/input/release1/sa/behavior/scenario_sa_full_scenario.arcadia": {
"bytes": 1116,
"sha256": "5dce6b00a3a441162e8dee3e23d9ca5ab7bed4a24de669b1fba03664a3086dda"
},
"native/input/release1/sa/behavior/state_machine_sa_monitor.arcadia": {
"bytes": 1358,
"sha256": "17dbcfa5bbfd10fc8e23a325fc950ce853c219b4aa89c74f96278cbb3f34acc8"
},
"native/input/release1/sa/capabilities.arcadia": {
"bytes": 630,
"sha256": "81706a0ca9d7db2aa0350888b9c00d853885185db2e6b90557d3d51a11c3d645"
},
"native/input/release1/sa/functions/function_sa_audit_thresholds.arcadia": {
"bytes": 280,
"sha256": "27f798f78f36e72b38edab3725af2feb0eaa010fd52a60b5c6b6bef57ad3b686"
},
"native/input/release1/sa/functions/function_sa_restore_work.arcadia": {
"bytes": 2251,
"sha256": "357c6acb80f86b4e8d8f5c98f9f79d140f85fbe09f3406b41b7ac6eba464ec8c"
},
"native/input/release1/sa/model.arcadia": {
"bytes": 531,
"sha256": "805b90262e27090ae7da81a51bcefa49c97fb0b3d3220b4a72323b4c4351c395"
},
"native/input/release1/sa/participants.arcadia": {
"bytes": 3604,
"sha256": "ca144d6a8f45fe720b5d18c7b59c7671444baa8c37e5ce9117bcba34b3a6aa84"
},
"native/input/release1/sa/types.arcadia": {
"bytes": 903,
"sha256": "540ddbc2bcd0ccf45298e3ea5660637341aff63ff65fd87ac2f26469d1adc064"
},
"native/input/release1/traceability.arcadia": {
"bytes": 28041,
"sha256": "474bd28d955ff7af7722f6e9a23220d816879e3f46202e79a233544b9c593116"
},
"native/input/release1/transition-before.json": {
"bytes": 35801,
"sha256": "2253e6434f741687e22327c37d520ed15c043bc36025498650bc2366bb6a874d"
},
"native/input/release1/transition-request.json": {
"bytes": 4276,
"sha256": "43b9a22e5f95ad3bc0ebd2478ead0e14406854abc989ae94ded9367b013d812a"
},
"native/input/release1/views/diagram_oa_allocation_view.arcadia": {
"bytes": 1786,
"sha256": "c45d7467839219d894804c1ef2d22ff4d4a5a84a6a273c526bafd4c1facde624"
},
"native/input/release1/views/diagram_sa_allocation_view.arcadia": {
"bytes": 1797,
"sha256": "352a3ca745517592b5c9b1239b5770f62f0466a329a91fd03457ef2b53c06ed6"
},
"native/input/release1/views/diagram_sa_capability_view.arcadia": {
"bytes": 1286,
"sha256": "8f18fcccbcddcf3c1ea0ffe727b5b23b77fc148c5da4fde953abe681e4c20cd2"
},
"native/input/release1/views/diagram_sa_service_view.arcadia": {
"bytes": 1960,
"sha256": "4c72cb7593d9c4be8d2600b0656a7a3a6ce84c45bb2f3e58a4ee5ae3bf91913c"
},
"native/input/release1/views/diagram_sa_state_view.arcadia": {
"bytes": 1576,
"sha256": "dd1bea81ac7dc549a53f9b190be6f13052e00ede345bd180444cc31edb892230"
},
"native/picker-baseline-1/01-start-0.jpg": {
"bytes": 116509,
"sha256": "d5c669575ffa4f8c0fe2027d39ad0b655bf6557a6315a9f27ef4cc81e3e4a9d8"
},
"native/picker-baseline-1/01-start.json": {
"bytes": 920,
"sha256": "f8ac2638390d3dc2a4bf7d64a9c6b8988c59ef377b53d7067fc41c1d30b5f091"
},
"native/picker-baseline-1/02-open-pending-0.jpg": {
"bytes": 115140,
"sha256": "d06c8963b5b752f6248ecead582c317cadfcf699955f7ecd629d6052ef157f70"
},
"native/picker-baseline-1/02-open-pending.json": {
"bytes": 927,
"sha256": "f326b85978dfa21cc2fd2e4454ebfee6a6d856e439de5ab41a8cec7c2998fb23"
},
"native/picker-baseline-1/03-open-dialog-0.jpg": {
"bytes": 674,
"sha256": "5a2387a1a593a32ea169b56b9a3e59333936ee1da95164564e4d9be61bf5a24b"
},
"native/picker-baseline-1/03-open-dialog-1.jpg": {
"bytes": 52573,
"sha256": "23ab19c5286f26ead42aec7e3690fda96cad512518243593f37e26579eb08ef6"
},
"native/picker-baseline-1/03-open-dialog.json": {
"bytes": 4915,
"sha256": "b1d4e6c59104b3d4d36210e855750cc7fc445a8d05e12f3c63cc43196df9df95"
},
"native/picker-baseline-1/04-teaching-path-0.jpg": {
"bytes": 674,
"sha256": "5a2387a1a593a32ea169b56b9a3e59333936ee1da95164564e4d9be61bf5a24b"
},
"native/picker-baseline-1/04-teaching-path-1.jpg": {
"bytes": 55565,
"sha256": "569f70974c0347c3accecb412de468751fc2294ba5f577b572765335a1cbca7c"
},
"native/picker-baseline-1/04-teaching-path.json": {
"bytes": 4919,
"sha256": "e7ef7212f637bff8125fa86892cc0b610a0c29a64c3715baeab12a6b36992620"
},
"native/picker-baseline-1/05-teaching-opened-0.jpg": {
"bytes": 115272,
"sha256": "c74bba329bca67a25c83352467842dadb8d393b17785b87929a4b7f10a8a2ae2"
},
"native/picker-baseline-1/05-teaching-opened.json": {
"bytes": 930,
"sha256": "dacbdbc29a7a61de78eb987069ed8e31f5818cdf6269b34836927109badd87ec"
},
"native/picker-baseline-1/06-teaching-ready-0.jpg": {
"bytes": 148650,
"sha256": "89957f78f5744c35f600b09f55dfbe52fddc4aac1b76ea1e820d1063b15c2833"
},
"native/picker-baseline-1/06-teaching-ready.json": {
"bytes": 1114,
"sha256": "4a708684d4469e4b5612ffe10216ad81d492eaf28730f0e11a4f5c4dcd30c903"
},
"native/picker-baseline-1/07-source-selected-0.jpg": {
"bytes": 146555,
"sha256": "ca53af5035524280f3144309a12c931193115e025bec6fe5b6b1799de3c8c949"
},
"native/picker-baseline-1/07-source-selected.json": {
"bytes": 1115,
"sha256": "4a725e4db48f2f8db7bc41a37ce69d49b2cd01a9b046791fee57b1bb580183a0"
},
"native/picker-baseline-1/08-source-actions-0.jpg": {
"bytes": 153686,
"sha256": "367876958aa58ed08c913d33530ba8da663d247ee3b0b9503625e7d3f945480c"
},
"native/picker-baseline-1/08-source-actions.json": {
"bytes": 1114,
"sha256": "4d244ccced055bdff280bbf99269554173fe85c816d15f9dae03aca40ad64ece"
},
"native/picker-baseline-1/09-transition-dialog-0.jpg": {
"bytes": 147596,
"sha256": "f197b031669070f731e354bca993ffc77ff43966349f4699d69e361287f2a704"
},
"native/picker-baseline-1/09-transition-dialog.json": {
"bytes": 1127,
"sha256": "3de33b54e5b15a598a901103acfa48650170d94724c5f5708b90d8fac6bcf6dd"
},
"native/picker-baseline-1/10-unfiltered-counterparts-0.jpg": {
"bytes": 138703,
"sha256": "a3c8464125657ea37550eb46a269f9a0c0646001ba387a8188b987ba74c6a270"
},
"native/picker-baseline-1/10-unfiltered-counterparts.json": {
"bytes": 1133,
"sha256": "977c0bbc366937ce2242b871b12f350cb56af4661f09c214e4945296195f4c55"
},
"native/picker-baseline-1/11-incompatible-selection-0.jpg": {
"bytes": 144348,
"sha256": "53884020ceb9c0b416386ceaa1e06801614ac132760e58c7c3320c820c251d04"
},
"native/picker-baseline-1/11-incompatible-selection.json": {
"bytes": 1132,
"sha256": "94f6814f61b75dc4ad9f9f14babed492865079c45fa5de17e6fc9c8a4fabc349"
},
"native/picker-baseline-1/12-cancel-preserved-0.jpg": {
"bytes": 145507,
"sha256": "d3db7643267cdc532f630c972d6c53823679326910e482179d447efad6289a61"
},
"native/picker-baseline-1/12-cancel-preserved.json": {
"bytes": 1116,
"sha256": "259769191f79cd381e2c8be3230b0da8912d35ef0ce5c362b2dd4f1189773647"
},
"native/picker-baseline-1/inputs.json": {
"bytes": 5843,
"sha256": "c1e7219e30171f3854c97cfe6b2fa2411d7b3f39c60a12ad41b57ca61a8f4dff"
},
"native/picker-baseline-1/process.json": {
"bytes": 61,
"sha256": "a99574647fda39a1529b9733794064c5214005df5a5ea821f7e0d0e61e4136a9"
},
"native/picker-baseline-1/result.json": {
"bytes": 1247,
"sha256": "a4cfe1faa8edc3ae8a7eaef9bb0714b357ecbaf91ad5a5bc6e787fea7fe013d3"
},
"native/picker-baseline-1/started.json": {
"bytes": 780,
"sha256": "b059824ca0e7b1a528ec8ececbe1b8d37335bcd5a34de921c3c9b6fda448acbe"
},
"native/picker-baseline-1/stderr.log": {
"bytes": 0,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
"native/picker-baseline-1/stdout.log": {
"bytes": 0,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
"native/picker-corrected-1/inputs.json": {
"bytes": 5843,
"sha256": "c1e7219e30171f3854c97cfe6b2fa2411d7b3f39c60a12ad41b57ca61a8f4dff"
},
"native/picker-corrected-1/process.json": {
"bytes": 61,
"sha256": "749d13d70bb8c9d7b9ad35a11e63980562b5e2140a1325454d61c6fd2cc5ef7d"
},
"native/picker-corrected-1/result.json": {
"bytes": 1256,
"sha256": "98d9e77ed93d56f2fb79f21c70ba5dceb012605f491fa6d704bd4f2f3bb2f8ea"
},
"native/picker-corrected-1/started.json": {
"bytes": 780,
"sha256": "9260aaee5e9e733eeb38a334a96881a1ed796149d9ece8438e0682f1990a8404"
},
"native/picker-corrected-1/stderr.log": {
"bytes": 0,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
"native/picker-corrected-1/stdout.log": {
"bytes": 0,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
"native/picker-corrected-2/01-opened-0.jpg": {
"bytes": 118158,
"sha256": "84db89be21fabc31958b4521587ff708619a4a4cbf0ded6fe8b2c58e94a26a68"
},
"native/picker-corrected-2/01-opened.json": {
"bytes": 922,
"sha256": "44cb8a61c47648bdcdfdc278ebfd43e45942eb5211e8f77624b9982186c89c25"
},
"native/picker-corrected-2/02-teaching-reopened-0.jpg": {
"bytes": 117821,
"sha256": "aa6fd6a99c2f808c6b55c734c83b0aa8b2fbd1ce03a14b02f609e5c84275bc66"
},
"native/picker-corrected-2/02-teaching-reopened.json": {
"bytes": 933,
"sha256": "b8a9dd0a0462f9b2703bfd9d88823b9ca00efba304f0f1cd923d637e8a01b6da"
},
"native/picker-corrected-2/03-loaded-0.jpg": {
"bytes": 148546,
"sha256": "dfe2700568a0e2f688c8c197224d75d7c15dfde5f060dfe2aba09631837687ef"
},
"native/picker-corrected-2/03-loaded.json": {
"bytes": 1107,
"sha256": "e8b18a5e75b52649dd87118f70c55be4b0e9c7e2ee00c2a3db730376a6c36aa4"
},
"native/picker-corrected-2/04-source-selected-0.jpg": {
"bytes": 146472,
"sha256": "74baf9f9059b4f0741d500a1f6d84ccb91474ff48de7466a03736e5decc868ca"
},
"native/picker-corrected-2/04-source-selected.json": {
"bytes": 1116,
"sha256": "45f7cfeab95e2ca2ca2ed2cd45bea9a269c1c6a1515dbe9cbcb3087fb2917c55"
},
"native/picker-corrected-2/05-actions-0.jpg": {
"bytes": 153725,
"sha256": "c00b412af778af51cb2a6658bae4f4e11bb024a0770c3ea5aa4a54383be381c4"
},
"native/picker-corrected-2/05-actions.json": {
"bytes": 1108,
"sha256": "1132f230694873a7e9536fd59f06b976e3b52041b119e2e9632b7c66de53d400"
},
"native/picker-corrected-2/06-transition-dialog-0.jpg": {
"bytes": 149653,
"sha256": "a4736781ac296164ddffe12005f9f1ad29d73ddd2bc752d14fb2b63ac2579153"
},
"native/picker-corrected-2/06-transition-dialog.json": {
"bytes": 1126,
"sha256": "2a2b8e8d9ac75a7c354e79bd5ba764c4024b045db009d5feb2b7f3909423f753"
},
"native/picker-corrected-2/07-compatible-menu-0.jpg": {
"bytes": 148938,
"sha256": "80621a49998cc7fc2a643e7338a6867c722b4365fabc713fac0b39f7812703f9"
},
"native/picker-corrected-2/07-compatible-menu.json": {
"bytes": 1126,
"sha256": "0f3c6a3d512e163953b9773de45df81d4129c0ded48a13c4d79de32dd8ab2d9e"
},
"native/picker-corrected-2/08-menu-dismissed-0.jpg": {
"bytes": 149416,
"sha256": "867236aeac2dcf839104692f73cb63fb45d7dcc38d95eaabd5cba8d0a7ff862a"
},
"native/picker-corrected-2/08-menu-dismissed.json": {
"bytes": 1125,
"sha256": "65ce375a1918a848cc57739f90eb15fbd55e9173c2343701791d0cf9740b818e"
},
"native/picker-corrected-2/09-search-focus-0.jpg": {
"bytes": 149668,
"sha256": "4a08a7955982956d518397832a0befdda951347f04da9af75af787fe5a163332"
},
"native/picker-corrected-2/09-search-focus.json": {
"bytes": 1123,
"sha256": "de505bdab419c6c60a3ad260479036d373f915333eb6fc62cd68c57ce2974af9"
},
"native/picker-corrected-2/10-alias-query-0.jpg": {
"bytes": 149373,
"sha256": "dceaa7f1e0e0959aa6e654d0b9f84cbe20badec8d99315e3fd2ebc3cb47f6f5f"
},
"native/picker-corrected-2/10-alias-query.json": {
"bytes": 1122,
"sha256": "c080f52656f6f603065e763cefb4a6ef790f4cfad96a3ddd3b6d58c5053cd62d"
},
"native/picker-corrected-2/11-filtered-menu-0.jpg": {
"bytes": 149367,
"sha256": "6893983b46da3a6f3da1ee3eac90dc50f34dbb257671835d7e893244d18b1b03"
},
"native/picker-corrected-2/11-filtered-menu.json": {
"bytes": 1124,
"sha256": "f8c5c8573840faef1337a7b429645e46652d29340104c2d6b15a9e1fbc14f152"
},
"native/picker-corrected-2/12-existing-selection-0.jpg": {
"bytes": 147991,
"sha256": "839dee6d22448d12d90758ca76f5503fae69a099af0e055e20cf814d7dd498ec"
},
"native/picker-corrected-2/12-existing-selection.json": {
"bytes": 1129,
"sha256": "84bc484447049dbafa1cda1b4c1ce0ade93c7ddcbf1c0489f2b18167deabed00"
},
"native/picker-corrected-2/13-refocus-search-0.jpg": {
"bytes": 148017,
"sha256": "70f5a9d3ea5d2b82b68e19d5682659ba0bba1abaeac0f56aca4a88ffeec74b76"
},
"native/picker-corrected-2/13-refocus-search.json": {
"bytes": 1125,
"sha256": "c2e42830b2d2364699210f716c375f2e63a447f910e729be0aa60c157f8341f6"
},
"native/picker-corrected-2/14-query-selected-0.jpg": {
"bytes": 148447,
"sha256": "e0e5807ed5f0f535110ceecc6c6aabb9d077ea67fa1d83367d2c50141e8cdc53"
},
"native/picker-corrected-2/14-query-selected.json": {
"bytes": 1125,
"sha256": "5d97ec91b3fd752d9f5e896c6fe97abc97ca40a91b6a708209663d4c198cc2e0"
},
"native/picker-corrected-2/15-no-results-selection-retained-0.jpg": {
"bytes": 150165,
"sha256": "25be189c30acb4c8dab03d458b2fc73bd09d0766513d5c9ee4e0e0bffcee2457"
},
"native/picker-corrected-2/15-no-results-selection-retained.json": {
"bytes": 1140,
"sha256": "a4f3c3e357aed8f66cad8b3c89f0e89d9eaf0662e3d740a06ba8eee6e7a33748"
},
"native/picker-corrected-2/16-parent-search-focus-0.jpg": {
"bytes": 149957,
"sha256": "84bdd2c3ed40178a16452a680932d27d42142220f09f79dc747906732aafb97d"
},
"native/picker-corrected-2/16-parent-search-focus.json": {
"bytes": 1130,
"sha256": "c3346906c51cf087a1fa850b46c867491630c2ec9a1565671e5674f2600ee261"
},
"native/picker-corrected-2/17-independent-card-queries-0.jpg": {
"bytes": 149082,
"sha256": "a48e99ca120c7e34ce2823bc0140a99778b88849eb7a6b245867f129a0e46aff"
},
"native/picker-corrected-2/17-independent-card-queries.json": {
"bytes": 1135,
"sha256": "ced4961b7bffb3d5f82757036ac77dab3cffba705bdd0a596183e686b3a59691"
},
"native/picker-corrected-2/18-field-review-0.jpg": {
"bytes": 144509,
"sha256": "734e83058cc66d1af13098b5b69c7e2cbad8daf2a3afd2a25780e65faa38bf71"
},
"native/picker-corrected-2/18-field-review.json": {
"bytes": 1123,
"sha256": "7489d77a9b1d4718f43aa10edca688e42a76a05507198538bd714ac8de638432"
},
"native/picker-corrected-2/19-keep-target-reviewed-0.jpg": {
"bytes": 144547,
"sha256": "8eafc33857650748a059a2c1dc345112b300a6d16d0395b33ff8f7e596d18d04"
},
"native/picker-corrected-2/19-keep-target-reviewed.json": {
"bytes": 1131,
"sha256": "509c9c3f0237a860c61e2413f05ab7bd9c51e5ffcbf97caede5c7298de0d479a"
},
"native/picker-corrected-2/20-reviewed-search-focus-0.jpg": {
"bytes": 144893,
"sha256": "0e46bb4787aba3c19cb27461cf70a22fdb579f3a63839a91ee68d37d7f9dd1d9"
},
"native/picker-corrected-2/20-reviewed-search-focus.json": {
"bytes": 1132,
"sha256": "33325c0cdac00fd49f25d4606c88a241dc6957f91f38d97b3ebafd4f5c42eaf2"
},
"native/picker-corrected-2/21-reviewed-query-selected-0.jpg": {
"bytes": 144916,
"sha256": "bec9c198292a5d4f6883c003112da1634fc7b8b8694a2910e51ed29079eaeb4b"
},
"native/picker-corrected-2/21-reviewed-query-selected.json": {
"bytes": 1134,
"sha256": "cf01d193ba011ed141e98236dff49fc06f789bbb26bb92b5a848a3f059c4319a"
},
"native/picker-corrected-2/22-cleared-search-review-retained-0.jpg": {
"bytes": 145507,
"sha256": "ff8670ba72a6b516e01bfcb9a8036940682e8f2caec92342b0902d9c0bb26b87"
},
"native/picker-corrected-2/22-cleared-search-review-retained.json": {
"bytes": 1141,
"sha256": "9a6a51578618a6c8aaf238329624102f3f37fda306be1dadffbc97dca178c221"
},
"native/picker-corrected-2/23-cancel-restores-saved-0.jpg": {
"bytes": 145951,
"sha256": "c79b52574c7ad9ea3a442679fbccb7e2683be6c0c8d015260398bc31fe9c705c"
},
"native/picker-corrected-2/23-cancel-restores-saved.json": {
"bytes": 1122,
"sha256": "6524ed5fd1cadc41de60d60818ec69e47a406189d03ede0822ea70ed502798b9"
},
"native/picker-corrected-2/inputs.json": {
"bytes": 5843,
"sha256": "c1e7219e30171f3854c97cfe6b2fa2411d7b3f39c60a12ad41b57ca61a8f4dff"
},
"native/picker-corrected-2/process.json": {
"bytes": 61,
"sha256": "6c4ea33570bd26271c5ec70c884dd4aeeab0642c33f46896ecd8832cb7ff5361"
},
"native/picker-corrected-2/result.json": {
"bytes": 2837,
"sha256": "b483791514deb4c1e16272dfabdf78f5a8092ddc8deeefc01a23baaed2913c2d"
},
"native/picker-corrected-2/started.json": {
"bytes": 2370,
"sha256": "cd97a270ae7e4eae8c6777ec311eb2437c4dd55e8b5882cbd8011c82fca2f7af"
},
"native/picker-corrected-2/stderr.log": {
"bytes": 0,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
"native/picker-corrected-2/stdout.log": {
"bytes": 0,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
"native/run-picker-baseline.py": {
"bytes": 2106,
"sha256": "86c88e94ccb5fcfd8f81db429db9991170839bc4a20dab408ab1d284c7e234f1"
},
"native/run-picker-corrected-2.py": {
"bytes": 2162,
"sha256": "e61347fa440337bc9629c0d3b532c39e16cfb8d1966132992676bfa1c8f546f5"
},
"native/run-picker-corrected.py": {
"bytes": 2124,
"sha256": "0e82acfca234bade68a6263f1c5a2222e1eeaff932fa7c92eb88063f254f6701"
}
},
"externalEvidence": {
"correctedCargo": {
"repositoryPath": "harness/experiments/transition-lifecycle-20260913/evidence.zip",
"commit": "fe43730bec196cc6ebbc06dcf827169f04c120f0",
"bytes": 8496343,
"sha256": "5f44ad5d83024a04a873142e77c5b8961c278a19d76492618c3ef2db63801070"
},
"baselineNative": {
"repositoryPath": "harness/experiments/ordinary-text-native-20260913/evidence.zip",
"commit": "3f9e6a18af96b5cd10940da4093be8da28b9c835",
"bytes": 5704887,
"sha256": "b827d0bdaec76b7b86d8a169530dcf98767c9883187ecdda02b4ea87171a42b5"
}
},
"captureCount": 35,
"imageCount": 37
}
@@ -0,0 +1,63 @@
"""Read-only portable receipt and frame verification, without archived execution."""
from pathlib import Path, PurePosixPath
from datetime import datetime
import hashlib,json,zipfile
ROOT=Path(__file__).resolve().parent
def pin(b):return {'bytes':len(b),'sha256':hashlib.sha256(b).hexdigest()}
def need(ok,msg):
if not ok:raise ValueError(msg)
def main():
files=json.loads((ROOT/'files.json').read_bytes())
for n,p in files.items():need(pin((ROOT/n).read_bytes())==p,'outer hash '+n)
manifest=json.loads((ROOT/'manifest.json').read_bytes())
with zipfile.ZipFile(ROOT/'evidence.zip') as z:
names=z.namelist()
need(len(names)==len(set(names)) and set(names)==set(manifest['members']),'exact ZIP membership')
need(all(not PurePosixPath(n).is_absolute() and '..' not in PurePosixPath(n).parts and '\\' not in n and ':' not in n for n in names),'safe members')
for n,p in manifest['members'].items():need(pin(z.read(n))==p,'member hash '+n)
def obj(n):return json.loads(z.read(n))
input_names={n[len('native/input/'):]:n for n in names if n.startswith('native/input/')}
reference=None;frame_count=0;image_count=0
for run,spec in manifest['runs'].items():
prefix='native/'+run+'/'
start=obj(prefix+'started.json');result=obj(prefix+'result.json');process=obj(prefix+'process.json')
need(all(result[k]==v for k,v in start.items()),run+' start/result relationship')
need(result['exitCode']==spec['exitCode'],run+' exit code')
need(isinstance(process['pid'],int) and process['pid']>0,run+' process ID')
need(datetime.fromisoformat(start['start'])<=datetime.fromisoformat(process['observed'])<=datetime.fromisoformat(result['finish']),run+' process time order')
need(result['helper']==pin(z.read(spec['helper'])),run+' launch helper')
need(result['inputs']==pin(z.read(prefix+'inputs.json')) and result['inputUnchanged'] is True,run+' input receipt')
inputs=obj(prefix+'inputs.json')
need(set(inputs)==set(input_names),run+' input inventory')
for n,p in inputs.items():need(pin(z.read(input_names[n]))==p,run+' input bytes '+n)
if reference is None:reference=inputs
need(inputs==reference,run+' all trials identical fixture')
for stream in ['stdout','stderr']:
b=z.read(prefix+stream+'.log');need(not b and result[stream]==pin(b),run+' empty stream '+stream)
build=obj(spec['build'])
need(result['buildReceipt']==pin(z.read(spec['build'])) and result['buildHead']==build['head'],run+' build receipt')
need(build['exitCode']==0 and build['inputsUnchanged'] is True,run+' build status')
need(datetime.fromisoformat(build['start'])<=datetime.fromisoformat(build['finish'])<=datetime.fromisoformat(start['start']),run+' build before launch')
gui=[a for a in build['artifacts'] if a['cargo']['target']['name']=='arcadia-studio']
need(len(gui)==1,run+' unique GUI artifact')
need(result['binary']==result['binaryAfter']==gui[0]['readback'],run+' unchanged GUI pin')
need(result['command']==[gui[0]['cargo']['executable']],run+' exact launch command')
images=[]
for metadata in spec['captures']:
frame=obj(metadata)
need(datetime.fromisoformat(start['start'])<=datetime.fromisoformat(frame['time'])<=datetime.fromisoformat(result['finish']),metadata+' capture interval')
need(frame['window']['app'].replace('\\','/')=='process:'+result['command'][0].replace('\\','/'),metadata+' window application path')
for image in frame['images']:
n=prefix+image['file'];need(n in manifest['members'] and z.read(n).startswith(b'\xff\xd8'),metadata+' JPEG reference')
need(image['width']>0 and image['height']>0,metadata+' frame dimensions')
images.append(n)
actual_images=[n for n in names if n.startswith(prefix) and n.endswith('.jpg')]
need(len(images)==len(set(images)) and set(images)==set(actual_images),run+' all images referenced once')
actual_metadata=[n for n in names if n.startswith(prefix) and n.endswith('.json') and 'images' in obj(n)]
need(set(actual_metadata)==set(spec['captures']) and len(actual_metadata)==len(spec['captures']),run+' all capture metadata')
frame_count+=len(actual_metadata);image_count+=len(images)
print(run,'exit',result['exitCode'],'captures',len(actual_metadata),'images',len(images))
need(frame_count==manifest['captureCount'] and image_count==manifest['imageCount'],'aggregate frame counts')
print('PASS:',len(names),'raw members;',len(reference),'unchanged fixture files;',frame_count,'capture records;',image_count,'original JPEGs.')
print('Build source inventories are external. Frames prove recorded states, not action execution or PID attestation. Capture helper was not start-pinned.')
if __name__=='__main__':main()

Some files were not shown because too many files have changed in this diff Show More