feat(runtime): execute terminate in actions - #335
Merged
Merged
Conversation
…dy terminates `action stop terminate;` declares a terminate action usage: the parser records it as `Usage.IsTerminate` instead of dropping the keyword, and no longer reads a keyword after an action node's name as an action reference. `lowerBody` includes `terminate` statements of a nested node's body, and `lower.Effect` carries the resolved target: the containing performance for a bare `terminate;`, the action node a name reaches, or the occurrence or unknown name it stands for otherwise. The RDF mapping writes the usage as TerminateActionUsage and reads it back. Co-Authored-By: jason.han <hanhuijun@gmail.com>
Contributor
Author
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
A terminate node, a terminate action usage reached by a succession, and a terminate statement of a nested action node's body end the performance they are written in: later nodes do not run, every other token of that performance is dropped in a recorded order, and a nested node's parent continues along the node's succession with the outputs assigned so far. A named target ends the ongoing performance of that action node of the flow around it. An ended performance, a name that is no action node, and an occurrence target are typed errors; calculations and state bodies refuse terminate as before. Co-Authored-By: jason.han <hanhuijun@gmail.com>
Co-Authored-By: jason.han <hanhuijun@gmail.com>
…action-execution Co-Authored-By: jason.han <hanhuijun@gmail.com> # Conflicts: # README.md
… a terminate A terminate node several successions reach performs as the token their arrivals collapse into; the step now takes that token's identity before it runs, so the terminate unwinding out of it finds the token and ends the performance. Co-Authored-By: jason.han <hanhuijun@gmail.com>
Co-Authored-By: jason.han <hanhuijun@gmail.com>
…s only Co-Authored-By: jason.han <hanhuijun@gmail.com>
Co-Authored-By: jason.han <hanhuijun@gmail.com>
…reading RDF A declared usage and the terminate statement share the TerminateActionUsage metaclass; the decoder wrote both as the statement, so a named usage came back as a bare terminate; and its successions lost their target. A declared usage states sysx:hasBody, a statement never does, so that tells them apart. Co-Authored-By: jason.han <hanhuijun@gmail.com>
…action-execution Co-Authored-By: jason.han <hanhuijun@gmail.com> # Conflicts: # README.md # docs/project/spec-compliance.md
…action-execution Co-Authored-By: jason.han <hanhuijun@gmail.com> # Conflicts: # README.md # docs/project/spec-compliance.md
A repeated `terminate`, a typing, a modifier, a value or any other clause written after the marker is diagnosed and skipped up to the body, where the grammar's TerminateNode admits nothing but a body after the keyword. Co-Authored-By: jason.han <hanhuijun@gmail.com>
A merge can bring a second token to a node whose earlier performance is still paused, so the frame the parent's subactions map holds is only the latest one. Named termination now collects every performance of the node from the tokens that run in it or hold it paused, ends them earliest first, and ends a paused leaf performance through the token that holds exactly that performance rather than the first token found at the node. Co-Authored-By: jason.han <hanhuijun@gmail.com>
…t names A qualified name that reaches no action node of an enclosing flow is resolved as a feature path like a simple name is, so an occurrence it names is the occurrence refusal rather than an unknown target. Co-Authored-By: jason.han <hanhuijun@gmail.com>
…action-execution Co-Authored-By: jason.han <hanhuijun@gmail.com> # Conflicts: # README.md # docs/project/spec-compliance.md
… body A terminate unwinding out of a flow that a node of an if branch or a loop body owns left that flow's tokens (a forked waiter) in the executor, so the action deadlocked once the terminated node's parent had completed. Both sites that end a node by its body unwinding now drop what still runs in the performance before completing the node. Co-Authored-By: jason.han <hanhuijun@gmail.com>
… a node names of itself A terminate action usage's body may declare pins, which its lowering skipped, so a flow into one was refused as undeclared. A `terminate <node>;` run inside a performance of that node ended only that performance; it now ends every ongoing performance of the node, the earliest begun first, as one run beside them does. Co-Authored-By: jason.han <hanhuijun@gmail.com>
…nwinding A terminate naming several ongoing performances of a node, run within one of them, ended the later ones in place before unwinding out of its own, so their successions and trace records came first. The unwinding now carries the performances named after its own, which end once it has completed. Co-Authored-By: jason.han <hanhuijun@gmail.com>
…tion The body of a terminate action usage was replaced by the terminate alone, so statements written in it were dropped without report. They now lower as a leaf node's and run before the terminate; a body stating a flow of its own is refused as an invalid action flow at initialize. Co-Authored-By: jason.han <hanhuijun@gmail.com>
Co-Authored-By: jason.han <hanhuijun@gmail.com>
…ut sysx:hasBody Co-Authored-By: jason.han <hanhuijun@gmail.com>
…action-execution Co-Authored-By: jason.han <hanhuijun@gmail.com> # Conflicts: # README.md # docs/project/spec-compliance.md
…action-execution Co-Authored-By: jason.han <hanhuijun@gmail.com> # Conflicts: # README.md # docs/project/spec-compliance.md # internal/core/libs/stdlib.snapshot
…ds the enclosing action A `terminate;` (or `terminate stop;`) stated in the body of `action stop terminate` ended only the usage's own performance, so the action it is a step of went on past it. The unwinding terminate now goes on to the terminate the usage stands for, so the action or node holding the usage ends as it does when the body runs through. Restores the action terminate rows of spec-compliance.md that the merge from develop dropped. Co-Authored-By: jason.han <hanhuijun@gmail.com>
A block node performed from a body statement that paused inside it is held only by that body's paused frame; the node's parent keeps just the latest performance and the token holds no frame of it. `terminate <node>` now finds every such performance through the paused bodies of the tokens, ends it where it stood (abandoning what paused inside it) and lets the body go on past the node as completed; a snapshot captures those performances too. Co-Authored-By: jason.han <hanhuijun@gmail.com>
A leaf action node's body — a terminate action usage's included — lowered only its send, assign, loop, if and terminate statements, so a performed action written among them was dropped and the statements after it read outputs that never came back. lowerBody now lowers the same members BodyStatementMembers recognizes, performed actions included. Co-Authored-By: jason.han <hanhuijun@gmail.com>
…action-execution Co-Authored-By: jason.han <hanhuijun@gmail.com> # Conflicts: # README.md # docs/project/spec-compliance.md
…action-execution Co-Authored-By: jason.han <hanhuijun@gmail.com> # Conflicts: # internal/core/libs/snapshot_test.go # internal/core/libs/stdlib.snapshot
…rformance around it A terminate action usage ending its containing action or node returned the unwinding before its own performance was completed, so the out pins its body assigned never returned to enclosing features or reached their bindings. Both the flow-node and the block-statement paths now end the usage's performance first, exactly once, then go on to the enclosing termination. Co-Authored-By: jason.han <hanhuijun@gmail.com>
6 tasks
HuiJun
added a commit
that referenced
this pull request
Sep 17, 2026
…fresh * docs(roadmap): record E1, the fUML referee and the state-executor findings as landed on develop The develop head is fcfb0a7 (#352), 64 pull requests past v0.8.0. E1 landed in two halves (#335, #352) and the PSSM referee reads 46 pass / 17 fail, every failure attributed and nine of them waiting on the region-order design record (#342). The fUML referee for actions is complete (#319, #321, #334; 15 pass / 0 fail / 36 not-expressible / 4 differs-by-design), modeled randomness sits beside A3 (#344), the MSI runs a setup wizard (#350), the errata overlay covers the bundled library (#304), and the build-time test figures are re-quoted at the head. The release paragraph names the open release/0.8.1 patch cut from v0.8.0 and keeps the next cut from develop a minor bump. Co-Authored-By: jason.han <hanhuijun@gmail.com> * docs(roadmap): count the PSSM failures the region-order choice point releases as eleven, Transition 017 apart Co-Authored-By: jason.han <hanhuijun@gmail.com> --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: jason.han <hanhuijun@gmail.com>
6 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What and why
terminatewritten in an action had two silent no-op paths:action stop terminate;lost its keyword at parse time and ran as an empty action, andlower.lowerBodydropped aterminate;/terminate c1;written in a nested action node's body. This PR makesterminateexecute in actions.Actions only. A
terminatein a state'sentry/do/exitbody, a transition to a terminate action usage (accept Abort then stop; action stop terminate;), andterminate <expr>naming an occurrence instance (terminate this;) are not part of this change; they keep typed refusals ('terminate' in a body is not executable,ErrTerminateOccurrence) and are marked ❌ indocs/project/spec-compliance.md.Parser, AST, lowering, export
ast.Usage.IsTerminatemarks a terminate action usage; the parser records it instead of dropping the keyword, and no longer reads a keyword after an action node's name as an action reference. Dumped inast/dump.go, carried by the AST codec (snapshot format bumped to 17 so a pre-change blob is refused as stale; embedded stdlib snapshot regenerated). The keyword is accepted on action usages only;part p terminate;is diagnosed.lower.lowerBodyincludes*ast.TerminateStatement; a terminate action usage lowers to a one-statement body.lower.Effectcarries the resolved target so the executor re-resolves nothing:TerminateContainingis a bareterminate;;TerminateEnclosingis a terminate action usage (it ends the flow it is a step of, not its own one-statement performance);TerminateNodenames an action node;TerminateOccurrenceis a feature or expression that is not an action node.sysml:TerminateActionUsageand read back asaction <name> terminate;.Executor (
internal/core/runtime/action_terminate.go)actionStmtHost.effectdispatchesEffectTerminatetoperformances.terminate, which resolves the target performance (terminateTarget: the body's own frame, its parent, or the named node's frame found asf.nodeor inf.subactionswalking up the frame chain) and either unwinds the body with a typed*terminated{perf}error when the body runs within the target, or ends the target in place (endOther) when it is a sibling flow.stepTokencatches the unwinding (endTerminatedFor) at the token whose step is the outermost within the target and ends it (endAround): every other tokeninFlowOfthe performance is dropped lowest ID first (dropTokensIn) — pausedbodyRuns ended, nested frames marked ended — and recorded asterminate <perf>: dropped token N@node, ...; the surviving token leaves the node (leaveTerminated) throughendPerformance+completeNode, so the parent takes the node's succession with the outputs assigned so far and no inner succession fires. The root ends withStateCompletedandendPerformanceLife.performNode) and a body-driven flow (usageWork.perform,runSubflow) treat a terminate of their own performance as completion.ErrTerminateTarget(name is no action node of a flow around the statement, or nothing runs in it),ErrPerformanceEnded(target already ended),ErrTerminateOccurrence(the out-of-scope occurrence target). Calculations still refuse withErrCalcSideEffect; an unsequencedterminateamong an action's members is stillErrStatementOutsideFlow.Docs
docs/project/spec-compliance.md: Actions map gains rows for the flow node, the named terminate action usage, the nil-target body statement, the named-node body statement, occurrence targets (❌) and state positions (❌); the Track E paragraph drops the action clause only.docs/guide/06-behavior.md: aterminateparagraph with the action-only scope.changes/unreleased/terminate-action-execution.added.md.Known limitations
19. Terminate Actions/Terminate Actions Example-1.sysmlstill stops atno initial node found in action node performCriticalActivity: its body has nofirst/start. That is the flow-start rule, unchanged here;terminateis proven on fixtures that declare a start.terminate <expr>on an occurrence, and every state-machine position, are typed refusals pending the state-machine half.Specification basis
SysML v2 §7.17.10 (terminate action usages): a
terminatewith no target ends the immediately containing performance; a named target ends that action node's ongoing performance; the parent continues along the node's succession.How it was verified
go build ./...,go vet ./...,gofmt -l .(empty),go test ./...,go test -race ./...,make lint: all clean.go test -run TestGolden ./internal/core/parser— new fixtureaction_terminate.sysml(then terminate;,action stop terminate;,then action a2 terminate;,terminate c1;andterminate;in a body); no other golden moved.TestNegativegainspart_terminate,attribute_terminate,state_terminate.go test -run TestStdlibConformance ./internal/core/libs.go test -run 'TestExecutionConformance|TestExecutionTrace' ./internal/core/runtime— nine new fixtures undertestdata/conformance/action_terminate_*: flow node, nested body, named usage, named usage reached over two successions that synchronize (trace golden), fork dropping a sibling (trace golden), nested fork (trace golden), naming the node from its own body, naming the enclosing node, naming a sibling flow's node (trace golden).go test -run TestRuntimeRobustness ./internal/core/runtime—terminate_of_an_ended_performance,terminate_of_an_unknown_name,terminate_of_a_non_action_feature,terminate_of_an_occurrence_expression,terminate_of_a_node_of_a_sibling_flow;calc_terminate_is_rejectedunchanged.OPENSYSML_REQUIRE_TRAINING_CORPUS=1 OPENSYSML_REQUIRE_PILOT_CORPORA=1 go test -count=1 ./internal/core/model -run 'TestTrainingExamples|TestPilotCorpora'— ok;training_examples_expected.txtunchanged.OPENSYSML_REQUIRE_TRAINING_CORPUS=1 OPENSYSML_REQUIRE_PILOT_CORPORA=1 go test -count=1 ./internal/core/export -run TestCorpusRoundTrip— ok, no file moved.make docs-counts,make docs-check,python3 scripts/changelog.py check— ok.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