Arcadia Studio OA / SA Language and Editing Contract

Version:
2
Status:
Normative current implementation contract; first-release integration
Core implementation:

crates/oa-core

Text suffix:

.arcadia

1. Scope and conformance

This specification defines the shared Operational Analysis (OA) and System Analysis (SA) model, its textual language, graphical editing obligations, validation, persistence, and explicit migration from version 1. MUST, MUST NOT, and MAY indicate requirements, prohibitions, and permitted choices.

The first-release capability/mission, contextual process/chain, typed scenario and state/event additions are temporarily integrated under schema 2. This does not declare the first release complete or waive its remaining scenario, state, data, transition and usability requirements. A distinct release schema and explicit V2 migration will be specified after those interfaces are frozen. This document describes the current implemented records, rather than inventing compatibility rules for that future schema.

OA describes stakeholders, operational work and needs. SA describes the selected system boundary, external actors, functions, services and expected behavior. SA permits at most one System of Interest; its absence is an incomplete-model warning when other SA elements exist. The SA System MUST NOT contain internal components. Functional decomposition is independent of that structural boundary. Logical Architecture, Physical Architecture, deployment, Capella file interchange, and executable behavioral simulation are outside this specification. This independent language does not claim complete Arcadia, Eclipse Capella, or SysML conformance.

An implementation conforms only when each supported field and relationship is reachable in both graphical and textual editing, preserves identity through save/open and undo, and uses the same structural validation. Merely displaying a name or storing an opaque payload does not establish graphical support. Actual verification evidence is separate from this contract.

2. One authoritative model

A Project stores schema_version (2), id, alias, name, description, and maps named elements, relations, diagrams, artifact_links, and traces. Each map is keyed by the UUID contained in its record. Duplicate map keys and mismatching keys/identities are errors.

All objects share one UUID and alias namespace: Project, Element, Relation, Diagram, NodePlacement, ArtifactLink, and Trace. UUIDs are durable identity; aliases are durable, editable source labels. Display-name changes MUST NOT regenerate existing aliases. Different objects MAY have the same display name.

Every Element has id, alias, name, description, parent (optional element reference), and a typed body. Its kind and OA/SA layer are derived from its body. A Relation has id, alias, name, description, and a typed body; it has no independently editable kind tag that can disagree with its payload.

Diagram placement does not determine semantic ownership. NodePlacement records belong to diagrams and reference semantic elements. Multiple diagrams can show the same element with independent placement and collapse state. Within one diagram, each element appears at most once.

All stored strings MUST contain only XML 1.0 characters: U+0009, U+000A, U+000D, U+0020–U+D7FF, U+E000–U+FFFD, or U+10000–U+10FFFF. Strings are not trimmed or Unicode-normalized. Escaping a forbidden character does not permit it. Coordinates and sizes are finite IEEE-754 binary32 values with absolute value at most 1000000. Width and height MUST be positive. Signed zero and subnormal coordinates are retained.

3. Element declarations and typed fields

Each element declaration starts with its layer and kind, for example oa_activity or sa_function. Every element body accepts description (default "") and parent (default none). The table lists all additional fields. ref means @alias; ref? also accepts none. Lists preserve order. Unknown or duplicate fields are errors.

Element body field catalog

Declarations

Additional fields

Defaults

oa_entity, oa_actor, oa_role, oa_activity, oa_capability, oa_mission

None

Empty body

sa_system, sa_actor, sa_function, sa_capability, sa_mission

None

Empty body

oa_process, sa_functional_chain

steps: occurrence refs

[]

oa_exchange_item, sa_exchange_item

kind: Information, Material, Energy, Event

Information

oa_data_type, sa_data_type

definition: Boolean, Integer, Real, String, Record, or Enumeration constructor

String

oa_data_field, sa_data_field

type_ref; minimum: u32; maximum: optional u32; unit: string

String; 1; 1; ""

oa_interface, sa_interface

items: exchange-item refs

[]

oa_port, sa_port

direction: In, Out, InOut; interface: ref?

InOut; none

oa_scenario, sa_scenario

viewpoint: ScenarioViewpoint; context: ref?; precondition, postcondition: strings; steps: occurrence refs

MixedLegacy for omitted schema-2 source; none; ""; ""; []

oa_lifeline, sa_lifeline

represented: participant, work, or role ref?

none

oa_occurrence, sa_occurrence

body: occurrence constructor; precondition, postcondition: strings

Exchange with none references, Asynchronous message kind and no values; two empty strings

