Files
ARCADIA-Studio/docs/research-v2-architecture.md
T

40 KiB

V2 architecture research and proposed extension

Status: implementation proposal, 2026-09-05. This document maps the current source and recommends a concrete OA/SA extension. It does not certify that V2 behavior exists, change the current normative language contract, or claim Capella interchange/conformance. No source code, harness, builds, or GUI sessions were changed for this investigation.

The investigation began with harness/STATE.md, harness/PLAN.md, harness/DECISIONS.md, the architecture review, README, and docs/{scope,architecture,acceptance,validation}.md, followed by the normative docs/language.rst and core/desktop implementation and tests. Domain names below describe a proposed bounded dialect; the accompanying method research should settle precise OA/SA terminology before the V2 specification is frozen.

Keep one typed Rust project and one validated transaction boundary. Extend the existing core with layer-specific records, explicit semantic containment, separate diagram placements, stable stored aliases, artifact links, and first-class OA/SA traces. Both editors and a small REST service must use the same commands, transition planner, source analyzer, and validator.

The smallest useful implementation is an extension of the current two-crate design plus a server adapter. It does not need a second metamodel, automatic continuous synchronization between layers, a general-purpose graph database, a runtime metamodel/plugin framework, or a physical deployment model disguised as System Analysis. Larger modules can be split when the concrete records below arrive.

Current implementation and concrete fault lines

Existing seam What is implemented What V2 must change
crates/oa-core/src/model.rs:11 Five closed OA element kinds; layer is implicit. Use an explicit OA/SA kind distinction so a System Function cannot accidentally become an Operational Activity with an arbitrary layer tag.
model.rs:54 Every element has one absolute position and a steps vector, including kinds that cannot use steps. Separate semantic ownership from placements; move process/chain steps into typed bodies.
model.rs:78 Relation admissibility is a predicate of two kinds only. Check layer, ownership, endpoint roles, allocation cardinality, and any exchange/port contract in shared semantic functions.
model.rs:118 Project has only two UUID maps; strict decoding rejects unknown fields. Introduce a schema-2 DTO and explicit schema-1 migration. New fields cannot simply be added and expected to load old JSON.
session.rs:33,40 Session.project is publicly mutable and Session::new trusts its caller. Make accepted state private; try_new validates. This becomes necessary once REST and multiple frontends are callers.
session.rs:70 Clone, mutate, validate, adopt, and snapshot history form one atomic command. Preserve this behavior; add batch preparation/commit, structured results, revision preconditions, and transition plans.
session.rs:148,203 Delete knows incident relations and process steps; reidentification knows endpoints and process-step IDs. Cover containment, role realization, exchange items, lifelines, occurrences, state machines, traces, artifacts, and placements through one explicit reference visitor/index.
session.rs:170 Arrange groups every project element into five global kind columns. Layout the active view and visible hierarchy; moving/arranging must not reposition a different layer or hidden diagram.
validation.rs:13 Structural errors and incomplete-model warnings share one pass. Allocation is direct and OA-only; process continuity compares adjacent exchange endpoints. Add stable diagnostic codes/object/field paths, layer-aware rules and hierarchy-aware warnings. Preserve repeated process occurrences and warning-only incomplete models.
language.rs:39,109 Printer regenerates aliases from names in UUID-map order, with collision suffixes. Persist aliases. A rename currently rewrites references and can renumber unrelated same-name aliases; a smaller UUID insertion can also change collision assignments.
language.rs:27,203,401 Lexer/parser are private; parsing usually returns the first error, source locations are starts only, comments are dropped. Expose tolerant token spans and a noncommitting analysis result for highlighting/linting; keep strict Apply separate.
persistence.rs:15,47 Content-based text/JSON loading, shared validation, sibling-temp atomic save. Version dispatch, deterministic migration, read/write conflict fingerprints, and server repository commit semantics. Atomic replacement alone does not detect external modifications.
oa-desktop/src/app.rs:614,769,846,853,959 One selection ID, one property draft, one source buffer, one camera; accepted commands regenerate canonical text. Preserve single-draft ownership, add typed selection/context and source baseline revisions, and use command-result remaps instead of selected-ID-specific fixups.
app.rs:1044, views.rs:617 Pickers filter kinds; inspector handles only project/element/relation. Use core candidate queries for legal parents/endpoints/allocations; add graphical controls for every new mutable record field.
views.rs:329 Monospaced text editor and Apply diagnostics; no highlighting or live linting. Highlight the actual language and display debounced diagnostics without committing the draft.
diagram.rs:10,31,322, app.rs:1532 Fixed-size flat cards, reverse-map hit testing, global Fit and global SVG export. Resolve active-view bounds, paint parents before children, hit-test deepest visible nodes, and share a view projection between canvas, Fit, and SVG.

