Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
14 commits
Select commit Hold shift + click to select a range
2906f57
feat(migrate): write composite-state entry and exit points as junctio…
devin-ai-integration[bot] Sep 21, 2026
569381e
fix(migrate): read a transition guard serialized as a reference to it…
devin-ai-integration[bot] Sep 21, 2026
e7626c7
fix(migrate): name the shape when an exit point several regions reach…
devin-ai-integration[bot] Sep 21, 2026
93bf17d
fix(migrate): refuse entry-point routes a junction cannot follow; ind…
devin-ai-integration[bot] Sep 21, 2026
2b44d18
fix(migrate): refuse triggered entry-point routes and name region-lis…
devin-ai-integration[bot] Sep 21, 2026
cef58b4
fix(migrate): skip point-only regions and refuse exit points reached …
devin-ai-integration[bot] Sep 21, 2026
7826323
fix(migrate): word the default-entry note of an entry point for one r…
devin-ai-integration[bot] Sep 21, 2026
ca751a7
fix(migrate): rename a region-listed connection point that clashes wi…
devin-ai-integration[bot] Sep 21, 2026
222f208
fix(migrate): let a connection point written as no member take no nam…
devin-ai-integration[bot] Sep 21, 2026
3835977
fix(migrate): rename a clashing connection point past written members…
devin-ai-integration[bot] Sep 21, 2026
7a43f7b
Merge remote-tracking branch 'origin/develop' into feature/composite-…
devin-ai-integration[bot] Sep 21, 2026
9754bc1
fix(migrate): validate an exit point's outgoing route before writing …
devin-ai-integration[bot] Sep 21, 2026
f77f91e
fix(runtime): record a default history route's segments when it ends …
devin-ai-integration[bot] Sep 21, 2026
576b8fd
merge: bring the empty-effect writer fix and PSSM migration gate into…
devin-ai-integration[bot] Sep 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- **The SysML v1 migrator writes a composite state's entry and exit points.** A UML `State.connectionPoint` pseudostate, on a nested composite state or on one with orthogonal regions, is a `junction` of the state, reached by path (`then Work::start;`, `first Work::leave then Idle;`), so the runtime runs the state's entry behavior before the entry point's outgoing transition and the transition into the exit point before the state's exit behavior, the order UML and PSSM give connection points. An entry point whose transitions each start a region of an orthogonal state is a `fork`, an exit point its regions reach from each side a `join`, and an entry point no transition leaves is the state's default entry. An entry point leading straight to an exit point of the same state, a route from a connection point on into a history pseudostate, and the other shapes with no faithful form are refused with the shape named in the report; before, every connection point on a state and every transition through it was unmapped. The OMG PSSM test suite's connection points, which the report refused wholesale, migrate under this rule, and the migrated suite validates with no syntax errors.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- **A compound transition through a pseudostate inside a composite state exits and runs its effects segment by segment.** A transition into a junction of a composite state, continued by the junction's outgoing transition out of the state, now exits the source, runs the first effect, exits the composite state, then runs the second effect and enters the target, as UML 2.5.1 §14.2.3.8.4 orders the segments; before, every exit ran before any effect. A join of the state its orthogonal regions leave through runs each region's exit and effect, then the state's exit, then the outgoing transition; a route ending at a terminate action or a history pseudostate keeps its effects in that order.
1 change: 1 addition & 0 deletions changes/unreleased/empty-transition-effect-braces.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- **The SysML v1 migrator writes a transition effect with no body as `do action effect { }`.** An effect activity with no nodes was written `do action effect;`, which ended the transition clause before its `then`, a syntax error in the migrated notation; the braces are now kept so the `then` still belongs to the transition.
1 change: 1 addition & 0 deletions changes/unreleased/transition-guard-by-reference.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- **The SysML v1 migrator reads a transition guard serialized as a reference.** A UML `Transition.guard` some exporters write as a `guard="…"` reference to an owned rule of the transition, rather than as a `guard` child, is now found and written as the `if` clause, reported and kept in a comment when it has no v2 form; before, such a transition was written unguarded and its constraint left out of the report. A `LiteralBoolean` guard whose `value` the file omits is read as `false`, the UML default, where a transition guard is concerned; before, it was taken for `true` and dropped.
2 changes: 1 addition & 1 deletion docs/internals/design/precise-semantics-alignment.md
Original file line number Diff line number Diff line change
Expand Up @@ -1516,7 +1516,7 @@ which supersede the hand count this section was first written with — the moves
| Deferrable trigger | `defer Sig;` — this project's extension | extension |
| Fork, join, junction, choice, shallow and deep history pseudostates | State-body `fork`/`join`/`junction`/`choice`/`history`/`deep history` — this project's extensions | extension |
| Terminate pseudostate | A terminate action usage in the region, `action t terminate;`, that a transition ends at with `then t` (§7.18.3; SM38) | standard |
| Entry point, exit point (connection points and connection point references) | none | no spelling |
| Entry point, exit point (connection points and connection point references) | none in standard notation. The migrator's reading, established since this table was drawn: a composite state's entry or exit point is a `junction` of that state (a `fork`/`join` when its transitions each start, or come from, a different orthogonal region), reached by path — `then Work::start;`, `first Work::leave then Idle;` — so the runtime runs the state's entry behavior before the junction's outgoing transition, and the transition into the junction before the state's exit behavior, the order UML §14.2.3.4.5 and PSSM §8.5 give connection points; see [sysml-v1-migration.md](../../reference/sysml-v1-migration.md#behaviors). The referee's classifier still counts the construct here, so the table below is unchanged until the referee is rewired onto the migrator | no spelling (referee); extension (migrator) |
| 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 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 |
Expand Down
1 change: 1 addition & 0 deletions docs/project/spec-compliance.md
Original file line number Diff line number Diff line change
Expand Up @@ -818,6 +818,7 @@ checked after the result is bound is not a form the runtime offers, and none is
| Junction pseudostates: the route through a junction is resolved statically, before the incoming transition fires and before any effect runs; every outgoing guard is read against the data as it then stands, several enabled branches are the transition choice point at the junction (`ChoiceTaken` drawn and recorded only as the transition fires, after the region order among several candidates and the transition's own guard read again, enumerated by `explore`, replayed by a seed; a candidate another region's reaction disarms draws nothing; no branch guard is read again — the route beyond each enabled branch, through any further junction, is settled with the transition — so a branch enabled at selection is taken along it though another region's effect since changed what its guards read; the draw is made as the move begins, so a replay refused at a choice beyond the junction undoes the draw, its note and its `ChoiceTaken` with the move), the unguarded branches are the default when no guard holds, and no enabled branch leaves the compound transition unenabled; a history's default transition through such a junction draws and records the same way | `state_route.go` resolveRoute, followOut, enabledBranches (later guards read in a preview that is undone), junctionDraw, travel, travelResolving, settleDraws, pickBranch; `state_executor.go` fireTransition, defaultHistoryRoute | `state_junction_pseudostate.sysml`, `state_completion_through_pseudostate.sysml`, `state_junction_several_enabled_branches.sysml`, `state_junction_drawn_as_its_transition_fires.sysml`, `state_junction_guards_read_once.sysml`, `state_junction_beyond_a_draw_read_once.sysml`, `state_history_default_through_junction.sysml` (+ trace goldens, `.check.expected.json`), `explore_test.go:TestExploreStaticJunctionBranches`, `:TestExploreJunctionDrawnAsTransitionFires`, `:TestExploreHistoryDefaultThroughJunction`, `replay_test.go:TestReplayRefusedChoiceUndoesTheJunctionDrawBeforeIt`, `:TestReplayRefusedChoiceUndoesTheHistoryDefaultsJunctionDraw`, `robustness_test.go:region_pseudostate_without_satisfied_guard` | ✅ Faithful (static, unlike a choice; which of several enabled branches is taken is open, as at a choice) |
| Fork pseudostates: the branches enter one composite state's orthogonal regions, one target per region, at least two, none guarded or triggered (UML 2.5.1 §14.2.3.5 Pseudostates: the transitions outgoing from a fork "must not have a guard or a trigger"), bypassing the initial state of each region they name — a target may lie below a region's own substates, the region being the one it lies in transitively, and the branch enters every state on the way down; a region the fork leaves out starts at its own initial state, and a region without one may be entered by a fork's branch alone — a machine with another way into the composite state that would start such a region by default (a transition to the state itself, to another of its regions or its history, the machine's entry naming it, or another fork — nested in one of its regions, or above it — passing through) is refused when lowered. Entering through a fork exits the source configuration down to the ancestor the source and the composite share — every region of the composite, in declaration order, when the source lies inside them, the composite itself staying active — then the first branch in region order runs its effect and enters the states still on the way down, and every region enters in declaration order, each branch's effect before its target — a region the fork leaves out never brings the composite in ahead of a branch, however early it is declared — the effects reading and writing the attributes of the state declaring the fork, as a transition leaving one of its substates does; branches ending at `done` complete the composite, or the machine, as an ordinary entry does. A fork's static footprint covers the regions it leaves to start by default, so the checker's reduction keeps their entry behaviors' reads, writes and completion dependent on what other units do | `lower/fork_plan.go` `ForkPlan`, `planFork`, `forkOwner`, `enclosingRegion`, `ForkStarted`, `checkForkOnlyRegion`, `defaultEntryInto`, `defaultStart`, `forkStartsByDefault`; `lower/state_footprint.go` `stateFootprintBuilder.pseudostate` (fork case), `entersRegion`; `lower/state_graph.go` `ownTransitionEffects`; `state_executor.go` `fireForkTransition`, `leaveForFork`, `exitRegionsOf`, `completeIfDone`; `state_region_entry.go` `forkEntry`, `enterForkBranches`, `enterRegion`, `enterLazily` | `lower/fork_plan_test.go` (`TestToStateGraph_ForkEntersRegionsWithoutInitial`, `:TestToStateGraph_ForkBranchEffectsOwnedByDeclaringState`, `:TestToStateGraph_ForkKeepsRegionInitialApart`, `:TestToStateGraph_ForkBranchTargetsNestedState`, `:TestToStateGraph_ForkBranchesNestedInOneRegionFail`, `:TestToStateGraph_RegionWithoutInitialOrForkFails`, `:TestToStateGraph_ForkOnlyRegionEnteredByDefaultFails`, `:TestToStateGraph_ForkOnlyRegionEnteredByAnEnclosingEntryFails`, `:TestToStateGraph_ForkOnlyRegionOmittedByAnotherForkFails`, `:TestToStateGraph_ForkOnlyRegionEnteredByANestedForkFails`, `:TestToStateGraph_ForkOnlyRegionKeepsExplicitEntries`, `:TestToStateGraph_ForkShapeRejected`), `lower/state_footprint_test.go:TestTransitionFootprintsCoverForkOmittedRegions`, `state_fork_join_pseudostate.sysml` + trace golden, `state_fork_enters_regions_without_initial.sysml`, `state_fork_in_composite_enters_parallel_substate.sysml`, `state_fork_through_inactive_ancestors.sysml`, `state_fork_within_active_ancestor.sysml`, `state_fork_within_active_region.sysml`, `state_fork_completes_owner.sysml`, `state_fork_completes_nested_owner.sysml`, `state_fork_enters_nested_region_states.sysml`, `state_fork_enters_nested_parallel_state.sysml`, `state_fork_omitted_region_declared_first.sysml`, `state_fork_from_within_owner_regions.sysml`, `state_fork_from_within_nested_owner_regions.sysml` (+ trace goldens), `state_fork_branch_effect_owns_state_attribute.sysml`, `fork_join_test.go:TestForkBypassesTargetedRegionInitials`, `robustness_test.go:fork_branches_share_region`, `:fork_leaves_a_region_without_a_way_in`, `:fork_only_region_entered_by_default`, `:fork_branch_with_a_trigger`, `:nested_fork_starts_an_outer_region_by_default` | ✅ Faithful |
| Join pseudostates | `state_executor.go:782` fireJoinTransition, `:827` joinSources (declaration order) | `pseudostate_test.go:TestJoinWaitsForEveryBranch`, `fork_join_test.go:TestForkJoinVisitOrderIsDeterministic` | ✅ Faithful |
| A compound transition through a pseudostate declared inside a composite state exits and runs its effects segment by segment (UML 2.5.1 §14.2.3.8.4, the segments' behaviors "executed in sequence", each after the exits of the states its segment leaves; PSSM §8.5 on exit points): a transition `first Inner accept Sig do { A } then Owner::x;` into a junction `x` of `Owner`, continued by `first Owner::x do { B } then Out;`, exits `Inner`, runs `A`, exits `Owner`, runs `B`, then enters `Out` — not every exit first. The segments' effects are grouped by segment (`routeEffect.segment`); a segment leaving from a pseudostate of a state exits that state's descendants the move leaves and the states up to that segment's boundary before its effects, and a segment from the machine's body exits what the move has still to leave. Into a join of `Owner` from its orthogonal regions, each region's transition exits its source and runs its effect (the regions in an open order), then `Owner` exits, then the join's outgoing transition runs. A route that ends at a terminate action, or at a history pseudostate, keeps its effects in that order. This is the runtime form the SysML v1 migrator writes a composite state's exit point in (`docs/reference/sysml-v1-migration.md`, *Pseudostate exitPoint on a composite State*); a transition into a junction of a composite state from outside runs the state's entry behavior before the junction's outgoing transition and the target's entries, which the migrator's entry point form relies on | `state_route.go` travelResolving, leaveAlong, leftBySegment, segmentBoundary, vertexState; `state_executor.go` terminateAlong, terminateAt, moveToHistory (effects passed through) | `state_junction_exit_effect_before_owner_exit.sysml` + `.trace.golden`, `state_join_exit_from_regions_before_owner_exit.sysml` + `.trace.golden` (`admissible` orders), `state_junction_entry_skips_default_initial.sysml` + `.trace.golden` (an entry through a junction runs the state's entry behavior, skips the region's `entry; then`, and history restores the substate); `robustness_junction_exit_route_test.go:TestRuntimeRobustnessJunctionExitRoute` (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); `tests/migrate/states_test.go:TestCompositeStateConnectionPointsKeepTheUMLOrder` (the migrated `station_points.xmi` run end to end) | ✅ Faithful |
| History pseudostates (shallow and deep). A region is recorded per region, in the state it was left in, so a region left by a transition that started inside its own composite state's region is restored to that composite state and its inner configuration rather than to the region's initial state; a region left with no active state at all has nothing to restore; a region or body left at `done` completed and leaves no history. A shallow or deep history with nothing recorded takes its own outgoing transition when it has one and otherwise performs the owning state's default entry, through its `entry` transition, as a plain transition into the composite would; an owner with no entry transition either is the typed `ErrHistoryWithoutEntry` naming the history and its owner | `parser/behavior.go` parseStateMember (`history <name>;`, `shallow history <name>;`, `deep history <name>;`); `state_executor.go` fireHistoryTransition, `:historyEntry`, `:historyRecorded` (an empty history, read by `state_route.go` `resolveRoute` too when settling the default transition), `:hasDefaultEntry`, `:deepestRecorded`, `exitState` (records the configuration left), `:recordChildHistory`, `:recordRegionHistory`, `:forgetRegionHistory`, `errors.go` `ErrHistoryWithoutEntry`, `state_region_transition.go` `leaveRegion`, `exitRegionTo`, `lower/state_graph.go` PseudostateOwner | `tests/parser/testdata/parse/state_history.golden`, `parser/state_notation_test.go:TestHistoryPseudostateParsing`, `lower/state_notation_test.go:TestToStateGraph_HistoryNotation`, `state_shallow_history.sysml`, `state_deep_history.sysml`, `state_history_revisit.sysml` + trace golden, `state_deep_history_region_composite.sysml`, `history_test.go:TestShallowHistoryRestoresLastSubstate`, `:TestDeepHistoryRestoresInnermostSubstate`, `:TestHistoryRestoresOrthogonalRegions`, `:TestDeepHistoryRestoresBelowRegion`, `:TestDeepHistoryRestoresARegionLeftFromInsideItsCompositeState`, `:TestHistoryTakesDefaultTransitionWhenUnvisited`, `:TestHistoryOverACompletedConfigurationIsADefaultEntry`, `state_history_empty_default_entry`, `state_deep_history_empty_default_entry`, `state_history_after_completion_default_entry`, `state_history_after_completion_default_transition` (+ trace goldens), `robustness_test.go:history_outside_composite_state`, `:history_without_record_default_or_entry` | ✅ Faithful |
| Composite state with regions entered by a plain transition | `state_executor.go` transitionToInto (keeps the region configuration entering it just built) | `history_test.go:TestHistoryRestoresOrthogonalRegions` | ✅ Faithful |
| Leaving a composite state exits only its own regions | `state_executor.go` exitState (scoped to `CompositeStates[state]`) | `history_test.go:TestExitingNestedRegionsKeepsSiblingRegions` | ✅ Faithful |
Expand Down
Loading
Loading