Action flows¶
An ActionDef describes behaviour as sub-actions, control nodes and successions (§8.7). With a few additions it is executable: it can be compiled to an action-graph IR, run on a virtual clock with token semantics, checked with Gherkin scenarios, measured for coverage and mutated to test the scenarios (GH #276; requirements REQ-TRS-ACTSCHEMA-001, REQ-TRS-ACTEXEC-001). Everything is additive: an action without variables: or assign: validates and behaves as before, and its guards stay opaque text.
A small executable action¶
---
type: ActionDef
name: Classify
parameters:
- {name: level, direction: in}
- {name: verdict, direction: out}
variables:
- {name: margin, value: "0"}
subActions:
- {name: measure, kind: AssignmentAction, assign: "margin := level - 10"}
- {name: high, kind: AssignmentAction, assign: "verdict := 1"}
- {name: low, kind: AssignmentAction, assign: "verdict := 0"}
controlNodes:
- {name: d, kind: DecisionNode}
successionConnections:
- {after: measure, before: d}
- {after: d, before: high, guard: "margin > 0"}
- {after: d, before: low, guard: else}
---
Classifies a level against a threshold of 10.
parameters:andvariables:are the action's data. A name in a guard, acondition:or anassign:is a parameter, a variable, anasbinding of anAcceptAction, or<sub-action>.<output parameter>of a performed action.AssignmentActioncarriesassign: "name := expression; …"in the expression language (syscribe help eval).- A
DecisionNodetakes the first outgoing succession whoseguard:holds, else the one markedguard: else. IfAction(condition,then,else) andLoopAction(loopKind: while|until,condition,body) run nested blocks; a loop is limited to 1000 iterations.PerformAction(or an action withtypedBy:) runs anotherActionDefto completion; itsparameters:bind the callee's inputs ({name, value: <expression>}), andflowConnections: [{from: a.p, to: b.q}]andbindingConnectionscopy values when the source completes.SendActionrecords the event it sends;AcceptActionwaits for an event (payload,via,as) or forafter: <duration>on the virtual clock;TerminateActionends the whole action.
Type and unit errors in an action's expressions (a succession guard that is a number, a loop condition comparing metres with a plain number, an assignment of the wrong dimension) are found without running anything and reported as E935; parameters without a default, sub-action pins and accept bindings are never errors (docs/design/static-typing.md).
Semantics¶
Token semantics as in UML activities and fUML. The action starts with a token on every node that has no incoming succession (or is named after start). A node fires when every incoming succession holds a token (a merge fires on any one); an action then offers a token on every outgoing succession whose guard holds, a fork on all, a join once all its inputs are present, a decision on one. Enabled nodes fire in document order (sub-actions first, then control nodes), so parallel branches run deterministically; the trace records each firing. The run completes when no token is left and nothing waits, is deadlocked when tokens remain but nothing is enabled (a node with several incoming successions needs a token on each — join alternatives with a merge), is waiting while an AcceptAction has no event, and is terminated by TerminateAction.
Mutation analysis shows where this ordering matters: removing the succession into a decision node survives when the node before it is declared first, because the tie-break runs it first anyway.
Running it¶
syscribe -m model compile Classify # the IR, with hash
syscribe -m model simulate Classify --do "set level=14; run"
syscribe -m model simulate Classify --coverage # all scenario TestCases of the action
syscribe -m model mutate Classify --min-score 90
A TestCase with subject: Classify is executable when its steps use the closed vocabulary:
| Step | Meaning |
|---|---|
Given level is 14 | set an input parameter or variable |
When the action runs | start it |
When Pkg::Go is injected with v = 7 | send an event to a waiting AcceptAction |
When 2 s elapses | advance the virtual clock |
Then verdict == 1 | a boolean over parameters, variables and outputs |
Then high ran / Then low did not run | whether a node executed |
Then the action completed / is waiting / terminated / deadlocked | the run status |
A step outside it is E886. --record stores the verdicts as evidence exactly as for state machines.
From a state machine¶
An entryAction, doAction, exitAction or effect that names an executable ActionDef runs it to completion at that point of a state-machine run: its in and inout parameters are read from the machine's variables of the same name, its out and inout values written back. An action that waits for an event, or writes a value of the wrong type or dimension, stops the run. The SCXML export lists such actions as notes: they have no SCXML form.
A SendAction in that activity (also inside a loop body, an if branch or a performed callee) is emitted as a message of the machine, in firing order, exactly like a send: effect written on the machine: it shows in the step's actions as send <event> via <port> (…), leaves through the part's port in a composite and is delivered along the connection with its delay (REQ-TRS-ACTSEND-001). If the activity fails or waits, none of its sends are emitted. Two things to know. An activity is executable only when it declares variables: or uses assign:; one that only sends opts in with variables: [], otherwise it stays a recorded reference and emits nothing. And a parameter with no typedBy: or unit: is a plain number, so declare unit: SI::m on a parameter whose value carries a unit, or the payload loses it.
Checks¶
E883/E884 (variables and assignments, shared with state machines), E887 (decision, loop and accept wiring) and W935 (guards and conditions outside the expression language or naming unknown things) apply to executable actions only.
Limits¶
Callee coverage is not merged into the caller's, and mutation seeds only the top-level nodes and successions of the action itself (not nested blocks or callees); an AcceptAction inside an if/loop block cannot suspend and is E887; loopKind: for is not supported; static types are checked when an expression runs, not when it is compiled.