feat(migrate): write composite-state entry and exit points as junctions, forks and joins - #488
devin-ai-integration[bot] wants to merge 10 commits into
Conversation
…ns, forks and joins A UML State.connectionPoint pseudostate is written as a junction of its state (a fork or join when its transitions each start, or come from, a different orthogonal region), reached by path, so the runtime runs the state's entry behavior before an entry point's outgoing transition and the transition into an exit point before the state's exit behavior. Shapes with no faithful form are refused with the shape named. The runtime exits and runs a compound transition's effects segment by segment (UML 14.2.3.8.4), so an effect out of a pseudostate inside a state runs before that state's exit. An empty transition effect keeps its braces. Co-Authored-By: jason.han <hanhuijun@gmail.com>
…s owned rule A UML Transition.guard written as a guard="..." reference to an owned rule, as Cameo and the PSSM suite serialize it, is now found and written as the if clause; a LiteralBoolean whose value is omitted is false, the UML default. The join conformance case gains its check expectation. Co-Authored-By: jason.han <hanhuijun@gmail.com>
… is also reached from outside Co-Authored-By: jason.han <hanhuijun@gmail.com>
|
I'll fix CI failures and address comments from users with write access. I'll skip comments containing "(aside)".
|
…ex nested machines once An entry point whose single transition leaves its state, leads back to the state itself, runs on into a history pseudostate or has no target was still written as a junction, which the runtime either follows to the wrong place or reports as a dead end; each such route is now refused with its shape named, the transitions through it with it. indexTransitions walked into state machines nested in the one being indexed, so a nested machine named after its owner was indexed twice and a fork entry point saw each branch twice. Co-Authored-By: jason.han <hanhuijun@gmail.com>
|
Confirmed and fixed in 93bf17d. On the PSSM suite this moves one point: Transition 011 B's |
|
Confirmed and fixed in 93bf17d. |
…ted connection points through their state An entry point whose outgoing transition has a trigger is refused: a junction's transition is followed at once, so the runtime would not wait for the event. A guard on that transition is kept, with a test proving the runtime evaluates it when selecting the transition, before the state's entry behavior, as UML evaluates a compound transition's guards before it fires. A connection point a tool lists among a region's vertices rather than as the state's connectionPoint is written in the state's body and named through the state (Sync::Both), not through the region's path. Co-Authored-By: jason.han <hanhuijun@gmail.com>
…from outside their state A region listing nothing but its state's connection points, which a tool may place there, no longer counts as a populated region: it is skipped in the report and its transitions are written in the state's body, so an orthogonal state with one real region is not written parallel with an empty branch. An exit point any transition reaches from outside its state is refused with that transition named, however few reach it, since a junction would enter and leave the state; a local transition of the state into its own exit point stays a junction under the local-transition rule. An entry point no transition leaves notes the owner's regions that have no initial pseudostate. Co-Authored-By: jason.han <hanhuijun@gmail.com>
…egion Co-Authored-By: jason.han <hanhuijun@gmail.com>
…th a member of its state A connection point a tool lists in a region of an orthogonal state is written in the state's body, but the duplicate-name pass only saw the members of a state's own children and of its one inline region, so such a point could be declared beside an entry action, a directly owned connection point or another listed point of the same name. namespaceMembers now includes a state's connection points whichever region lists them, and treats the one region a body is written for as inline, so the later duplicate is renamed 'Name 2' as any sibling is. memberOwner returns the logical owner only for a state's points, so a state machine's region-listed points keep the path of the region they are declared in. Co-Authored-By: jason.han <hanhuijun@gmail.com>
…e from its state The duplicate-name pass ran over a state's connection points before their form was settled, so a point entered by default entry or refused, which declares nothing, still made a later member of its name 'Name 2'. A state's connection points are now left out of that pass and distinguished in namePoints, once their form is known: a written point whose name a member of the body bears yields to it with the same 'Name 2' policy, shared through distinct. Co-Authored-By: jason.han <hanhuijun@gmail.com>
… only A written point that yields its name took the first suffix no child of the state bore, so a connection point written as no member could still hold a suffix. It now takes the first name neither a written member of the body nor a synthesized name of the body has. Co-Authored-By: jason.han <hanhuijun@gmail.com>
What and why
The SysML v1 migrator (
bin/sysml model.xmi -convert sysml) wrote a state machine's connection points but refused the same pseudostates when a composite State owns them (State.connectionPoint, kindentryPoint/exitPoint): the point was reported as "owned by a State, not written", and every transition into or out of it was unmapped. On the OMG PSSM test suite that alone refused 38 transitions across the Entry, Exit, Exiting, Fork, Junction, Transition and Standalone tests.This PR gives composite-state connection points a v2 form that runs the UML/PSSM order, generically for any nesting depth and for orthogonal owners, and refuses the shapes that have no faithful form with the shape named.
Representation (documented in
docs/reference/sysml-v1-migration.md):junction x;declared in the state; the transition into it is written by path,then Work::x;fork x;in the state, each branchfirst Sync::x then A1;written inside its regionjunction x;, the outgoing transitionfirst Work::x then Idle;join x;, left through once every region's transition has firedSync::x; a region listing nothing else is skipped, not written as a branch of aparallelstateWhy
junctionand not a state: astatestanding in for the point would run an extraentry/exitpair and a completion transition, and — what matters — a transition ending at a substate ofWorkentersWorkbefore the substate, so a transitionthen Work::xinto a junction ofWorkrunsWork's entry behavior, then the junction's outgoing transition (its effect), then the target's entries, in one run-to-completion step — the PSSM §8.5 order for an entry point. On the exit side the runtime had to be extended (below) so a routefirst Inner do A then Owner::x;+first Owner::x do B then Out;runsInner(exit); A; Owner(exit); B; Out(entry)— the PSSM order for an exit point — rather than every exit first.Runtime (
internal/exec/runtime/state_route.go,state_executor.go): a compound route's exits and effects are now grouped by segment (leaveAlong,leftBySegment,segmentBoundary,vertexState): a segment leaving from a pseudostate of a state exits that state's descendants the move leaves and the states up to the segment's boundary before its effects; a segment from the machine's body exits what the move still has to leave. Routes ending at a terminate action or a history pseudostate keep their effects in that order (they were dropped before). This fixes the general compound-transition order for any junction/choice declared inside a composite state, not only the migrator's output.Migrator side tables (
internal/translate/migrate/migrate.go,states.go):points(each connection point's classified form),incoming/outgoing(transition index per vertex), andvertexNamesnow name nested vertices so endpoints are written by path (Work::Run::Deep). Classification is structural —entryPointForm/entryBranchWhy/exitPointForm/regionsCrossed/forkBranchWhy— keyed on nothing model-specific; every outgoing route of an entry point is checked before the junction or fork form is chosen, so no point is written whose junction the runtime could not follow. A guard on an entry point's single outgoing transition is kept: UML evaluates a compound transition's guards before it fires, not after entering the composite state, and the runtime evaluates the junction's guard when it selects the transition, before the state's entry behavior (TestGuardedEntryPointRouteIsKeptdrives the case where the entry behavior would falsify it). A connection point a tool lists among a region's vertices rather than as the state'sconnectionPointis written in the state's body and named through the state (Sync::Both), not the region:memberOwnergives the element whose body a vertex is written in, andpath/endpoint/segmentswalk it.indexTransitionsstops at a state machine nested in the one it indexes (that machine indexes its own when it is named), so a nested machine's transitions are listed once however early its owner is named.Adjacent fixes this needed:
do action effect;followed bythen …, a syntax error the PSSM output hit.writer.bracedkeeps the braces for a clausethenmust follow:do action effect { } then Busy;. Fixed minimally inwriteTransitionEffect; if the separate fix for this lands first, this branch takes develop's side.Transition.guardasguard="id"naming anownedRuleof the transition, not as aguardchild. The migrator saw no guard, so it took fork branches for unguarded and wrote choice/junction branches unguarded (a random draw at run time).guardOfresolves both spellings; aLiteralBooleanguard whosevalueis omitted isfalse, the UML default (it was read astrueand dropped).Specification basis
StatePerformances.kerml/TransitionPerformances.kerml: aTransitionPerformanceinto a nested state performs the enclosing state's entry before the nested one's;junction/fork/joinare the OpenSysML state-body extension (no standard pseudostate production — the validator'snonstandard-notationwarning says so on each one).docs/project/spec-compliance.mdgains a row for compound transitions through a pseudostate declared inside a composite state (✅ faithful), anddocs/internals/design/precise-semantics-alignment.mdstates the reading beside its earlier "no spelling" row (the row is kept).How it was verified
Fixtures and tests added
tests/migrate/testdata/xmi/station_points.xmi(hand-written, MagicDraw-free): entry point into a nested state's own entry point (Work::Start→Run::Deep), exit point out of a nested state (Run::Out→Prep), an exit point reached from both orthogonal regions (Sync::Gather, ajoin) and an entry point starting both (Sync::Both, afork), an entry point on a state that also has a default initial pseudostate, history and an entry point on the same state, a transition to an entry point no transition leaves (Work::Plain), a guard serialized as a reference (a[true]fork branch, a valueless[false]guard), and the refused entry→exit chain (Through→Leave), with.golden.sysmland.golden.report.txt(96 mapped, 26 approximated, 4 unmapped — the refused chain and its three transitions).tests/migrate/states_test.go:TestExitPointJoinShapesAreRefusedPrecisely(the three refused join shapes, each with the shape named);TestEntryPointRoutesAreRefusedPrecisely(an entry point whose route leaves the state, runs into its history, has no target, or leads back to the state: refused, no junction written);TestNestedMachineTransitionsAreIndexedOnce(a machine nested in another that a third machine names first still gets its fork entry point);TestCompositeStateConnectionPointsKeepTheUMLOrderexecutes the migratedstation_pointsmodel over five scenarios and asserts the entry/exit/effect order (e.g.GothenFinish:Go(effect) Work(entry) Prep→ Run(entry) Slow … Run(exit) Finish(effect) Work(exit) Leave(effect) Idle);TestEmptyTransitionEffectKeepsItsBraces.tests/parser/testdata/parse/state_nested_pseudostate_qualified_ends.{sysml,golden}: nested junction/fork/join, qualified transition ends, an empty braced effect.internal/exec/runtime/testdata/conformance/):state_junction_exit_effect_before_owner_exit,state_join_exit_from_regions_before_owner_exit(admissible set: the regions' order is open;.check.expected.jsonbeside it),state_junction_entry_skips_default_initial, each with.expected.jsonand trace goldens.robustness_junction_exit_route_test.go— a junction left through with no outgoing transition, one whose outgoing transition ends at a shallow history, one whose every guard is false: typed errors naming the junction, no hang.PSSM suite (
./scripts/download-pssm-suite.sh, thenbin/sysml build/pssm/PSSM_TestSuite.xmi -convert sysml -migration-report … -o …, thenbin/sysml -validate). The "before" isorigin/developplus only the empty-effect writer fix, because without it develop's output does not parse.Every movement, by element id:
done, now to the point). 24 connection-point pseudostates, and the 19 effect activities of the formerly unmapped transitions with their 18 control flows, 18 initial/final nodes, 61 pins, 8 triggers, etc., enter the report as mapped/approximated for the first time (the report never listed elements under an unmapped transition). 2 pseudostates enter as unmapped:Transition 011 E's entry point (entry→exit chain) andEntry 002 E's (a guarded branch into an orthogonal region); their 5 transitions stay unmapped. That accounts for the 38.Transition 011 B's entry point and its 2 transitions (a local transition from the entry point back to its own stateS1, which UML enters by its default entry; the runtime would leave and re-enterS1) are refused rather than mapped, and the 13 elements of that transition's effect activity and its trigger leave the report with it: −10 mapped, −6 approximated, +3 unmapped against the counts before this refusal.if this.value < 5was previously an unguarded draw), the others kept as comments — an honest downgrade, not a loss.-validatereports 21 analysis errors before and 21 after, the identical set: 19 unresolved members inside the generatedSemanticTest/TraceBuildertest infrastructure, and 2junction Junction2 has no outgoing transitionin Junction 004/005 whose route ends at aFinalStateof an enclosing region, a pre-existing refusal. Warnings 2,900 → 2,916: +16junction <name>; is an OpenSysML extensionfor the new connection-point junctions.Hand-driven PSSM tests — the migrated test's part was instantiated in the REPL, the tester's signals sent in the suite's order, and the trace compared with
expectedTracesin the XMI (exact set membership):T1.1(effect)missing: the effect of the region's initial transition is dropped when the state has its own entry behavior — the pre-existing rule the report states onT1.1::effect(unmapped); not a connection-point issueFork('data') has no successors) fails before the state machine runs; unrelated to connection pointsS1.1.1→S1::ExitPoint1)S1(exit)afterT1.2(effect))S1, join from three regions)T1.1(effect)/T2.1(effect)are set aside: those are the effects of Region1/Region2's initial transitions, which the migrator writes as the region'sentry action, so they run on an explicit fork entry too — the same pre-existing initial-transition rule as Entry 002 DTarget::traceunresolved in the generated action); unrelatedS1itself; the runtime has no spelling for entering the state by its default entry from inside it)expectedTraces(they live on the semantic test's superclass)S2.3(entry), then the test'sIntegerDatasignal activity fails; unrelatedreturnTMT model (
TMT.mdzipfrom Open-MBEE/TMT-SysML-Model,bin/sysml TMT.mdzip -convert sysml -migration-report … -o …; bin/sysml -validate):TMT has no state-owned connection points, so the only movement is the guard-by-reference fix: 18 transitions mapped → approximated (their Rhino guards, now found, are kept as comments) and 25 constraints enter the report (7 mapped — written as
if, e.g.first choice if this.currentStar < this.numStarsForCalibration; 18 approximated).-validate: no errors before and after (832 warnings both).On the (non-public) TMT-2024x model this branch gives 28,615 mapped / 10,355 approximated / 5,191 unmapped, 0 validation errors, and comparison-harness output identical to
develop; guards read by reference there are 7 executable and 18 kept as comments, the same split as above.Gates
training_examples_expected.txtis untouched.Checklist
make testandmake lintpass locallychanges/unreleased/<slug>.<section>.md, not as an edit toCHANGELOG.mdmake docs-countsrun if a gate count moved (compliance rows need nothing: the census is counted at docs build)F4,K5) in the body, docs, or changelog