The existing test suite supplies valuable foundations: exact parse/print equality and canonical idempotence, exact strings and floating-point coordinates, parallel interactions, repeated ordered steps, rejected-edit history preservation, identity remapping, strict JSON, atomic saves, and desktop draft/identity guards. The repository records 50 tests in the completed MVP; this investigation read the tests and did not rerun them. None of that historical evidence demonstrates V2 behavior.

Typed schema-2 records

Retain UUID identity and ordered maps. Avoid opaque properties: Map<String, Value> as the representation of new semantics. The following is an API sketch, not compilable Rust or a finalized JSON grammar:

enum Layer { Operational, System }
enum ElementKind { Operational(OaKind), System(SaKind) }
enum OaKind { Entity, Actor, Role, Activity, Capability, Process }
enum SaKind { System, Actor, Function, Capability, Mission, FunctionalChain }

struct Header { id: Id, alias: Alias, name: String, description: String }
struct Element {
    header: Header,
    parent: Option<Id>,              // semantic decomposition, not performer
    body: ElementBody,               // kind derived from this closed enum
}
enum ElementBody {
    OperationalEntity, OperationalActor, OperationalRole,
    OperationalActivity, OperationalCapability,
    OperationalProcess { steps: Vec<ExchangeOccurrence> },
    System, SystemActor, SystemFunction, SystemCapability, SystemMission,
    FunctionalChain { steps: Vec<ExchangeOccurrence> },
}
struct ExchangeOccurrence { id: Id, alias: Alias, exchange: Id }
struct Relation { header: Header, body: RelationBody }

struct Project {
    schema_version: u32,
    header: Header,
    elements: BTreeMap<Id, Element>,
    relations: BTreeMap<Id, Relation>,
    exchange_items: BTreeMap<Id, ExchangeItem>,
    scenarios: BTreeMap<Id, Scenario>,
    state_machines: BTreeMap<Id, StateMachine>,
    traces: BTreeMap<Id, Trace>,
    artifact_links: BTreeMap<Id, ArtifactLink>,
    diagrams: BTreeMap<Id, Diagram>,
}

ElementBody is the source of its kind and layer. Do not store both kind and an independent contradictory body/layer. Existing method calls can expose kind() and layer() so UI matches remain exhaustive. An SA project may be incomplete: no system yet is a warning; more than one system of interest in the project's single SA analysis is an error. SA actors are outside that system boundary. Do not introduce logical components, physical nodes, or deployment as SA kinds.

Use ObjectRef with explicit variants for project, element, relation, exchange item, scenario, occurrence, state machine/state/transition, trace, and diagram as needed. IDs still occupy one global namespace, including occurrences and diagram nodes; references also check their expected category. This makes artifact targets and diagnostic focus precise without making all ordinary relations accept arbitrary endpoints. Plain typed helpers (visit_refs, remap_refs, dependencies) suffice; a generalized runtime reflection system is unnecessary.

Containment and allocation

Persist one parent reference per semantic element, with children derived. Do not persist both children lists and parent pointers. Canonical declarations remain flat with parent alias;, so moving a subtree changes one ownership line rather than indenting and relocating every descendant. The graphical explorer/canvas still presents a hierarchy.

Recommended initial containment matrix:

Child Allowed semantic parent
OA entity OA entity, or layer root
OA actor OA entity, or layer root; actor remains a distinct participant kind
OA activity OA activity, or layer root
OA capability OA capability, or layer root
OA role/process Layer root initially; involvement is represented by relations
SA function SA function, or layer root
SA capability SA capability, or layer root
SA system/actor/mission/chain Layer root initially

These are deliberately explicit product rules to reconcile with the method research. Adding an allowed parent is a small validator/spec change. Allowing every kind under every kind would lose meaningful validation. Cross-layer parents, self-parenting, multiple ownership, and cycles reject a transaction. Empty roots and incomplete allocations remain editable. Neither graphical overlap nor dragging inside a card silently changes semantic parent or performer: a dedicated nest/reparent/allocate action previews the semantic edit.

Allocation is independent of decomposition. An activity may be decomposed into activities performed by different operational participants. A function's decomposition is independent of whether the system or an external actor performs it. Persist direct allocations only. The first release should not infer or materialize allocation inheritance: a parent allocation does not silently allocate descendants. Diagnose unallocated leaves; label any aggregate coverage as derived. If later requirements demand inheritance, define effective-performer resolution and explicit override rules before changing this behavior.

Keep at most one direct performer per OA activity and SA function. OA roles organize responsibility through explicit participant-to-role realization and role-to-activity assignment; validate that resolving an assigned role does not create contradictory direct performers. A role may be unfilled or filled by multiple participants as a warning/incomplete modeling state only if the chosen dialect permits it; do not silently choose the first participant. The simplest initial policy is to keep role assignment separate from the existing authoritative direct allocation and report any mismatch. This preserves schema-1 meaning and avoids inventing implicit execution semantics.

Layer-specific relationships

Use typed RelationBody variants with meaningful field names, even when several variants are stored in the same map. Each variant defines its live references, layer rule, and allowed endpoint query in core.

Relationship family Endpoints and principal constraints
OA allocation OA entity/actor → OA activity; one direct performer per activity
OA interaction OA activity → OA activity; same layer, distinct endpoints; parallel distinct-ID exchanges remain legal
OA contribution / process realization Preserve existing activity → capability and process → capability semantics; label these as the existing dialect's OA support links
Role realization / role assignment OA entity/actor → OA role; OA role → OA activity; independent of containment
Capability involvement OA participant → OA capability, or SA system/actor → SA capability; never an accidental cross-layer ordinary relation
SA allocation SA system/actor → SA function; one direct performer per function
SA functional exchange SA function → SA function; same layer, distinct endpoints; carries exchange-item references
SA capability support SA function/functional chain → SA capability; distinct variant from an OA↔SA trace
SA mission capability link SA mission → SA capability; exact label should follow the method vocabulary adopted for V2
Exchange item carriage OA interaction or SA functional exchange → same-layer exchange item; no deduplication that merges parallel exchanges
State/mode availability Same-layer element → state/mode belonging to a relevant context; stored as an explicit typed association

Use distinct names for process-to-capability realization, function-to-activity traceability, and external requirement satisfaction. The existing Realization enum must not become a catch-all for unrelated semantics. Direction and admissibility must be testable without GUI code.

Practical behavioral records

Add the requested practical fields as explicit records rather than free-form descriptions:

  • ExchangeItem { header, layer, item_kind, fields: Vec<ItemField> }, with stable field IDs/aliases, field name, a bounded type reference (primitive or another item), multiplicity bounds, and unit/description where needed. Use cases include information and material; do not fabricate a physical communication protocol. Validate field references, multiplicity, and item layer. A self-referential data type can be permitted explicitly without confusing it with forbidden containment cycles.
  • Scenario { header, layer, context: ObjectRef, lifelines, steps }. Lifelines have stable IDs and reference same-layer participants or supported behavior elements. Each ordered occurrence has its own ID; exchange occurrences identify the exchange and sending/receiving lifelines; action occurrences identify the activity/function and lifeline; state observations reference a defined mode/state. Repeated exchanges remain separate occurrences. Bounded Sequence, Alternative, Optional, Loop, and Parallel fragments contain ordered branches, each with a guard and child occurrences. Guards/events remain expressions or text in a specified limited language; there is no claim of executable simulation merely because a scenario is drawn.
  • StateMachine { header, layer, owner, states, transitions }. State/mode records have stable IDs, an explicit State versus Mode kind, parent/region membership, initial marker and entry/exit/do text. Transitions have source, target, trigger, guard, and effect. Start with hierarchical exclusive regions: at most one initial child per region, absence is a warning, cross-machine endpoints are errors. Orthogonal regions and executable guard semantics require an explicit later contract rather than pretending arbitrary nesting implements them.

