Module: github.com/Open-MBEE/OpenSysML
Language: Go 1.23+
A SysML v2 and KerML 1.1 implementation delivering the integrated tooling experience systems engineers expect from modern language ecosystems (Python, Rust, Go).
- Language Server (
sysml-lsp) — IDE support with live diagnostics, semantic hover, go-to-definition, intelligent completion, and workspace-wide symbol search - Interactive REPL (
sysml) — Exploratory modeling: define models incrementally, evaluate expressions, instantiate parts, inspect runtime state - Execution Runtime — Instantiate parts, evaluate constraints, execute calc/analysis cases, simulate behavioral models
- Toolchain — Workspace management, dependency resolution, incremental compilation, bundled stdlib, persistent caches
- Performance: sub-millisecond parsing, a single static binary, and no JVM or Eclipse runtime
- Completeness: SysML v2 textual notation support (98 of 98 standard library files parse cleanly: 94 vendored OMG files and 4 OpenSysML extensions)
- Executable models: beyond validation, a runtime that instantiates, evaluates and simulates
- Incremental and lazy: parse immediately and resolve semantics on demand, following the precedent set by gopls and rust-analyzer
- Immutable AST: all semantic state resides in side tables keyed by node or symbol
This architecture is also written as a SysML v2 model of itself in
examples/self-model/, where the stages carry the Go
packages that implement them, the invariants below are requirements the tool evaluates, and
make self-model renders the diagrams.
┌─────────────────────────────────────────────────────────┐
│ Frontends: LSP Server │ Interactive REPL │
├─────────────────────────────────────────────────────────┤
│ Workspace: Multi-file projects, dependency management │
├─────────────────────────────────────────────────────────┤
│ Semantic Engine: Types, resolution, validation │
├─────────────────────────────────────────────────────────┤
│ Execution Runtime: Expressions, instances, behaviors │
├─────────────────────────────────────────────────────────┤
│ Parser/Lexer: Hand-written recursive descent │
├─────────────────────────────────────────────────────────┤
│ AST: Syntax-only, immutable (semantics in side tables) │
└─────────────────────────────────────────────────────────┘
github.com/Open-MBEE/OpenSysML
├── cmd/
│ ├── sysml-lsp/ # LSP server binary
│ ├── sysml-grpc/ # gRPC server binary
│ └── sysml/ # Interactive REPL binary
├── internal/ # One directory per layer; a package imports only the layers below it
│ ├── syntax/
│ │ ├── source/ # Source files, spans, line indexing
│ │ ├── diag/ # Diagnostic and Severity, quick fixes, ConformanceMode
│ │ ├── lexer/ # Hand-written scanner (~200 keywords)
│ │ ├── parser/ # Recursive-descent parser
│ │ ├── ast/ # Syntax tree nodes (immutable); ast/astcodec encodes them
│ │ ├── pack/ # Packed model files
│ │ └── format/ # Notation formatter
│ ├── semantic/
│ │ ├── symbols/ # Symbol tables, scope trees
│ │ ├── resolve/ # Name resolution (lazy, memoized)
│ │ ├── suggest/ # Name suggestions for unresolved references
│ │ ├── semantics/ # Type system, conformance, multiplicity, invocation selection
│ │ ├── identity/ # Element identities and the normative ids
│ │ ├── highlight/ # Semantic token classification
│ │ └── query/ # The API element-query model and its OSLC prefix map
│ ├── ir/
│ │ ├── lower/ # AST → execution IR (ActionGraph/StateGraph)
│ │ ├── queryplan/ # Document-query definitions → immutable query plans
│ │ ├── docplan/ # Document definitions → immutable document plans
│ │ └── view/ # View and viewpoint evaluation
│ ├── check/
│ │ ├── passes/ # Validation passes (syntax → constraints); kit, behavior, document, diagram, identity
│ │ └── edit/ # Workspace edits and the rename conflict check
│ ├── exec/
│ │ ├── runtime/ # Execution engine (eval, instances, builtins)
│ │ ├── solve/ # SMT-backed solving and configuration
│ │ ├── smt/ # SMT-LIB translation
│ │ ├── analysis/ # Analysis questions, engines and the graphs:1/sources forms (analysis/modelform, analysis/enginewire)
│ │ ├── engines/ # External analysis engines
│ │ └── objref/ # Object references
│ ├── translate/
│ │ ├── rdf/ # RDF graphs, Turtle reading and writing, the SysML vocabulary (rdf/ontology)
│ │ ├── export/ # The RDF mapping: ToRDF (tree → graph) and ToSysML (graph → notation)
│ │ ├── xmi/ # XMI element tree indexed by xmi:id; xmi/sysmlv1 reads SysML v1 exports over it
│ │ ├── migrate/ # SysML v1 XMI → SysML v2 notation
│ │ ├── convert/ # Conversion entry point: formats, Convert, Migrate, SyntaxError
│ │ ├── codegen/ # Code generation from models
│ │ └── interop/ # Flexo MMS (interop/flexo) and repository sync (interop/reposync)
│ ├── doc/
│ │ ├── queryexec/ # Query plan execution → typed, ordered row sets
│ │ ├── docir/ # Document plan evaluation → backend-agnostic document tree
│ │ ├── docrender/ # Markdown and HTML document backends
│ │ └── docpdf/ # PDF document backend
│ ├── workspace/
│ │ ├── model/ # Workspace, document management
│ │ ├── libs/ # Standard library bundling & caching; libs/errata declares its errata
│ │ ├── project/ # Project files
│ │ └── envvar/ # Environment variables the binaries read
│ └── frontend/
│ ├── protoconv/ # Runtime values and instance graphs ↔ API protobuf messages
│ ├── grpc/ # gRPC service implementation
│ ├── lsp/ # LSP protocol implementation
│ ├── repl/ # REPL loop implementation
│ ├── stdiorpc/ # JSON-RPC over stdio
│ └── usage/ # Command-line usage and manual pages
├── client/python/ # Python client bindings (opensysml)
├── client/rust/ # Rust client (opensysml) and its conformance runner
├── api/proto/ # Protobuf service definitions
├── tests/ # Black-box suites, benchmarks, shared fixtures (tests/parser, tests/grpc, tests/testdata, …)
├── tools/ # Nested module: referees, censuses and generators never linked by a shipped binary
├── examples/ # Example models and demos
└── docs/ # Documentation
The layering test (tests/hygiene/layering_test.go) checks the directory order against go list -f '{{.Imports}}':
| layer | may import |
|---|---|
syntax |
syntax |
semantic |
syntax |
ir |
syntax, semantic |
check |
syntax, semantic, ir |
exec |
syntax, semantic, ir |
translate |
syntax, semantic, ir, exec |
doc |
syntax, semantic, ir, exec, translate |
workspace |
every layer below it, check included |
frontend |
every layer |
Two edges cross the order by design and stay pinned: translate/export imports syntax/parser, because the graph → notation decoder parses by nature, and exec/runtime receives its expression parser and argument typer from the caller (SetExpressionParser, SetArgumentTyper) rather than importing check. The remaining upward edges the test tolerates are listed in its tolerated table with the package that carries each.
Static Analysis Path:
source → lexer → parser → AST → symbol index → resolve → passes
- SourceFile: Input file (.sysml or .kerml) with byte content
- Notation text:
sourcealso owns the keyword sets (Keywords,IsKeyword,IsKeywordIn,IsIdentifier) and the helpers that read and write notation text without a parse —NameText/QualifiedNameText,StringValue/StringText,CommentBody— so layers that never tokenize (semantics, runtime, export) do not import the lexer - Lexer: Hand-written scanner producing tokens with full position tracking
- Trivia: Comments and whitespace tracked as leading/trailing trivia
- Keywords: ~200 SysML keywords (case-sensitive, taken from
source.Keywords())
- Hand-written recursive descent (chosen over ANTLR4/yacc/JNI bridge)
- Rationale: Zero overhead, full error recovery, sub-ms parses for keystroke-latency feedback
- Entry:
parser.New(source).ParseFile() → *ast.RootNamespace - Always produces tree: ErrorNodes on bad input, parsing never fails
- Grammar source: OMG pilot Xtext grammars (SysML.xtext + KerMLExpressions)
Key architectural rule: AST is syntax-only, immutable after parse
- Node interface:
{Span() source.Span; LeadingTrivia()/TrailingTrivia() []Trivia} - NodeBase: Embedded by all nodes
- No semantic info in AST: All derived data lives in side tables keyed by node/symbol
- Expression AST: Full SysML v2 expression grammar (literals, operators, feature refs, invocations, collections, lambdas)
- Behavioral AST: Action control-flow nodes (InitialNode, FinalNode, ForkNode, JoinNode, MergeNode, DecisionNode, ActionExecutionNode), succession edges with guards
- Symbol:
{Name, Kind, Decl ast.Node, Visibility, Scope, OwnerScope} - Scope:
{Parent(), Node(), Children(), LookupLocal(name), MemberNames()} - Index:
DocumentRoot(name) *Scope— global qualified-name index - Resolver: Lazy name resolution, memoized,
ResolveQualified(scope, *ast.QualifiedName) (*Symbol, bool) - Deduplication: Short+primary names alias same
*Symbol— dedupe by pointer when walking
Runtime's primary substrate. Built via NewModel(*resolve.Resolver). All results memoized in side tables.
model.go:DirectSupertypes(sym)— resolved generalization edges (specializes/subsets/redefines/typing)AllSupertypes(sym)— transitive, cycle-safeConforms(a, b) bool— conformance checkingHasSpecializationCycle(sym) bool
members.go:MembersOf(sym)— local + inherited members with maskingLookupMember(sym, name)— member lookup- Effective feature list per type (substrate for runtime instantiation)
multiplicity.go:MultiplicityOf(sym) (Range, bool)— parse multiplicity boundsRange{Lower, Upper Bound};Bound{Value int64, Infinite bool, Known bool}
eval.go:Eval(n ast.Node) (Value, bool)— constant-folder (seed of runtime)Value{Kind ValueKind, Int, Real, Bool}— int/real/bool/infinity only- Returns
ok=falsefor feature refs, strings, null, invocations, collections - Runtime Tier 3 extends this to full evaluator
invocation.go:SelectCall(scope, e, performs)/SelectInvocation(scope, e, args, performs)— overload selection for a call, memoized per call siteInvocationArgs(e),ChainCallee(e)— the positional arguments and the chain a callx.f(a)applies, as the checker and the runtime both read themArgumentTyper— the seam through which the checker's static argument typing (passes.NewArgumentTyper) is installed withSetArgumentTyper;HasArgumentTyperreports whether one is. Without one,SelectCalltypes arguments by arity and names alone
Pluggable validation tiers:
- PassLevel:
{LevelSyntax, LevelNameResolution, LevelType, LevelConstraint} - Pass:
{Level() PassLevel; Run(ctx, name, root) []diag.Diagnostic} - Context: Exposes
Resolver()+Model()(both lazy, memoized) andDownstreamOfFailure(ref)— did a lower tier report a blocking diagnostic inside this reference? - DefaultRegistry: SyntaxPass, NameResolutionPass, TypeCheckPass, ConstraintPass
- Packages: the framework —
Pass,PassLevel,Context,Options,Gathersand the symbol and member walkers — isinternal/check/passes/kit, a leaf the root re-exports as type aliases. The checks are grouped by domain:passes/behavior(state transitions, succession endpoints, control nodes),passes/document(document plans and queries),passes/diagram(layout, view renderings),passes/identity(identity metadata, with the workspace-wide identity gather), and the root for the rest — the registry, the static expression typer and the checks that call into it, the constraint checker and the structural rules. The root is the one place a check is registered, so it imports every domain package and no domain package imports the root;tests/hygienepins both directions - Tiered execution: a document-scoped pass at a higher tier is skipped once a lower tier errors; a pass marked
ElementScopedruns and gates itself per subject throughContext.DownstreamOfFailure(element-scoped tier gating) - Diagnostics:
DiagnosticandSeveritylive ininternal/syntax/diag, a leaf package besidesource, so the runtime and the parser report findings in the same type without importing the validation suite; the strict/default conformance switch isdiag.ConformanceModein the same package - Quick fixes: A
Diagnosticcarries thediag.Fixvalues the layer reporting it attached, so an editor offers edits without parsing messages - Argument typing:
NewArgumentTyperis the checker's expression typing as asemantics.ArgumentTyper;NewTypedModel(resolver)is a semantic model with it installed, which is what every path that builds aruntime.Model(REPL, LSP workspace, gRPC cache, the analysis drivers) constructs. The runtime never installs it itself: a call selected on a model without one fails withruntime.ErrNoArgumentTyperrather than selecting on weaker typing than validation used, andtests/hygienechecks the production construction sites
- Semantic tokens:
Tokens(content, root, scope, SegmentResolver)— keywords, comments and literals from the lexer; declared names from the symbol table; reference segments from the resolver - Ordered and disjoint: the result is sorted by offset with overlaps dropped, semantics winning, so a consumer encodes it directly
- Vocabulary: LSP token types and modifiers (
Classes(),Modifiers()give legend order)
- Single source of truth: Owns document set + global index + diagnostic cache
- reverse reference index
- Document:
{source, AST, scope, version} - One Workspace per session (LSP/REPL)
- Reverse reference index (
refindex.go): every name segment written in a workspace document, keyed by the element it denotes (symbols.KeyOf, i.e. declaring document + declaration span — stable across reindexing, unlike a*Symbol). Each segment is stored under two identities: the element it reaches (after invocation overload selection; a tied call reaches nothing) and the name it writes (an alias, where one was written), with theresolve.Referenceit is a segment of. Find References matches either; Rename edits only the written name, andRenameConflictchecks each occurrence for capture throughedit.CheckRename— a trial reading of the reference with that segment respelled (Resolver.ProbeReading, which keeps what each segment reached even where the whole name then fails), so a chain member is read in its operand's type, a redefinition target among the generals, and a qualifier respelled onto an element lacking the rest of the name is still seen — the check the batch edit API shares. Built lazily on the first query after a change, over all documents with one shared resolver and semantic model, under the workspace's write lock; never built on thedidChangepath. Any mutation (reindexLocked,removeLocked, a conformance-mode switch) drops the whole index, because an edit to one document can change what a name in another resolves to (a shadowing declaration, an import target, an alias, an overload that ties a call). Library documents are never enumerated; only workspace documents are.
- Source of truth: the 98 library files under
internal/workspace/libs/stdlib/, embedded in the binary;OPENSYSML_LIBRARY_PATHsubstitutes a directory of files for them. - Shared base:
libs.SharedBase()builds one frozensymbols.Indexof the library per process; every model is an overlay over it (NewOverlay), reading the library without copying it. - Snapshot:
internal/workspace/libs/stdlib.snapshotis that frozen index — syntax trees, scopes, symbols, wildcard-import expansion, facts — serialized at generation time (go generate ./internal/workspace/libs,make stdlib-snapshot) and embedded. A process decodes it instead of parsing, when its recorded digest of the library files and its format version match the files in hand and its CRC-32C over the stream holds; otherwise (an edited file, a library-path override, a stale or damaged blob) it parses the files as before. The snapshot is a derived artifact: never edit it, regenerate it, andTestEmbeddedSnapshotIsCurrentplusmake stdlib-snapshot-checkin CI fail when it lags the files. - Encoding:
internal/syntax/pack(varint scalars over a string table) andinternal/syntax/ast/astcodec(a node table, every node type, index references in place of pointers);symbols.WriteSnapshot/ReadSnapshotnumber scopes and symbols the same way. No reflection orencoding/gob. Decoding reproduces the object graph a fresh load builds, sharing and all, whichTestSnapshotIndexMatchesFreshLoadchecks structurally. - Facts cache:
$XDG_CACHE_HOME/sysml-ls/libsstill holds derived facts for library sets the snapshot does not cover, keyed by content digest and build.
- Entry point:
internal/translate/convertnames the formats (ParseFormat,FormatOfPath) and drives every conversionsysml -convert,%save,%printand the service'sConvertmake:Convert/ConvertTolerantparse notation and report aSyntaxError,Migrateruns the SysML v1 migration and writes its notation,SysMLToRDFparses and encodes.cmd/sysml,repl,grpcandinterop/flexocall it; nothing below it imports it. - The mapping:
internal/translate/exporttranslates between a parsed tree and a graph —ToRDFandToSysML— and never migrates; it parses only where the decoder needs the grammar (to check preserved source text still encodes to the graph, to judge an expression's binding, and to read names).internal/translate/migratereads SysML v1 XMI and writes SysML v2 notation, and knows nothing of RDF. A hygiene test pinsexportfree ofmigrateand the entry point as the only package besides the CLI (which prints the migration report) that imports it. The result sidecar a migration writes (-migration-results) has its schema ininternal/translate/simresults, which the migrator fills and the comparison (repl,-compare-results) reads without the migrator.
Package: internal/exec/runtime
Not a Pass: Execution is stateful/iterative/value-producing (different shape than diagnostic-emitting pass)
Harden MembersOf into stable, ordered effective-feature list per type:
- Own + inherited − redefined/masked
- Each entry: type + multiplicity + default-value expression
- Schema for instance materialization
- Value: Extends
semantics.Value→null, strings, instance references, collections (sequences/sets) - Instance: Typed object with one feature value per effective feature (Tier 1)
- Instantiation: Materialize instance graph from
part/itemusage- Recursively instantiate composite features
- Multiplicity governs feature value cardinality
- Lazy feature value materialization
Full evaluator with user-defined calc invocation, constraint evaluation, and requirement evaluation:
- Feature access
x.y.zresolved against instance feature values - KerML operator library (
->select,->collect,size, string ops) - Calc invocation: Resolve calc symbol → extract params/return → bind args to parameters → evaluate return expression. Overloads are selected through
semantics.Model.SelectCallwith the argument typing the model carries (ErrNoArgumentTyperwhen it carries none) - Function values (
function_value.go,ValFunction): a calc definition, a calc usage with an unsupplied input or anin calcparameter read as a value is the calc's loweredcalcShapeplus the environment it was read in — declaring scope, the object it was read off, and, for a calc declared inside a behavior body, the frames through the innermost active run of that behavior (EvalContext.enclosingRun, byframe.runs) — never a caller's frames, and none when no such run is active. Invoking one (f(a)through a calc-typed parameter, orSampledFunctions::Sampleapplying itscalculation) takes the calc invocation path (invokeCalcShapeIn), never a closure over statements;ValExprremains the distinct kind for an expression body a collection operation evaluates per element - Constraint evaluation: Extract
assert/assumemembers → evaluate boolean expressions → check satisfaction (with optionalnotnegation) - Requirement evaluation: Extract
subject/assume/require/actormembers → validate bindings → evaluate conditions - Scoped evaluation:
EvalContext.scopefor name resolution, frame stack for parameter bindings - Membership unwrapping: Runtime automatically unwraps AST Membership nodes when extracting members
- Compiled calc tier (
compile.go,compiled_ops.go,compiled_stmts.go): a calc whose body is scalar — Integer/Real/Boolean literals, its effectiveinparameters (flattened through the specialization chain and redefinition exactly ascalcShapelays them out forbindCalcParameters), the arithmetic, comparison, equality, identity, logical and conditional operators, body-local scalar declarations,returnandif/elsestatements, invocations of other such calcs (cycles included, positional or by name), and the standard library's scalar functions and constants (sqrt,ln,sin,TrigFunctions::pi, … — dispatched through the resolved symbol to the same Go implementation the evaluator calls, never by bare name, so a model's ownsqrtis an ordinary calc) — is compiled on its first invocation into a tree of Go closures over an unboxed scalar frame, held in a side table on theContext'scalcShape(the AST is untouched, and a newContextcompiles afresh). Statements are compiled from the loweredcalcShape.Steps: each declaration takes a fresh frame slot at compile time, masking an earlier binding of its name for the rest of its block, so shadowing and order behave asstmtEnginedoes, and a name read before its declaration is declined rather than guessed. It reproduces the evaluator's values, errors and per-node step charges exactly; the differential test (compile_differential_test.go) checks that over every calc in the fixture and example trees, andcompile_constructs_test.goover focused fixtures intestdata/compiled/. Anything outside the subset — calc usages,outfeatures, feature chains,self, collections and the library functions over them, quantities, strings, loops and assignments, a local without a value, a body that may run off its end, non-literal defaults — keeps the calc, and every calc calling it, on the evaluator.- Fallback rule: a traced
Context(ctx.trace != nil), a non-scalar argument, an unbound parameter without a default, a receiver object where the body reads a library constant, andOPENSYSML_CALC_COMPILE=0run the whole invocation on the reference evaluator; the tier never falls back for a sub-expression. Argument checking (calcShape.checkArgs: arity and unknown names; the evaluator's refusal of a receiver beside named arguments) precedes the dispatch, so both tiers report those identically.
- Fallback rule: a traced
- Unlocks: Constraint checking against concrete values,
calcexecution, requirement validation, runtime behavioral verification
Parse + model all behavioral bodies with unified fallback grammar:
- Calc bodies —
returnexpressions + mixed parameter declarations (✅ fully executable) - Constraint bodies —
assert/assumewith optionalnotnegation (✅ fully executable) - Requirement bodies —
subject/assume/require/actordeclarations (✅ fully executable) - Action bodies — Control flow nodes (initial/final/fork/join/merge/decision) + action execution nodes + succession edges (✅ parsed, executor infrastructure complete)
- State bodies — Entry/do/exit behaviors, substates, transitions with triggers/guards/effects (✅ parsed, executor infrastructure complete)
- Unified Grammar: Body parsers use graceful fallback to general member grammar (no terminal keyword whitelists)
- Status: All parsers complete. Calc/constraint/requirement fully executable. Action/state executors complete with control flow keywords, nested invocation, send statement.
Package: internal/exec/runtime
Status: Complete. Conformance gate: every case passing (calc/constraint/requirement/satisfy/action/state all functional); count in the measured counts.
Spec Alignment: The governing reference is the SysML v2 metamodel or the bundled KerML semantic library (internal/workspace/libs/stdlib/); UML 2.5.1 is a fallback only where the SysML v2 notation has no production for a concept and the KerML library no performance for it (state-body fork/join, history, regions). The runtime is not a UML or fUML activity engine: "token" names the executor's bookkeeping for where each performance is along the successions of the lowered graph, an implementation device, not the semantic model. What the tokens realize is succession order: a succession is a KerML HappensBefore link (Occurrences.kerml), which orders occurrences in time and carries no values — a SuccessionFlow is the form that carries a payload (KerML.kerml: Succession specializes Connector, SuccessionFlow specializes Succession, Flow). State machine execution is Occurrences::Occurrence::isRunToCompletion over its runToCompletionScope ("determines whether transition performances might happen during state entry performances within the run to completion scope"), with event dispatch isDispatch / dispatchScope. See SPEC_COMPLIANCE.md for the detailed compliance mapping, and the pilot differential for what is checked against the reference implementation.
Architecture:
-
ActionExecutor — succession-ordered action execution, scheduled as a token queue over the lowered
ActionGraph- Token-based control flow (initial → action → final, first/done keywords)
fork/joinfor parallelism,decide/mergefor branching (Actions::ForkAction,JoinAction,DecisionAction,MergeAction)- Nested action invocation with attribute initialization
- Send statement for message passing
- ObjectFlow for pin-to-pin data routing
- Deadlock detection via progress tracking
- Golden trace recording with deterministic token ordering
- APIs:
Step(),RunToCompletion(),Tokens(),SetBreakpoint(),SetTrace() - Breakpoints and stepping observe action nodes, so a calc an action invokes is one step to them; only a
TraceRecorderobserves sub-expressions, and it keeps the calc on the evaluator (Tier 3's fallback rule)
-
StateExecutor — Event-driven state machine execution
- Initial/final state keywords (initial/final)
- Entry/exit/do behaviors (
doruns while its state is active, one action per round, interleaved with the do behaviors of the states active alongside it) - TimeEvent waits registered on the
Context'sClock, which every executor of the context shares (clock.go,advance.go:Context.Advanceruns all due work instant by instant; executors due together are a scheduler choice) - ChangeEvent condition polling
- Guard evaluation for transitions
- Transition effect actions
- Hierarchical states with LCA-based entry/exit propagation
- Orthogonal regions with multi-region event broadcasting; the order sibling regions react in is a scheduler choice, drawn per firing among the regions still active (
dispatchInOrder), for change triggers as for queued events - Choice + Junction pseudostates
- Golden trace recording for transitions/entry/exit
- APIs:
ProcessNextEvent(),CurrentState(),EventQueue(),StateData(),SetTrace() - Deferred events: an event no active transition handles is retained while a state deferring it is active, and delivered afterwards in arrival order
- CallEvent matches the operation named by the trigger (
signal.go,state_executor.go;signal_test.go:TestCallEventMatchesOperationName)
-
Scheduler and choice points — one resolution rule for what the library leaves unordered (design note)
- Six
ChoiceKinds (choice.go): token order within a step, decision branch, same-step write order, transition, region order, due order; each site resolves through the run'sschedulerand then records aChoicePoint, an informationalRunNote(diagnostic codechoice-point) that never alters the run - Every decision guard is evaluated so a second holding one is seen; a later guard that cannot be evaluated is an
UnevaluableGuardnote (guard-unevaluable), not a failure SchedulePolicy(scheduler.go):reverse(default and zero value — exactly what every run did before policies existed),declared,seed:<n>(a PCG generator the run consumes, replayed by the seed),replay:<file>(a witness's choice lines followed move for move, thenreverse's picks one token a step; a move the run cannot make is a typedReplayError,replay.go),explore[:runs=N,depth=D]Explore(explore.go) replays whole runs from a freshContexteach, a recorded choice prefix then an untried alternative, the first run's choices each varied once, earliest first, before any twice (explore_queue.go), withinExploreBudget(default 1024 runs, 64 choice points); reports distinct outcomes byOutcome.identitywith linearization counts and a witness, andincompletewhen a bound stopped it- The scheduler lives in the run's
runStatebeside the budget and notes; a run driven call by call (beginExecutorRun) keeps its own across interleaved runs, and a probe (beginProbe) restores the scheduler's position and notes nothing
- Six
-
Context Integration — Public runtime APIs
InvokeCalc(symbol, args)— Invoke calculation with arguments, return resultEvaluateConstraint(symbol)— Evaluate constraint, return satisfaction boolean (assert/assume)EvaluateRequirement(symbol)— Evaluate requirement, return satisfaction boolean (require/subject/actor/assume/nested)ExecuteAction(symbol)— Run action to completion, return resultsExecuteState(symbol)— Run state machine until final/suspendedCreateActionExecutor(symbol)— Create executor for debuggingCreateStateExecutor(symbol)— Create executor for debuggingSetSchedule(policy),Schedule()— the policy runs started from now on resolve their choice points under (exploreis refused:Exploredrives it)Notes(),Choices(),UnevaluableGuards()— what the last run recorded
-
Notation text the run reads — the runtime imports no parser. The two places a run receives notation as text — a witness file's
input <feature> = <value>lines (replay.go) and the unit a tool answers a value in (tool.go,Context.UnitOf) — are read through theruntime.ExpressionParserthe frontend installs on theModel(Model.SetExpressionParser, normallyparser.ParseOneExpression). Reaching either with none installed is the typedErrNoExpressionParser, never a refused witness or a tool's malformed output;tests/hygienechecks that every shipped construction site installs it and thatinternal/exec/runtimedoes not depend oninternal/syntax/parser.
Implementation:
context.go(460 lines) — Public Execute/Invoke/Evaluate APIs, step budget enforcementaction_executor.go(729 lines) — Token-flow engine with nested actions, send statementstate_executor.go(1149 lines) — Event-driven state machine with do behaviorsexecutor_common.go— Token, Event, EventQueue, ExecutionStatescheduler.go,choice.go,action_choice.go,explore.go— scheduling policies, choice-point notes, bounded explorationtrace.go(154 lines) — Deterministic execution trace recordereval.go— Expression evaluation (binary/unary operators, literals, feature references, qualified names, type coercion)- Lowering to execution IR lives in
internal/ir/lower/(ToActionGraph,ToStateGraph)
Testing:
- Golden ASTs:
tests/parser/testdata/parse/— count in the measured counts - Negative tests:
tests/parser/negative_test.goand the*Negative*tests beside the parser — count in the measured counts - Unit tests:
action_executor_test.go,state_executor_test.go(action, state) - Conformance gate:
.sysml+.expected.jsonpairs, all passing -conformance_test.go— counts and per-category breakdown in the measured counts; a case whose model admits several results lists them asoutcomes, each cited to the semantic oracle, and is explored to prove every one reachable and nothing else;TestExecutionConformanceUnderPoliciesre-runs the suite underdeclaredandseed:1 - Golden traces:
.trace.goldenfiles -trace_test.go— count in the measured counts;.trace.orderfiles state the partial order a trace must respect (a < b), and a case withoutcomesowns a<case>.<policy>.trace.goldenper sweep policy - Exploration:
explore_test.go— every linearization reached once, determinism, each budget's incompleteness, an error as an outcome, transition, region and due order - Robustness: failure-mode cases (deadlock, unbound params, missing features, dangling transitions, sourceless accept, step budget, pseudostate dead ends and cycles, history and defer misuse, send/accept misrouting, calc arity/recursion,
performreference failures) -robustness_test.goand the per-featurerobustness_*_test.go - Coverage: All behavioral types fully functional. Action: 14/14 features ✅. State: 13/13 features ✅. Calc: 8/8 ✅. Constraint: 5/5 ✅. Requirement: 5/5 ✅. Evaluation: 7/7 ✅.
Measured Compliance: See SPEC_COMPLIANCE.md for semantic rule → implementation → test case mapping with status (✅ faithful /
- Analysis case: subject → calc chain → result values
- Verification case: evaluate requirements → pass/fail
- Entry points: REPL/LSP commands (
%run,%verify) - Design: the analysis framework — registered engines behind one contract, one scale for the strength of an answer, parallel isolated runs; and bring your own engine — how a user's engine, strategy or tool registers and what its answers are worth
- Landed:
internal/exec/analysis— the contract (Question,Engine,Result,Claim,Strength,Budget), a per-ownerRegistrywithautodispatch, and therun,explore,sweepandsolveengines as adapters over the runtime andinternal/exec/solve; the REPL session and the gRPC service ask every check, run, exploration, sweep and solver question through it with no change to what they print or return
Package: internal/frontend/lsp
Binary: cmd/sysml-lsp
Status: ✅ Complete (stdio protocol, 10 LSP features, tested end to end)
Lifecycle:
initialize— Advertise server capabilities, record the session's foldersinitialized— Scan those folders and index every.sysml/.kermlfile they hold, so cross-file names resolve without the editor opening each fileshutdown/exit— Graceful termination
Document Synchronization:
textDocument/didOpen— Track opened documents (the buffer becomes authoritative)textDocument/didChange— Incremental updates (UTF-8 byte offsets)textDocument/didClose— Revert to the file's on-disk content; the document stays indexed, since other documents resolve names through it, but its markers are withdrawn — only open documents carry diagnosticstextDocument/didSave— Refresh diagnostics for every open documentworkspace/didChangeWatchedFiles— Reindex files created, edited or deleted outside the editor; a deletion leaves an open buffer aloneworkspace/didChangeWorkspaceFolders— Walk a folder added mid-session and unindex what a removed one contributed, open buffers aside
Diagnostics:
- Publish on document open/change; the edited document immediately, the other open ones on a coalesced sweep once the edit burst settles, since each sweep re-analyzes them
- Withdrawn (empty set) for a document the workspace no longer holds
- Syntax errors (parser)
- Semantic errors (name resolution, type checking, validation passes)
- Real-time feedback
Hover (textDocument/hover):
- Symbol info: name, kind, type, multiplicity
- Definition source location
- Documentation comments (future)
Go-to-Definition (textDocument/definition):
- Navigate to symbol declaration
- Follows qualified name chains
- Cross-document navigation
Find References (textDocument/references):
- Find all usages of symbol, in every workspace document, at whichever segment of a qualified name denotes it
- Include declaration option, reported in the declaring document
- Answered from the workspace's reverse reference index (a lookup, not a scan); Rename reads the same index
Rename (textDocument/prepareRename, textDocument/rename):
- Rewrites the name under the cursor — long or
<short>— at its declaration and wherever a reference in any workspace document writes it - Refused with an error naming the element the new name would mean when that
name is already taken where the element is declared, or when a rewritten
reference would afterwards read another element (
edit.CheckRename)
Completion (textDocument/completion):
- Trigger characters:
:,. - Symbol-based suggestions
- Future: keyword completion, snippet support
Semantic Tokens (textDocument/semanticTokens/full, /range):
- Legend advertised at
initialize; tokens classified byinternal/semantic/highlight - Keywords, comments and literals from the token stream; names from the symbol table and the resolver, with declaration/definition/readonly/abstract modifiers
- Encoded relative to the previous token, split per line; no delta support
Code Actions (textDocument/codeAction):
- Quick fixes only, from the
diag.Fixvalues parser and resolver diagnostics carry — spelling of an unresolved name, importing the namespace declaring it, inserting a semicolon the parser located exactly
Document Symbols (textDocument/documentSymbol):
- Outline view (packages, parts, attributes, actions, states)
- Hierarchical structure
- Navigate within file
Workspace Symbols (workspace/symbol):
- Global symbol search
- Fuzzy matching
- Aggregates across every indexed document, opened or not
Architecture:
server.go— Server lifecycle, stdio transportbase.go— Stub handlers for unimplemented LSP methodshandler.go— Custom didChange with pointer-valued Range (full vs incremental edits)sync.go— Document synchronization (didOpen/didChange/didClose/didSave)files.go— Folder scan and watched-file events (the on-disk half of the workspace)lifecycle.go— Initialize capabilities advertisementdiagnostics.go— Error publishinghover.go,completion.go,definition.go,references.go,symbols.go— Feature implementationsposmap.go— UTF-8 offset ↔ LSP line/character conversionsemantictokens.go,codeaction.go— Semantic tokens and quick fixeswalk.go— Reference lookup overresolve.References
Testing:
- Tests covering every feature — count in the measured counts
- Integration tests with mock clients
- Incremental sync edge cases (astral plane characters, multi-change, offset-zero insertion)
Usage:
go build -o sysml-lsp ./cmd/sysml-lsp
./sysml-lsp # stdio mode for editorsEditor Setup:
- VS Code: Generic LSP Client extension + workspace settings
- Neovim: nvim-lspconfig custom server
- Emacs: lsp-mode manual server registration
See the guide for VS Code configuration.
Package: internal/frontend/repl
Binary: cmd/sysml
Document management:
%help— Show help%list— List current session declarations%clear— Reset session%load <file>— Load .sysml file
Runtime execution:
%instantiate <name>— Create instance from part def%eval <expr>— Evaluate expression (feature refs + literals)%features <object> [all|depth <n>] [json]— Show an object's features and their values, bounded unless asked for whole or to a depth;jsonwrites the graph in the API'sInstantiateResponseshape%instances— List all created instances
Behavioral execution:
%calc <name> [args...]— Invoke calculation with literal arguments (e.g.,%calc add 10 20)%constraint <name>— Evaluate constraint, check assert/assume satisfaction%requirement <name>— Evaluate requirement, validate subject/require/actor conditions%satisfy [name]— Evaluate satisfaction assertions, with the requirement's subject bound to the objectbynames%schedule [policy]— Show or set the policy the next run resolves its choice points under (reverse,declared,seed:<n>,replay:<file>);exploreis refused, since a debugging session steps one run
Action debugging:
%action <name> [<object>]— Start debugging action execution, optionally performed by an instantiated object%step— Advance all tokens one step%continue— Run action to completion, or to the first breakpoint hit%tokens— Show active tokens with location + data%break <nodeName>— Set breakpoint on node;%continuestops when a token reaches it
State machine debugging:
%state <name> [<object>]— Start debugging state machine, optionally performed by an instantiated object%events— Show event queue length%current— Show current state, stack, stateData, time%advance <time>— Advance simulation time by<time>units, processing every event due%stop— Stop debugging session
- Session: Manages document + runtime context + instances + debugging sessions
- getOrCreateRuntime(): Lazy init, builds index from current document
- Runtime commands wire to:
runtime.Context.Instantiate(),runtime.Context.Eval(),runtime.Context.InvokeCalc()runtime.Context.EvaluateConstraint(),runtime.Context.EvaluateRequirement()runtime.Context.ExecuteAction(),runtime.Context.ExecuteState()runtime.Context.CreateActionExecutor(),runtime.Context.CreateStateExecutor()
- Argument parsing:
%calcparses literal args via wrapper parsing (part { attribute arg = <expr>; }) + Membership unwrapping - Debugging sessions: Session tracks active ActionExecutor/StateExecutor for step-by-step control
- Protobuf output:
%features … jsonserializes the instance graph throughinternal/frontend/protoconv(InstanceGraphToProtoWithin,GraphBounds), the same conversion the gRPC service uses forInstantiate; the REPL, and sosysml, do not link the service or its transports
- Goroutines: Concurrent reindex/query handling
- Single binary: Cross-platform, no JVM/runtime dependencies
- LSP track record: gopls demonstrates Go's suitability for language servers
- Performance: Fast compilation, efficient memory model
Alternatives rejected: ANTLR4-Go, goyacc, JNI/gRPC bridge to pilot
Rationale:
- Zero runtime overhead
- Full control over error recovery
- Sub-millisecond parses (keystroke-latency diagnostics)
- Trade-off accepted: Manual grammar translation from Xtext
Precedents: gopls, rust-analyzer
- Parse immediately (syntax errors visible instantly)
- Defer name resolution / type checking until requested
- Memoize all semantic queries
- Result: Interactive performance even on large workspaces
| Component | Status |
|---|---|
| Lexer/Parser (structural + behavioral) | ✅ Operational (98/98 stdlib clean - see conformance gate) |
| Symbol resolution & type system | ✅ Complete |
| Validation passes (syntax → constraints) | ✅ Complete |
| Expression evaluator & instance model (Tiers 1-3) | ✅ Complete |
| Workspace/reindex/file watching | ✅ Complete |
| Behavioral parser (all behavioral bodies) | ✅ Complete |
| Calc invocation & constraint evaluation | ✅ Complete |
| Action execution engine (Tier 5) | ✅ Complete |
| State machine runtime (Tier 5) | ✅ Complete |
| REPL debugging commands | ✅ Complete |
| REPL implementation | ✅ Complete |
| Standard library bundling | ✅ Complete |
| LSP server implementation | ✅ Complete |
Parser coverage: 98/98 bundled library files parse cleanly — the 94 official SysML v2 standard library files and four non-normative OpenSysML extensions: OpenSysML Libraries/OpenSysMLMathFunctions.kerml, OpenSysML Libraries/DocumentQueries.sysml, OpenSysML Libraries/IdentityMetadata.sysml and OpenSysML Libraries/OOSEM.sysml. Conformance verified by stdlib_conformance_test.go. Grammar reference available at OMG Xtext grammar.
New grammar features require a four-layer test contract to ensure correctness and prevent regressions:
- Purpose: Ensure stdlib continues to parse cleanly
- Location:
internal/workspace/libs/stdlib_conformance_test.go - Test:
TestStdlibConformanceloads all 96 bundled library files - Acceptance: 98/98 files parse without errors
- Allowlist:
testdata/stdlib_known_failures.txt(currently empty) - Failure mode: Regression breaks previously-working stdlib files
Usage:
go test -v -run TestStdlibConformance ./internal/workspace/libs- Purpose: Verify AST structure matches expected output
- Location:
tests/parser/golden_test.go - Fixtures:
testdata/parse/*.sysmland*.kerml(one representative file per construct) - Goldens:
testdata/parse/*.golden(AST dumps) - Acceptance: Parse output matches golden file
- Update flag:
go test -run TestGolden -update(regenerate goldens after intentional changes)
Coverage:
- Package/namespace declarations
- Part/attribute definitions and usages
- Connections and relationships
- Requirements and constraints
- State machines
- Calculations
- Enumerations
- Imports and aliases
- Metadata annotations
- Status: Explicitly deferred (no faithful SysML printer exists)
- Rationale:
ast.Dump()is debug-only, not spec-compliant - Future work: If SysML printer added, verify
parse(print(parse(input))) == parse(input)
- Purpose: Verify parser rejects malformed input gracefully
- Location:
tests/parser/negative_test.go - Test:
TestNegative, one subtest per malformed input - Acceptance: each case produces diagnostics rather than panicking
- Coverage: Unclosed blocks, unexpected tokens, invalid syntax, incomplete behavioral members
Example:
{
name: "unclosed_package",
input: "package Foo {",
wantError: true,
},New behavioral features (actions, states, calc, constraints, requirements) require a four-layer test contract to ensure execution correctness:
- Purpose: Lock in parse structure before execution changes
- Location:
tests/parser/testdata/parse/(behavioral fixtures) - Coverage: the behavioral fixtures (action, calc, constraint, requirement, state) among the whole set
- Acceptance:
TestGoldenpasses, AST dumps match expectations - Update flag:
go test -run TestGolden -update
Behavioral fixtures:
action_control_flow.sysml,action_if_branch_body.sysml,action_mixed_params.sysml,action_send_port.sysmlstate.sysml,state_full.sysml,state_transition_variants.sysml,state_call_trigger.sysml,state_def_region_pseudostate.sysml,state_defer.sysml,state_fork_join.sysml,state_history.sysml,state_timed_triggers.sysmlcalc.sysml,calc_defaults_and_invocation.sysml,calc_return.sysml,calc_return_parameter.sysmlconstraint_assert_assume.sysmlrequirement.sysml,requirement_members.sysml
- Purpose: Verify behavioral execution produces expected outcomes
- Location:
internal/exec/runtime/conformance_test.go - Test:
TestExecutionConformanceruns.sysml+.expected.jsonpairs;TestExecutionConformanceUnderPoliciesruns them again underdeclaredandseed:1 - Schema:
internal/exec/runtime/testdata/conformance/README.md(outcome format for each behavioral type;outcomeswith anadmissiblecitation when the model admits several) - Allowlist:
known_failures.txt(currently empty — all cases pass) - Acceptance: Expected outputs/satisfaction match actual execution results under every policy; a case listing
outcomesis explored, and every listed outcome must be reached and no other (design note)
Coverage (by fixture prefix, all passing; counts in the measured counts):
- Calc: parameter binding, return values, defaults, inherited parameters, unary operators, type coercion, qualified names, body-local usages, statement bodies, nested and from-constraint invocation
- Action: token flow, outputs, nested invocation, send/accept, port communication,
performreference and shorthand, accept...then, flows, loops and decisions - State: simple, do behavior, concurrent do, transition effect, choice/junction/fork-join pseudostates, orthogonal regions and region pseudostates, shallow/deep history, deferred/undeferred events, call and timed triggers, signal discrimination/unmatched, self signal
- Requirement: require/subject/actor/assume satisfaction, nested
- Instance: derived feature values, constraint binding, inherited constraints, nested usage bodies
- Unit and quantity evaluation
- Constraint: assert/assume satisfaction, negation
- Satisfy assertions, variations, redefinitions, variants, feature chains, string operations, nested behaviors, element filters, the ball-and-chain model, and one each of attribute, connector, cubesat and view
Usage:
go test -v -run TestExecutionConformance ./internal/exec/runtime- Purpose: verify how execution proceeds (ordering, scheduling), not only the final result
- Location:
internal/exec/runtime/trace_test.go - Test:
TestExecutionTracecompares executor traces against.trace.golden, and against the partial order a.trace.orderstates (a < bper line) - Determinism: Token sorting by ID, fixed event queue tie-breaking; each
choicethe run made is a trace line, so a golden pins one linearization and a case withoutcomesowns one golden per sweep policy (<case>.<policy>.trace.golden) - Acceptance: Trace output matches golden file
- Update flag:
go test -run TestExecutionTrace -update-traces - Coverage:
.trace.goldenfiles for action, calc, state, constraint, accept and string execution
Trace format:
- Action:
step N: token T1@node1, token T2@node2(sorted) - State:
entry: StateName [hasEntryAction],transition: From -> To [event],exit: StateName [hasExitAction]
- Purpose: Verify malformed/pathological behaviors fail gracefully (typed errors, no panics/hangs)
- Location:
internal/exec/runtime/robustness_test.goand onerobustness_<feature>_test.goper feature - Test: the
TestRuntimeRobustness*functions, one subtest per failure mode; a feature's cases live in its own file and function, so two features never edit one registry - Acceptance: All return typed errors, never panic, timeout guard (60s) prevents hangs
Failure modes:
- Deadlocked action (join starvation)
- Decision with no satisfied guard
- State machine with dangling transition
- Sourceless accept...then written first in its body or after a member that is not a state
- Calc with unbound parameter, surplus or unknown-named arguments, no result, non-calc target, direct or mutual recursion
- Constraint referencing missing feature
- Step budget exceeded
- Fork/join misuse (branches sharing a region, join with one incoming branch)
- Region pseudostate with no satisfied guard, or a cycle
- Non-numeric time trigger
- Send that reaches only its addressee, accept of an unsent type, send through an unconnected port
- History outside a composite state, or without a record or default
- Defer of a non-deferrable trigger
- Non-terminating do behavior
- Call of an unhandled operation, call argument of the wrong type
performof a missing action,performreference cycle
Usage:
go test -v -run TestRuntimeRobustness -timeout 60s ./internal/exec/runtimeThe three pilot oracles read OMG-published material, which is sometimes wrong itself.
tools/oracle/errata is the registry of those defects: file, line, published bytes, the specification
clause violated, the derivation, and the corrected text where the intended reading is unambiguous.
The overlay mechanism and the entries for the bundled standard library live in the product's
internal/workspace/libs/errata, which internal/workspace/libs applies on read; the registry adds the
corpus entries and the corrected copy of a corpus root an oracle runs over a second time.
The published corpus is never written to — corrections are applied to a copy under the oracle's
output directory — and an entry whose published text no longer matches the bytes on disk fails a
test rather than rotting. Each oracle reports both censuses; the as-published one stays the
conformance statement. See the declared errata overlay.
See: SPEC_COMPLIANCE.md for semantic rule → implementation → test case → status mapping.
Every behavioral feature must have:
- Semantic rule reference: the SysML v2 metamodel or the bundled KerML semantic library, and UML 2.5.1 only where neither has the concept
- Implementation location (file:function)
- Test case(s) exercising the feature
- Status: ✅ Faithful /
⚠️ Approximate / ❌ Not Yet Implemented / ⛔ Deliberate Divergence / 🚧 Known Failure
Measured against the pinned reference (PILOT_TAG=2026-08, artifact 0.62.0). Every number below is generated by make docs-counts from the committed baselines and gated; none of them is typed in by hand.
- Corpus agreement: 347 of 378 files agree diagnostic-by-diagnostic; 38 diagnostics are ours alone and 1185 the reference's alone, and the first number must be read by root: our diagnostics against the reference's own corpora fell while our non-standard-notation warnings on our own example models rose (differential,
go run -C tools ./cmd/pilot-diff). - Declared-diagnostic silence: of the 512 declared
errorsrows in the reference's own Xpect suites, we report nothing for 0. 245 we report word-for-word; 248 wording-only and 7 location-only differences are agreement in substance and are not counted as gaps; 0 more we report as a warning and 2 elsewhere in the file (Xpect oracle,go run -C tools ./cmd/pilot-xpect). - Scope agreement: 230 of 230 declared scope assertions match exactly (same source).
- Permissiveness gaps: of 306 invalid models we wrote ourselves, the reference rejects 4 that we accept by default, and 293 both reject; 4 further cases agree only when we are asked strictly. We authored every one of these cases ourselves, so the denominator measures the reach of our own corpus and not our conformance; agreement reached only under an opt-in strict mode is weaker evidence than agreement by default (rejection oracle,
go run -C tools ./cmd/pilot-reject). - Declared errata: the registry declares 12 defect(s) in the published reference material — 4 with a specification-derived correction, 8 documented without one, since no intended reading can be inferred (OMG issues,
tools/oracle/errata). Every figure above is as published and stays the conformance statement; running the same oracles over the corrected text instead reports 348 of 378 files agreeing, 37 diagnostics ours alone and 1185 the reference's alone, 0 declared rows we are silent on, and 0 of 306 authored cases the reference alone rejects. The corrected figures are diagnostic only: an erratum never reclassifies a divergence category, and the published corpus is never edited. - Self-assessed surface: the action, state-machine and classifier-behavior rows have no external referee at all — the four refereed figures above cannot see them, because the pinned artifact evaluates expressions but executes neither actions nor state machines. Spec compliance counts them.
What these numbers cannot show: the OMG corpora are demonstrations rather than an official conformance suite; the differential is one-directional, comparing the diagnostics the two implementations report on the same files; the Xpect suites are the pilot authors' test intent rather than a certification oracle; and none of these is a percentage of the specification — no global compliance figure is claimed anywhere.
Row bookkeeping: the ✅/
Calc/constraint/requirement functional. Action/state executor infrastructure complete (fork/join/decision, TimeEvent/ChangeEvent, guards, hierarchy, orthogonal regions all tested); every conformance case passes. Fork/join, shallow/deep history and deferred events are implemented and reachable from source text — see docs/project/spec-compliance.md and docs/reference/grammar/README.md.
- Unit tests: Per-package test coverage (lexer, parser, semantics, runtime)
- Integration tests: End-to-end REPL/runtime scenarios
- Test fixtures:
tests/testdata/*.sysml,tests/testdata/*.kerml - Golden files: Expected parse/resolve/diagnostic outputs
- Verification:
go test ./...(all tests pass),go build ./...(clean build)
When adding parser support for new SysML v2 constructs:
- ✅ Add representative example to
testdata/parse/*.sysml - ✅ Run
go test -run TestGolden -updateto generate golden - ✅ Verify
TestStdlibConformancestill passes (no regressions) - ✅ Add negative test case if construct has error conditions
When adding execution support for behavioral constructs (actions, states, calc, constraints, requirements):
- ✅ Add golden AST fixture to
tests/parser/testdata/parse/(if not already covered) - ✅ Implement semantics in
internal/exec/runtime/(executor or evaluator) - ✅ Add conformance case:
.sysml+.expected.jsonininternal/exec/runtime/testdata/conformance/ - ✅ Add golden trace case:
.trace.goldenfor ordering-sensitive features (fork/join, transitions) - ✅ Add robustness test for failure modes (deadlock, unbound params, missing refs)
- ✅ Update
docs/project/spec-compliance.mdwith semantic rule → implementation → test → status - ✅ Verify all tests pass:
go test ./internal/syntax/parser/ ./internal/exec/runtime/
See CONTRIBUTING.md for full contribution guidelines.
- OMG SysML v2.1 Beta 1 Spec: https://www.omg.org/spec/SysML/2.0 (2026-08 release)
- Pilot Implementation: SysML-v2-Pilot-Implementation 2026-08
- Pilot Xtext Grammar:
SysML.xtext+KerMLExpressions(OMG reference implementation) - Metamodel: OMG SysML v2 metamodel (semantic foundation)
- Precedents: gopls (Go LSP), rust-analyzer (Rust LSP), IPython/Jupyter (REPL design)