oa_state_machine, sa_state_machine, oa_region, sa_region

None

Empty body

oa_state, sa_state, oa_mode, sa_mode, oa_initial, sa_initial, oa_final, sa_final

kind: StateKind; entry, exit, do_activity: strings

Kind selected by declaration; three empty strings

oa_choice, oa_junction, oa_fork, oa_join, oa_shallow_history, oa_deep_history, oa_entry_point, oa_exit_point, oa_terminate and corresponding sa_ declarations

Same State body; StateKind is Choice, Junction, Fork, Join, ShallowHistory, DeepHistory, EntryPoint, ExitPoint or Terminate

Kind selected by declaration; three empty strings

oa_event, sa_event

body: Signal, Time or Change constructor

Signal with items []

oa_constraint, sa_constraint

expression: string; constrained: semantic record refs

""; []

The explicit state kind field MUST agree with its declaration keyword. u32 is an integer from 0 through 4294967295. A DataField's maximum MUST be at least its minimum; none means unbounded. Primitive TypeRefs are Boolean, Integer, Real, and String. A named TypeRef uses a DataType reference. An Enumeration contains unique string literals. For example:

definition { Enumeration { literals ["Clear", "Fog"]; }; };
type_ref { Named @weather_classification; };

These are typed constructors with named fields, not embedded JSON documents. Constructor field names and enum spellings are case-sensitive. When replacing a constructor, all of its fields MUST be supplied; the containing declaration defaults do not silently fill a different constructor's missing fields. During schema-2 compatibility, the new Exchange fields message_kind, reply_to and values are exceptions: omitted values hydrate as Asynchronous, none and [] respectively. Canonical output writes them explicitly. This preserves older schema-2 source/JSON without inventing call correlation or value annotations. It is not a rule for an unspecified future schema.

Occurrence body constructors

Constructor

Required fields

Exchange

exchange: exchange relation ref?; sender, receiver: lifeline refs?; message_kind: MessageKind; reply_to: occurrence ref?; values: MessageValue records

Action

action: activity/function ref?; lifeline: ref?

Control

kind: Choice, Merge, Fork, Join, Loop

SequenceReference

sequence: process/functional-chain ref?

ScenarioUse

scenario: scenario ref?; bindings: LifelineBinding records

State

state: state/mode ref?; lifeline: ref?

Fragment

operator: Alternative, Parallel, Loop, Optional; branches: branch occurrence refs

Branch

guard: string; occurrences: occurrence refs

For example, body { Action { action @assess; lifeline @operator_line; }; }; selects an Action occurrence. Processes and functional chains contain Exchange, Action, Control and SequenceReference occurrences. Process/chain Actions are contextual work contributions and MUST have lifeline none. A Control or SequenceReference MUST belong directly to a Process or FunctionalChain, not a Scenario. Scenarios contain Exchange, Action, State, Fragment, Branch and ScenarioUse occurrences according to their enclosing ownership rules. Each listed occurrence belongs to its enclosing sequence or branch through parent. A Branch belongs to a Fragment; the Fragment's branches list references the branches it owns.

An exchange and an occurrence of that exchange have different identities. Repeating an exchange requires distinct occurrence records. Repeating one occurrence identity within an ordered list is invalid. Conditions, guards, entry/exit/do text, and constraint expressions are descriptive strings; this version does not parse or execute them as a programming language.

4. Containment, allocation, and relationships

Containment MUST be acyclic and stay within one layer. Entities may contain entities, actors, and roles. Actors are not structural containers. Activities contain activities; functions contain functions. Capability grouping uses same-layer capability parents; it does not mean inheritance or inclusion. Operational and system missions are root semantic records; they do not own capabilities by containment. MissionCapability expresses that association. DataFields belong to ExchangeItems or Record DataTypes. Ports belong to participants or work. Lifelines belong to scenarios. State-machine ownership uses participant → machine → region → state/mode; a state or mode can contain a nested region. All other state vertices belong to Regions as well. Events are root semantic records and do not acquire a machine owner when referenced.

DataFields, ports, lifelines, occurrences, Regions and all state vertices require an owner. An unowned StateMachine can be saved as incomplete work and produces a warning. The System cannot own internal components. The present Studio profile does not decompose SA Actors.

All relations have a header identity, alias, display name, optional description (default ""), and required source and target references. Their kind is the declaration keyword:

Relation catalog

Declaration

Endpoint meaning

Additional fields / defaults

allocation

Same-layer participant → leaf activity/function

None

interaction