Scenario ordering and state-machine transitions are different structures. An ordered OA process/SA chain remains a lightweight exchange sequence and can coexist with richer scenarios. Do not coerce every scenario into the legacy Element.steps vector. GUI editors must be able to create/reorder repeated occurrences and edit all fragment/branch/guard, lifeline, state, and transition fields that source accepts.

Diagram records

Introduce at least independent OA and SA views and a mixed traceability view. The smallest concrete record is Diagram { header, kind, nodes: BTreeMap<Id, NodePlacement> }, with NodePlacement { id, alias, element: Id, position: Position, size: Size, collapsed: bool }. Initially permit at most one placement per element per diagram. The diagram kind is a closed enum naming its layer and projection (overview, decomposition, allocation, or mixed traceability); a second independently contradictory layer flag is unnecessary. Edge endpoints/routes are derived, so no edge-layout map is necessary until route editing is actually supported. The same element may appear in different views without duplicated semantics. Keep positions absolute in diagram coordinates initially to preserve exact existing values and avoid coordinate-frame ambiguity.

A decomposition presentation derives visible container relationships from semantic parents; an allocation presentation derives performer/activity or system/function grouping from allocation relations. These are different projections of the same records, with fixed documented rules per diagram kind. No visual parent pointer is persisted. Moving a visible container translates its visible descendants in that view in one transaction. Collapsing hides descendants without deleting semantic records. Exact X/Y/width/height and collapse controls are available in the inspector and text. Fit and SVG use the same visible projection and container bounds as the canvas. Selection should distinguish semantic object and diagram occurrence, so changing an element's name affects all views while moving a node affects its selected view. Arbitrary grouping, editable routing, and a plugin view framework can wait.

Use a first-class record:

struct ArtifactLink {
    header: Header,
    owner: ObjectRef,
    relation: ArtifactRelation, // references, satisfies, verifies, implements,
                               // derived_from, or explicitly labeled custom
    uri: String,
    revision: Option<String>,
    fragment: Option<String>,
    rationale: String,
}

Store the exact URI, revision, and fragment supplied by the user. A revision is an opaque external version/commit/tag, not a project revision number or a Git-only SHA. Support arbitrary well-formed URI schemes and documented relative URI references resolved against project location. No HTTP-only restriction, Windows path coercion, lowercasing, eager dereferencing, or URL normalization should change identity. A native path may be accepted by a UI convenience action and converted explicitly to a file: URI.

The validator checks syntax, text limits, and internal owner existence; external reachability is an optional nonblocking diagnostic. Loading, linting, previewing, and displaying a model must not fetch links. Opening a supported URI is a separate user action; unfamiliar schemes can remain copyable even if no opener is registered. This supports requirement tools, source repositories, documents, CAD/data artifacts, and proprietary systems without baking a provider into the metamodel.

Bidirectional transitions and traceability

A transition is an explicit modeling operation that can create or update selected counterpart records in either direction. It does not change an existing object's layer/kind in place or imply that OA and SA are identical. Operational activities can be refined into several functions, and several operational concepts can support one system concept.

Store traces canonically as oa: ObjectRef and sa: ObjectRef, independent of whether the user initiated OA→SA or SA→OA. A trace includes ID/alias/name, a typed mapping kind, rationale, and a last-reviewed synchronization baseline. Endpoint-kind rules allow appropriate element pairs, exchange pairs, process/chain pairs, item pairs, and scenario pairs. A generic explanatory trace may connect other supported pairs but does not make them eligible for automatic copying. Many-to-many semantic traceability is valid; duplicate identical mapping links should be rejected or explicitly distinguished by purpose.

Minimum transition mappings:

