Skip to content

feat(migrate): write v1 interactions, pseudostates, receptions and ported calls executably - #385

Merged
HuiJun merged 52 commits into
developfrom
feature/v1-state-interaction-fidelity
Sep 20, 2026
Merged

HuiJun merged 52 commits into
developfrom
feature/v1-state-interaction-fidelity

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

What and why

The SysML v1 migrator wrote state machines and signal-only interactions, and left the rest of a v1 behavior model as comments or report entries: call and reply messages, lifelines that are not a direct part, combined fragments, transitions across regions, junction/choice/fork/join/history pseudostates, entry/exit points of submachine states, internal transitions, absolute time events, receptions, and operation calls over ports. This change writes each of those in the v2 form the runtime executes, or refuses it with the specific reason, so a migrated model's scenarios and machines run under -action/-state/-analysis on the migrated system individual.

internal/translate/migrate:

  • Interactions (interaction.go, context.go): a synchCall/asynchCall message is perform action x : Op ::> <lifeline path>.op { in p = arg; } with arguments bound to the operation's in parameters by name or position; a reply assigns the call's result to the caller lifeline's attribute; create/delete messages and message-less timing traces are refused with the reason. A lifeline's represents is resolved through the context block's parts, ports, references and in parameters; a lifeline that does not resolve makes the scenario a report entry. alt/opt/loop/par fragments become if/while/for/fork when their guards parse and resolve, otherwise the fragment is refused with the guard text.
  • State machines (states.go, events.go): transitions whose source and target lie in different regions or nesting levels are written by path; junction, choice, fork, join, history and deep history pseudostates are written executably; entry/exit points of a submachine are addressed as states of its state def; internal transitions are self transitions; a referenced absolute TimeEvent is accept at <instant> over a Time::TimeInstantValue attribute when when is a literal or resolves; an event no trigger names is skipped as a model element nothing refers to, counted apart from profile/library content in the header, rather than reported as unmapped. A state entered only by transitions carrying one signal values its entry/do parameters from that signal (carriers.go).
  • Receptions and operation calls (call_port.go, arrivals.go): a Reception with a method is an action def the block performs from creation, accepting the signal — from the object and via each port the document's connectors or declarations bring it to — and running the method with the signal's attributes bound to its parameters, then accepting again; a CallOperationAction over a port performs the operation on the part a connector of the caller's block joins to that port, resolved the way connector paths resolve; a trigger naming no port also accepts via each port the document's connectors carry its signal to.
  • Values that never arrive (arguments.go, datawait.go): a call or send that v1 fires without a value for a parameter or attribute that must hold one — no argument, or a pin fed only by flows no value travels (a parameter nothing values, an unmigrated opaque or value specification action, or a callee whose own activity gives that out parameter no value, judged through nesting) — is written as an empty action carrying the token with the reason in its comment and report line, and the flow is kept as a comment; v2 admits no typed perform with an input unbound. Control and buffer nodes only object flows lead to route their values from source to pin; a control node no edge leaves ends the token; an action fed from outside its control path waits on the value only when the producer runs on every pass of the surrounding loop.

Runtime and resolution, where the migrated forms needed them (internal/exec/runtime, internal/ir/lower, internal/semantic/resolve):

  • perform action x : Def ::> part.action { … } runs the part's action with the part as performer and binds the callee's inputs from the body; an accept payload is visible by simple name to other nodes of the same body, so a typed nested action can read the accepted message.
  • via sends and accepts route through bound references (via ctx.p), delegated ports, ports of a redefined part under the name its connectors use, and untyped ports (materialized as Ports::Port) so a binding connector joins them and an inward send reaches the bound part's machine; a ref usage holds what is bound to it rather than an object of its own.

Documentation: docs/reference/sysml-v1-migration.md (mapping rows, "Behaviors", "Interactions", "Receptions and operation calls", "Values that never arrive", "Control nodes carrying data", "The report"), docs/project/spec-compliance.md rows for the resolution and runtime rules, changelog fragment changes/unreleased/v1-state-interaction-fidelity.added.md.

