Skip to content

feat(migrate): encode deferred triggers in standard SysML v2 under -strict - #632

Merged
HuiJun merged 23 commits into
developfrom
feature/strict-deferred-events
Sep 28, 2026
Merged

HuiJun merged 23 commits into
developfrom
feature/strict-deferred-events

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Sep 27, 2026 •

Copy link
Copy Markdown
Contributor

What and why

Built on #625 (feature/strict-migration, now merged), targeting develop.

Under -strict a UML deferrableTrigger on a SignalEvent used to be dropped as unmapped; by default it is written as the OpenSysML-only defer Sig;, which the OMG pilot's parser rejects (and in a large model such as TMT one rejected line cascades into tens of thousands of parse errors). Strict output now carries the deferral in standard SysML v2:

state Off {
    @MigrationMetadata::DeferredEvent { ref :>> signal : Door; }
    item deferred : Door[*] ordered;
    do action buffer {
        first start then receive;
        action receive accept kept : Door;
        then action keep { assign deferred := SequenceFunctions::including(deferred, receive.kept); }
        then receive;
    }
    exit action flush {
        for kept in deferred { send kept to self; }
    }
}
  • One buffer, accept loop and flush loop per deferred signal; with several, the loops (and the state's own do behavior) are forked inside the one do action; the state's own exit behavior runs first in the exit action, the flush after it.
  • Every synthesized member goes through the SynthesizedName machinery and is chosen clear of the state's own members; a state's nested do/exit behavior keeps its name, with its qualified name (used by view exposes and diagram layouts) now routed through the generated action.
  • Every route the signal reaches the object by gets its own accept loop into the shared buffer, resolved through the same portRoutes a transition trigger uses: the direct accept plus accept … via <port> for each connector-derived port, or only the ports the deferrable trigger names. The flush replays to self, which a port-less trigger accepts (noted).
  • Precedence: a signal accepted by an unguarded (or statically true) transition out of the state itself is not kept (Approximated, note names the transition). A guarded transition, or a transition out of a substate, does not suppress the deferral: the transition takes the occurrence while its guard holds / its substate is active, the loop keeps it otherwise (noted as an approximation of UML's priority). A state left by a completion transition keeps nothing (Unmapped, with a not migrated: defer Sig; comment in the state body: the accept loop would never let the do action complete).
  • A state's own do behavior joins the fork only when it is written as an action; one with no action form (a StateMachine doActivity) leaves the accept loop alone, with the existing comment.
  • Known limitation, documented in the report note and reference: with several deferred signals the flush replays one signal at a time, each in arrival order, so interleaving between signal types is not preserved (UML leaves event-pool order open, so this is a permitted approximation).
  • Both modes now write @MigrationMetadata::DeferredEvent { ref :>> signal : Sig; } (metadata def DeferredEvent { ref signal : Base::Anything[1]; } added to the bundled library; the ref :>> signal : Sig form is what the pilot accepts). Default mode still writes defer Sig;. Non-signal deferrable events stay unmapped as before.
  • Runtime support so the encoding executes: internal/ir/lower treats a state's item usages as state data (like its attributes), and lowers an explicit send … to self as a send to the sending object (the same route as a send with no target).

Specification basis

UML 2.5.1 §14.2.3.9 (deferred events: kept while the state is active, dispatched on exit; a transition on the same event takes precedence). No row of docs/project/spec-compliance.md moves. Documented in docs/reference/sysml-v1-migration.md (deferrable-trigger rows and a new "Deferred signals under -strict" subsection, including that the composite state's do action covers its substates' activation) and docs/guide/11-migrating-from-sysml-v1.md.

How it was verified

  • go build ./... && go vet ./... && gofmt -l . && go test ./... clean; python3 scripts/changelog.py check, scripts/check-doc-links.py, scripts/check-doc-ids.py clean.
  • tests/migrate/behavior_more_test.go: the ovenMachine default and strict tests now assert the metadata, the strict encoding and the absence of defer; new TestStrictDeferredSignalsAreKeptAndReplayed covers two deferred signals in one state with its own do and exit behavior, a composite substate, outgoing-transition precedence, a completion-transition state, report targets through the generated action, and executes the migrated model in the REPL session: signals sent while in the state are kept and delivered after it exits, a transition-triggered signal is not buffered, and the state's own do/exit behavior still runs. New TestStrictDeferredSignalsAreKeptByEveryRoute covers a connector-delivered signal (via inbox), a trigger naming a port (via side) and a guarded matching transition whose guard is initially false, executing that the connector-delivered occurrence is kept, then replayed and taken by the transition once its guard holds. New TestStrictDeferralSurvivesInactiveSubstateTransition executes a composite state deferring a signal that one substate's transition accepts: sent while the other substate is active it is kept and replayed on exit. New TestStrictDeferralWithoutRunnableDoBehavior covers a doActivity that is a StateMachine.
  • Pilot batch validator (validate-sysml-batch, pinned pilot) over the strict output of every tests/migrate/testdata/xmi fixture plus our libraries: oven.sysml (the deferred-trigger fixture) 0 errors; 0 errors mention defer, the buffer, or the flush. The remaining 472 errors in that run are unrelated buckets (unresolved this.x references in opaque behaviors, transition/fork/else parse breaks in empty_behaviors, plant_states, station_points, .kerml libraries not collected).
  • TMT (Open-MBEE/TMT-SysML-Model, TMT.mdzip, 823 deferred triggers), bin/sysml TMT.mdzip -strict -convert sysml, pilot over the output plus our libraries — before (base branch) vs after:
    • parse errors (no viable alternative / mismatched input / missing EOF): 50 → 50, none attributable to defer (the 50 are the decision-else ordering and transition/first/accept/do breaks owned by other sessions).
    • total error: lines: 32,536 → 32,592. The +56 are all additional references to signal/item types the pilot already fails to resolve on the base branch (e.g. GCFrame, PEAS-TCS signals): each such deferral now names the type three more times (metadata, buffer, accept). Not fixed here.
  • Not done: a dedicated internal/exec/runtime conformance fixture for item state data / send … to self; that behavior is covered by the migrate execution test only.

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

Link to Devin session: https://nasa-jpl-demo.devinenterprise.com/sessions/2a00d846dce845f1b62739e756690000
Open in Devin Desktop: https://nasa-jpl-demo.devinenterprise.com/desktop/session/2a00d846dce845f1b62739e756690000?variant=devin
Requested by: @HuiJun

…trict

A state's deferrableTrigger on a SignalEvent is written under -strict as an
item buffer, a do action whose accept loop keeps each occurrence, and an exit
action that sends the kept occurrences to self, merged beside the state's own
do and exit behavior; a signal an outgoing transition accepts is not kept.
Both modes annotate the state with MigrationMetadata::DeferredEvent.

The runtime lowers a state's item usages as state data and an explicit
`send ... to self` as a send to the sending object, so the encoding executes.

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

Copy link
Copy Markdown
Contributor Author

I'll fix CI failures and address comments from users with write access. I'll skip comments containing "(aside)".

  • Disable automatic comment, CI, and merge conflict monitoring

…eature/strict-deferred-events

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

# Conflicts:
#	internal/translate/migrate/states.go
#	tests/migrate/behavior_more_test.go
@devin-ai-integration
devin-ai-integration Bot marked this pull request as ready for review September 27, 2026 02:39
devin-ai-integration[bot]

This comment was marked as resolved.

A state's deferred-signal buffers are initialized once, so without clearing
them a second visit's exit replayed every occurrence the first visit kept.

Co-Authored-By: jason.han <hanhuijun@gmail.com>
Base automatically changed from feature/strict-migration to develop September 27, 2026 03:24
devin-ai-integration Bot and others added 2 commits September 27, 2026 03:26
…rded transitions

Under -strict the accept loop that keeps a deferred signal now runs once per
route the signal reaches the object by (direct and via each port portRoutes
finds), so port-delivered occurrences are buffered too. A guarded transition
out of the state accepting the same signal no longer suppresses the deferral:
the transition takes the occurrence while its guard holds, the loop keeps it
otherwise. Only an unguarded (or statically true) transition still wins.

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 Bot and others added 2 commits September 27, 2026 03:37
…ly when written

The generated do action rendered the state's own do behavior after the fork
lines, so a behavior that is not written as an action (a state machine, an
unwritten reference) left a succession to a missing action.

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.

…tate's transition

Only a transition out of the deferring state itself, under no guard, takes the
signal from the deferral outright; one out of a substate wins only while that
substate is active, so the state keeps the signal the rest of the time. A
deferral dropped for a completion transition is kept as a comment in the
state's body.

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

Copy link
Copy Markdown
Contributor Author

Fixed in 17f0ed7: a deferral dropped for a completion transition is now kept as a /* not migrated: defer Sig; — … */ comment in the state's body (and the bare state Name; shortcut no longer applies when such a comment exists). TestStrictDeferredSignalsAreKeptAndReplayed asserts the comment on Prep, and the no-defer checks now match whole statements so the comment is not mistaken for one.

@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

Re "Interleaved deferred signals replay out of order" (thread PRRT_kwDOTp2CH86mXFkH): confirmed — with one item buffer per signal the flush replays A1, A2, B1 for arrivals A1, B1, A2. This follows from the per-signal-buffer design the task specified ("one buffer per deferred signal, or one buffer typed by the common supertype if the signals share one"); a shared arrival-ordered buffer would need a common supertype the pilot accepts as the accept/send payload type, or an ordering key on every kept occurrence merged at flush time. Leaving this open as a design decision for the maintainer rather than changing the encoding silently.

… time

With several deferred signals in one state the flush keeps arrival order within
each signal but not between signals; UML leaves the event pool's order open, so
the report note and the reference say this is a permitted approximation.

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

Copy link
Copy Markdown
Contributor Author

Resolved as a documented limitation (maintainer decision): UML 2.5.1 §13.3.3.3 makes the dequeuing order of the event pool a semantic variation point, so replaying one signal at a time, each in arrival order, is a permitted approximation. 77be761 states this in the migration report note on every deferred trigger of a state with several deferred signals (asserted in TestStrictDeferredSignalsAreKeptAndReplayed) and in docs/reference/sysml-v1-migration.md.

devin-ai-integration Bot added a commit that referenced this pull request Sep 27, 2026
…ge name

#629 writes an owner-context activity as an action usage (action issue)
rather than a definition (action def Issue); #632's REPL step still named
the definition, so the Cmd was never sent.

Co-Authored-By: jason.han <hanhuijun@gmail.com>
@devin-ai-integration devin-ai-integration Bot mentioned this pull request Sep 27, 2026
3 of 6 tasks
devin-ai-integration Bot and others added 2 commits September 27, 2026 18:24
Co-Authored-By: jason.han <hanhuijun@gmail.com>
…vior's usage name

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

This comment was marked as resolved.

… its route alone

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 2 commits September 27, 2026 18:41
…on takes

Co-Authored-By: jason.han <hanhuijun@gmail.com>
@devin-ai-integration
devin-ai-integration Bot changed the base branch from develop to fix/migrate-library-root-names September 27, 2026 22:22
…into feature/strict-deferred-events

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

# Conflicts:
#	internal/translate/migrate/migrate.go
#	internal/workspace/libs/stdlib.snapshot
#	internal/workspace/libs/stdlib/OpenSysML Libraries/MigrationMetadata.sysml
devin-ai-integration[bot]

This comment was marked as resolved.

…ing names clear of what they refer to

A transition out of a deferring state on a general of the deferred signal
accepts its occurrences, as a v2 accept typed by the general does, so it
takes precedence over the deferral; one on a specialization contests it
only for those occurrences. The members the strict encoding adds no longer
take the name of the deferred signal, its root namespace, a port, or the
library packages it refers to, and SequenceFunctions::including is written
from the root when a member of an enclosing scope shadows the package.

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 2 commits September 27, 2026 22:44
…s not written

A transition into a pseudostate -strict refuses (choice, junction, history)
no longer takes the deferred signal's routes, since the output never
writes it; the deferral keeps its accept loop instead. The note and
reference doc also state that the runtime keeps an occurrence a substate's
transition takes without leaving the deferring state.

Co-Authored-By: jason.han <hanhuijun@gmail.com>
…sing do-behavior accepts

A transition chosen for a message now takes it from the accept of a do
behavior of the state it fires out of and of every state enclosing that,
whether or not the firing leaves those states; a do behavior in a sibling
orthogonal region still shares the occurrence. Under -strict a deferring
state whose completion transitions are all guarded keeps its accept loop
(Approximated, noted) instead of dropping the deferral; an unguarded
completion transition still drops it.

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 2 commits September 27, 2026 23:16
…sed completion transitions

A deferrable trigger's own SignalEvent is reported approximated when an
outgoing transition takes every route, instead of falling to the unwritten-
events sweep; a completion transition -strict does not write no longer drops
the deferral.

Co-Authored-By: jason.han <hanhuijun@gmail.com>
…into feature/strict-deferred-events

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

# Conflicts:
#	internal/workspace/libs/stdlib.snapshot
devin-ai-integration[bot]

This comment was marked as resolved.

A deferred signal yields to, or is dropped for, only a transition the strict
output writes: transitionWritten shares the writer's end and target checks,
so a transition into a final state of another region or a state of another
machine no longer suppresses the accept loops. The note on a deferral names
an internal transition of the state, whose self-transition form flushes the
buffer to self on re-entry and keeps the occurrences again.

Co-Authored-By: jason.han <hanhuijun@gmail.com>
Base automatically changed from fix/migrate-library-root-names to develop September 28, 2026 03:17
devin-ai-integration[bot]

This comment was marked as resolved.

…d a general of it

Under -strict a deferral of a signal the same state also defers a general
of, or defers again by another trigger, gets no accept loop by the routes
the other deferral's loop already accepts by, so the exit action sends
each kept occurrence once.

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

This comment was marked as resolved.

…fold through a chain

Co-Authored-By: jason.han <hanhuijun@gmail.com>
@HuiJun
HuiJun merged commit 10cebfd into develop Sep 28, 2026
19 checks passed
@HuiJun
HuiJun deleted the feature/strict-deferred-events branch September 28, 2026 05:01
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