Source/target pair Creation and update behavior
OA activity ↔ SA function Copy selected name/description fields; create/reuse counterpart and trace. Decomposition can be included using an explicit parent mapping.
OA participant ↔ SA actor or system OA→SA requires the user to select system-of-interest versus external-actor treatment. Reverse mapping requires a chosen OA entity/actor kind; do not guess from labels.
OA capability ↔ SA capability Copy selected descriptive fields and create/reuse trace; support/involvement relations require mapped endpoints.
OA interaction ↔ SA functional exchange Require or explicitly create both endpoint mappings; items map by selected item traces; preserve parallel exchange identities.
OA process ↔ SA functional chain Map each ordered occurrence, preserving order and repetition; unresolved exchange mappings block that proposed sequence, not the entire ability to model.
OA exchange item ↔ SA exchange item Copy selected fields through an explicit item/type mapping; report unsupported types rather than dropping them.
OA scenario ↔ SA scenario Map context, lifelines and occurrences/fragments through existing/selected traces; unresolved participants or behavior are shown as plan conflicts.
Modes/states and roles Trace and manual mapping are always possible where valid. Automatic conversion needs an explicit mapping rule; OA roles do not automatically become SA components.

Preview/commit algorithm

  1. plan_transition(project, request) reads an accepted snapshot and selected source IDs, direction, mapping choices, fields to copy, dependency scope, and requested destination view. It never mutates the active model.
  2. Resolve eligible traces first. For one-to-many matches, require explicit selected targets or create-new choice. Display create, reuse, update, skipped, and conflicted records separately; never match solely by name.
  3. Expand only the chosen dependency closure. For example, an exchange needs mapped endpoints, and a chain needs its mapped exchanges. Do not silently migrate the entire model or relocate an existing counterpart's semantic parent.
  4. Produce proposed typed commands with new IDs/aliases generated once, source/destination field changes, trace creation/updates, unresolved choices, and structural diagnostics. Preview includes the base revision and a plan fingerprint. Retry uses the same planned IDs; apply does not regenerate a fresh duplicate model.
  5. Commit verifies the base revision and accepted plan fingerprint, revalidates the complete candidate, and adopts the entire batch as one history operation. Any unresolved error, stale plan, or persistence failure rejects without changing accepted state. A repeated successful API request with the same idempotency key returns its original result.
  6. Show both layers and their traces after commit. Reverse transition uses the same planner and canonical trace orientation. Repeated planning of an unchanged, already-linked projection proposes no duplicate creation or meaningless history entry.

Drift and conflicts

Persist the last explicitly accepted pair of synchronization projections on an eligible trace. Projections contain only mapped semantic fields and typed references; aliases, layout, timestamps, and unrelated properties do not cause propagation. Baselines are historical values, not extra live domain objects. They are serialized, inspectable in text and GUI, and updated only by accepting a transition/reconciliation operation. A copied status flag alone cannot detect drift.

Compare current OA and SA projections against their respective baselines. If neither changed, there is no update. A source-only change can propose copying the selected fields. A destination change, changes on both sides, missing baseline, changed mapping cardinality, or unsupported conversion requires a review choice per affected field/relationship. Present preserve destination, take proposed source, or supply an explicit merged value. Do not silently overwrite the destination because its trace happens to exist. A coarse pair of hashes is sufficient for conservative whole-object conflicts, but structured baseline fields enable a useful property diff and safe independent-field merging.

One-to-many mappings do not imply broadcasting names, deleting surplus targets, or reversing a decomposition into a flat record. A transition may offer such changes only as visible, selected plan operations. Deleting an endpoint requires an impact plan for its traces; it must not automatically delete a counterpart in another layer. Removing a trace simply unlinks the objects. Reidentification rewrites live references and uses an explicit remap policy for baseline references; historical values must not masquerade as dangling live references.

Shared command and API boundary

Extend Session incrementally rather than placing transition semantics in app.rs:

struct CommandEnvelope {
    expected_revision: Revision,
    request_id: RequestId,
    commands: Vec<Command>,
}
struct ChangeResult {
    revision: Revision,
    created: Vec<ObjectRef>,
    changed: Vec<ObjectRef>,
    deleted: Vec<ObjectRef>,
    identity_remap: BTreeMap<Id, Id>,
    diagnostics: Vec<Diagnostic>,
}
// prepare applies to a candidate; commit adopts only that verified candidate.
fn prepare(&self, envelope: CommandEnvelope) -> Result<PreparedChange, Errors>;
fn commit(&mut self, prepared: PreparedChange) -> Result<ChangeResult, Conflict>;

Add concrete create/edit commands for new records, Reparent, Allocate, occurrence insert/reorder/remove, diagram placement changes, trace/artifact CRUD, Replace, and transition-plan application. Whole-document Replace is still one candidate transaction. Use IDs for ordered occurrences; stale remote indices should not accidentally reorder a different step. Structural checks occur after the whole batch, so creating a child and parent or remapping all relations does not require a temporarily valid intermediate model.

Make session state private and validate every accepted entry point. Keep revision and runtime history outside the persistent model to avoid one Git diff line changing on every command. An accepted revision changes for undo/redo too. Undo/redo remain session-local committed model operations; a shared server must not let one client unknowingly undo another client's work. The initial backend can expose one coordinated writer/session with revision checks instead of claiming collaborative multi-user undo.

Deletion needs a core impact query. Offer explicit subtree-delete versus reparent-children behavior, show removed incident relations/occurrences/traces/artifact links/view nodes, and preserve other-layer counterparts. Both GUI and REST submit the selected policy. Text replacement naturally expresses the final candidate but receives the same dangling-reference errors. One shared reference traversal powers impact, reidentify, validation, trace queries, and test fixtures.

REST adapter

Add crates/oa-server around core/application services. Desktop defaults to in-process calls; when connected to a server it uses that server's accepted model and revisions rather than independently mutating a duplicate authoritative session. Core must not depend on HTTP, Iced, sockets, dialogs, or provider APIs.

Suggested initial routes:

Route Contract
GET /v1/projects/{id} Schema-2 snapshot and ETag/current revision
GET /v1/projects/{id}/source Canonical .arcadia representation and same accepted revision
POST /v1/analyze Noncommitting text analysis, tokens/diagnostics and optional candidate summary
POST /v1/projects/{id}/commands Typed command batch, revision precondition, idempotency key; structured change result
PUT /v1/projects/{id}/source Parse and Replace with revision precondition; same validators and history
POST /v1/projects/{id}/transitions/preview Direction, source selection, explicit mapping/field choices → concrete plan
POST /v1/projects/{id}/transitions/apply Plan/fingerprint, choices, revision and idempotency key → atomic change
GET /v1/projects/{id}/diagnostics Stable codes, severity, ObjectRef/field, related references
GET /v1/projects/{id}/traces?object=... Both incoming and outgoing traceability, drift status, counterpart references

Use structured serialization and publish an OpenAPI contract/example requests alongside the normative modeling specification. Invalid syntax is 400; invalid semantic candidates can use 422; stale revisions are 409/412 according to the documented conditional-request contract; missing projects/objects are 404. Never return a success status for a partial transition. Service revisions and external artifact revisions are distinct fields/types.

A single-file repository is sufficient initially. Hold a project write lock while checking revision, preparing, atomically persisting the candidate, and adopting it; all fallible validation/serialization occurs before adoption. A failed write leaves accepted memory/history unchanged. Crash after a successful disk replace reloads the persisted candidate. Retain an external-file fingerprint on load/save and reject stale overwrites instead of relying on temp.persist alone. Do not let a backend request provide an arbitrary host filesystem destination; it acts on configured project IDs/locations. Local-only binding can be the initial launch mode; remote deployment/authentication is a separate configured service capability.

Language, stable diffs, highlighting, and linting

Schema 2 must persist aliases for elements, relations, traces, artifacts, diagrams and other source-addressable records. UUID remains identity; alias is an explicit human-readable property. Generate an alias once at creation and keep it across rename, reparent, layout, and layer transitions. Renaming a display name must change that string only. Renaming an alias is an intentional refactoring that updates printed references; UUID references internally remain stable.