OA activity → OA activity

items refs / []

functional_exchange

SA function → SA function

source_port, target_port / none; items / []

boundary_exchange

Participant-owned output-compatible port → input-compatible port

items refs / []

capability_involvement

Participant, activity/function, or OA role → capability

None

chain_involvement

Process/functional chain → capability

None

role_realization

OA entity/actor → OA role

None

role_assignment

OA role → leaf OA activity

None

mission_capability

OA mission → OA capability, or SA mission → SA capability

None

mission_involvement

OA entity/actor → OA mission, or SA System/Actor → SA mission

None

capability_include

Including capability → included capability in one layer

None

capability_extend

Extending capability → extended capability in one layer

condition string / ""

capability_generalization

Specific capability → general capability in one layer

None

sequence_link

Preceding contribution → following contribution in one process/chain

guard string / ""

state_transition

State vertices in one machine, subject to section 4.4

trigger, guard, effect strings / ""; transition_kind External; triggers, effects reference lists / []

availability

Work, participant, capability, or process/chain → state/mode

None

exchange_allocation

Boundary-exchange relation → SA functional-exchange relation

None

interface_provision, interface_requirement

Participant/port → provided/required interface

None

state_action

State/mode → activity/function

phase: Entry, Do, Exit / Do

Relation endpoints and carried ExchangeItems stay in the same layer. Parallel exchanges with distinct identities are permitted. Self-relations are invalid except for a state transition that returns to its source state.

Direct Allocation has at most one performer per leaf. Role sharing is a different relationship: several OA participants can realize a Role. A direct allocation inconsistent with its role's realized performers produces an advisory warning; it does not prevent saving the model. Shared roles do not imply a unique performer. Allocation of a non-leaf work element is invalid. A computed graphical summary of its allocated leaves MUST NOT create an extra semantic allocation.

Functional ports, when selected, belong to the corresponding endpoint function and have compatible direction. Boundary ports belong to participants and have compatible direction. An interface groups ExchangeItems; an exchange using that port interface must carry items supported by it. Studio's OA boundary exchange is a simplified representation of operational communication; it is not a claim of complete Capella CommunicationMean equivalence.

An exclusive region has at most one initial state. Missing initial state is a completeness warning. Initial states have no incoming transitions and at most one outgoing transition; their outgoing transition has no trigger/guard. Final states have no outgoing transitions. Mixing State and Mode entries is permitted by this Studio profile. StateAction links provide typed work references alongside the descriptive entry/exit/do strings.

4.1. Capabilities and missions

Include means the including capability requires the included capability's behavior. Extend means the source capability adds conditional behavior to the target; its condition records when that behavior applies. Generalization means a specific source capability specializes a general target capability. Each relationship stays within OA capabilities or within SA capabilities. Include and Generalization graphs MUST each be acyclic, including transitive cycles. Extend does not introduce an additional cycle restriction; the ordinary no-self rule still applies. An empty Extend condition is an engineering warning so an incomplete capability map can be authored and saved.

MissionCapability and MissionInvolvement express need and stakeholder/system participation. They MUST NOT create a capability parent, a performer allocation, or a cross-layer realization. These relationships remain distinct when the same objects appear in a diagram or share names.

4.2. Contextual process and chain topology

A Process or FunctionalChain owns contribution occurrences with stable identities. steps is their persistent membership and presentation order; an identity MUST occur at most once in that list. A work contribution points to an existing same-layer Activity or Function. Reusing the same work or exchange requires separate occurrence identities and MUST NOT duplicate the referenced engineering element. A SequenceReference points to another same-layer Process or FunctionalChain of the owner's kind. Its target may be none while the draft is incomplete. Direct and transitive recursive sequence reuse are errors.

SequenceLinks connect distinct occurrences directly owned by the same process or chain. Their source is the predecessor and target is the successor. A link from work to an exchange requires that work to be the referenced exchange's source; an exchange-to-work link requires the target work to be its endpoint. Two adjacent exchange contributions require the first exchange's target to match the second exchange's source. A mismatch is a structural error. Other links express contextual precedence without manufacturing a global exchange.

When links or work/control/reuse contributions exist, topology is explicit. Reordering steps MUST NOT add, remove or reorder SequenceLinks. The editor MUST retain the difference between alternative Choice/Merge and parallel Fork/Join contributions. Choice/Fork expect one incoming and at least two outgoing links; Merge/Join expect at least two incoming and one outgoing link. Loop expects at least one incoming and two outgoing links for repetition and exit. These cardinalities are completeness warnings, not barriers to building an incomplete flow. Choice and Loop outgoing paths with empty guards warn; an explicit else guard is permitted. Guards remain descriptive strings.