Specification basis

SysML v2 1.0 / KerML 1.0: action usages with a feature chain (SysML 7.16.2, 7.16.4 PerformActionUsage), accept action payload scoping (KerML 8.2.3.5.3), state transitions and accept at (SysML 7.17.4), interfaces and connector ends over ports (SysML 7.13.5, 7.14.4). PSSM (OMG formal/19-05-01) for pseudostate and cross-region transition semantics. Adds three rows to docs/project/spec-compliance.md: via routing over bound references and port kinds (✅), typed perform on a chained part (✅, self-assessed), and accept payload name scoping (⚠️ approximate: the payload is contributed to the scope the accept node is declared in rather than made a member of the body).

How it was verified

Gates: gofmt -l . (empty), go vet ./..., make lint, go build ./..., go test ./...; make docs-check, python3 scripts/changelog.py check, python3 scripts/check-doc-ids.py; the corpus gates with the corpora downloaded and OPENSYSML_REQUIRE_TRAINING_CORPUS=1 OPENSYSML_REQUIRE_PILOT_CORPORA=1 OPENSYSML_REQUIRE_PILOT_LIBRARY_XMI=1 (no baseline moved).

Tests added: migrate fixtures rig_interactions, plant_states, heater_receptions, ported_calls (XMI + golden notation + golden report under tests/migrate/testdata/xmi), and unit tests per construct (interaction_test.go, states_test.go, reception_test.go, call_port_test.go, context_test.go, carriers_test.go, arrivals_test.go, arguments_test.go, datawait_test.go, sinks_test.go), each migrating, validating with no errors and running the result; runtime conformance fixtures (perform_typed_action_on_nested_part, accept_payload_bound_into_typed_action, accept_via_bound_context_port, send_via_this_port, send_delegated_port_nested_binding, send_redefined_part_port_end, send_untyped_port_inward_binding, instance_reference_usage_not_materialized) with traces where ordering matters; robustness TestRuntimeRobustnessPerformTypedOnPart, TestRuntimeRobustnessUntypedPort; parser golden perform_typed_subsetting; resolve/accept_payload_test.go.

Proof on a 44k-element Cameo 2024x model (TMT), migrated on the base branch feature/v1-simulation-configs (66f257f, which carries develop) and on this branch; both outputs validate with 0 errors. The header figures move with the base (its applied-stereotype rendering shifts 626 Dependency elements from mapped to approximated on both sides, and its simulation-configuration and probability notes are on both sides too); the per-metaclass rows below are the ones this change moves:

base (feature/v1-simulation-configs) this branch
Header 44604 element(s): 27967 mapped, 10261 approximated, 6376 unmapped (1713 skipped as profile or library content) 44136 element(s): 28114 mapped, 10367 approximated, 5655 unmapped (1713 skipped as profile, library or notation-only content, 764 as model elements nothing refers to)
Interaction 9 approximated / 24 unmapped 13 approximated / 20 unmapped
Message 39 mapped
Lifeline 26 mapped
StateMachine 3 mapped / 31 approximated 3 mapped / 31 approximated
Transition 334 mapped / 2 approximated / 33 unmapped 349 mapped / 7 approximated / 13 unmapped
Pseudostate 87 mapped / 3 approximated / 3 unmapped 92 mapped / 1 unmapped
SignalEvent 302 mapped / 829 approximated / 30 unmapped 320 mapped / 819 approximated / 12 unmapped / 10 skipped
TimeEvent 19 mapped / 710 unmapped 19 mapped / 1 unmapped / 709 skipped
Trigger 334 mapped / 823 approximated / 11 unmapped 293 mapped / 882 approximated / 12 unmapped
Reception no instances in the model no instances in the model
CallOperationAction 11 mapped / 6 approximated 9 mapped / 8 approximated