Use a project-wide alias namespace initially. It avoids ambiguous references, path-dependent aliases that change when reparenting, and special quoting of cross-layer references. New generated collisions use a stable UUID-derived suffix; never rename existing objects to make room for an insertion. A user-supplied duplicate alias is an error. Keep UUID-based declaration ordering initially: it is stable on ordinary edits and does not reorder blocks when names/aliases change. Separate semantic records, links/traces, and diagram sections, with fixed field order and one independently editable property per line.

The conceptual source shape is:

arcadia 2;
project mission "Coastal rescue" id "..." {
    oa activity assess "Assess incident" id "..." {
        description "Determine the response needed.";
        parent coordinate;
    }
    sa function classify "Classify incident" id "..." {
        description "Determine incident class.";
    }
    trace assess_classify "Function refinement" id "..." {
        oa assess;
        sa classify;
        kind function_refinement;
        rationale "Classification supports operational assessment.";
    }
    artifact assessment_requirement "REQ-42" id "..." {
        owner classify;
        relation satisfies;
        uri "requirements://rescue/REQ-42";
        revision "baseline-2026-09";
    }
    // Diagram node placements live in a separate diagram section.
}

This illustrates syntax direction only; ... is not a valid UUID, and the excerpt omits the referenced parent declaration. Final grammar must define every record, field, default and rule before implementation. Keep JSON-compatible exact strings and finite-coordinate guarantees. Optional IDs remain useful for new authoring, but linting must not allocate new persistent IDs on each keystroke. Parse into syntax/semantic candidates with unresolved generated identities and materialize IDs once on successful Apply/creation. Saved text always includes IDs. Reparse of an existing ID-bearing document must preserve identity.

Expose analyze(source) -> Analysis { tokens, syntax, references, diagnostics } independently from strict parse/validate and commit. Tokens carry byte ranges and classes for keywords, layer/kind names, declarations/references, strings, numbers, punctuation, and comments. A tolerant lexer must still color incomplete strings/documents. Synchronize parser recovery at declaration/field boundaries to provide multiple useful errors. Core diagnostic codes, severity, primary range, ObjectRef/field path and related ranges serve both the Iced editor and REST linting. Preserve one-based Unicode-scalar display columns; future LSP adapters must convert explicitly to the negotiated position encoding.

Use debounced background analysis with a draft-generation number; discard stale results and never replace the committed model while typing. Warnings should include unallocated leaves, unsupported capabilities, process/scenario gaps, incomplete state initialization, and trace drift. Errors include illegal cross-layer ordinary relations, cycles, dangling references, duplicate identities/aliases, incompatible mappings, and bad field types. Quick fixes, if added, produce ordinary commands or explicit source edits.

V1 discards comments; V2 must state its preservation policy clearly. The smallest Git-friendly extension can retain normalized formatting while persisting model documentation in description/rationale fields. If comments must survive graphical edits, preserve attached trivia in a document layer keyed by IDs and reconcile it on Replace, with orphan-comment behavior and tests. Do not promise lossless source editing while continuing to overwrite source_editor from a comment-free printer. Syntax highlighting and useful linting do not require a full language server or a second semantic store.

Deterministic schema-1 migration

Dispatch on format and version before decoding the final model. Keep strict ProjectV1/text-v1 readers and validate with the old rules; do not temporarily disable current validation or silently deserialize unknown fields. Reject future versions with a located/actionable message.

  1. Preserve project, element, and relation UUIDs and every stored name/description exactly.
  2. Map all five legacy kinds and four relation kinds to their explicit OA equivalents. Parent is absent; no SA system, actors, roles, or external requirements are invented.
  3. Move each legacy element position into a generated OA overview node, preserving IEEE f32 values including signed zero in the representations where the existing contract requires it.
  4. Preserve process step order and repeated interaction references. If occurrences gain identities, derive stable migration IDs from project UUID, process UUID, and original occurrence index.
  5. Generate the OA overview and node IDs deterministically from a documented namespace; resolve even pathological collisions deterministically against the complete old/new identity set. Random migration IDs would make opening the same legacy file yield unrelated Git changes.
  6. For legacy JSON, compute old canonical aliases once using the V1 algorithm and persist them. For V1 source, choose and specify one policy: retaining supplied aliases is more helpful to source authors; using V1 canonical aliases preserves the historical JSON/text equivalence baseline. Recommended compatibility policy is canonical V1 aliases for both, with an explicit import option to retain authored aliases if needed. Generate the newly introduced project/view/occurrence aliases after reserving all legacy object aliases so they cannot rename old references. No policy may silently change aliases on later saves.
  7. Initialize new record collections empty, validate the complete V2 project, and return an import/migration report. Migration never overwrites the source merely because it was opened.
  8. Save schema 2 explicitly. Do not label a V2 file schema 1 or silently drop SA/trace/artifact/behavior data on downgrade. A legacy export can exist only as a separately named, validated operation with a concrete loss report.

Migration should be idempotent and deterministic: migrating identical input produces identical V2 project and canonical text. Tests must compare the old/new semantic projection as well as full migrated text/JSON round trips; equality alone between two incorrectly migrated results would not prove preservation.

Graphical/textual parity and acceptance gates

Maintain a field matrix in the V2 specification: each persistent mutable field names its source syntax, graphical editor/control, core command, validator rule, and round-trip fixture. This includes aliases, parents, trace endpoints/mapping/rationale/baselines, arbitrary artifact URI/revision/fragment, scenario branches/guards/occurrences, states/transitions, item fields, and diagram coordinates. A read-only inspector does not meet editing parity. System-generated identifiers/baselines may have dedicated explicit operations, but both representations must expose their supported editing contract.

Preserve the established draft guard: source and property drafts cannot coexist as competing writers. Layer/view switches, transition launch, artifact edits, hierarchy actions, and mode/scenario editors first apply or explicitly revert the active draft. Invalid source keeps its buffer and Help remains read-only/reachable. A source draft carries the base accepted revision, so remote changes produce an explicit conflict instead of being overwritten by Apply. Command results carry all identity remaps for selections, pickers, diagrams and transitions; do not extend the current handful of Message::Command selected-ID rewrites for each new feature.

Meaningful new verification should cover:

  • Full-field V2 parse/print and JSON equality with OA+SA, hierarchy, repeated occurrences, branches, modes/states, items, artifacts and traces; canonical idempotence and exact strings/coordinates.
  • Display rename changes only its field; inserting a colliding-name object preserves existing aliases/references; reparent changes only its parent line; arrangement changes only the active diagram section.
  • Every legal/illegal endpoint and containment family, direct allocation cardinality, parent cycles, state/occurrence references, no accidental cross-layer ordinary relations, and same-kind cross-layer confusion rejection.
  • Forward and reverse traced creation, reuse, update and manual merge; unchanged repeat is a no-op; one-to-many ambiguity, unmapped endpoints, stale plans, repeated API requests and failed writes cannot leave partial results or duplicate counterparts.
  • Drift from either side, both sides, layout-only changes, alias rename, deletion and identity changes; deleting a traced source preserves the counterpart and provides an explicit trace impact result.
  • Cascade/identity reference coverage over every reference-bearing record, with one Undo restoring the entire transaction and failed operations preserving redo.
  • Deterministic migration from both bundled schema-1 formats, malformed/future input rejection, old-field preservation and collision handling for generated IDs.
  • GUI editing for every mutable field and text→graph→text round trips, nested drag/reparent distinctions, multiple-view Fit/SVG, invalid drafts across layer switches, and actual OA→SA→OA preview/commit workflows.
  • REST contract tests proving parity with in-process commands, stale ETag/revision rejection, structured diagnostics, arbitrary URI storage without fetch, and source Replace using the same validation.

Recommended implementation order is schema/spec and migration; transaction/reference hardening; OA hierarchy/roles and SA semantic records; source/parser/printer parity; traced transition planner; graphical views/editors; shared analysis/highlighting/linting; REST adapter; then cross-platform builds and actual GUI/API acceptance. Ship no V2 feature as complete before both editing forms and its rejection/undo/migration paths are verified.