Files
ARCADIA-Studio/harness/release-state-contract.md

15 KiB

R5 state/event implementation contract proposal — 2026-09-05

Status: adopted by the lead after the focused R1/R2/R4 correctness review closed. R5 core, source/API and graphical authoring implementation is in progress; runtime acceptance is not yet claimed. The lead adopted the four Studio choices at the end of this contract; current evidence and ownership are in STATE.md.

Evidence and intended outcome

The native benchmark actually created an H* Deep History node inside Maintenance's region. It also added [AFTER]30 s beside an existing function trigger (Load Digital Media) in a transition inspector. It did not establish history fallback connectivity or validate the resulting transition. Preserve that distinction when writing acceptance evidence. See harness/release-capella-workflow-benchmark.md:71.

The implementation currently has State/Mode/Initial/Final, nested regions, StateAction links, availability, and trigger/guard/effect strings. Rendering shows Initial/Final glyphs and ordinary boxes; creation only offers State and Initial. Keep its one typed state payload and existing command/reference boundary. R5 should let an engineer build degraded operation, parallel work, recovery history and a timeout without creating unrelated custom objects.

Capella exposes typed time/change events, transition kinds and additional state vertices. Its relative/absolute time choices distinguish AFTER from AT, and its transition definition retains descriptive triggering text separately from semantic trigger references. Those are the implementation features to retain. CapellaCommon schema.

Exact model changes

  1. Extend both StateKind and corresponding Kind entries with Choice, Junction, Fork, Join, ShallowHistory, DeepHistory, EntryPoint, ExitPoint, Terminate. Continue using SharedBody::State { kind, entry, exit, do_activity }. No separate graph or state-machine framework. Keep is_state as the vertex-family query, but add is_stable_state (State/Mode only) and use it for behaviors, availability, observation and composite ownership so expanding the family cannot grant pseudostates inappropriate features. This is nine additional vertices.

  2. Add one Kind::Event, shared in OA/SA:

    SharedBody::Event { body: EventBody }
    enum EventBody {
        Signal { items: Vec<Id> },
        Time { kind: TimeEventKind, expression: String },
        Change { expression: String },
    }
    enum TimeEventKind { After, At }
    enum TransitionKind { External, Local, Internal }
    

    An Event is an ordinary root semantic record with durable name/alias/UUID. Signal identifies a named occurrence with optional same-layer ExchangeItem payloads. Time and Change are expression-based definitions. AFTER is relative to activation of the transition's source state; AT names an absolute instant. Change means the named Boolean condition becomes true. No expression evaluator, timer runtime, event queue or signal transport is introduced. Empty time/change expressions warn; invalid XML text, duplicates or invalid item references reject.

  3. Extend existing RelationBody::StateTransition with transition_kind: TransitionKind, triggers: Vec<Id>, effects: Vec<Id>. Retain source, target, trigger, guard, effect exactly. triggers accepts same-layer Event or Activity/Function IDs; the referenced kind resolves their meaning without another wrapper object. effects accepts same-layer work IDs. Duplicate references reject. The text fields remain authored prose, not inferred UUIDs or executable code. Factory defaults are External and empty lists. Entry/do/exit typed work stays in existing StateAction relations.

Multiple referenced triggers are alternatives (OR), not an implicit conjunction. Work triggers identify that work's occurrence/completion as an engineering trigger; no call to that work is executed. Do not require a trigger function to be allocated to the machine's owner: an external participant's work can cause the observed transition. Existing owner-relative StateAction checks remain.

Activation, hierarchy and transition kinds

The following is the proposed Studio contract, not an execution engine:

  • Nonempty triggers: semantic triggering references; trigger is additional descriptive text. Empty references plus nonblank trigger: legacy/prose-only triggering, with unresolved_trigger warning. Do not call this completion or infer an Event by matching its name. Empty references and blank trigger: completion transition from a stable state, or transient routing from a pseudo.
  • Completion of a simple state means its entry/do behavior is complete; for a composite, all its active regions must complete. Final completes its region; it does not terminate every region or delete the modeled participant. Terminate ends the whole containing state-machine activation. These meanings are documented and rendered; the application never asserts they ran.
  • External may leave/re-enter the source configuration. A stable self-transition is permitted and includes exit and entry behavior. Internal requires equal State/Mode endpoints; it preserves the active configuration, including nested regions, and does not imply exit/entry actions.
  • Local requires a composite State/Mode source and a proper descendant target; the source remains active while the affected nested configuration changes. Local from an EntryPoint is also permitted when it targets a vertex within that point's boundary composite. Local to self or outside that composite rejects. Pseudostates otherwise use External; outgoing EntryPoint routing uses Local.

The internal/self and local/composite distinction follows the primary UML2 transition model. Its entry-point and fork/join segment restrictions also prevent the common mistake of treating every arrow as an interchangeable external transition. Transition constraints, transition kinds.

Endpoints must be existing vertices in the same layer and StateMachine. A normal transition may cross nested regions of that machine; do not impose a blanket same-immediate-region rule. Stable self-edges remain possible; pseudo self-edges reject. Regions remain directly owned by a StateMachine or State/Mode. Two or more sibling regions represent orthogonal/concurrent regions, without adding a parallel boolean that can disagree with the hierarchy.

Pseudostate ownership and validation policy

All new vertices remain region-owned. For EntryPoint/ExitPoint, the boundary is the containing Region's owner (State/Mode or StateMachine); rendering attaches their symbols to that owner's border while retaining the explicit region destination. This is a deliberate Studio encoding that reuses current ownership, not a claim of byte-identical UML/Capella containment.

Reject invalid references, wrong owners, upper-cardinality contradictions and forbidden directions/transition kinds. Warn about absent required connections, unfinished guards/defaults and unreachable vertices, so insertion can be followed by wiring in separate undoable edits. No pseudostate may own a nested region or typed StateAction. Retained legacy entry/exit/do prose on Initial/Final warns and is preserved, not silently discarded or advertised as executable behavior.

Vertex Proposed exact invariant Incomplete-model warning
Initial At most one per Region; no incoming; at most one outgoing; outgoing has no triggers, trigger prose or guard, but may have effect Missing initial/default-entry path or missing outgoing
Final No outgoing; no nested region or behaviors No incoming; unreachable
Terminate No outgoing; no nested region or behaviors No incoming
Choice At most one incoming; outgoing edges are transient guarded alternatives Missing incoming, fewer than two outgoing, empty branch guard, absent else
Junction One or more incoming/outgoing transient segments; no implicit concurrency Missing side; ambiguous unguarded alternatives
Fork At most one incoming; outgoing targets are stable states in distinct orthogonal regions of one composite/machine Missing input, fewer than two outputs
Join At most one outgoing; incoming sources are stable states in distinct orthogonal regions of one composite/machine Fewer than two inputs, missing output
ShallowHistory At most one per Region; at most one outgoing fallback to a direct stable child in that Region; no fallback trigger/guard Missing fallback/default-entry path
DeepHistory At most one per Region; at most one outgoing fallback to a stable vertex inside that Region's subtree; no fallback trigger/guard Missing fallback/default-entry path
EntryPoint Incoming from outside its boundary, outgoing into it; at most one outgoing per destination region; outgoing Local No outgoing or incomplete affected-region entry
ExitPoint Incoming from inside its boundary; at most one outgoing to outside it; outgoing External Missing input/output; top-machine exit without a caller is an exit endpoint, not automatic termination

Fork outputs and Join inputs must not have guards or triggers: conditional dispatch belongs at Choice/Junction, not in a partial synchronization. With more than one arm, using the same immediate orthogonal region twice rejects. Compute the first region below the nearest common composite, not simply each state's immediate region, so deeper nesting does not create false concurrency.

Other pseudo outgoing edges have no new event trigger. Initial restrictions retain the current strict rule. For the newly introduced pseudos, forbidden triggers should be structural errors when committing a complete edge; the native benchmark's accepted Choice trigger was not validated and does not prove a different semantic rule. The UI should move the timed/function trigger onto the incoming stable-state transition, retaining Choice guards on the alternatives. This is an explicit resolution of that observed ambiguity, not hidden loss.

Choice has at most one literal else outgoing guard; duplicate else rejects. Do not parse arbitrary guards to claim exhaustiveness/disjointness. Junction chooses a compound path from guard conditions before traversing it; Choice is a dynamic decision after incoming effects. Keep their symbols and labels distinct. Shallow history remembers only the immediate state; deeper regions use default entry. Deep history remembers the nested configuration. Fallback applies where no resumable history exists; do not synthesize history IDs or running state. The same-region history policy is a Studio choice supporting independent orthogonal regions; top-level history is allowed, with no submachine-execution promise. Primary sources have an acknowledged disagreement about top-level history placement. Pseudostate constraints, OMG history issue.

Reference lifecycle, source and REST

Extend the existing visitors for Event Signal items, transition triggers/effects. Reidentification remaps every reference. Deleting a trigger/effect record removes its reference but retains the transition and prose; return an actionable cleanup notice. If removing the last typed trigger would make a transition appear to be completion, retain a descriptive placeholder in trigger naming the removed trigger (atomic command) so the edit does not silently change activation meaning. Undo restores the exact original record and references. No transition deletion cascades to shared Events or counterpart-layer state machines.

Use oa_event / sa_event, existing oa_<vertex> / sa_<vertex> declarations, typed Event body constructors, transition_kind External, triggers [@event, @work], and effects [@work]. Reuse the existing typed parser/formatter/form adapter; update reference/enum field catalogs and every closed selector/schema. State traces allow matching new vertex kinds and Event definitions in opposite layers. Required transition closure includes referenced Events, item payloads, work and effects. R3 reviewed drift must include all new fields and mapped refs.

No REST route is added: current model, element, relation, transaction, validation and transition routes expose these records. OpenAPI must enumerate every vertex, Event body and transition field. A POST Event + EditRelation batch is atomic and conditional; editing the Event name or UUID must not break trigger references.

Old V2 migration preserves all IDs and exact trigger/guard/effect strings. Missing transition kind becomes External; missing lists become empty. Do not parse after 30s prose into a Time Event, or invent function references by display-name match. Old prose-only transitions warn until explicitly linked. No old pseudo vertex is rewritten. Preserve golden V2 source/JSON, add these defaults to its comparison allowlist only after schema migration is explicit and tested.

UI and acceptance sequence

Behavior adds a region-context vertex palette with distinct Initial ●, Final ◉, Choice diamond, Junction dot, Fork/Join bars, H/H* circles, entry/exit border symbols and Terminate cross. Every symbol gets a readable text name and usable hit target; nested regions must stay visible and selectable. Add State/Mode internal-transition rows inside the state card rather than a misleading self-loop.

One transition editor shows kind, typed trigger chips, trigger notes, guard, typed effects and effect notes. Add timed event opens one inline form with After/At and expression, creates the Event and attaches it in one undo step. Existing function/event selectors are searchable and filtered by layer. A full inspector/list displays all trigger names; the edge shows a concise summary and never clips the only accessible copy. Completion is visibly distinguished from unresolved descriptive triggering. No nested modal stack is needed.

Implement/verify in observable milestones: (1) events + references + concise editor and original [AFTER]30 s/function example; (2) nested vertex palette, choice/history + completion; (3) parallel Fork/Join and transition-kind rendering; (4) source/API/migration/transition parity and independent review. Meaningful tests must cover every vertex insertion incomplete state, invalid endpoint and upper-cardinality case; shallow/deep scope; nested orthogonal arms; internal vs external self edges; local source retention; trigger/effect UUID edits/deletion undo; old prose preservation; source/JSON/API equality. Personally exercise the degraded machine, event creation/rename, H* fallback, branch guard, resize and save/reopen in the native application. No execution/simulation claim is allowed.

Before implementation, lead should adopt or amend the explicit Studio choices: region-owned entry/exit symbols, history per Region, pseudo-trigger strictness despite the unvalidated benchmark edge, and preserving removed-trigger intent as descriptive text. These are concrete design decisions, not additional user permission questions.