Two of the model's duration scenarios (sequence-diagram interactions that inject signals into the system individual) were run end to end on the migrated file: -instantiate the scenario, -state the fifteen component state machines, -analysis the scenario verification, -advance, under -seed 1 since the components draw uniform delays. Both complete: the executive machine steps Initializing → StandBy → its working state (do behavior running) → StandBy, and in the off-axis scenario on to done, at simulation times 360 s and 3590 s; the verification verdict is "inconclusive — the case body bound no VerdictKind value", since the source verifications bind none.

Known limitations, each reported by the migration rather than dropped:

  • Opaque JavaScript bodies and guards are not translated here (a sibling change), so the timing observables Cameo's result snapshots record (tFinal and the other t*) are not computed by the migrated model, and steps depending on their values are placeholders that keep the token.
  • A feature whose lower bound exceeds the runtime's materialization bound (Integer[100000]) reports a multiplicity violation at -instantiate and materialization continues bounded.
  • A v1 property left untyped with a string default read into an Integer parameter is written as the source declares it and fails the typed assignment at run time.
  • The verification definitions migrated from duration scenarios bind no verdict; no pass/fail is synthesized.
  • A duration constraint whose interval is open on one side (a min with no max, a max of *) is reported unmapped, since no single wait stands for it; one such constraint in the model (Acquire and Lock TTF OIWFS NS Logical Actual) is refused, while the scenarios' {60s}-style constraints — MagicDraw's min beside an expressionless max — keep their fixed waits.
  • Three of the model's command activities (Take PIT Exposure, Take SH Exposure, Request Single APT Exposure) declare only in parameters, yet thirteen calls of them carry a result pin; such a pin stands for no parameter and carries no value, so the flows from it are kept as comments and the ten analysis calls they alone feed (and three more fed only from those) are reported as never firing rather than written as typed calls with an input unbound. Neither duration scenario reaches them.
  • A call is checked and bound against the callee's action def signature — the operation's parameters and then those its local method adds (actionParameters); a call passing no argument for a required method-only input is refused with the reason, one supplying it flows to the declared in. One such pin exists in the model (Indexer::index calling stepMotor, whose method alone declares velCmd); it is now mapped.
  • Expansion regions are not iterated: an action inside one whose required pin is fed only from the region's input element is reported as never firing (its object flows trace to no producer), as is one fed from a fork that only a control flow enters.

Checklist

  • make test and make lint pass locally
  • Tests added or updated for the change
  • Documentation extended where it already covers the surface (see CONTRIBUTING.md)
  • Changelog entry added as changes/unreleased/<slug>.<section>.md, not as an edit to CHANGELOG.md
  • baselines regenerated and make docs-counts run if a gate count moved (compliance rows need nothing: the census is counted at docs build)
  • No internal work-item labels (waves, slices, F4, K5) in the body, docs, or changelog

devin-ai-integration Bot and others added 17 commits September 17, 2026 22:02
…e events executably

Co-Authored-By: jason.han <hanhuijun@gmail.com>
…and connection points

Co-Authored-By: jason.han <hanhuijun@gmail.com>
…ecutable scenario steps

Interactions resolve lifelines through the context block's part tree, write
synchronous and asynchronous operation calls as typed performs of the part's
operation usage with positional and named arguments, replies as assignments of
the call's results, and alt/opt/loop/par fragments as if, for and fork
structures; create and delete messages and unresolvable lifelines are reported.
A test-case interaction becomes a verification def running on its subject.

The runtime's nested usage performs the action its reference subsetting names
before the one its typing names, so a typed perform runs as the part.

Co-Authored-By: jason.han <hanhuijun@gmail.com>
…ration calls over ports

A reception whose method is an activity becomes an action def that accepts the
signal and runs the method with the signal's attributes bound to its same-named
parameters; the resolver now finds accept payloads from nested action bodies so
those bindings resolve. A CallOperationAction over the caller's port performs the
operation's usage on the part its connector reaches; over the target's own port
it performs it on the target.

Co-Authored-By: jason.han <hanhuijun@gmail.com>
…it records

Co-Authored-By: jason.han <hanhuijun@gmail.com>
…nd cover deep history

Co-Authored-By: jason.han <hanhuijun@gmail.com>
…ldens under testdata

Co-Authored-By: jason.han <hanhuijun@gmail.com>
…r it names

Co-Authored-By: jason.han <hanhuijun@gmail.com>
…and the report's skipped counts

Co-Authored-By: jason.han <hanhuijun@gmail.com>
…delegated and untyped ports

A via path rooted at a feature bound to another object leaves that object's port; a
part's ports are known to inherited connectors under the name they were declared with
before redefinition; reference usages hold what is bound to them rather than an object
of their own; and an untyped port materializes as a Ports::Port so binding connectors
can join it.

Co-Authored-By: jason.han <hanhuijun@gmail.com>
…nd route arrivals over connectors

A state whose entry or do behavior takes parameters that the incoming signal's
properties match gets a state-owned carrier item the transitions assign and the
behavior binds to; incompatible or unavailable cases are refused with the reason.
Signal arrivals are indexed over the document's connectors and delegations so
triggers accept via each port the signal reaches, calls resolve their receiver
through the activity's context, and activities whose required inputs no flow
values are reported instead of written starved.

Co-Authored-By: jason.han <hanhuijun@gmail.com>
…inputs and via routing through bound references

Co-Authored-By: jason.han <hanhuijun@gmail.com>
…ive, route data through control nodes and wait on values conservatively

Co-Authored-By: jason.han <hanhuijun@gmail.com>
…l nodes and loop-aware value waits

Co-Authored-By: jason.han <hanhuijun@gmail.com>
…itten

A ValueSpecificationAction whose literal is no value of its result's type is a
placeholder, so the pass finding nodes that produce no value now counts it too:
a call requiring that value stands in for itself instead of being written with
an unbound input. A value action's value specification is no longer declared as
an input pin of the placeholder.

Co-Authored-By: jason.han <hanhuijun@gmail.com>
…from

Co-Authored-By: jason.han <hanhuijun@gmail.com>
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

@devin-ai-integration
devin-ai-integration Bot marked this pull request as ready for review September 18, 2026 06:10
devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration Bot and others added 2 commits September 18, 2026 06:21
…pes in scenarios

A call message leaving an in parameter with lower bound 0 and no default
unbound was refused as unbound; requiresValue now decides, as it does for
activities. partPaths tracked visited types globally, so a second sibling
part of a type already walked was never entered and a lifeline standing
for a part below it resolved to the first sibling alone; the type guard
is now per path, so both paths are found and the lifeline is reported as
ambiguous.

Co-Authored-By: jason.han <hanhuijun@gmail.com>
devin-ai-integration[bot]

This comment was marked as resolved.

…ar names and via bindings distinct

A signal send or interaction message that binds no argument to a signal attribute with no default and a lower bound above zero, inherited attributes included, is refused with the attribute named instead of written executably. Parallel fragments reserve their join and operand names through the scenario's used-name set so a source message named like a generated name does not collide. A via path rooted at a name the behavior binds routes through that binding even when the performer owns a feature of the same name, as any expression's lookup does.

Co-Authored-By: jason.han <hanhuijun@gmail.com>
devin-ai-integration[bot]

This comment was marked as resolved.

…nd name only populated regions

carrierMatch and the carrier's attribute list now come from signalAttributes, so a state's entry and do parameters bind attributes the signal inherits from its generals rather than being refused or left unbound. nameMachine names the same populatedRegions the writer emits, so an empty sibling region no longer produces a parallel wrapper name and cross-level transition paths through a state that is never written.

Co-Authored-By: jason.han <hanhuijun@gmail.com>
devin-ai-integration[bot]

This comment was marked as resolved.

…rlier message

A duration constraint between two messages with steps between them was written as
the whole interval before the later step, so the steps between added to it. The
wait is now forked after the earlier step and joined before the later one, and
its bound drawn once there; a constraint whose ends lie in different fragments is
reported instead of written.

Co-Authored-By: jason.han <hanhuijun@gmail.com>
…arameter's type and multiplicity

Co-Authored-By: jason.han <hanhuijun@gmail.com>
devin-ai-integration[bot]

This comment was marked as resolved.

…on's action def

A reception whose method is also the method of an operation of the block now performs that operation's action def, the one form the method is written in, binding the parameters it declares; the hidden activity is no longer referenced. An operation call binds only its in and inout parameters, stated explicitly rather than as everything not out.

Co-Authored-By: jason.han <hanhuijun@gmail.com>
devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration Bot and others added 3 commits September 19, 2026 21:08
… a cycle together

contextOf marked an activity as having no context while it was still being resolved, so a callee reached back through a cycle of calls read the caller as context-free and either settled on its owner or got no parameter, leaving the caller's ports unbound. Activities are now visited as a graph of calls: a cycle settles at once with the union of the ports its members name, and the report says why none is written. The migration reference now describes the context parameter.

Co-Authored-By: jason.han <hanhuijun@gmail.com>
Co-Authored-By: jason.han <hanhuijun@gmail.com>
devin-ai-integration[bot]

This comment was marked as resolved.

@devin-ai-integration
devin-ai-integration Bot added this pull request to stack #455 September 19, 2026 21:38
…r referred state behaviors

A send passing a pin of a type a required signal attribute cannot take is
refused like one passing no pin, through one type test shared with the
emitted argument list, instead of writing a send without the attribute.
A state's entry or do behavior it refers to rather than owns takes part in
carrier discovery and receives the accepted signal's attributes as in
bindings of its action usage beside its context binding; where a required
parameter has no such source the behavior is not run and the state says why.

Co-Authored-By: jason.han <hanhuijun@gmail.com>
devin-ai-integration[bot]

This comment was marked as resolved.

A call message's argument for an operation's inout parameter, a referenced
entry/do behavior's inout parameter and a reception method's inout parameter
were all written as in bindings, so the runtime never wrote the callee's value
back. Bindings now carry the parameter's direction; in parameters are unchanged.

Co-Authored-By: jason.han <hanhuijun@gmail.com>
devin-ai-integration[bot]

This comment was marked as resolved.

…ided duration intervals

An unnamed message argument now takes the next parameter or attribute no named argument claims, so `mixed(tag = 7, 4, 5)` binds 4 to the first unnamed target instead of the argument's own position.

A duration interval lacking a usable bound (no max, a max of `*`, an unwritable bound) is reported unmapped with the missing side: no single wait stands for an interval open on one side. A min beside an expressionless max in a MagicDraw document keeps its fixed wait, as that is the tool's encoding of a constraint written with one value.

Co-Authored-By: jason.han <hanhuijun@gmail.com>
devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration Bot and others added 3 commits September 19, 2026 23:20
…rries no value

calleeOutput returned nothing both for a pin of no call and for a call's pin
that stands for no out parameter, so producesAt took the latter for an
ordinary producer: a consumer whose required input only such a pin fed was
written as the typed call with the input unbound. The callee is now returned
alone in that case, producesAt treats it as no value, and the flow from it
is kept as a comment naming the reason, like one from a parameter the
callee never values.

Co-Authored-By: jason.han <hanhuijun@gmail.com>
…nto feature/v1-state-interaction-fidelity

Co-Authored-By: jason.han <hanhuijun@gmail.com>

# Conflicts:
#	internal/translate/migrate/activity.go
#	internal/translate/migrate/states.go

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note

This report is out of date. Scroll down for Devin Review's latest report on this PR.

Devin Review found 1 new potential issue.

2 flags not posted on this PR by your GitHub settings — view them in Devin Review. (Configure)

Devin Review

Comment on lines +32 to +33
if viaSelf || len(segments) < 2 {
return self, path, nil

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Bound port references misroute messages

A single-segment bound port in viaHolder bypasses its binding and stays rooted at the performer. Messages then fail or use a same-named performer port.

Learn more

A via target can itself be a reference whose value is a port object. Multi-segment paths such as context.command work because viaHolder evaluates context, but a single-segment path such as via command returns early before evaluating command. Routing then asks the performer for a feature named command, even when the behavior parameter holds another object's port.

Example: An action declares in ref port command : CommandPort; and sends via command. The caller binds command to device.command. The send routes from the action performer instead of the bound port, so it either reports an unknown port or uses an unrelated performer port named command.

Recommended fix: Resolve single-segment paths through EvalContext.Lookup when the binding holds a port instance. Preserve the current performer-rooted behavior for ordinary unbound port names and for paths explicitly rooted at this.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed, and deferred to a follow-up change. A single-segment via command whose root is the action's own in ref port parameter bound to another object's port is routed at the performer: with a same-named port there the send uses that port (port "command" is joined to no port that can receive it when it is unconnected), and without one it is an unknown port. This behavior predates this change — develop routes single-segment via targets the same way — and viaHolder here only adds the re-rooting of multi-segment paths (via ctx.p), which is the form the migrator writes; it never emits a single-segment via over a bound reference, so migrated models are not affected. Resolving a bound single-segment root that holds a port object to that port's owner and name, with a conformance fixture, will land separately rather than moving this PR's head.

Base automatically changed from feature/v1-simulation-configs to develop September 20, 2026 02:14
devin-ai-integration[bot]

This comment was marked as resolved.

A `send … via dev.p` whose `dev` the behavior binds to another object leaves that object's port, but the behavior's own `connect dev.p to snk.local` kept its ends as written and was matched against the re-rooted port, so it never carried the send. postVia now routes the behavior's connections under the written path, resolving ends rooted at a binding through the objects it holds, and the port holder's connections under the re-rooted one; both deliver.

Co-Authored-By: jason.han <hanhuijun@gmail.com>
devin-ai-integration[bot]

This comment was marked as resolved.

…ject its via path is re-rooted to

An addressed send over a via path starting at a bound reference,
`send Ping() via ctx.tx to listener`, was refused before the path was
re-rooted: the receiver validation read the written path against the
performer, where `ctx.tx` is no port of its own. postVia now re-roots
first, checks the port on the object holding it and resolves the
receiver among that object's machines; diagnostics keep the written
path. The behavior's own connectors still route under the written
path, the holder's under the re-rooted one.

Co-Authored-By: jason.han <hanhuijun@gmail.com>
devin-ai-integration[bot]

This comment was marked as resolved.

…arameters included

An operation's action def declares the operation's parameters and then the
extra ones its local method adds, but call validation, pin declaration and
argument mapping read only the operation's own parameters, so a required
input the method alone declares got no pin or binding and the written
perform was rejected at run time instead of refused with a reason.

Route unarguedParameter, activity/operation call pin declaration and
interaction call/reply mapping through actionParameters, and write an
operation's parameters from the same list (a method owned elsewhere adds
none, matching the abstract action def written for it).

Co-Authored-By: jason.han <hanhuijun@gmail.com>
devin-ai-integration[bot]

This comment was marked as resolved.

A UML internal transition may omit its target, since it stays in its source. The
transition was refused as lacking an end before its kind was read, dropping its trigger
and effect. It is now written as the self transition like a targeted one; an internal
transition that names another vertex, or leaves a pseudostate, is refused with the reason.

Co-Authored-By: jason.han <hanhuijun@gmail.com>
@HuiJun
HuiJun merged commit 2129801 into develop Sep 20, 2026
12 checks passed
@HuiJun
HuiJun deleted the feature/v1-state-interaction-fidelity branch September 20, 2026 05:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant