diff --git a/changes/unreleased/pssm-call-results-tester-traces.added.md b/changes/unreleased/pssm-call-results-tester-traces.added.md new file mode 100644 index 000000000..6bc33dcc1 --- /dev/null +++ b/changes/unreleased/pssm-call-results-tester-traces.added.md @@ -0,0 +1,2 @@ +- **A synchronous call of an operation a state machine accepts as a call event returns the operation's outputs to the caller.** `StateExecutor.Call` queues the call event, runs the machine through the run-to-completion step dispatching it — later events that step queued and timers it armed wait for the machine's next run, while a call a state defers holds its caller until the machine recalls it — and releases the caller with the values the behaviors that step fired — the transition's effect, an entry or an exit — returned or assigned to the operation's `out` and result parameters, by name — the parameters the operation declares as a member of the machine's owner when it declares one, an `inout` the step left unwritten going back as passed, every output the step returned otherwise — as PSSM §8.5.9 resumes a synchronous caller after the run-to-completion step; a call the run leaves queued or deferred is reported as `ErrCallNotReturned`, one no transition accepts is discarded. The arguments are checked against the declaration the call selects among same-named operations before the call is queued — an unbound, unknown or wrong-typed one is refused, an omitted input carries its default — and the queued call event carries that declaration, so it fires only the triggers naming it and same-named overloads whose parameter names differ reach their own transitions. A snapshot and a held image capture the call in flight. A nested action's `return` or output assignment reaches the enclosing behavior's parameter of that name on the way. Conformance case `state_call_trigger_results` and `TestRuntimeRobustnessCallResults` cover it. +- **The PSSM referee drives the tester's stimulation in the tester's order and reads a standalone state machine as the class under test.** The driver performs each send, synchronous call and `trace(...)` of the tester's behavior as the tester does, appending a traced value to the target's `log` once the call it embeds has returned, with the suite's test library (`Concat`, `ToString`, `formatParameterValue`) read into the model and evaluated generically; the reader reads a `StateMachine` that is itself the class under test as a target with its attributes, operations and constructor. *Event 019 A* moves from `not-expressible` to `pass` (52 pass / 13 fail / 37 not-expressible / 1 differs-by-design); tests needing an entry, exit or do behavior with parameters, or an effect that returns the call's result, stay `not-expressible` on exactly those reasons until the emitter spells them, and every other test's result and reason is unchanged. diff --git a/docs/internals/design/precise-semantics-alignment.md b/docs/internals/design/precise-semantics-alignment.md index e30683bd2..8080c57b1 100644 --- a/docs/internals/design/precise-semantics-alignment.md +++ b/docs/internals/design/precise-semantics-alignment.md @@ -1198,7 +1198,47 @@ member of an object's type with the object as performer, synchronously, and retu (`state_call_trigger`, `state_call_trigger_guard`, `state_call_trigger_regions`). Arguments bind by name only; the roadmap's "operation invocation with positional arguments" entry under Track E holds the positional form. Both fUML calls and both runtime paths are synchronous and -by-position versus by-name is notation, not semantics. **agrees.** +by-position versus by-name is notation, not semantics. *The caller's return:* PSSM §8.5.9 +(`CallEventOccurrence`, `SM_ObjectActivation`) releases the caller of a synchronous call once the +run-to-completion step (§8.5.10) the call event triggers is done, with the return values the triggered +behaviors — the transition's effect, an entry or an exit — wrote to the operation's output +parameters, the last write winning. `StateExecutor.Call` (`perform.go`) does the same: it queues +the call event, runs the machine at the current instant through the step dispatching it and no +further — a completion event that step queued or a timer it armed is the machine's next step, +after the caller has resumed, as §8.5.10 makes each occurrence its own step, while a call a +state defers holds its caller through the steps until the machine recalls and dispatches it, as +§8.5.9's blocked caller waits for the deferred occurrence — collects what a +behavior the event fires `return`s or assigns to an output parameter of that name, and hands the +outputs back typed and by name — under the `out`/`inout` parameters the operation declares as a +member of the machine's owner (or of the machine standing alone) when it declares one — among +several so named, the one the call's arguments select as `InvokeOperation` would, and +`ErrAmbiguousInvocation` before the call is queued when they select none; the arguments +checked against the declaration's inputs as `InvokeOperation` checks them, so an unbound or +unknown one is `ErrUnboundParameter` and one of the wrong type `ErrTypeMismatch` before the call +is queued, and an input the caller omits carries its default, since §8.5.9's +`CallEventExecution` holds a value for each of the operation's parameters; the call event carries +the selected declaration (`Call.Declared`) and fires only the triggers naming it — a trigger +`accept op(x)` naming the owner's `op` declarations with an input for each trigger parameter, +those with exactly the trigger's parameters when any has, and all of several differing in +their parameters' types alone, since the notation writes no types (`callTriggerOperations`), as +a UML `CallEvent` names one `Operation` (UML §13.3.3) — so same-named overloads whose parameter +names differ reach their own transitions — as §8.5.9 +returns the operation's own parameters — an `inout` no behavior of the step wrote going back as the +caller passed it, since §8.5.9's `CallEventExecution` holds the argument as that parameter's value +until a behavior writes it — and under every name the step returned when the trigger +names no declared operation; a call the run left queued or deferred is `ErrCallNotReturned`, +since its caller would still be waiting, and an unhandled call returns nothing, as PSSM's +discarded occurrence does (`state_call_trigger_results`; `TestRuntimeRobustnessCallResults`: +held, recalled, untaken, empty, repeated and erroring calls, the caller released ahead of the +completion step and the timer its step set up, a declared operation returning its own +parameters alone and an `inout` argument as passed, as written and not at all when nothing takes +the call, a wrong-typed argument refused, a default filled in, an overload firing the trigger +naming its declaration). Only a write to a behavior's own output parameter comes back — a nested action's +`return` or its assignment to an `out`/`inout` (`state_statements.go:returnAround`) — because +§8.5.9 collects the values of the triggered Behavior's output parameters and nothing else; an inline +`assign` in an entry, exit or effect writes a feature of the machine's owner, not a parameter, so it +stays state and is no result even when its name coincides with a declared `out`. +**agrees.** **A15. One firing per token, or one performance per node.** fUML §8.9.1 and §8.10.1 (`ActionActivation::fire`, `isReady`, `takeOfferedTokens`): an action whose input pin has @@ -1456,8 +1496,8 @@ which supersede the hand count this section was first written with — the moves | Aspect of the suite | Verdict | Why | |---|---|---| -| **Expressing the test model in SysML v2 textual notation** | **Can, for 65 of 103** (34 with standard notation, 31 with this project's extensions); **cannot, for 38** (30 use a construct v2 has no spelling for, 8 more use a behavior shape the notation cannot bind) | Every test's state machine is classified by the UML constructs it uses; the table below gives the construct-to-notation mapping and the per-area result | -| **Driving the test** | **Can, with one normalization** | PSSM's `Tester` sends `Start` and the follow-up signals from its own behavior, interleaved with the target's steps by fUML's scheduling; the conformance harness queues a case's `events` before the first step (`conformance_test.go:injectEvents`). The two coincide when every send precedes the target's first reaction, which is what the tests' "received when in configuration ..." lists state; a test that needs a signal to arrive mid-run needs a tester `part` in the model instead | +| **Expressing the test model in SysML v2 textual notation** | **Can, for 66 of 103** (35 with standard notation, 31 with this project's extensions); **cannot, for 37** (29 use a construct v2 has no spelling for, 8 more use a behavior shape the translation does not spell) | Every test's state machine is classified by the UML constructs it uses; the table below gives the construct-to-notation mapping and the per-area result | +| **Driving the test** | **Can, in the tester's order** | PSSM's `Tester` sends `Start` and the follow-up signals from its own behavior, interleaved with the target's steps by fUML's scheduling, and blocks on each operation it calls until the call's run-to-completion step is done (§8.5.9, `CallEventOccurrence`). The referee's driver (`tools/referee/pssm/run.go:drive`) performs the tester's steps in that order: a send is queued where the tester sends it, a call is `StateExecutor.Call` and returns the operation's outputs, and a `trace(...)` of the tester's own is appended to the target's `log` where the tester makes it, once the call it embeds has returned. The sends coincide with the conformance harness's queued `events` when every send precedes the target's first reaction, which is what the tests' "received when in configuration ..." lists state; a test that needs a signal to arrive mid-run needs a tester `part` in the model instead | | **Comparing the expected trace** | **Can, on a model-level string; `%trace` is not the comparand** | PSSM's expected trace is built by the model — every entry, exit and effect behavior calls `trace("(entry)")` on the `TraceBuilder` (501 call actions target the `trace` operation in the XMI). Its translation is an `assign log := log + "(entry)"` in the corresponding `entry`/`exit`/`do` body, compared through the case's `slots`/`outputs`; the runtime's `%trace` and `TestExecutionTrace` goldens record steps, not segments, and would need a projection (enter/exit/effect lines to segments, everything else dropped) to be comparable at all | | **Alternative expected traces** | **Can, and exactly** | 36 tests declare more than one admissible trace. The conformance schema's `outcomes` with the `explore` policy replays a case once per linearization of its choice points (`ChoiceRegionOrder`, `ChoiceTransition`, `ChoiceDueOrder`) and fails when a listed outcome is unreachable or an unlisted one is reached — the same set-equality PSSM's alternatives ask for, and stricter than the single-run comparison the PSSM harness performs | | **The run-to-completion step table** | **Cannot compare** | Each test's "RTC steps" table lists the pool's contents and the fired transitions per step, including completion events (`CE()`). The runtime has no pool of completion occurrences (SM9) and the `%trace` records no pool; only the fired transitions and the final trace are comparable | @@ -1479,9 +1519,10 @@ which supersede the hand count this section was first written with — the moves | Entry point, exit point (connection points and connection point references) | none | no spelling | | Local transition, internal transition | none (SM36, SM37) | no spelling | | State machine generalization: extended regions, redefined transitions | none | no spelling | -| Entry, exit or do behavior with parameters (reading the triggering event's data) | none: the notation binds event data on the transition (`accept d : Data`), never on an `entry`/`exit`/`do` action | no translation | -| Call event whose operation returns a value the tester traces | none: the runtime's call events carry no result back to the caller | no translation | -| A `trace(...)` call in the tester's own behavior | none: only the target's behaviors append to the model's `log` | no translation | +| Entry, exit or do behavior with parameters (reading the triggering event's data) | none written: the notation binds event data on the transition (`accept d : Data`, §7.18.2; `TransitionPerformances.kerml`'s `accepter`), never on an `entry`/`exit`/`do` action, so the payload has to be routed through the transition — the reading is recorded under [Behavior parameters](#behavior-parameters-operation-results-tester-traces-and-standalone-machines) below | no translation | +| Call event whose operation returns a value the tester traces | the runtime returns the outputs the triggered behaviors wrote to the caller (A14, `StateExecutor.Call`) and the driver traces them where the tester does; none written for the behavior that produces the value — an effect, entry or exit with an `out`/`return` parameter, which the notation spells as an action's `out` parameter (§7.16.2) — see below | no translation | +| A `trace(...)` call in the tester's own behavior | the tester is the referee's driver, not a model element: its trace is appended to the target's `log` where the tester makes it, once the call it embeds has returned (`run.go:drive`, `tracer.value`); a trace that embeds no call and does not follow one is refused, since the machine may still be running (`stimulation.go:traceStimulus`) | standard, driven | +| The UML `StateMachine` as the class under test (a standalone machine with attributes, operations, a constructor) | `part def` with `attribute`s, `action def`s and `exhibit state`, as an owned machine's: the reader (`reader.go`) reads the machine as the `Target` whose `Machine` is itself, with its attributes, operations and their methods, and its constructor | standard | | A guard whose behavior acts on the model (calls `trace(...)` before returning its value) | none: a v2 guard is a Boolean expression (§7.18.3, `validateTransitionFeatureMembershipGuardExpression`; `bool guard[*]` in `TransitionPerformances.kerml`, the effect a separate `step`), and an expression has no spelling for an action. UML 2.5.1 §14.5.11 `Transition::guard` itself calls such a guard ill formed | no translation | | A guard whose behavior is an opaque behavior, not an activity | none: the reader follows an activity's nodes to tell whether the behavior acts, and does not read an opaque body, so the guard is refused rather than carried as its Boolean text alone. A `FunctionBehavior` is the exception — it accesses no object by UML's contract (§13.2.3.3) — and is translated as the expression it spells | no translation | | Fork into states of orthogonal regions that have no initial pseudostate | `parallel` regions spell the shape and the `fork` extension the fork; a region a fork enters needs no `entry; then` (finding 6 below, fixed) | extension | @@ -1498,7 +1539,7 @@ area: |---|---:|---:|---:|---:| | Behavior | 5 | 4 | 0 | 1 | | Transition | 15 | 8 | 1 | 6 | -| Event | 16 | 10 | 0 | 6 | +| Event | 16 | 11 | 0 | 5 | | Entering | 5 | 4 | 0 | 1 | | Exiting | 5 | 4 | 0 | 1 | | Entry (entry points) | 6 | 0 | 0 | 6 | @@ -1514,38 +1555,41 @@ area: | Redefinition | 6 | 0 | 0 | 6 | | Standalone | 3 | 0 | 0 | 3 | | Other | 1 | 0 | 0 | 1 | -| **Total** | **103** | **34** | **31** | **38** | +| **Total** | **103** | **35** | **31** | **37** | -Of the 30 with no v2 spelling, 14 use an entry point, 12 an exit point, 9 a local transition, 2 -an internal transition and 6 the redefinition machinery (several use more than one). Of the 62 -expressible and runnable tests, 20 use orthogonal regions, 8 a do activity, 9 deferral, 8 -history, 6 a junction, 4 a choice and 5 a fork or join; no expressible test has a call event, -since every test with one also traces its result from the tester. +Of the 29 with no v2 spelling, 14 use an entry point, 12 an exit point, 9 a local transition, 2 +an internal transition and 6 the redefinition machinery (several use more than one). Of the +expressible tests, 20 use orthogonal regions, 8 a do activity, 9 deferral, 8 +history, 6 a junction, 4 a choice and 5 a fork or join; one, *Event 019-A*, has a call event +the tester calls synchronously and traces after. The other seven with a call event also trace +its result, which the behavior that returns it has no spelling for yet. #### Moves from the hand count This section was first written with a hand count of 37 / 33 / 3 / 30, which classified by the state-machine constructs alone. Writing the emitter showed nine of those 73 tests to have no exact translation, for reasons the construct table did not list; two of the nine (*Fork 002*, -*Join 001*) have one since the lowerer accepts a fork-entered region without an initial; adjudicating the failures -found a tenth. Each is recorded here with the -classifier's reason; the count ratchet in `docs/project/pssm-referee.md` is where a later -translation moves them back. +*Join 001*) have one since the lowerer accepts a fork-entered region without an initial, and a +third (*Event 019-A*) since the driver performs the tester's calls and traces in the tester's +order; adjudicating the failures found a tenth. Each is recorded here with the classifier's +reason; the count ratchet in `docs/project/pssm-referee.md` is where a later translation moves +them back. | Test | Was | Reason | |---|---|---| | *Event 017-B* | standard | the composite state and its substate have entry, exit and do behaviors with parameters, reading the triggering event's data; the notation binds event data on the transition only | -| *Event 019-A* | standard | the tester itself calls `trace("End")` after the target's operation returns; only the target's behaviors write the model's `log` | +| *Event 019-A* | standard | *translated since the driver performs the tester's steps in order:* the tester itself calls `trace("End")` after the target's operation returns; the driver now makes the call synchronously and appends the trace to `log` when it returns, reaching the one admitted trace: the source's exit, the call transition's effect, `End`, the next state's segment | | *Event 019-B* | standard | both top-level states have parameterised entry and exit behaviors | | *Event 019-C* | standard | the three nested states have parameterised entry, exit and do behaviors | -| *Event 019-D* | standard | the call trigger's operation `T2` returns a value, which the tester traces; the runtime's call events return nothing to the caller | -| *Event 019-E* | standard | parameterised behaviors in two substates, an operation result on the call trigger's `T2`, and a tester-side trace of it | -| *Deferred 007* | extension | the deferred call trigger's operation `T4` returns a value the tester traces | +| *Event 019-D* | standard | the call trigger's operation `op` returns a value that `T2`'s effect produces (`return "output"`) and the tester traces; the runtime returns it to the caller and the driver traces it, but the translation spells no effect with a `return` parameter | +| *Event 019-E* | standard | parameterised behaviors in two substates, and an operation result on the call trigger's `T2` | +| *Deferred 007* | extension | the deferred call trigger's operation `op` returns a value that `T4`'s effect produces from its `in` parameter (`return T4_effect(p)`) | +| *Standalone 003* | standard | *the standalone machine is read as the target since the reader does so;* what remains are the parameterised entry behaviors of the two regions' substates, which also produce `or`'s result | | *Fork 002* | extension | *translated since finding 6 was fixed:* the fork enters the two regions of a nested composite state, which have no initial pseudostate; the lowerer used to refuse a `parallel` region with no `entry; then` — this project's gap, not v2's | | *Join 001* | extension | *translated since finding 6 was fixed:* the fork enters the two regions of the top-level composite state, which have no initial pseudostate; the same lowerer refusal | | *Choice 005* | extension | the guards of the junction's and the choice's four outgoing transitions each call `trace("T1.n(guard)")` and the admitted trace records the calls, to show when each guard is read; a v2 guard is an expression with no room for an action, so the translation keeps only the guard's value and cannot reach the trace, and is refused rather than run short | -The last two were kept apart from the other seven and from the 30 with no spelling: UML allows +The last two were kept apart from the other seven and from the 29 with no spelling: UML allows a fork to target states inside orthogonal regions that have no initial pseudostate, SysML v2 `parallel` regions can spell the shape, and only the lowerer's check stood in the way. The lowerer now accepts a region a fork enters (finding 6), so the two run and the referee reports @@ -1554,6 +1598,94 @@ into it is still refused, and the classifier names that *lowerer refuses an orth with neither an entry transition nor a fork branch into it* (*Entry 002 E*, which is not expressible on other grounds too). +#### Behavior parameters, operation results, tester traces and standalone machines + +Four of the reasons above are not a missing v2 spelling but a translation, driver or runtime +that did not carry the construct. Each is read here against PSSM and against SysML v2/KerML, +and either translated — the same behaviors in the same order, the suite's admitted traces the +oracle — or left refused with what a spelling has to reach. + +**The tester's own `trace(...)`** — *translated, in the driver.* PSSM Clause 9.2: the tester is +the test's second object; it sends signals to the target from its own behavior and, where it +calls one of the target's operations, blocks until the call returns (§8.5.9 +`CallEventOccurrence`: the caller is released once the run-to-completion step the call event +triggers is done), then goes on — in *Event 019-A*, to `this.testable.trace("End")`, which +appends to the target's trace after the source's exit and the call transition's effect, and +before the `Continue` it sends next makes the next segment. The tester is not a model element of the translation; it is the referee's +driver, so its steps are performed by `run.go:drive` in the tester's order: a send is queued and +not waited for, as the tester does not wait for a signal (the pool is FIFO, so every send before +a call is dispatched before the call event, whatever the tester's and the target's relative +speed), a call is `StateExecutor.Call` (A14) and returns the operation's outputs, and a trace is the value +the tester computes appended to the target's `log` — the same store the target's own `trace` +writes — where the tester makes it. The value is evaluated by `tracer.value` over the suite's test +library read into the model (`library.go`: `Concat`, `ToString` for Boolean, Integer and +UnlimitedNatural, `formatParameterValue` spelling `[in=v]`/`[out=v]` as `Util::Tracing` does), +and a call the trace embeds is the same synchronous `Call`, made once per call action however +many of its output pins the trace reads (*Event 019-E* reads `result` and `return` of one +`or(true, true)`; `TestTracerMakesEachCallOnce`). The ordering is PSSM's under every +scheduling policy because it is fixed by the call's return, not by a draw: nothing of the +target's runs between the step's end and the tester's next action, and the referee's result is +identical under `-jobs 1` and `-jobs 8`. A trace that embeds no call and does not directly follow +one has no such anchor — the tester's `trace` and the target's steps would be interleaved by +fUML's scheduling — and `stimulation.go:traceStimulus` refuses it rather than order it by fiat; +no test of the suite is refused on that ground. *Event 019-A* moves from not expressible to +`pass` on its one admitted trace, `End` third of four segments; `TestDriveTesterTraceAfterCallReturns`, +`TestDriveRefusesATraceWhileTheMachineMayRun`, `TestDriveCallNotReturnedFails`. + +**The standalone machine** — *translated, in the reader.* UML 2.5.1 §13.2.3 and §14.2: a +`StateMachine` is a `Behavior`, hence a `Class`; the suite's *Standalone* tests type the +tester's `testable` by the machine itself and give it attributes, operations with method +activities and a constructor. Its v2 reading is the one an owned +machine already has: a `part def` with `attribute`s, `action def`s for the operations and +`exhibit state` for the machine, since a `part def` is what the emitter spells a target class +as and the machine's regions, states and transitions are read the same way whichever element +owns them. `reader.go` reads the standalone machine as the `Target` whose `Machine` is itself, +with the attributes, operations and their methods, and the constructor; the constructor's +literal writes are the attributes' initial values, as for an owned class (the suite's +standalone constructors call the base constructor and return `this`). The classifier therefore no longer refuses the +kind; *Standalone 001* and *Standalone 002* are refused on their entry and exit points, and +*Standalone 002* and *Standalone 003* on their parameterised behaviors, the reasons otherwise +unchanged (`TestSuiteNoTranslationReasons`). `standalone_test.go` reads and runs a standalone +machine with an attribute the constructor initialises and a method that writes it. + +**A call trigger whose operation returns a value** — *the runtime and driver carry it; the +translation does not yet spell the behavior that produces it.* PSSM §8.5.9 `CallEventOccurrence` +and `SM_ObjectActivation`: the call's arguments bind the operation's `in` parameters, the +behaviors the occurrence fires may write the operation's `out`/`return` parameters, and the +caller is released with those values once the step is done. In the suite the value is produced +by a *behavior*: *Event 019-D*'s `T2` effect `return "output"`; *Deferred 007*'s `T4` effect +`return T4_effect(p)` from the call's `in`; *Event 019-E*'s and *Standalone 003*'s entry +behaviors of two orthogonal regions' substates, each returning its own value, the trace admitting +either region's as the one the tester sees — the last write wins. The runtime side is A14: +`StateExecutor.Call` returns what a fired behavior returned or assigned to an output parameter +of the operation's name, typed and by name, and the driver traces it (above). The v2 reading of +the producing behavior is an action with an `out` parameter (§7.16.2) whose value the +transition's `accept op(...)` hands back to the caller; the emitter spells an effect, entry or +exit as a parameterless action today, so the classifier keeps *operation result* as the reason +for the four tests. What a spelling must reach is fixed by the traces: the effect's `return` +before the same effect's `trace`, and for the two-region case one value per region's entry +with the last returned. + +**Entry, exit or do behavior with parameters** — *not translated; the reading is recorded.* +PSSM §8.5.5 (`StateActivation::enter`, `exit`, `getExecutionFor`): a state's behaviors are +executed with the triggering occurrence's data — a signal's attribute values, a call's `in` +arguments — bound to their parameters in order when the behavior declares them; a completion +or a data-less occurrence binds nothing. *Event 019-B* traces the source's exit and +the target's entry as `exit(p1, p2)` and `entry(p1, p2)` with the same two values the +`Continue(p1, p2)` carried; *Event 017-B*, +*019-C*, *019-E*, *Entry 002-F*, *Standalone 002* and *003* bind entry, exit and do behaviors +the same way. In SysML v2 the data is the transition's: `accept d : Data` (§7.18.2) declares a +payload the transition's guard and effect read, and KerML's `TransitionPerformance::accepter` +holds the transfer, while `StatePerformance::entryAction`, `exitAction` and `doAction` +(`StatePerformances.kerml`) are the state's, performed with no reference to the transfer that +caused them. A faithful spelling must route the payload from the transition to the state's +action with the same values, in the same step, and without a unit the trace could see; the +suite's traces fix two ordering constraints on it — the *source's exit* reads the payload +before the transition's effect runs (§7.18.3: exit, effect, entry), so an effect assigning a +part attribute reaches the entry but not the exit — and the completion case must leave the +parameters unbound rather than stale. No spelling meeting these is written, so the classifier +keeps *behavior parameter* as the reason for the seven tests. + ### What a translated test looks like *Deferred 001* (PSSM §9.3.16.2, Figure 9.90) exercises deferral in a simple state: `Continue` diff --git a/docs/project/pssm-referee-baseline.json b/docs/project/pssm-referee-baseline.json index c6dfe2c3e..becca05a4 100644 --- a/docs/project/pssm-referee-baseline.json +++ b/docs/project/pssm-referee-baseline.json @@ -6,14 +6,14 @@ "url": "https://www.omg.org/spec/PSSM/20181101/PSSM_TestSuite.xmi", "suiteDigest": "c355b249c356774377a46b60345019d827af1ce417bde88e533aa5f39206ae07", "tests": 103, - "recorded": "2026-09-19", - "develop": "b36c7c0f0fd8069a11df862b185eb2b101478fb7" + "recorded": "2026-09-21", + "develop": "2a652735904510298960f56ab48d5e2713423f3f" }, "buckets": { "differs-by-design": 1, "fail": 13, - "not-expressible": 38, - "pass": 51 + "not-expressible": 37, + "pass": 52 }, "tests": [ { @@ -524,11 +524,15 @@ { "name": "Event 019 A", "area": "Event", - "class": "not-expressible", - "bucket": "not-expressible", - "reasons": [ - "tester trace this.testable.trace(\"End\")" - ] + "class": "standard", + "bucket": "pass", + "expected": [ + "S1(exit)::Call(op)::End::S2(entry)" + ], + "reached": [ + "S1(exit)::Call(op)::End::S2(entry)" + ], + "runs": 1 }, { "name": "Event 019 B", @@ -554,7 +558,7 @@ "class": "not-expressible", "bucket": "not-expressible", "reasons": [ - "operation result T2; tester trace this.testable.trace(formatParameterValue(false, this.testable.op()))" + "operation result T2" ] }, { @@ -563,7 +567,7 @@ "class": "not-expressible", "bucket": "not-expressible", "reasons": [ - "behavior parameter S1.S1.1; behavior parameter S1.S2.1.S2.1.1; operation result T2; tester trace this.testable.trace(Concat(formatParameterValue(false, this.testable.or(true, true)), formatParameterValue(false, this.testable.or(true, true))))" + "behavior parameter S1.S1.1; behavior parameter S1.S2.1.S2.1.1; operation result T2" ] }, { @@ -872,7 +876,7 @@ "class": "not-expressible", "bucket": "not-expressible", "reasons": [ - "standalone state machine Standalone001_Test; exit point ExitPoint1; exit point ExitPoint1; entry point EntryPoint1" + "exit point ExitPoint1; exit point ExitPoint1; entry point EntryPoint1" ] }, { @@ -881,7 +885,7 @@ "class": "not-expressible", "bucket": "not-expressible", "reasons": [ - "standalone state machine Standalone002_Test; exit point ExitPoint1; entry point EntryPoint1; behavior parameter S2; behavior parameter S2; behavior parameter S2.S2.1; behavior parameter S2.S2.2" + "exit point ExitPoint1; entry point EntryPoint1; behavior parameter S2; behavior parameter S2; behavior parameter S2.S2.1; behavior parameter S2.S2.2" ] }, { @@ -890,7 +894,7 @@ "class": "not-expressible", "bucket": "not-expressible", "reasons": [ - "standalone state machine Standalone003_Test; behavior parameter S1.S1.1; behavior parameter S1.S2.1.S2.1.1; operation result T2; tester trace this.testable.trace(Concat(formatParameterValue(false, this.testable.or(true, true)), formatParameterValue(false, this.testable.or(true, true))))" + "behavior parameter S1.S1.1; behavior parameter S1.S2.1.S2.1.1; operation result T2" ] }, { @@ -1187,7 +1191,7 @@ "class": "not-expressible", "bucket": "not-expressible", "reasons": [ - "operation result T4; tester trace this.testable.trace(formatParameterValue(false, this.testable.op(true)))" + "operation result T4" ] }, { diff --git a/docs/project/pssm-referee.md b/docs/project/pssm-referee.md index 9a936fc74..eb1945376 100644 --- a/docs/project/pssm-referee.md +++ b/docs/project/pssm-referee.md @@ -61,9 +61,9 @@ note's [construct-to-notation table](../internals/design/precise-semantics-align | Class | Meaning | Count | |---|---|---:| -| **standard** | every construct has a spelling in standard SysML v2 notation | 34 | +| **standard** | every construct has a spelling in standard SysML v2 notation | 35 | | **extension** | spellable with this project's state-body extensions (`fork`, `join`, `junction`, `choice`, `history`, `defer`) | 31 | -| **not-expressible** | uses a construct with no spelling (entry and exit points, local and internal transitions, state-machine redefinition), a behavior shape the notation cannot bind, or a shape this project's lowerer refuses | 38 | +| **not-expressible** | uses a construct with no spelling (entry and exit points, local and internal transitions, state-machine redefinition), a behavior shape the translation does not spell, or a shape this project's lowerer refuses | 37 | A test using any construct with no spelling or no translation is not expressible whatever else it uses; otherwise the extensions win over standard. A terminate pseudostate is standard @@ -73,17 +73,31 @@ lowered `terminate` without executing it (alignment finding 1, fixed), and are s The alignment note was first written with a hand count of 37 / 33 / 3 / 30; the classifier is the record from now on, and the note's test-suite section carries its figures. Nine tests moved from the hand count when the emitter was written, two of them moved back when the lowerer -learned to accept a fork-entered region, and a tenth moved when its failure was adjudicated; -each is listed with its reason in the note under -[Moves from the hand count](../internals/design/precise-semantics-alignment.md#moves-from-the-hand-count): +learned to accept a fork-entered region, a third when the driver learned to perform the +tester's calls and traces in the tester's order, and a tenth moved when its failure was +adjudicated; each is listed with its reason in the note under +[Moves from the hand count](../internals/design/precise-semantics-alignment.md#moves-from-the-hand-count), +and the four constructs the translation rather than the notation stood in the way of are read +under [Behavior parameters, operation results, tester traces and standalone machines](../internals/design/precise-semantics-alignment.md#behavior-parameters-operation-results-tester-traces-and-standalone-machines): - **Entry, exit or do behaviors with parameters** that read the triggering event's data: - *Event 017-B*, *Event 019-B*, *Event 019-C*, *Event 019-E*. The notation binds event data on - the transition (`accept d : Data`), never on an `entry`, `exit` or `do` action. + *Event 017-B*, *Event 019-B*, *Event 019-C*, *Event 019-E*, and among the tests not + expressible on other grounds *Entry 002-F*, *Standalone 002*, *Standalone 003*. The notation + binds event data on the transition (`accept d : Data`), never on an `entry`, `exit` or `do` + action, and no spelling routing it from the one to the other is written. - **An operation the tester calls and whose result it traces**: *Event 019-D*, *Event 019-E*, - *Deferred 007*. The runtime's call events carry nothing back to the caller, and only the - target's behaviors write the model's `log`. -- **A `trace(...)` in the tester's own behavior**: *Event 019-A* (and *019-D*, *019-E*). + *Deferred 007*, *Standalone 003*. The runtime returns the outputs the triggered behaviors + wrote to the caller (`StateExecutor.Call`, alignment row A14) and the driver traces them + where the tester does; the emitter spells no effect, entry or exit that returns a value. +- **A `trace(...)` in the tester's own behavior** is translated: the driver performs the + tester's steps in order and appends the trace to `log` once the call it follows has + returned, so *Event 019-A* runs and passes. A trace embedding no call and not directly + following one is refused, since the machine may still be running; no test is. +- **A standalone state machine** as the class under test is translated: the reader reads the + machine as the `Target` whose `Machine` is itself, with its attributes, operations and + constructor. *Standalone 001*, *002* and *003* stay not expressible on entry and exit points + and on parameterised behaviors, their other reasons byte-identical + (`TestSuiteNoTranslationReasons`). - **A guard whose behavior acts on the model**: *Choice 005*, whose four guards each `trace("T1.n(guard)")` before returning, and whose admitted trace records the calls. A v2 guard is a Boolean expression (`bool guard[*]` in `TransitionPerformances.kerml`, the effect a @@ -107,8 +121,10 @@ test, following the note's table and its worked example: - Regions become nested state bodies; an orthogonal state's regions become a `parallel` body's substates. Every state and pseudostate is named by its path as a bare identifier (`S1_S1_1`), since pseudostate declarations take no quoted name. -- The tester's `Start` and its follow-up sends become the run's queued events, in the tester's - order, with a signal's scalar payload bound on the accepting transition's parameter. A guard +- The tester's stimulation is read once for the emitter and the driver + (`tools/referee/pssm/stimulation.go`): `Start` and the follow-up sends, calls and traces in + the tester's order, a signal's scalar payload bound on the accepting transition's parameter, + a call's literal arguments typed by the operation's `in` parameters. A guard on a choice or junction that reads the payload of the event that reached it is served by an attribute the triggered transition stores the payload in (UML 14.2.3.8.5). - An initial transition whose target is a pseudostate starts the region in an empty helper @@ -131,8 +147,14 @@ cannot translate exactly rather than dropping it. ## Running and comparing For each expressible test the referee parses the emitted model, resolves the state usage `M`, -and runs it through the runtime's shared state driver (`runtime.PerformState`, the -same entry point the execution-conformance harness uses) under the `explore` schedule policy, +and drives a state executor (`Context.CreateStateExecutorFor`, the executor the runtime's +shared state driver and the execution-conformance harness use) through the tester's steps in +the tester's order (`run.go:drive`): a send is queued, a call is `StateExecutor.Call` — the +call event queued, the machine run through the step dispatching it and no further, the +operation's outputs returned to the driver as PSSM §8.5.9 returns them to a synchronous caller — and a tester `trace(...)` is +evaluated over the suite's test library read into the model (`library.go`: `Concat`, +`ToString`, `formatParameterValue`) and appended to the target's `log` where the tester makes +it. The run is under the `explore` schedule policy, which replays the run once per linearization of its choice points. The set of `log` values reachable is compared with the test's set of admitted traces **as sets, in both directions**: a reachable trace the suite does not admit is a failure naming that trace, an admitted trace the @@ -182,7 +204,8 @@ send with no receiver) are not state-machine rows and no test in the suite reach ## Baseline -Recorded **2026-09-19** on develop commit **`b36c7c0f0`** with completion events queued in the +Recorded **2026-09-21** on develop commit **`2a6527359`** with the tester's calls and traces +driven in the tester's order and standalone machines read as targets, with completion events queued in the order their sources are entered (the pool's order following the entry draw, finding 11's runtime part), the order of orthogonal regions drawn as choice points at finding 9's four sites (region entry, region exit, @@ -199,15 +222,42 @@ baseline — `go run -C tools ./cmd/pssm-referee` prints the current ones. | Bucket | Tests | |---|---:| -| `pass` | 51 | +| `pass` | 52 | | `fail` | 13 | -| `not-expressible` | 38 | +| `not-expressible` | 37 | | `differs-by-design` | 1 | | **Total** | **103** | ### Movements since the previous baseline -No count moved since the previous baseline (develop `e823e6b82`, 2026-09-19), and four rows +Two counts moved since the previous baseline (develop `b36c7c0f0`, 2026-09-19), `not-expressible` +38 → 37 and `pass` 51 → 52, and six reasons changed without moving a bucket. The driver +performs the tester's steps in the tester's order, a synchronous call returning the operation's +outputs after its run-to-completion step and a tester trace appended to `log` when the call it +embeds has returned (PSSM §8.5.9 `CallEventOccurrence`; the alignment note's A14 row and its +section [Behavior parameters, operation results, tester traces and standalone +machines](../internals/design/precise-semantics-alignment.md#behavior-parameters-operation-results-tester-traces-and-standalone-machines)), +and the reader reads a standalone state machine as the target class, so the classifier refuses +neither the tester's trace where the driver orders it nor the standalone kind. Every other +test's result and reason is byte-identical to the previous baseline's. + +| Test | Construct | Movement | Adjudication | +|---|---|---|---| +| Event 019 A | tester trace (translated) | `not-expressible` → `pass` | Expected. The tester calls `this.testable.op()` while `S1` is active: `T2` fires on the call event, `S1`'s exit logs `S1(exit)` and `T2`'s effect `Call(op)`; the call returns once that step is done, the tester's `this.testable.trace("End")` appends `End`, and its `Continue` then fires `T3` out of `S2`, whose exit behavior logs `S2(entry)`. The one admitted trace `S1(exit)::Call(op)::End::S2(entry)` is reached and nothing else, in one run: no draw is involved, since the trace's place is fixed by the call's return | +| Event 019 D | tester trace (translated), operation result | `not-expressible` → `not-expressible`, reason changed | Expected. The tester's trace of `this.testable.op()`'s result is driven, so *tester trace* leaves the reason; *operation result T2* stays, since `T2`'s effect produces the value (`return "output"`) and the emitter spells no effect with a `return` parameter | +| Event 019 E | tester trace (translated), operation result, behavior parameter | `not-expressible` → `not-expressible`, reason changed | Expected. *tester trace* leaves the reason; the parameterised entry behaviors of `S1.1` and `S2.1.1` and the result they produce for `T2`'s operation stay | +| Standalone 001 | standalone machine (translated) | `not-expressible` → `not-expressible`, reason changed | Expected. *standalone state machine* leaves the reason; the machine's two exit points and entry point stay, the reason otherwise byte-identical | +| Standalone 002 | standalone machine (translated) | `not-expressible` → `not-expressible`, reason changed | Expected. *standalone state machine* leaves the reason; the exit point, entry point and `S2`'s parameterised behaviors stay, the reason otherwise byte-identical | +| Standalone 003 | standalone machine (translated), tester trace (translated), operation result, behavior parameter | `not-expressible` → `not-expressible`, reason changed | Expected. *standalone state machine* and *tester trace* leave the reason; the parameterised entry behaviors of `S1.1` and `S2.1.1`, which also produce `or`'s result, stay | +| Deferred 007 | tester trace (translated), operation result | `not-expressible` → `not-expressible`, reason changed | Expected. *tester trace* leaves the reason; *operation result T4* stays, since `T4`'s effect produces the value from the call's `in` parameter (`return T4_effect(p)`) | + +Of the eight tests the four constructs held out of the run, +one moves; the seven that need a behavior with parameters or a returning behavior stay refused +on exactly those reasons until the emitter spells them. + +### Movements before that + +No count moved since the baseline before (develop `e823e6b82`, 2026-09-19), and four rows did: the runtime queues a state's completion event as the state's entry unit is performed, so the pool holds two regions' completions in the order the entry draw entered their sources (§8.5.9; SM10, which now agrees under every policy), where it queued them once the move had @@ -479,11 +529,11 @@ short trace to a budget exhaustion: with SM11 its `S1` now completes and fires ` history, and the history-record timing of finding 7 makes that re-enter `S1.1` without end. The remaining failures' reasons are byte-identical to the previous baseline's. -### `pass` (51) +### `pass` (52) Behavior 001, Behavior 002, Behavior 003 A, Behavior 003 B, Transition 001, Transition 007, Transition 011 C, Transition 015, Transition 016, Transition 020, Transition 022, Event 001, Event 002, Event 008, Event 009, -Event 010, Event 015, Event 016 A (reports on SM11), Event 016 B, Event 017 A, Event 018, Entering 004, +Event 010, Event 015, Event 016 A (reports on SM11), Event 016 B, Event 017 A, Event 018, Event 019 A, Entering 004, Entering 005, Exiting 001, Exiting 003, Exiting 005, Fork 002, Choice 001 and Choice 002 (report on SM30), Choice 003, Choice 004, Final001 (reports on SM11), Deferred 001, Deferred 002, Deferred 003 (reports on SM7), Deferred 004 A and Deferred 004 B (report on SM7), Deferred 005, Deferred 006 A (reports @@ -537,7 +587,7 @@ quoted and the number given. The full sets are in the baseline file. Every reason in full — each extra trace, each missing trace, each error — is in the baseline file's `reasons`. -### `not-expressible` (38) +### `not-expressible` (37) By reason, as the classifier names them: @@ -550,11 +600,14 @@ By reason, as the classifier names them: Entering 009, Entry 002 B, Entry 002 C, Entry 002 F, TransitionExecutionAlgorithm. - **redefined state machine, extended region, redefined transition** (no spelling): Redefinition 001 to 006. -- **standalone state machine** (the machine under test is not a `Target`'s classifier - behavior): Standalone 001, Standalone 002, Standalone 003. -- **behavior parameter, operation result, tester trace** (no translation): Event 017 B, Event - 019 A, Event 019 B, Event 019 C, Event 019 D, Event 019 E, Deferred 007, and among the above - Entry 002 F, Standalone 002, Standalone 003. +- **behavior parameter** (no translation: the emitter spells no entry, exit or do behavior + bound from the triggering event's data): Event 017 B, Event 019 B, Event 019 C, Event 019 E, + Standalone 003, and among the above Entry 002 F, Standalone 002. +- **operation result** (no translation: the emitter spells no effect, entry or exit that + returns a value; the runtime carries the result and the driver traces it): Event 019 D, + Event 019 E, Deferred 007, Standalone 003. +- A standalone state machine is read as the target class, and a tester's `trace(...)` after a + call is driven, so neither is a reason any longer; Event 019 A runs and passes. - **lowerer refuses an orthogonal region with neither an entry transition nor a fork branch into it** (ours): Entry 002 E, which is not expressible on other grounds too. Fork 002 and Join 001, filed here while the lowerer refused every region without an entry transition, diff --git a/docs/project/spec-compliance.md b/docs/project/spec-compliance.md index 5868cb49d..e78883fa3 100644 --- a/docs/project/spec-compliance.md +++ b/docs/project/spec-compliance.md @@ -803,7 +803,8 @@ checked after the result is bound is not a form the runtime offers, and none is | A signal in flight on the context bus is dispatched by a single step as well as by a run to completion, so the REPL debugger and `RunToCompletion` agree; it is due now, so a step takes it ahead of a timer set for later | `state_executor.go` `ProcessNextEvent`, `acceptableMessage`, `AcceptsMessage`, `HasPendingSignal`, `HasPendingWork`; `repl/meta.go` `%advance` | `repl/runtime_commands_test.go:TestAdvanceDeliversPendingPortSignal`, `state_transition_accept_via_port.sysml`, `runtime/signal_injection_test.go:TestProcessNextEventTakesAPendingSignalBeforeALaterTimer` | ✅ Faithful | | A signal injected from outside the model (`%send` at the REPL) travels the same bus as `send Signal(args) to ` from a behavior, typed by the signal definition and addressed to the object, and an argument the signal has no feature for is refused | `runtime/signal.go` `Context.SignalMessage`, `NamedSignalMessage`; `state_executor.go` `Performer`; `classifier_behavior.go` `Context.ExhibitedMachineOf`; `repl/send.go` `%send` (addressed to the object as a whole: every machine it exhibits and action it performs is asked, `receiversOf`); `repl/meta.go` `%state ` attaches to the exhibited machine of that kind | `runtime/signal_injection_test.go:TestSignalMessageDrivesTheExhibitedMachine`, `:TestExhibitedMachineOf`, `:TestAcceptTakingNamesThePerformedActionsAccept`; `repl/send_test.go` (`TestSendDrivesAnAcceptTransition` through `TestSendIsInHelpAndCompletion`, `TestStateOnAnObjectAttachesToItsRunningMachine`, `TestStateOnAnObjectStartsWhatItDoesNotRun`, `TestSendReachesAPerformedActionParkedAtItsAccept` through `TestSendIsDispatchedToTheDebuggedActionAtItsAccept`) | ✅ Faithful, **self-assessed** (the pinned reference has no prompt to inject a signal from, so nothing external adjudicates this) | | A message in flight is taken by one machine of the object it reaches: a machine whose guards would drop it leaves it for a sibling machine of the same object that would fire on or defer it, in attachment order, so a run and a single step route it alike; deciding a message beforehand is a probe that leaves nothing behind — no budget spent, no behavior started, no object, variant selection or feature value materialized by a guard kept | `state_executor.go` `takesMessage`, `yieldsTo`, `siblingsAccepting`, `Decide`; `classifier_behavior.go` `abandonInstancesSince`, `forgetVariantsNaming`, `forgetValuesNaming`; `signal.go` `TakeMessage` | `runtime/signal_injection_test.go:TestSignalGoesToTheSiblingMachineThatFiresOnIt`, `:TestDecideLeavesNoVariationSelectionAGuardMaterializes`; `repl/send_test.go:TestSendReachesTheMachineWhoseGuardLetsItThrough` | ✅ Faithful, **self-assessed** (the pinned reference runs one machine per test, so nothing external adjudicates the choice among siblings) | -| CallEvent triggers (`accept op(param)` notation, operation and argument matching, arguments bound for guard/effect) | `parser/behavior.go` parseTriggerEvent/parseCallEvent; `symbols/bodyscopes.go` triggerParameterDefiner (parameters are members of the transition, reachable from its own guard/effect); `state_executor.go` matchesEvent EventCall case, bindTriggerArguments, InvokeOperation | `tests/parser/testdata/parse/state_call_trigger.golden`, `lower/trigger_test.go:TestTriggerClassification_CallTrigger`, `model/behavior_body_resolve_test.go` call-trigger parameter cases, `state_call_trigger{,_guard,_nested,_regions}.sysml` conformance, `signal_test.go:TestCallEventMatchesOperationName`, `:TestRejectedCallLeavesNoArgumentsBehind`, `robustness_test.go:call_of_unhandled_operation`, `:call_argument_of_wrong_type` | ✅ Faithful (a call trigger on an enclosing composite state sees the invocation while a substate is active) | +| CallEvent triggers (`accept op(param)` notation, operation and argument matching, arguments bound for guard/effect) | `parser/behavior.go` parseTriggerEvent/parseCallEvent; `symbols/bodyscopes.go` triggerParameterDefiner (parameters are members of the transition, reachable from its own guard/effect); `state_executor.go` matchesEvent EventCall case, bindTriggerArguments, InvokeOperation; `perform.go` `callPayload` (a queued call carries the declaration its arguments select among the owner's same-named operations, `Call.Declared`), `callTriggerOperations` (a trigger names the declarations with an input for each of its parameters, those with exactly its parameters when any has — all of several differing in their parameters' types alone, since the notation writes no types — so a call of a declared operation fires only the triggers naming its declaration, as a UML `CallEvent` names one operation) | `tests/parser/testdata/parse/state_call_trigger.golden`, `lower/trigger_test.go:TestTriggerClassification_CallTrigger`, `model/behavior_body_resolve_test.go` call-trigger parameter cases, `state_call_trigger{,_guard,_nested,_regions}.sysml` conformance, `signal_test.go:TestCallEventMatchesOperationName`, `:TestRejectedCallLeavesNoArgumentsBehind`, `robustness_test.go:call_of_unhandled_operation`, `:call_argument_of_wrong_type`, `robustness_call_results_test.go:TestRuntimeRobustnessCallResults/overload_fires_the_trigger_naming_its_declaration` | ✅ Faithful (a call trigger on an enclosing composite state sees the invocation while a substate is active) | +| A synchronous call of an operation a call trigger accepts returns to the caller once the run-to-completion step the call event triggers is done, with the values the behaviors that step fires — the transition's effect, an entry or an exit — returned or assigned to the operation's `out`/result parameters, by name, the last write winning — the parameters the operation declares as a member of the machine's owner (or of the machine itself when it stands alone) when it declares one, among several so named the one the call's arguments select as a call in the model would (`ErrAmbiguousInvocation` before the call is queued when they select none), the arguments checked against that declaration's inputs as an invocation's are (`ErrUnboundParameter` before the call is queued for an unbound or unknown one, `ErrTypeMismatch` for one of the wrong type, an omitted input carrying its default), the call event carrying that declaration so it fires only the triggers naming it — an `inout` the step writes nothing to going back as the caller passed it, since its parameter value is the argument until a behavior writes it (PSSM §8.5.9 `CallEventExecution`) — every output the step returned when the trigger names no declared operation; a call a state defers holds its caller through the machine's later steps until it is recalled and dispatched; a call the run leaves queued or deferred has not returned, and one no transition accepts is discarded and returns nothing (PSSM §8.5.9 `CallEventOccurrence`; the caller is released only after the step) | `runtime/perform.go` `StateExecutor.Call` (queues the call event with `InvokeOperation`, runs the machine at the current instant until that event has been dispatched — `callReleased` stops the run loop after the unit dispatching it, so events the step queued and timers it armed stay for the machine's later runs, while a deferred call keeps the run going through the steps until it is recalled — and reports a held call as `ErrCallNotReturned` through `eventDisposition`), `pendingCall` (captured by `snapshot.go` `capture` and `held_image_behavior.go` `imagedState`, so an undone step or a materialized image restores the call as it stood), `callPayload` (the declaration through `invoke_operation.go` `memberCalled`, the selection `InvokeOperation` makes, its inputs through `operationInputs`, the check `InvokeOperation` makes, and each input's value or default checked against its parameter as an assignment is, `callInputs`), `newPendingCall`, `callTaken` and `recordCallOutput` (outputs kept only from a behavior the pending call's own event fires, not from nested behavior another occurrence runs, and only under the names the declared operation returns, so a helper's other `inout`/`out` stays the object's own; a declared `inout` argument fills in under a transition that fired on the call and no behavior wrote to, so a discarded call still returns nothing); `action_frame.go` `performanceOwner.returnAround` and `assignEnclosingBy` route a nested action's `return` or output assignment to the enclosing behavior's parameter of that name (`action_executor.go`, `calc_statements.go`, `state_statements.go` hosts) | `conformance/state_call_trigger_results.sysml` (`.expected.json`, trace golden: `compute` doubled by the effect, negated by the entry), `robustness_call_results_test.go:TestRuntimeRobustnessCallResults` (held, recalled, untaken, empty, repeated and erroring calls; the caller released before the completion step its call queued and before a timer it armed, a signal queued ahead dispatched first, a declared operation returning its own parameters alone, an `inout` argument returned as passed, as written and not at all when nothing takes the call; an overloaded operation returning the outputs of the declaration the arguments select, firing the trigger naming that declaration, and refused as ambiguous when they select none; a call leaving a declared input unbound or naming none, or carrying a wrong-typed one, refused before it is queued; an omitted input carrying its default; a calc and a constraint returning their result alone; a rolled-back step restoring the call), `call_capture_test.go:TestStateCaptureTakesThePendingCall`, `:TestHeldImageCarriesThePendingCall` | ✅ Faithful | | Sourceless transitions (`accept … then`, `if … then`, `then`, `transition if … then`, `transition then`) — SysML v2 §7.18.3 `TargetTransitionUsage`: a transition usage written without a source part, whose source "is taken to be the closest lexically previous state usage" in the body that declares it, so it is a member of the body that declares the state it leaves, written after that state, at any depth (a state def body, an exhibited or performed state usage body, a composite state's body, an orthogonal region's body); the pilot's `UsageUtil.getPreviousFeature` derives it the same way, looking back over the other transitions chained off that state. A pseudostate declared before the shorthand is not that state usage: a `choice`, `junction`, `join` or `fork` is left by `transition first … then …;` only | `ast/transition_source.go` `ImplicitTransitionSource` (the previous-member rule over the complete ordered body, looking past the sourceless transitions chained off the same state and the succession `then state s;` lists after `s`); `lower/transition_source.go` `ImplicitSource`, `IsEntryTransition`, `IsStateSource` (a `PseudostateNode`, `InitialNode` or `FinalNode` is not a state source), the typed `ErrNoTransitionSource` and `TransitionSourceError` (`TransitionSourceNotVertexFormat`, `TransitionSourceRegionFormat`, `TransitionSourcePseudostateFormat`, `TransitionSourceMarkerFormat`); `lower/state_graph.go` `lowerTransitionMember` lowers the shorthand from the vertex the rule names, over the inherited and own members `lower/state_inheritance.go` materialises with their owner and scope; `passes/state_transition.go` `(*transitionChecker).checkImplicitSource` reports the same rule at the constraint tier (`CodeNoTransitionSource`, `CodeTransitionSourceNotVertex`) | `parser/behavior_test.go` `TestParseStateBody_SourcelessTransitionForms`, goldens `state_target_transition_guard.sysml` and `state_target_transition_placements.sysml` (top-level, composite, orthogonal-region placements with trigger, guard, effect and dotted targets, each `source=""`; both accepted clean by the pinned pilot), `lower/transition_source_test.go` (`TestToStateGraph_SourcelessTransitionLeavesThePrecedingState`, `:…InheritedSourcelessTransitionLeavesEachMaterialization`, `:…SourcelessTransitionWithNothingBefore`, `:…SourcelessTransitionAfterANonVertex` — start marker, explicit transition, succession usage, triggered and guarded shorthand after a `choice`, triggered shorthand after a `join`, `:…SourcelessTransitionAfterARegion`), `passes/state_transition_test.go:TestSourcelessAcceptTransitionIsLegal`, `:TestSourcelessTransitionChainAndSuccessionAreLegal`, `:TestSourcelessTransitionWithNothingBeforeIsReported`, `:TestSourcelessTransitionAfterANonVertexIsReported` (a pseudostate before the shorthand among them, and the explicit `transition first pick …` form it names staying legal), `:TestSourcelessTransitionAfterARegionIsReported`, conformance `accept_then_transition.sysml`, `state_target_transition_top_level_timed.sysml` (+ trace golden), `state_target_transition_nested_timed.sysml` (+ trace golden: one firing, one entry action, no self-loop), `state_target_transition_guard.sysml`, `state_target_transition_after_do_action.sysml`, `robustness_test.go:sourceless_transition_with_nothing_before`, `:sourceless_transition_after_a_non_state` (a `do` action, an attribute, a `choice` pseudostate) | ✅ Faithful (the earlier reading — the shorthand written *inside* the state it leaves, with that containing state as its source, and refused at the machine's top level — was wrong: the pinned pilot rejects the nested placement with parse errors (`no viable alternative at input 'accept'`), and accepts the flat placement this implementation now lowers, so `accept_then_transition.sysml` was rewritten into the flat form. A shorthand written first in its body, or after a member that is not a state of this machine — a `do` action, an attribute, an `in` parameter, a written succession, documentation, a pseudostate, a region of a parallel state — is reported by the constraint tier with the member named, and the lowering keeps the same typed errors as a backstop; the pilot rejects each of those placements it can parse too, by its grammar or by `A transition with an accepter must have a state as its source`, and has no grammar for `choice`/`junction` to referee the pseudostate placement against) | | ChangeEvent triggers (when expr) | `state_executor.go` matchesEvent, RunToCompletion (polls after each micro-step and again at quiescence); `state_change_trigger.go` pollChangeEvents, SuspendReason | `state_executor_test.go:TestStateChangeEvent`, `state_change_trigger_test.go:TestChangeTriggerRunsWithoutAnExternalPoll`, `:TestChangeTriggerFiresOnRiseFromDoBehavior`, `:TestChangeTriggerDoesNotRefireUnchangedCondition`, `:TestChangeTriggerFalseConditionIsReported`, `conformance/state_change_trigger_autonomous.sysml`, `:state_change_trigger_rising_edge.sysml`, `:state_change_trigger_event_order.sysml` + trace golden | ⚠️ Approximate (driven by the run itself and fired on the condition rising; KerML has no clock, so re-testing once per micro-step is a tool-defined cadence — see the known limitation) | | TimeEvent triggers (`accept after ` relative, `accept at