Validation warns about missing/ambiguous entry and exit, isolated or unreachable contributions, contributions with no reachable exit, and splits/merges that lack explicit controls. Topology cycles are permitted for loops; the recursive SequenceReference prohibition is a different rule. A legacy exchange-only sequence without explicit links retains its ordered linear meaning and adjacent-exchange gap diagnostics. Implementations MUST NOT silently invent topology links when loading or reordering such a sequence.

4.3. Typed scenarios and reusable behavior

ScenarioViewpoint is one of OperationalActivity (OA activity lifelines), ParticipantInteraction (OA entity/actor/role lifelines), Functional (SA function lifelines), BoundaryInterface (SA System/Actor lifelines), or MixedLegacy. The first two are OA viewpoints; the next two are SA viewpoints. Lifeline references MUST agree with that perspective. OperationalActivity messages use Interactions; Functional messages use FunctionalExchanges. Participant views may use their layer's work exchanges through effective performer allocation, or participant boundary exchanges through the relevant ports. Corresponding lifelines and endpoints MUST agree; a viewpoint is not a label that suppresses reference checks.

Omitted schema-2 viewpoints hydrate as MixedLegacy in both source and JSON. This preserves pre-extension mixed work/participant scenes without discarding their lifelines or silently assigning a stricter meaning. MixedLegacy emits a review warning. New graphical factories explicitly select OperationalActivity or Functional by layer. Authors can select a strict viewpoint after reviewing the legacy scene; canonical source then records that choice.

MessageKind is Asynchronous, Call, Reply, Create, Delete, or { Timer { expression "after 5 s"; }; }. A process/chain exchange contribution MUST remain Asynchronous with no reply correlation. Call/reply, creation/deletion and timer semantics belong to scenarios. Reply's reply_to references a Call occurrence in the same scenario. The call MUST precede the reply on a guaranteed common sequence/enclosing branch path. Reply lifelines reverse the call's sender/receiver; it may reuse the call's exchange in reverse or use an explicit reverse-direction exchange. A second reply to the same call on that guaranteed path is an error. Missing reply correlation is a draft warning; non-Reply messages MUST NOT have reply_to.

Create identifies the receiver lifeline's creation. An earlier guaranteed use of that lifeline is an error. Delete identifies its destruction; a later guaranteed use is an error. Ordering checks respect branch ownership; they MUST NOT flatten alternative branches into a false unconditional order. A Timer is local to its sender lifeline, has no global exchange or values, and has either no receiver or the same receiver as sender. Its nonempty expression describes the delay/deadline; an empty expression warns. Conditions and expressions are stored engineering annotations, not an executable simulation language.

Each MessageValue is { field @data_field; expression "value expression"; }. The field MUST be a DataField carried by the message exchange, including fields inside named nested records. At most one annotation for a given field is allowed in one message. Values do not overwrite the referenced type or global exchange item. Expression text is preserved exactly; an empty expression warns.

ScenarioUse references an existing same-layer scenario and maps its lifelines with bindings [ { referenced @callee_line; local @caller_line; } ];. Each binding is one-to-one and references a direct lifeline of the appropriate scenario. Bound lifelines represent the same semantic object, or an explicitly realized operational role. Strict viewpoints MUST agree; MixedLegacy retains the compatibility case until reviewed. Missing bindings produce warnings. Direct/transitive recursive scenario use is invalid; repetition uses a Loop fragment instead. ScenarioUse owns neither its referenced scenario nor its lifelines; deleting a use does not delete the reusable definition.

4.4. State vertices, events and transition contracts

StateKind comprises State, Mode, Initial, Final, Choice, Junction, Fork, Join, ShallowHistory, DeepHistory, EntryPoint, ExitPoint and Terminate. Every vertex belongs to a Region. Only State and Mode are stable states: only these may own nested Regions, typed StateAction links, availability and scenario state observations. Multiple sibling Regions describe orthogonal configurations. EntryPoint and ExitPoint retain Region ownership; their boundary is that Region's owning State/Mode or StateMachine. Their graphical symbols belong on that boundary. This is Studio's explicit containment encoding.

Events are ordinary same-layer semantic elements, independent of diagram placements. EventBody has exactly these constructors:

Event definitions

Constructor

Fields and meaning

Signal

items is a unique list of same-layer ExchangeItem references; empty is valid

Time

kind is After or At; expression is descriptive time text

Change

expression describes the Boolean condition becoming true

After measures relative time from activation of a transition's source state; At describes an absolute instant. Empty Time/Change expressions are located completeness warnings. The application does not execute expressions, fire timers or transport signals. For example:

sa_event timeout "Recovery timeout" {
    body { Time { kind After; expression "30 s"; }; };
}
sa_event recovered "Pressure recovered" {
    body { Change { expression "pressure > 4 && status == \"Ready\""; }; };
}

StateTransition's triggers references Events or layer-appropriate work (OA Activity / SA Function). Referenced work need not have the machine owner's performer: external work can cause the observed transition. Multiple triggers are alternatives. effects references work describing effects, without executing it. Duplicate, missing, wrong-layer or wrong-kind references are errors. The existing trigger, guard and effect strings remain authored prose; they MUST NOT be renamed, parsed as UUIDs or inferred by name.

With typed triggers, trigger adds descriptive information. With no typed triggers and nonblank trigger prose, the transition has an unresolved descriptive trigger and emits a warning. Both empty denotes completion from a stable state or transient routing from a pseudostate. Deleting the final typed trigger when prose is empty MUST leave an explicit descriptive reminder; it MUST NOT silently create a completion transition. Guards/effect prose remain unchanged. Signal payload references and typed effect references are pruned when their targets are removed.

TransitionKind is External, Local or Internal. External leaves/re-enters the affected configuration; a stable self-transition includes exit and entry behavior. Internal requires the same stable source and target, preserves nested configuration, and does not imply entry/exit. Local requires a composite stable source and proper descendant target, preserving the source activation; an EntryPoint also routes locally into its boundary. Local self/outside targets are invalid. Other pseudostates route externally. Endpoints MUST share a layer and StateMachine; crossing nested Regions within that machine is not generally forbidden. A pseudostate self-transition is invalid.

A simple state's completion means entry/do behavior is complete. A composite completes when its active Regions complete. Final completes its containing Region; Terminate ends the containing machine's activation. These are modeling meanings, not evidence of a running state-machine engine.

Missing connections/guards and unfinished initial/history paths produce warnings, so an engineer can insert vertices before wiring them. Contradictory upper cardinalities, forbidden directions/kinds and invalid references reject.

Vertex connection constraints

Vertex

Structural restrictions

Incomplete-model warnings

Initial

At most one per Region; no incoming; at most one outgoing; no outgoing trigger or guard

Missing initial/default entry or outgoing

Final / Terminate

No outgoing or nested Region

No incoming

Choice

At most one incoming; outgoing transient alternatives

Missing incoming, fewer than two outgoing, missing guard or else

Junction

Transient routing, no implicit concurrency

Missing side or ambiguous unguarded alternatives

Fork

At most one incoming; outgoing stable targets in distinct orthogonal Regions

Missing incoming or fewer than two outgoing

Join

At most one outgoing; incoming stable sources in distinct orthogonal Regions

Fewer than two incoming or missing outgoing

ShallowHistory

At most one per Region; at most one fallback to a direct stable child of that Region; no fallback trigger/guard

Missing fallback/default entry

DeepHistory

At most one per Region; at most one fallback to a stable descendant of that Region; no fallback trigger/guard

Missing fallback/default entry

EntryPoint

Incoming from outside boundary, outgoing Local into it; at most one outgoing per destination Region

Missing outgoing or incomplete affected-Region entry

ExitPoint

Incoming from inside boundary; at most one outgoing External to outside

Missing input/output; top-level exit is an exit endpoint, not implicit termination

Fork outputs and Join inputs MUST NOT carry guards or typed/descriptive triggers. Their distinct orthogonal Regions are the first Regions beneath the nearest common composite or machine, not merely each endpoint's immediate parent. Choice/Junction routing expresses conditional alternatives. Retained pseudostate entry/exit/do prose produces a warning and remains editable; it does not become a typed state behavior. StateAction remains the maintained reference for entry/do/exit work on stable states.

Other pseudostate outgoing segments MUST NOT introduce event/work triggers or trigger prose. A Choice has at most one literal else guard; the tool does not prove arbitrary guard expressions disjoint or exhaustive. Choice expresses a dynamic decision after incoming effects; Junction expresses a compound path selected by guards before traversing it. ShallowHistory remembers the immediate state and uses default entry for deeper configuration; DeepHistory remembers nested configuration. Fallback applies when no resumable history exists. History is per Region, including top-level Regions in this Studio profile; no running history state is persisted or synthesized.

5. Diagrams, artifacts, and cross-layer traceability

diagram has name, identity, alias, description, layer (Oa by default), and projection (Overview by default). Other projections are Decomposition, Allocation, and Traceability. Traceability diagrams can show both layers; other diagrams show their chosen layer.

node has an alias and identity but no display-name string. Required fields are diagram and element references. Optional fields are position (record with x/y, default 0/0), size (width/height, default 224/94), and collapsed (false). Node identity is independent of its semantic element. Coordinates are absolute diagram coordinates. Placement does not alter parent, allocation, or realization. Hiding an element from a diagram must remain distinguishable from deleting it from the model. Changing a node's diagram preserves the placement identity, alias, coordinates, size, collapse state, and attached artifacts. The destination must accept the element's layer and must not already contain another placement of that element.

artifact has name, identity, alias, description, required owner ref, relation (References, Satisfies, Verifies, Implements, DerivedFrom; default References), uri (default empty), revision (optional string), and fragment (optional string). The two optional strings default to none. URI must be nonempty, have no whitespace/backslash/control characters, and use valid percent escapes. The owner is another existing model object. Validation MUST NOT fetch the URI. Revision and fragment are retained verbatim.

trace has name, identity, alias, description, required oa and sa endpoint references, kind (default Refinement), and baseline (default none). Endpoints are canonical: SA realizes OA, regardless of which side initiated creation. Trace does not change endpoint identity or containment. Multiple distinct counterparts are permitted; duplicate endpoint/kind triples are invalid.

Trace kinds

Kind

OA → SA endpoint types

Refinement

Activity → Function

Participant

Entity/Actor → System/Actor

Capability

Operational Capability → System Capability

Mission

Operational Mission → System Mission

Exchange

Interaction relation → FunctionalExchange relation

Sequence

Process → FunctionalChain, or paired occurrence contributions directly owned by Process / FunctionalChain

Item

Same shared kind: ExchangeItem, DataType, DataField, Interface, or Port

Scenario

Same shared kind: Scenario, Lifeline, or Occurrence

State

Same shared kind: StateMachine, Region, any StateKind vertex, or Event

Manual

Engineer-defined relationship between OA/SA elements or relations

A nonempty baseline is { oa { name "..."; description "..."; }; sa { name "..."; description "..."; }; }. It records last-reviewed names and descriptions, not a full structural snapshot. Implementations MUST NOT present it as structural synchronization or a lossless inverse transition. Explicit counterpart creation, linking, comparison, and reviewed property update may start in either layer. Ordinary edits MUST NOT automatically overwrite the counterpart. Deletion MUST NOT silently cascade across layers.

Operational Capability normally creates a System Capability. Operational Mission and System Mission are default counterparts in both directions and use Mission traces. An explicit Operational Capability ↔ System Mission mapping remains supported with a Manual trace; a uniquely linked such counterpart is reused instead of silently creating a different mapping. Required/subtree process and chain transitions include their contextual links, owned contributions and referenced dependencies. New process/chain contribution mappings use Sequence traces; older Scenario-kind occurrence mappings remain accepted. The current name/description baseline does not reconcile later topology changes.

Name and description are compared independently against each endpoint's last-reviewed baseline. An unchanged source MUST NOT overwrite an accepted different target value. A source-only change proposes an update; different changes on both sides require an explicit decision to take the source, keep the target, or enter a merged value. An explicit decision records both current values as reviewed, even when they differ. A target-only edit is preserved without advancing that field's baseline, so it remains eligible to propagate in the reverse direction. Equal current values can be recorded as converged.

6. Text grammar and resolution

The input is UTF-8 without a byte-order mark. LF, CR, space, and tab separate tokens. // starts a comment through the next LF or end of input. Quoted strings use JSON string escaping, including \n, \r, \t, \\, and Unicode escapes. Literal unescaped newlines inside strings are invalid. Numbers use JSON decimal/exponent syntax; NaN, infinity, hexadecimal numbers, leading plus signs, and leading zeroes are invalid.

Aliases match [A-Za-z_][A-Za-z0-9_]*. The following aliases are reserved: arcadia, project, id, oa, sa, element, relation, diagram, node, artifact, trace, body, alias, name, description, parent, from, to, entity, actor, activity, capability, process, allocation, interaction, contribution, realization, position, steps, true, false, null. Other field/constructor spellings can be used in alias positions because references begin with @.

The grammar below uses * for repetition and ? for optional syntax. kind is exactly an element/relation keyword listed above. Typed field sets and enum constructors are closed by sections 3–5.

document    = "arcadia" "2" ";" project
project     = "project" alias string identity? "{" project-entry* "}"
project-entry = "description" string ";" | declaration
declaration = (kind | "diagram" | "artifact" | "trace")
              alias string identity? fields
            | "node" alias identity? fields
identity    = "id" uuid-string
fields      = "{" (field-name value ";")* "}"
value       = string | number | "true" | "false" | "none"
            | enum-word | "@" alias | list | fields
list        = "[" (value ("," value)*)? "]"

The project description defaults to empty and may appear once. There is no semicolon after a declaration or project closing brace. Every field inside a record ends with a semicolon, including fields whose values are nested records. Trailing list commas are invalid. Fields may appear in any order.

UUID strings use 8-4-4-4-12 hexadecimal spelling, with uppercase or lowercase hexadecimal accepted. Canonical output uses lowercase. Omitting an identity creates a fresh UUID for that parse. After Apply, canonical text includes the generated identity. Authors must keep identity clauses to preserve existing objects across subsequent edits.

All declaration aliases are collected before references resolve, so forward references are legal, including references to nodes and diagrams. Unknown @alias values are errors at their reference occurrence. Quoted UUIDs and bare words cannot substitute for reference-valued fields. Alias resolution produces UUID references; the alias itself remains stored on its declaration. Changing an alias in source requires updating its uses. Display names never resolve references.

Unknown fields, duplicate fields, incompatible constructor payloads, unsupported enum spellings, duplicate aliases/identities, and structural validation errors reject the entire candidate. No declaration silently replaces another. Ordinary string fields require quoted strings and cannot contain an unquoted @alias reference. Enum fields require unquoted named values or their typed constructor records. Typed values MUST NOT nest more than 64 levels; malformed deeply nested drafts produce a diagnostic instead of exhausting the parser stack.

7. Canonical output and source tooling

For every structurally valid Project P, parsing its canonical output MUST reproduce P, and canonicalizing that result MUST be idempotent:

parse(format(P)) == P
format(parse(format(P))) == format(P)

Equality includes all persistent fields, stored aliases, body variants, 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.

Canonical output uses LF, four-space indentation, explicit identities, defaults, and JSON-escaped strings. 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.

Comments and input whitespace are lexical trivia. Canonicalizing or applying source does not preserve them; the editor MUST explain that behavior. Camera position, selection, source cursor, and undo history are not stored model fields. Layout and collapse state inside NodePlacement are model data.

parse returns a complete structurally validated candidate or error diagnostics and no model. Engineering warnings alone MUST NOT reject a valid candidate. Parsing may stop at the first syntax/conversion error. Diagnostics contain severity (Error or Warning), a code identifying the diagnostic category, optional object UUID and field path, a message, one-based line and Unicode-scalar column, and a half-open UTF-8 byte Span. Tabs count as one scalar; LF advances the line. Structural errors and engineering warnings point to a relevant declaration or field when available. Clients MUST use severity/code rather than parse the human message; diagnostic wording is not a stable machine API.

parse_limited and decode_limited accept caller admission budgets for input bytes, graph objects, total persistent records and ordered step references. Counts MUST be checked before expensive structural validation; migration budgets include generated records before migration. Exceeding a budget does not mutate the model. Admission limits are host capacity policy, not hidden restrictions on otherwise valid language semantics; ordinary parse has no such model size policy. The REST service's fixed policy is specified in api.rst.

tokenize is tolerant: it covers every input byte with nonoverlapping TokenSpans, including incomplete strings and invalid characters. Token classes are Keyword, Identifier, String, Number, Comment, Punctuation, Whitespace, and Error. @ is punctuation followed by the referenced alias token. lint returns syntax, structural and engineering warning diagnostics for the current draft without adopting a model. A valid unallocated SA function, for example, can report missing System and allocation warnings before Apply. analyze combines tokens, diagnostics, and declaration spans with alias, optional explicit UUID, and declaration kind. A malformed later declaration does not erase earlier declaration spans. None of these APIs mutates a session.

8. Graphical parity and transactional editing

The graphical editor MUST expose all persistent properties through typed controls, including optional references, lists, variant constructors, aliases, IDs, nested parent changes, diagram placements, artifact metadata, and trace baselines. Inspector controls may provide operations unavailable as a canvas gesture. Exact strings, including tabs and line breaks in names, must remain editable through an appropriate escaped-string input when ordinary widgets cannot represent them.

A move into a functional parent, an allocation onto a performer, and a diagram 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.

Applying complete source is one model transaction. Graphical edits regenerate the matching canonical source. Both paths validate their candidate before adoption; failure preserves the committed model, save baseline, and history. Incomplete but structurally safe references and coverage gaps produce warnings that allow continued authoring and saving.

Source and property drafts contribute to unsaved-work state. Invalid drafts MUST NOT be replaced by generated text or lost on ordinary navigation. Undoing source-buffer edits and undoing committed model changes are distinct operations. Successful source Apply creates one model-history entry. Undo/redo restores the full stored model, including aliases, trace links, and occurrence identity.

Changing a graphical UUID must atomically update map keys and every reference. Alias changes preserve UUID identity. Deletion must account for owned elements, incident relationships, ordered occurrences, and diagram placements without 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.

9. Persistence and V1 migration

Loading detects JSON from its first non-whitespace {; other input is parsed as Arcadia source regardless of filename. Saving an .arcadia suffix uses canonical V2 text; other suffixes use V2 JSON. Both forms preserve the same complete model. JSON rejects unknown fields throughout typed payloads, duplicate maps, unsupported versions, and structural errors.

During first-release schema-2 integration, old Scenario bodies without a viewpoint retain MixedLegacy, and old Exchange occurrence bodies without message_kind, reply_to or values retain Asynchronous, none and []. Old StateTransition bodies without transition_kind, triggers or effects retain External, [] and []; existing trigger, guard and effect strings are preserved exactly. Source declaration defaults and JSON compatibility defaults MUST agree; no event or work reference is inferred from legacy prose. 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. A future schema-3 adapter remains a separate required migration and is not implied by this temporary compatibility behavior.

Save validates a snapshot, writes a sibling temporary file, flushes it, and atomically replaces the destination. Failed serialization or validation must not truncate a previous file. External concurrent-writer conflict resolution and crash recovery are outside this storage contract.

Version 1 text and JSON are accepted through an explicit adapter. Migration preserves project, element, and relation UUIDs; names and descriptions; relation endpoints; all coordinates; and exact process exchange order and repetition. Allocation and Interaction remain their corresponding V2 relations. Contribution becomes CapabilityInvolvement; the old Process-to-Capability Realization becomes ChainInvolvement, retaining its ID and name.

Legacy positions become nodes in one OA Overview diagram. Each old process step becomes a distinct occurrence element owned by its process. Generated diagram, node, and occurrence identities use UUID v5 with the old Project ID as namespace and deterministic role/index names. If such an identity collides, a deterministic suffix is tried. Reimporting the same fully identified V1 input therefore yields the same V2 result, including repeated occurrences. No System or SA content is invented during an OA-only migration.

V1 source aliases are retained when valid in V2. JSON imports generate the old canonical aliases deterministically. Any alias newly reserved in V2 is changed by a deterministic suffix; aliases were not persistent V1 semantic data. A project alias and aliases for new records are generated without colliding with retained aliases. The existing bundled V1 text and JSON migrate to equal V2 projects. Future versions remain errors until a separate migration exists.

10. Complete authoring example

This document intentionally omits IDs for new authoring. Apply and Save write the assigned UUIDs explicitly.

arcadia 2;
project harbor "Harbor assistance" {
    oa_entity control "Harbor control" {}
    oa_activity assess "Assess assistance" {}
    allocation performs "Performs" {
        source @control;
        target @assess;
    }
    sa_system service "Harbor assistance system" {}
    sa_function evaluate "Evaluate assistance request" {}
    allocation system_performs "Performs" {
        source @service;
        target @evaluate;
    }
    trace assessment_trace "Operational realization" {
        oa @assess;
        sa @evaluate;
        kind Refinement;
    }
    diagram operational_view "Operational allocation" {
        layer Oa;
        projection Allocation;
    }
    node assessment_card {
        diagram @operational_view;
        element @assess;
        position { x 360; y 70; };
    }
}

11. Verification and primary references

cargo test -p oa-core --test language --locked verifies typed round-trips, stable aliases, strict syntax/reference failures, Unicode source spans, deterministic V1 migration, occurrence identity, coordinate bits, persistence, and model-history integration. Core validation and session tests cover domain mutation rules; native UI and API verification are separate evidence.

The research record in docs/research-v2-semantics.md distinguishes method requirements from Studio design choices and links to original UI evidence. The following sources inform this language without extending its scope: