Skip to content

SysML v2 Specification Compliance

Purpose: Document implementation coverage of SysML v2 / KerML behavioral semantics. UML 2.5.1 is cited only as reference semantics for an OpenSysML extension the SysML v2 notation has no production for and the bundled KerML semantic library (internal/workspace/libs/stdlib/) no performance for.

Related: TESTING.md (test contracts), ARCHITECTURE.md (runtime architecture), grammar-coverage.md (which OMG grammar productions our test inputs exercise — input-presence evidence, which implies nothing about compliance either way)


Current Implementation Status

Loop and branch bodies that state their own token flow are lowered by lower/block_graph.go:lowerStatedBlock and run as transparent, per-iteration performances by runtime/action_statements.go:performBlockFlow through runtime/action_subflow.go:runSubflow. Their declared attributes are seeded for each performance, and terminate; ends the enclosing action node. The remaining typed refusals are accept in declaration-order bodies, stated flows in calculation bodies, and body succession probability metadata when lowering has no resolver.

✅ Fully Implemented & Tested

The map below tracks 1179 semantic rules: 1047 ✅ faithful, 127 ⚠️ approximate, 0 ❌ not implemented, 5 ⛔ deliberate divergence; 202 of them have no external referee. Read that as progress, not as a compliance percentage — the denominator is the list of rules we chose to track, so it moves when we add a row, and a specification-derived denominator does not exist. What is externally checked is enumerated in the pilot differential; what cannot be checked by anything is in What Can't Be Claimed for Spec Compliance.

Calculations (14/14 features): - Invocation with typed parameters - Result evaluation (both the body's trailing expression and a bound return parameter return : T = <expr>;) - Parameter binding (positional + named arguments) - Parameter defaults (own and inherited) - Inherited parameters and result through a typed calc usage, including redeclaration - Nested calc invocation, and invocation from a constraint - Statement bodies: local declarations, assignment, if/else, while, loop … until, for, and early return - Purity and termination: a side effect or an outside assignment is rejected, and every loop iteration spends a step of the budget - Control flow (if/else), including the conditional expression if c ? a else b evaluated lazily at runtime - Unary operators (not, -, +) - Type coercion (Integer→Real) - Qualified names (A::B::C) - Deterministic evaluation trace (parameter binding, sub-expression order, results) - Error handling (unbound/unknown parameters, arity, missing return, recursion and step budgets) - Calc usages as multi-output consumers: a usage's out features resolve, typecheck and evaluate as features (attribute z = c.b;), from one run of the body per usage per object - Composition of multi-output calcs: a usage nested among a calc def's members binds its inputs from the enclosing evaluation's parameters and locals, one run per usage per object per bound input tuple

Constraints (7/7 features): - Assert evaluation (boolean satisfaction) - Assume evaluation (trusted preconditions) - Bare expression as invariant - Negated constraints (assert not) - Unresolved feature detection - Conditions of a nested constraint (assert constraint [name] { <expr> }) - Parameters a typed usage binds (constraint limit : MassLimit { in m = mass; })

Requirements (8/8 features): - Require expression evaluation, in a requirement definition body as well as a usage - Conditions stated through an anonymous nested constraint (require constraint { <expr> }) - The requirement's own attributes, inherited or rebound, in its conditions - Subject binding evaluation - Actor binding evaluation
- Assume expression evaluation, in both spellings - Nested requirements - A violated condition names the condition that failed

Actions (18/18 features): - Initial/final node token placement - Fork node (1→N parallelism) - Join node (N→1 synchronization) - Merge node (N→1 non-blocking) - Decision node (guarded branching) - Action execution nodes - Nested action invocation - Assignment statements in an action node's body - Conditional statement (if <cond> { … } else { … }), nestable in either direction with a loop - Pre-condition loop (while <cond> { … }) and post-condition loop (loop { … } until <cond>;) - Iteration over a collection (for <x> in <collection> { … }, over every collection the expression layer produces; a non-collection input is reported) - Send statement (⚠️ typed messages addressed to an object's port or receiving node, or routed through a connected port) - Accept action (⚠️ takes the oldest message of its type, parking its token until one arrives; suspension is bounded by the executor — see the Action map) - Nested accept, send, assign, if/else, while, loop, for, and terminate action-body items; an empty else_body has no branch, while empty then/loop bodies remain explicit blocks (SysML.xtext:1607 ActionBodyParameter, 1442 AcceptNode, 1499 SendNode, 1535 AssignmentNode, 1596 IfNode, 1615 WhileLoopNode, 1624 ForLoopNode, 1641 TerminateNode; formal/2026-03-02). Accepts in nested loop/branch bodies remain a pre-existing runtime limitation. - Object flow (pin-to-pin data), streaming by default and completion-triggered as a succession flow - Succession edges - Deadlock detection - Token-flow tracing (infrastructure ready) - Step budget enforcement

State Machines (core: faithful; advanced: partial): - Initial state identification and completion on a transition reaching done - State entry/exit actions - State do behavior (runs while the state is active; concurrently active states interleave) - Transition firing - Transition guard evaluation - Transition effect actions - AcceptEvent triggers (when signal) - Sourceless transitions (accept … then, if … then, then): the source is the state declared before them in the same body - ChangeEvent triggers (when expression) - TimeEvent triggers (after duration, at instant) - Signal discrimination (name matching) - Unmatched signal dropped - Signals sent from entry/do/exit/effect actions reaching the machine - CallEvent triggers (accept op(arg) notation, operation and argument matching) - Completion transitions (nil trigger with guard evaluation) - Hierarchical substates - Orthogonal regions (concurrent states) - Choice pseudostates (dynamic branching) - Junction pseudostates (static branching) - Fork pseudostates (one branch per orthogonal region) - Join pseudostates (waits for every branch) - Choice/junction reached from inside an orthogonal region - Nested action invocation in entry/do/exit/effect behaviors - Run-to-completion semantics - Event queue management - Dangling transition detection (a transition names one source and one target vertex of its own machine; a routing pseudostate with no transition out of it reports) - State visits tracking - Multi-region event broadcasting - History pseudostates: shallow and deep restoration (history / shallow history / deep history <name>;) - Deferred events: retention and recall across hierarchy and orthogonal regions (defer <event>[, <event>]*;)

Expression Evaluation: - Binary operators (+, -, *, /, <, >, ==, and, or) - Exponentiation (**, ^) over Integer and Real operands, folded and evaluated by one implementation - Unary operators (-, not) - Literal values (Integer, Real, Boolean, String) - Feature reference resolution - Qualified name resolution (A::B::C) - Type coercion (Integer→Real) - Unresolved reference error handling - KerML function library: the numeric functions of RealFunctions, RationalFunctions, NumericalFunctions, IntegerFunctions, NaturalFunctions, TrigFunctions, VectorFunctions and ComplexFunctions, all of StringFunctions, plus the library feature values TrigFunctions::pi and ComplexFunctions::i (see the Function Library row below)

Name Resolution: - Inherited feature resolution (follows specialization chains) - Named argument parameter binding - Redefinition target resolution (:>> featureName) - Control flow node scope registration

Test Coverage: counted from the tree when the documentation site is built and published at opensysml.org; no figure is typed in or committed here, so a branch adding a test or fixture leaves this page as it is.

  • Execution conformance: 1224 conformance cases (all passing: state×329, action×219, calc×182, instance×54, analysis×51, send×47, performed×30, assign×18, extent×17, function×17, stochastic×17, requirement×16, constraint×15, accept×13, satisfy×12, clock×11, exhibited×11, library×11, binding×10, nested×10, verification×10, object×8, occurrence×8, redefinition×8, multiplicity×7, unit×7, string×6, value×5, variation×5, bare×4, f99×4, port×4, three each of attribute, ballandchain, enum, f63, feature, filter, meta, perform, this and variant, two each of connector, f62, f64, inherited, metadata, via, viewpoint and w7d, and one each of behavior, cubesat, derived, enumeration, part, snapshot, standalone, structured, stub, two, view and w6e) — the calc cases include the fixed-step RK4 lunar descent whose stages are body-local usages read over a range, the one-binding output case, the library complex/vector/trig cases, and recursion — factorial, fibonacci, a descent over a sequence, a mutually recursive pair and one whose result is its last expression; the action and state cases include a decision and a transition guard reading a calc usage; the new f62_send_body_payload, f62_transition_body_dotted_target, f63_control_node_body, f63_for_typed_variable, and f63_merge_body_runs_on_traversal fixtures cover node bodies, dotted transition targets, typed for variables, and merge traversal, and the action_merge_loop_reenters, action_merge_loop_three_passes, action_merge_fork_branch_and_loop and action_merge_body_flips_own_guard fixtures a loop re-entering a merge, a merge fed by a fork and a loop at once, and a merge body write read by the merge's own outgoing guard; the assign_chain_* fixtures write through a feature chain at depth two and three, through a port, through an inherited feature, through two features holding one occurrence, from a state's entry, do and exit behaviors and from a transition effect, with the write read back by a later node and by a guard, and a calculation body's chained target rejected; the assign_write_* fixtures write a subtype value and a widening numeric value legally, and refuse a wrong-typed write, a wrong-typed chained write, a collection the target's multiplicity cannot hold and an empty write where one value is required; the instance_quantity_coherent_units, calc_quantity_coherent_result and calc_quantity_coherent_mismatch fixtures report a product, quotient, fractional power, prefixed input and user-declared derived unit in the coherent unit of the declared quantity kind, keep a dimensionless ratio a number and a non-numeric exponent an error, and keep the dimension mismatch of a speed written to an acceleration)
  • Runtime robustness: 794 runtime robustness cases (first-level subtests across the TestRuntimeRobustness* functions), among them: a write of a wrong-typed value, of too many values and of none where one is required, each leaving the feature as it was, and such a write from a state entry behavior, through a feature chain, to a calc output, to a body-local, to an output, to a performer feature and to a performance occurrence; deadlock, an accept payload read by a node that runs before the accept binds it, a default whose element count does not conform to its feature's multiplicity, a calc output the body never assigns or only a branch that did not run would assign, an output bound both by its declaration and by an assignment or by two assignments, empty entry/do/exit bodies, a do body that never finishes, a behavior both performing an action and stating a body, an assignment to a qualified target, a chained assignment target whose final segment the object reached does not hold, whose step is not an object, holds several objects or holds no value, whose base the body cannot reach, whose value the target's multiplicity refuses, or that is written in a calculation body, a body-local usage typed by something that is not a calc, a body-local declaration with no execution, a range bound that is not an Integer, a range spending the step budget, a collection spending the element budget, a chain through a part stopping at a calc usage, an index naming no position, a collection operand of the wrong kind, a collection body of the wrong arity, a select predicate that is not a condition, a collection operation spending the step budget, a non-terminating loop, a calc usage leaving an input unbound, reading an output it does not declare or one with no value, a usage nested in a calc leaving an input unbound, reading an output it does not declare, an input default naming only itself, a nested usage chain reaching the recursion limit or spending the step budget, outputs valued from each other, a usage typed by something that is not a calc, a usage body spending the step budget, an invocation of a calc that computes several outputs and designates no result, a non-terminating calc loop, a calc body that never returns, a send or a terminate inside a calc, an assignment outside a calc body, a non-Boolean calc condition, a body-local declaration that must not leak, a body member that is not executable, a statement written directly among an action's members, accept suspension that can never end, an accept standing as a statement of a loop body that nothing can end, guards, budgets, sourceless accept, fork/join misuse, pseudostate dead ends and cycles, non-numeric time trigger, a time trigger argument of the type validation refuses, misaddressed send, accept of an unsent type, send through an unconnected port, history misuse, non-deferrable deferred trigger, non-terminating do behavior, calc binding/arity/recursion failures, unhandled call, call argument of the wrong type, missing and cyclic perform references, a library function outside its domain or with the wrong arity, an extension library function outside its domain, exponentiation beyond the Integer range, a flow end that names no action node, a flow from a node that produced no value, an action accept waiting on the clock — after and at fired by advancing it, at an instant already past fired at once, a negative after, a duration of another dimension — and the clock advanced by zero, by a negative amount, with nothing waiting, past a wait that stays queued and into a machine the event budget stops, a non-Boolean change trigger, a variation with no variant selected, a selection that is not one of a variation's variants, two variants selected at once, a variation read through its declaration, a chain through an unselected variation part, two variation points selecting one variant without an owning object, a variant declared outside a variation, a variant under a redefined variation, a deep chain of redefinitions, conflicting redefinitions at several levels, one feature valued under two of its names, a feature both valued and restated in a body, a flow that names no feature to carry, an accepted message carrying no single value to bind, a transition that names no target, a transition endpoint that names nothing, a transition endpoint naming a state of a different machine, a transition endpoint lowered with no name-resolution pass, whose edge is left out, a connector end naming no reachable feature, a connector holding more than one object, a connector attached to itself or to one that names it back, a write into a pair of values derived from each other)
  • Runtime tests: 1,737 runtime test functions (the top-level tests go test -v ./internal/exec/runtime reports), the conformance, trace and robustness gates above among them
  • Golden ASTs: 227 golden AST fixtures (199 SysML, 28 KerML), including the implicitly typed connector forms and the standard behavioral notation — a named flow with from, accept trigger expressions, an accept subsetting an event, sends, a succession to a loop with until, then done, a decision else branch, a bodied exhibit state, a transition with its trigger on its own line, a qualified namespace-level succession — body-local calc usages and ranges, the three loop forms, pseudostate, timed-trigger, call-trigger, calc default/invocation, calc statement bodies, n-ary connector-end parsing, prefix metadata, keyword-less members, node bodies and dotted transition targets, a chained assignment target, and occurrence typing)
  • Golden traces: 388 golden execution traces under the default schedule (state×195, action×97, calc×32, stochastic×9, clock×8, extent×6, accept×4, analysis×4, constraint×4, send×4, string×4, three each of exhibited and object, two each of f63, perform and verification, and one each of assign, f62, function, meta, occurrence, performed, two, w6e and w7d), and 166 more .trace.golden files pinning a case under a named policy, <case>.declared or <case>.seed-<n> — entry/do/exit ordering of inline action bodies and a do body run to its end inside one round, the standard loop until with then done, a decision's guarded and else branches, a named flow carrying a value between action nodes, an accept with a when trigger, an accept subsetting an event, a send invocation through a port, a transition accepting through a port, loop and conditional bodies, one calc usage body run feeding several output reads, a usage whose outputs are read either side of an assignment to what its input named, a usage nested in a calc read for two of its outputs, calc statement bodies and their loop iterations, fork/join branch ordering, region entry/exit ordering, do behavior interleaving across orthogonal regions, send/accept, an accept parked until its message arrives, a payload read by a node declared before the accept that binds it, calc and constraint evaluation, library function invocation, the dotted-target transition, control-node and merge-body traces, and the merge loops re-entered on every pass)
  • Negative parser tests: 277 negative parser subtests (first-level subtests of TestNegative; 432 across the TestNegative* functions, 68 of them KerML, and 490 across every *Negative* parser test)
  • gRPC: 33 gRPC conformance cases and 13 gRPC robustness cases (first-level subtests across the TestGRPCRobustness* functions) (tests/grpc/testdata/conformance/, internal/frontend/grpc/robustness*_test.go)
  • Test functions: 10,699 top-level Test functions across the module (go test -count=1 ./... runs them all, with the OMG corpora downloaded, OPENSYSML_REQUIRE_TRAINING_CORPUS=1 OPENSYSML_REQUIRE_PILOT_CORPORA=1 OPENSYSML_REQUIRE_SMT=1 and z3 installed). The figures on this list are counted from the tree by tools/cmd/doc-counts when the site is built, and go run -C tools ./cmd/doc-counts -check refuses one typed in; the test and subtest total of a run is not stated, since only a run can state it. A test skips only where it says why: TestHeldImageRoundTrip declines a conformance case that creates no instance, so there is no held image to round-trip. Three skip themselves: TestSubsettingTargetIsTheInheritedFeature and TestRequirementEvaluation_SubjectNotFound against a limitation they record, and TestHelperSolverProcess, which is a solver child process the parent invokes. The others skip for want of something the run did not provide, and each names it: the weasyprint, pandoc and prince subtests of TestRenderWithInstalledEngines and TestRenderInlineRunsWithInstalledEngines and TestRenderDiagramsWithInstalledMermaid want the PDF and Mermaid toolchain, TestExtractionMatchesBaseline and TestUpdateIsIdempotentAcrossDays the pinned pilot validator jar, TestEmitSuite, TestRefereeRowsAreWellFormed, TestSuiteRead and TestSuiteClassification the downloaded PSSM test suite, TestCRealNotationIsLocaleIndependent a non-C locale, TestRenderDocumentsRejectsCaseAliasedTargets a case-insensitive filesystem, and TestFlexoInterop and TestFlexoInteropApply a live Flexo stack.

Detailed Semantic Compliance Map

How to Read This Map

Each row documents one behavioral semantic feature:

  • Semantic Rule: the governing reference — the SysML v2 metamodel or the bundled KerML semantic library, and UML 2.5.1 only where neither has the concept
  • Implementation: File:function implementing the semantics
  • Test Case: Conformance/robustness test(s) exercising the feature
  • Status:
  • ✅ Faithful: Implements spec semantics with test coverage
  • ⚠️ Approximate: Partial implementation or known deviations
  • ❌ Not Yet Implemented: Parsed but not executable
  • ⛔ Deliberate Divergence: not implemented on purpose — the row states the reason, the alternative it rejects, and the test that pins the refusal
  • 🚧 Known Failure: Test exists but fails

Two parts of this page are not written by hand. The row census at the top is counted from the rows when the documentation site is built, and the analysis-library table is rendered by make docs-counts from analysis-library-census.json, which a runtime test writes and holds current; go run -C tools ./cmd/doc-counts -check fails in CI when the rendered block and the file disagree. Edit neither block in place: regenerate them.

Calculation (Calc)

Semantic Rule Implementation Test Case Status
Calc invocation with typed parameters invoke_calc.go InvokeCalc/invokeCalcShape calc_simple_add.sysml ✅ Faithful
Result expression evaluation (the body's keyword-less trailing expression) invoke_calc.go calcResult/resultExpression + eval.go Eval calc_simple_add.sysml ✅ Faithful
Result as a bound return parameter (return : T = <expr>;) invoke_calc.go resultExpression calc_return_parameter.sysml ✅ Faithful
Parameter binding (positional) invoke_calc.go bindCalcParameter calc_simple_add.sysml ✅ Faithful
Parameter binding (named arguments) eval.go evalInvocation + invoke_calc.go InvokeCalcNamed calc_named_arguments.sysml ✅ Faithful
Parameter default when no argument is passed invoke_calc.go bindCalcParameter calc_parameter_defaults.sysml ✅ Faithful
Parameters and result inherited through a typed calc usage invoke_calc.go calcShapeOf/calcChain/calcParameters calc_inherited_parameters.sysml, calc_return_parameter.sysml ✅ Faithful
Redeclared parameter keeps its inherited position and default, which stays the expression the supertype wrote and is evaluated where that calc wrote it invoke_calc.go calcParameters (the owner carried down with the inherited default) calc_return_parameter.sysml, calc_usage_nested_shadowed_input.sysml, calc_usage_nested_test.go:TestNestedCalcUsageInheritedDefaultOfRedeclaredInput ✅ Faithful
Nested calc invocation eval.go evalInvocation → invoke_calc.go invokeCalc calc_nested_invocation.sysml ✅ Faithful
Calc invoked from a constraint context.go EvaluateConstraint → eval.go evalInvocation calc_from_constraint.sysml ✅ Faithful
Deterministic evaluation trace (binding, sub-expression order, result) trace.go RecordCalcEnter/RecordCalcBind/EndEval, eval.go Eval *.trace.golden via TestExecutionTrace, trace_calc_test.go:TestCalcTraceIsStableAcrossRuns ✅ Faithful
Canonical rendering of unordered values in traces trace.go FormatTraceValue trace_calc_test.go:TestFormatTraceValueCanonicalizesSets ✅ Faithful
Unbound parameter detection invoke_calc.go bindCalcParameter (ErrUnboundParameter) robustness_test.go:testCalcUnboundParameter ✅ Faithful
Surplus positional arguments invoke_calc.go checkArgs (ErrCalcArity) robustness_test.go:testCalcTooManyArguments ✅ Faithful
Named argument that names no parameter invoke_calc.go checkArgs (ErrUnknownParameter) robustness_test.go:testCalcUnknownNamedArgument ✅ Faithful
Invoked symbol is not a calc invoke_calc.go calcShapeOf (ErrNotACalc) robustness_test.go:testCalcSymbolIsNotACalc ✅ Faithful
Recursive calc (direct or mutual) evaluates to its result, bounded by the run's calc depth budget invoke_calc.go Context.enterCalc (ErrCalcRecursionLimit), budget from budget.go BudgetsFromEnv (OPENSYSML_MAX_CALC_DEPTH, default 10000, ceiling 25000) calc_recursion_factorial.sysml, calc_recursion_fibonacci.sysml, calc_recursion_mutual.sysml, calc_recursion_descends_sequence.sysml, calc_recursion_beyond_nesting_bound.sysml, invoke_calc_recursion_test.go:TestRecursiveCalcSpendsTheDepthBudget, :TestRecursiveCalcDepthIsNotAFixedBound, :TestMutuallyRecursiveCalcsEvaluate ✅ Faithful
A recursion that does not terminate spends a budget and is reported, never hanging or exhausting the stack invoke_calc.go Context.enterCalc (ErrCalcRecursionLimit) and context.go step counter (ErrStepLimitExceeded); the ceiling on the depth budget keeps the report ahead of the goroutine stack limit robustness_test.go:testCalcDirectRecursion, :testCalcMutualRecursion, :testCalcRecursionSpendsStepBudget, :testCalcRecursionAtDepthCeiling, budget_test.go:TestBudgetFromValue (above the ceiling) ✅ Faithful
Step budget bounds calc evaluation context.go step counter (ErrStepLimitExceeded), budget from budget.go BudgetsFromEnv (OPENSYSML_MAX_STEPS, default 10000000) robustness_test.go:testStepBudgetExceeded, budget_test.go:TestBudgetFromValue ✅ Faithful
Ahead-of-time native compilation of a calc (sysml -compile, C or Go): the compiled program computes, prints and fails as the interpreter does, or the calc is refused with a typed error naming the construct codegen/compile.go Compiler.Compile (UnsupportedError), codegen/compile_seq.go (sequences: shapes, literals, ranges, indexing, for, the sequence and control libraries), codegen/compile_fn.go (function values by monomorphization: Compiler.compileCalcWith compiles a calc once per tuple of function values its in calc parameters are bound to, funcCompiler.compileFuncArg/functionValueOf fix each argument statically, compileSample/compileSampledRead for SampledFunctions::Sample/Domain/Range), codegen/emit_c.go EmitC + emit_c_seq.go, codegen/emit_go.go EmitGo + emit_go_seq.go, repl/compile.go Session.CompileCalc; scope and measurements in native-compilation.md repl/compile_test.go:TestCompiledCalcsAgreeWithInterpreter (scalar, Seq::* and Fn::* cases, value and failure agreement; Fn::* covers a calc def, a calc usage with an unsupplied input, RealFunctions::sqrt/floor and an in calc parameter passed on, invoked positionally and by name, through recursion and two at once, and Sample/Domain/Range over many, one, empty and null domains with the first failing sample reported), :TestCompileRefusesWhatItCannotCompile (Refused::EscapingParam, ReturnedFunction, FunctionEquality, FunctionAsValue, ValueAsFunction, ChosenFunction, WrongArity, WrongName, BodyClosure, OuterClosure, ObjectClosure, ObjectCalc, SampleArity, SampledValue, SampledEscapes, UnevaluatedFunction, UnrelatedTyped, LibraryTyped, GeneralForSub, ForwardedUnrelated, IntegerNullRange) ⚠️ Approximate — scalars and homogeneous sequences of Integer/Real/Boolean with any multiplicity, the element budget included, and function values that name a calc statically (see the function-value rows below); records, enums, mixed Integer/Real sequences and strings are refused, a function value that escapes, is stored, compared, chosen at run time or closes over a body's bindings or an object is refused by name, an in calc parameter of the entry calc itself is refused (which a program cannot take on its command line), and compiled code has no step budget
Statement body (SysML v2 7.19, CalculationBodyItem carries the items of an action body): local declarations, assignment (assign x := e;), if/else, while, loop … until, for, return parser/behavior.go parseCalcBody/atCalcStatement → lower/calc_body.go CalcBody → runtime/statements.go stmtEngine driven by invoke_calc.go runCalcBody calc_statement_body.sysml (golden AST), calc_iterative_factorial.sysml, calc_conditional_branch.sysml, calc_for_over_sequence.sysml, calc_loop_until_body.sysml ✅ Faithful
A kind-less x = e; or x := e; in a calculation body, a constraint body or a nested statement body (a while/loop/for/if body, a state's entry/do/exit block, a transition effect) declares a feature of that body (CalculationBodyItem → ActionBodyItem → NonOccurrenceUsageMember → DefaultReferenceUsage with a FeatureValue, SysML.xtext:632), as the pilot reads it; AssignmentNode (:1535) begins with the assign keyword, so no kind-less member is an assignment, and the trailing expression reads such a local by name parser/behavior.go atNamedCalcMember (parseCalcBody, atConstraintBodyDeclaration), parseActionMember, atCalcStatement parse/calc_default_reference_usage.golden, parser/f61_keywordless_members_test.go:TestF61AssignmentStaysAssignment (a body right after the name, twice { doc /* */ }, is the usage's UsageBody, not a body expression), conformance/calc_body_default_reference_usage.sysml ✅ Faithful (the pinned validator accepts every fixture)
ReturnParameterMember (:1961) is 'return' UsageElement, so a result may specialize without a name or a typing, its Identification may open with a short name, its FeatureDeclaration may open with a multiplicity, and a body may follow the identification directly — while a value or a body alone declares nothing: return :> ISQ::power = e;, return r :> ISQ::speed = e;, return :> T[*] = e;, return <r> result : T = e;, return <r> :> T = e;, return <r>;, return [*] = xs;, return [*] :> xs;, return r { doc /* */ }; return = e; and return { … } are reported once parser/behavior.go atReturnedUsage, parseResultMember (parseIdentification, parseRelationships, parseMultiplicity) parse/calc_default_reference_usage.golden, behavior_test.go:TestParseResultMember_AnonymousAndBodiedForms, negative_test.go:calc_return_subsets_no_target, :calc_return_named_subsets_no_target, :calc_return_short_name_unclosed, :calc_return_short_name_empty, :calc_return_value_only, :calc_return_body_only, :calc_return_multiplicity_unclosed ✅ Faithful (the pinned validator accepts every form)
Early return out of a branch or a loop unwinds the blocks entered lower/action_graph.go Return + runtime/statements.go flowReturn calc_early_return_from_loop.sysml ✅ Faithful
A body-local declaration of a branch or loop body is that block's own and does not leak runtime/statements.go stmtEnv passes/typecheck_calc_body_test.go:TestCalcBodyLoopLocalIsNotVisibleOutside ✅ Faithful
An inherited body evaluates in the scope of the calculation declaring it invoke_calc.go calcBody (specialization chain, as calcResult/calcParameters) + statement Scope carried by the lowered IR invoke_calc_body_test.go:TestInheritedCalcBodyRunsInDeclaringScope ✅ Faithful
A calculation is pure: send, perform, accept, terminate and an assignment to a feature it does not declare are rejected runtime/calc_statements.go (ErrCalcSideEffect, ErrCalcExternalAssignment) robustness_test.go:testCalcSendIsRejected, :testCalcTerminateIsRejected, :testCalcAssignmentOutsideTheCalc ✅ Faithful
A loop in a calculation terminates or fails: every iteration spends a step of the budget runtime/statements.go loop/forLoop → context.go incrementStep; InvokeCalc/InvokeCalcNamed beginRun robustness_test.go:testCalcNonTerminatingLoop, budget_test.go:TestStepBudgetIsPerRunForInstancesAndCalcs ✅ Faithful
A body running to its end without returning is an error, not a null result invoke_calc.go runCalcBody (ErrCalcNoReturn) robustness_test.go:testCalcBodyNeverReturns ✅ Faithful
Control flow (if/else) in calc, including the conditional expression if c ? a else b evaluated lazily runtime/statements.go ifStatement; eval.go evalConditional calc_conditional_branch.sysml, calc_conditional_operator_base_case.sysml ✅ Faithful
The type tier reaches the expressions of a body whose result is the value of its last expression, as it reaches an explicit return — including the dimensional warning passes/typecheck.go checkBehaviorMember (an expression body member inferred as the value it computes) typecheck_calc_implicit_result_test.go:TestCalcBodyImplicitResultIsChecked, :TestCalcBodyImplicitResultWarnsOnDimensions, :TestCalcUsageImplicitResultIsChecked, :TestCalcBodyImplicitResultSkippedAfterNameError, calc_recursion_implicit_result.sysml ✅ Faithful
A non-Boolean if/loop condition in a calc is a type error, and a diagnostic at runtime passes/typecheck.go checkBehaviorMember; runtime/statements.go condition typecheck_calc_body_test.go:TestCalcBodyNonBooleanWhileCondition, robustness_test.go:testCalcNonBooleanCondition ✅ Faithful
Statements and loop iterations in the evaluation trace trace.go RecordStatement/RecordLoopIteration calc_iterative_factorial.trace.golden, calc_early_return_from_loop.trace.golden ✅ Faithful
Missing return expression invoke_calc.go calcShapeOf (ErrNoResultExpression: no result expression, no returning body, no output features and no output its body assigns); a return that declares a result parameter without binding it (return h;) is named in the error with the two forms that state a computed result (unboundResultHint) robustness_test.go:testCalcWithoutResult, invoke_calc_body_test.go:TestUnboundResultParameterHint, f64_calc_return_usage_without_value.sysml conformance ✅ Faithful
An assignment in a body to an out the calculation declares binds that output for the activation, so a calc usage reads it (KerML 7.4.9: an invocation's outputs are features of that one evaluation). The assignment is written assign a := expr;; a kind-less a = expr; declares a feature a of the body instead (see the statement-body rows above) runtime/statements.go stmtEngine.assign/stmtEnv.assignLocal → runtime/calc_statements.go calcStmtHost.declaredOutput/assignOuter; runtime/calc_usage.go calcRun.output, assignedOutputs; invoke_calc.go calcShape.BodyOutputs tests/parser/testdata/parse/calc_output_assignment.golden, calc_output_assigned_in_body.sysml conformance (a loop and a branch computing outputs) ✅ Faithful
A body that both assigns an output and returns a value keeps the two apart: the return is the invocation's value, the assignment the output's, so reading the output answers what the body assigned runtime/calc_usage.go runCalcUsage (memoizing a returned value under an output's name only for calcOutput.IsResult, the rule the row on a valued output and a return states) calc_output_assigned_in_body.sysml conformance (AssignAndReturn: cr.a is 6, AssignAndReturn(5) is 500) ✅ Faithful
A calculation the model declares is evaluated from its own body — a result expression, or a body assigning an output it declares — and, without one, has no result; a library function of the same name never answers in its place runtime/library_functions.go libraryFunctionFor (Context.libraryDeclared) library_functions_test.go:TestLibraryFunctionDoesNotHijackADeclaredBody, :TestLibraryFunctionDoesNotHijackAnOutputAssignedInABody, :TestLibraryFunctionNeverAnswersAModelDeclaration ✅ Faithful
An assignment target naming more than one segment (assign other::c := 1) reaches outside the body no host binds, so it is lowered as unsupported and reported rather than writing the last segment lower/action_graph.go lowerStatement (Unsupported) robustness_test.go:testQualifiedAssignmentTargetInAStateEffect ✅ Faithful (an assignment through a feature chain, a.b := 1, is reported the same way; neither form is bound)
An inout is bound by the invocation and rebound by an assignment in the body, the read answering what the body left runtime/calc_usage.go calcOutput.IsInOut/calcRun.output; runtime/calc_statements.go calcStmtHost.declaredOutput calc_output_assigned_in_body.sysml conformance (Bump) ✅ Faithful
An output given a value by its declaration and assigned in the body is a typed error rather than a silent pick (the precedent of a feature valued two ways). Assignments to an output within the body are imperative like a body local's: the last one to run is the activation's binding, so an output may be initialized and then accumulated into, including once per loop iteration runtime/calc_statements.go calcStmtHost.assignOuter (ErrConflictingOutput) robustness_test.go:testCalcOutputValuedAndAssigned, :testCalcOutputAssignedTwice, calc_output_assigned_in_body.sysml conformance (Accum) ✅ Faithful
Reading a declared output the activation never assigned reports that output, not a missing result expression; an output only a branch that did not run would assign is unassigned for that activation runtime/calc_usage.go calcRun.output (ErrOutputNotAssigned, a kind of ErrNoValue) robustness_test.go:testCalcOutputNeverAssignedByTheBody, :testCalcOutputAssignedInABranchNotTaken ✅ Faithful
A calc usage's members are the parameters and outputs of the calc it is typed by, reachable through a feature chain (SysML 7.6.6, 7.17) resolve/target.go ResolveTarget/memberChain + resolve/document.go resolveMemberChain → semantics Model.LookupMember passes/typecheck_calc_usage_test.go:TestCalcUsageOutputTypesAsTheOutputItNames, :TestCalcUsageOutputInsideAPartDefinition ✅ Faithful
An output read through a chain types as that output declares, or as its default computes when it declares no type passes/typecheck_expr.go inferFeatureChain/featurePrimType passes/typecheck_calc_usage_test.go:TestCalcUsageOutputTypedByItsDefaultIsChecked, :TestCalcUsageDeclaredOutputTypeIsChecked ✅ Faithful
Reading a name the calc declares no output for is unresolved, reported once by the name-resolution tier resolve/document.go resolveMemberChain; runtime/calc_usage.go calcRun.output (ErrUnknownOutput) passes/typecheck_calc_usage_test.go:TestCalcUsageUnknownOutputIsUnresolved, robustness_test.go:testCalcUsageUnknownOutput ✅ Faithful
A calc usage evaluates its body once and every out feature it declares is readable from that run (SysML 7.17) runtime/calc_usage.go CalcUsageOutput/CalcUsageOutputs/calcUsageRun → invoke_calc.go calcShapeOf/bindCalcParameters → runtime/statements.go stmtEngine calc_usage_multiple_outputs.sysml, calc_usage_statement_body.sysml, calc_usage_inherited_parameters.sysml, calc_usage_instance_slots.sysml ✅ Faithful
Reading several outputs of one usage runs the body once, per usage and per object, reset with the run runtime/calc_usage.go calcUsageRun (calcUsageKey) + context.go beginRun/beginExecutorRun calc_usage_multiple_outputs.trace.golden, calc_usage_statement_body.trace.golden, calc_usage_inherited_parameters.trace.golden ✅ Faithful
A usage's inputs bind from its own member values, falling back to the defaults declared along its specialization chain, and may name a sibling feature of the object carrying the usage runtime/calc_usage.go calcUsageRun → invoke_calc.go bindCalcParameters/calcParameters calc_usage_inherited_parameters.sysml, calc_usage_instance_slots.sysml ✅ Faithful
A usage declared in a behavior's body — a calc's or an action's — binds its inputs in the environment of the evaluation reading it: the enclosing parameters and locals as the running body holds them, then the enclosing lexical scope, so an input naming an attribute the body assigned reads the assigned value rather than the declared one (SysML 7.17, a usage is a feature of the body declaring it) runtime/calc_usage.go bindCalcUsage/enclosedByBehaviorBody, invoke_calc.go isActionSymbol, eval.go EvalContext.nestedEnv, invoke_calc.go bindCalcParameters calc_usage_nested_in_calc.sysml, action_body_local_calc_usage.sysml, calc_usage_nested_test.go:TestNestedCalcUsageBindsFromEnclosingParameters, :TestNestedCalcUsageBindsFromEnclosingLocals, :TestNestedCalcUsageChain, :TestNestedCalcUsageReadsEnclosingObject, action_body_local_usage_test.go:TestActionBodyLocalUsageBindsCurrentValues, :TestActionBodyLocalUsageBindsPerIteration, classifier_behavior_test.go:TestCalcOperationNestedUsageSeesPerformingObject, :TestObjectScopedCalcUsageSeesPerformingObject ✅ Faithful (the steps of a usage's body see the object of the evaluation context that reached them — runCalcUsage passes the reading context's object to runCalcSteps — so a usage read from an object, or from a calc operation invoked on one, reads that object's current feature values rather than the declared defaults; a calc reached with no object context still has no performer, and a body's send, effect and assignment restrictions are unchanged)
A nested input bound from a name of its own (in vx = vx) reads the enclosing binding: the inputs being bound are not in the environment their own values are evaluated in, so every one of them resolves names in the enclosing environment alike — in n = m; in m = n; swaps the two values rather than the second reading the first's fresh binding runtime/calc_usage.go bindCalcUsage, eval.go EvalContext.nestedEnv/Lookup calc_usage_nested_shadowed_input.sysml (shadowed and swapped names), calc_usage_nested_test.go:TestNestedCalcUsageShadowsEnclosingName, :TestNestedCalcUsageInputsDoNotSeeSiblings, robustness_test.go:testNestedCalcUsageSelfCycle (a default with nothing outside to name stays ErrCyclicFeatureValue) ✅ Faithful
One run per usage, object and activation: two reads from one enclosing invocation run the body once, two invocations do not share it, and an iteration of a loop is an activation of its own. The activation replaces the input-value hash the memo key used, so two invocations whose inputs coincide still get their own run and no read can be answered from a run bound to other values runtime/calc_usage.go calcUsageKey/calcUsageRun, context.go newActivation/endActivation calc_usage_nested_in_calc.trace.golden, calc_usage_nested_shadowed_input.trace.golden, calc_usage_nested_test.go:TestNestedCalcUsageRunsPerInputs, :TestNestedCalcUsageOwnOutputPerInvocation, calc_usage_snapshot_test.go:TestCalcUsageMemoDistinguishesEnclosingArguments ✅ Faithful
Every output read from one evaluation of a usage sees one binding of its inputs: the inputs are bound when the evaluation starts and a later assignment to a feature they named does not rebind them for a later output read, so two outputs of one usage can never come from different input bindings (KerML 7.4.9, SysML 7.17 — a usage's outputs are features of one evaluation) runtime/calc_usage.go calcUsageRun/calcUsageKey/forgetCalcUsage, context.go newActivation/endActivation calc_usage_outputs_one_binding.sysml + .trace.golden, calc_rk4_lunar_descent.sysml, calc_usage_snapshot_test.go:TestCalcUsageOutputsShareOneInputBinding, :TestCalcUsageOutputsInAssignmentLoop, :TestCalcUsageMemoDistinguishesEnclosingArguments, :TestCyclicCalcUsageOutputStillDiagnosed ✅ Faithful
A calc usage declared in a loop or conditional body is executable, in the scope it is written in and with the lifetime of the block: an iteration binds it from that iteration's state and reading it again in the same iteration reuses that evaluation. This holds in an action's body as well as a calc's, both being run by the statement engine lower/calc_body.go usageStatement → lower.DeclareUsage, runtime/statements.go declareUsage, runtime/calc_usage.go bodyUsageSymbol/enclosedByBehaviorBody calc_body_local_usage_and_range.sysml (golden AST), calc_rk4_lunar_descent.sysml, action_body_local_calc_usage.sysml, calc_usage_body_local_test.go:TestBodyLocalCalcUsageInLoopBindsPerIteration, :TestBodyLocalCalcUsageInBranch, :TestBodyLocalCalcUsageInNestedBodies, action_body_local_usage_test.go:TestActionBodyLocalUsageBindsCurrentValues, :TestActionBodyLocalUsageBindsPerIteration, robustness_test.go:testBodyLocalUsageOfANonCalc, :testBodyLocalDeclarationNotExecutable ✅ Faithful
A feature chain through a part reads what the part's features carry, the part being materialized as the occurrence it denotes; an output of a calc usage the part declares evaluates in that part's context. The object being evaluated answers first: a feature value it carries for that part is the object read, and only a part no object in hand carries is materialized. A namespace-level usage of several occurrences denotes its lower bound of objects, so a chain through it reads each of them (wheels.radius for part wheels : Wheel[4]; is four radii), while a collection nested in an object whose count the model leaves open is undetermined at model level rather than read as any one member. A name of such a usage evaluates to those objects too, wherever it is written — a binding in a performed action's body names a sibling of the enclosing package the same way a chain does runtime/eval.go evalFeatureChain/chainBase/occurrenceReference, runtime/calc_usage.go occurrenceOperand, runtime/instance.go occurrenceOf/occurrencesOf/denotedValue/occursOnce/namesOneObject/namesObjects part_feature_chain_test.go:TestCalcUsageReadThroughPartChain, :TestPartChainReadsTheObjectInHand, :TestPartChainThroughSeveralOccurrences, :TestPartChainRejectsSeveralNestedOccurrences, :TestCalcUsageChainDiagnostics, robustness_test.go:testUsageReadThroughAPartWithoutAnOutput, :performed_action_binding_names_nothing, :structured_attribute_chain_of_an_unknown_feature, conformance perform_action_binding_package_sibling, pilot-exec-diff dot-perform-out, dot-machine-attr ✅ Faithful
A chain through a multi-valued feature has, for its last feature, that feature's values over every object the features before it name (KerML 1.0 §7.3.4.6): subsystem.volume is the volumes of every object subsystem holds, in the collection's order, flattened one level per step as ->collect flattens its mapping. An empty collection yields no values; a chain reaching an object with no value for the feature reports the unset feature value runtime/eval.go chainMemberValue/chainOverElements conformance feature_chain_rollup_over_subsets, feature_chain_nested_multivalued, feature_chain_empty_collection, cubesat_mass_rollup; robustness_test.go:feature_chain_through_an_unset_slot, :feature_chain_spends_the_element_budget, chain_trace_test.go:TestChainOverCollectionTraceOrder ✅ Faithful
A chain that stops at a calc usage rather than at one of its outputs names the outputs to read instead of reporting no value runtime/eval.go evalCalcUsageMembers (ErrNoValue) part_feature_chain_test.go:TestCalcUsageChainDiagnostics, robustness_test.go:testUsageReadThroughAPartWithoutAnOutput ✅ Faithful
A nested usage chain is bounded: the depth counted while an output binding is evaluated, the budget spent by the bodies it runs. One evaluation counts one level however its answer is reached, so an invocation whose result is a designated output and outputs of one calc naming each other spend nothing extra invoke_calc.go enterCalc (ErrCalcRecursionLimit)/runCalcBody, runtime/calc_usage.go calcRun.enter/calcRun.value robustness_test.go:testNestedCalcUsageRecursionDepth, :testNestedCalcUsageStepBudget, :testNestedCalcUsageUnboundInput, :testNestedCalcUsageUnknownOutput, calc_usage_nested_test.go:TestNestedCalcUsageDepthCountedOnce, :TestCalcOutputChainIsNotNesting ✅ Faithful
An out default evaluates in the calc's own scope and may name inputs, body locals and other outputs runtime/calc_usage.go calcRun.output/lookupOutput + eval.go EvalContext.calcRun calc_usage_statement_body.sysml, calc_usage_inherited_parameters.sysml ✅ Faithful
An output feature fed into a feature's default value (the parametric-budget pattern) eval.go evalFeatureChain/evalCalcUsageMembers calc_usage_multiple_outputs.sysml, calc_usage_instance_slots.sysml ✅ Faithful
Outputs valued from each other are a cyclic dependency, not a hang or a spent step budget runtime/calc_usage.go calcRun.output (ErrCyclicOutput) robustness_test.go:testCalcUsageCyclicOutputs ✅ Faithful
A usage leaving an input unbound, or reading an output with no value, is reported runtime/calc_usage.go calcUsageRun (ErrUnboundParameter), calcRun.output (ErrNoValue) robustness_test.go:testCalcUsageUnboundInput, :testCalcUsageOutputWithoutAValue ✅ Faithful
A calc usage typed by something that is not a calc is reported invoke_calc.go calcShapeOf (ErrNotACalc) robustness_test.go:testCalcUsageSpecializesANonCalc ✅ Faithful
The step budget bounds a usage's body the way it bounds an invocation runtime/calc_usage.go CalcUsageOutput beginRun → context.go incrementStep (ErrStepLimitExceeded) robustness_test.go:testCalcUsageStepBudget ✅ Faithful
An invocation yields exactly one result (KerML 7.4.9), so invoking a calc that computes several outputs and designates no result is rejected rather than answered with the first of them — and the diagnostic writes out the calc usage to declare instead, with the inputs to bind runtime/calc_usage.go calcShape.designatedOutput/usageSpelling (ErrAmbiguousResult) robustness_test.go:testMultipleOutputsInvokedAsAnExpression, repl/runtime_commands_test.go:TestCalcWithSeveralOutputsIsNotInvocable ✅ Faithful
A calc with exactly one output and no return is invocable, that output being its result runtime/calc_usage.go calcShape.designatedOutput calc_usage_single_output.sysml ✅ Faithful
A return in a calc that also states an output supplies the invocation's value only: an output keeps the value its own binding computes, so a body returning something else does not change what reading that output answers. Only the result parameter (return : Real = …) takes the returned value directly runtime/calc_usage.go runCalcUsage (memoizing a returned value under an output's name only for calcOutput.IsResult) calc_valued_output_with_return.sysml conformance (Apart: ca.a is 6 while Apart(5) is 500; Together, Both) ✅ Faithful
%calc on a calc usage lists the outputs of one evaluation; a chain into a usage evaluates at the prompt repl/meta.go doCalc/calcUsageOutputs repl/runtime_commands_test.go:TestCalcUsageOutputsAtThePrompt ✅ Faithful
A calc usage's outputs are evaluation results, not feature values of an object runtime/calc_usage.go (no instance materialization) calc_usage_instance_slots.sysml (the features fed by the outputs are feature values; the usage itself is not), pilot-exec-diff w6d:calc-usage ⚠️ Approximate (unrefereeable: the pinned artifact answers a CalculationUsage node rather than an output value. %instances and export show the features valued from outputs, not the usage's outputs themselves)
A calc definition, a calc usage with an unsupplied input, or an in calc parameter named where a value is expected is a function value (KerML 1.1 §7.4.4: a Function is a Behavior with a result, an Expression a Step typed by one, and a feature reference to either denotes it; §8.3.4.8 Function::result, FeatureReferenceExpression; SysML v2 §7.17: a calc def is a Function, a calc usage an Expression). The value is the calc's lowered invocation interface (calcShape) together with the environment it was read in — its declaring scope and the object it was read off — and nothing else: no statement closure is built, and the value is invoked through the same path a calc usage invocation takes (invokeCalcShapeIn). Reading a calc usage whose inputs are all bound evaluates it as before; a library function the runtime implements natively (RealFunctions::sqrt, floor) reads as a value carrying that implementation, while a library operation that binds its arguments unevaluated (SequenceFunctions::size and the other -> operations) is refused as ErrNotAFunction runtime/value.go ValFunction, runtime/function_value.go functionValue/EvalContext.functionValueOf/Context.readsAsFunction/EvalContext.calcAsValue, invoke_calc.go calcShapeOf (a natively implemented library function computes), eval.go evalFeatureReference, describe.go (the function Sq), trace.go FormatTraceValue (calc(Sq)), repl/meta.go function_value_read.sysml, function_value_probe.sysml (Fn(Sq, 3.0) is 9.0), function_value_calc_usage.sysml, function_value_library.sysml, robustness_test.go:function_value_of_a_built_in, value_kinds_test.go:TestFunctionValueIdentity, :TestEveryValueKindIsDispatched, eval_no_value_test.go, repl/evalin_test.go; compiled: repl/compile_test.go Fn::ApplySq, Fn::ApplyUsage, Fn::ApplySqrt, Fn::ApplyFloor, Refused::FunctionAsValue, Refused::UnevaluatedFunction ✅ Faithful; compiled ⚠️ Approximate — natively the value is fixed at compile time (codegen/compile_fn.go functionValueOf: a calc def, a calc usage with an unsupplied input owned by packages alone, or a compiled library function), so it is passed to in calc parameters and invoked but never read where a value is expected (the function value Sq where a value is expected), and a library operation binding its arguments unevaluated is refused as it is interpreted
An in calc parameter of a calc or an action (SysML v2 §7.17, §8.3.16 CalculationUsage as a parameter) accepts a function value or null and nothing else, positionally or by name; the body invokes it as f(a), through a chain (p.f(a)), nested (f(f(a))) and as an argument to another calc-typed parameter, binding the callee's inputs positionally and by name as a direct invocation does. A calc usage bound as an action input (in f = sq;) is the function value it reads as. A calc-typed parameter whose own value names a calc (in calc f = twice;) applies that calc wherever no run bound it — called by its bare name or its qualified name (Apply::f(a)) alike — and a run's binding takes precedence runtime/invoke_calc.go calcParameter.checkFunction, function_value.go EvalContext.invokeFunction, EvalContext.boundFunction/qualifiedBoundFunction/declaredFunction, eval.go evalInvocation/evalFeatureChain, parser/behavior.go parameterKindKeywords (calc) function_value_probe.sysml + function_value_probe.trace.golden (TestExecutionTrace), function_value_named_args.sysml, function_value_chain_call.sysml, function_value_action_parameter.sysml, function_value_qualified_call.sysml, function_value_qualified_default.sysml, parser golden action_calc_parameter.sysml, robustness_test.go:function_value_call_of_a_non_function (ErrNotAFunction), :function_value_bound_to_a_non_function (ErrNotAFunction), :function_value_arity_mismatch (ErrCalcArity), testCalcUnboundParameter (ErrUnboundParameter); compiled: repl/compile_test.go Fn::PassSq, Fn::IterateSq, Fn::TwoSq, Fn::ByNameSq, Fn::IntBoth, Fn::Fold2Add, Fn::CondApply, Refused::CalcParam, Refused::ValueAsFunction, Refused::ChosenFunction, Refused::ReturnedFunction, Refused::WrongArity, Refused::WrongName ✅ Faithful; compiled ⚠️ Approximate — codegen/compile_fn.go specializes the callee per function value bound (Compiler.compileCalcWith), so f(a) and f(v = a) are direct calls binding the value's own parameters as the interpreter does, and the value may be passed on to another in calc parameter or through recursion; a parameter typed by a calc (in calc f : Sq) takes only a function value whose calc conforms to it (paramDecl.typ, checked in bindArgs as checkFuncArgType; the interpreter's type mismatch at the binding becomes cannot bind the function value … to a parameter typed by …, Refused::UnrelatedTyped, LibraryTyped, GeneralForSub, ForwardedUnrelated; Fn::TypedSq, TypedUsage, TypedSub compile); the argument must name a calc statically (an if choosing one, an invocation producing one, or a plain value in its place is refused at compile time, as is a calc's own in calc parameter on the command line), a chain call p.f(a) is refused with every feature chain, and an arity or name mismatch is a compile-time refusal where the interpreter fails at run time
A call through a feature chain, holder.scale(3.0) (KerMLExpressions InstantiatedTypeMember → OwnedFeatureChain), is checked statically as a direct call is: the chain names the calc feature applied, its arguments are held to that feature's effective inputs positionally and by name (a wrong type, an unknown name, too many arguments, an unbound default-less input), a chain to a non-behavior is refused, and the call is typed by the calc's result, so binding it to an incompatible declared type is reported. At run time the chain in call position denotes the calc feature applied over the receiver's object whatever its inputs — one every input of which a default supplies, or one with no input, is applied (holder.scaled() is 10.0) where the bare read holder.scaled computes its result — and any other member is read as written and must hold a function passes/typecheck_expr.go inferChainInvocation, passes/invocation.go ChainCallee/InvocationArgs, passes/typecheck_value.go invocationResultParameter; runtime/eval.go evalChainInvocation/chainCallee passes/typecheck_expr_test.go:TestExprChainInvocationChecked; function_value_chain_call.sysml, function_value_chain_defaults.sysml ✅ Faithful
A calc read off an object (twice.scale, a calc usage owned by a part) is a function value over that object: invoked later it reads that object's features, so the same calc read off two parts is two values. A calc declared inside a behavior body (a calc's or an action's) closes over the bindings of the run it was read in — its enclosing parameters and locals — and a function returned from a calc (return : Unary = scale;) keeps them after the run ends. The closure reaches only the code written inside that body: a body the nested calc inherits from a calc declared elsewhere (calc again : RecursiveStep;) reads none of the enclosing run's bindings, so a case performing itself as a step nests its frames no deeper than its recursion. The run closed over is a run of the behavior the calc is declared in — the innermost active invocation of that calc (or of one specializing it) or performance of that action — not whatever calc happens to be evaluating: a nested calc applied from a calc between it and its owner reads the owner's k, not the caller's, and one applied (Outer::inner(2.0)) or read (Fn(Outer::inner, 2.0)) while no run of its owner is active closes over nothing, so its body's k is unresolved rather than a same-named parameter of the caller function_value.go functionValue.self/.enclosing, EvalContext.functionValueOf (enclosedByBehaviorBody); eval.go EvalContext.enclosingRun; frame.go frame.runs (a calc frame by calcShape.qualifiedBy, a performance frame by its scope's owner, a snapshot by the action it was copied from: frame.performs/frame.snapshot); calc_usage.go runOf, calcShape.bodyEnclosing/declaredWithin; invoke_calc.go Context.invokeCalcShapeIn function_value_feature_closure.sysml (Fn(twice.scale, 5.0) + Fn(thrice.scale, 5.0) is 25.0), function_value_body_closure.sysml (Outer, Maker, Shadowed), function_value_action_closure.sysml (an action-local calc passed as a function applies another that reads the performance's input and local), function_value_sampled_closure.sysml, action_nested_calc_reads_performance.sysml; robustness_test.go:function_value_inherited_body_outside_the_closure, :function_value_nested_calc_outside_its_run; analysis_robustness_test.go:recursion_through_a_step; compiled: repl/compile_test.go Refused::ObjectClosure, Refused::ObjectCalc, Refused::BodyClosure, Refused::OuterClosure, Refused::EscapingParam ✅ Faithful; compiled ⛔ Deliberate Divergence — the native backends carry no closure: a calc owned by a part (twice.scale, Scaler::scale) or declared inside a behavior body is refused by name (whose function value closes over that object / that run's bindings), and a function value cannot escape as a result (the function value f escaping as the result); native-compilation.md (Function values) states the trade-off
Function values are equal, and key a set, by the calc together with the object it was read off (Sq == Sq, twice.scale != thrice.scale); a value closing over a behavior body's bindings is equal to every read of the same calc within the same run of that body and to none from another run (inner == inner within one calc body; Maker(2.0) != Maker(2.0) across two invocations). Adoption into another runtime rebinds an ordinary function value to the same calc of the adopting model, the object it was read off adopted with it; one closing over a body's bindings is refused as a typed error (the run those bindings belonged to has ended and cannot be reconstructed) value_equality.go valueEqual/valueKeyFunc (ValFunction arm), adopt.go value_kinds_test.go:TestFunctionValueIdentity, conformance function_value_closure_equality, adopt_test.go:TestAdoptRebindsAFunctionValue ✅ Faithful
SampledFunctions::Sample(f, domain) samples a user calc passed as its in calc calculation argument: the library's own body runs, domainValues->collect { in x; new SamplePair(x, calculation(x)) } invoking the function value inside the collection body, and Range of the result reads the samples back the library body under invoke_calc.go invokeCalcShapeIn, function_value.go EvalContext.invokeFunction (from a collection body's frame), collections.go function_value_sampled.sysml (Range(Sample(Sq, (1.0, 2.0, 3.0))) is [1.0, 4.0, 9.0]), function_value_sampled_closure.sysml (a calc read off a part, sampled); compiled: repl/compile_test.go Fn::SqRange, Fn::RecipRange, Fn::HalfDomain, Fn::IncBoth, Fn::QuarterRange, Fn::SqrtRange, Fn::UsageRange, Fn::SumRange, Fn::DownRange/DownDomain (the recursion budget's last frames), TestCompiledBudgetChargesInputsAndWidening Fn::SizeDomain/SizeRange (each read charged), Refused::SampleArity, Refused::SampledValue, Refused::SampledEscapes ✅ Faithful; compiled ⚠️ Approximate — codegen/compile_fn.go compileSample collects the calc over the domain in order when the sample is taken (the first failing element is the failure; Sample, Domain and Range are each one frame against the recursion budget and each Domain/Range read a fresh sequence charged to the element budget, as the library calcs are; a null domain samples to [], a literal null domain typed by the sampled calc's parameter — Fn::NullDomain, NullRange, NullSqrtRange; Real for a kind-preserving library function, so Refused::IntegerNullRange is refused where the interpreter computes []), and a SampledFunction is compiled only bound to an attribute or read at once by Domain/Range; one returned, passed on or used as a value, and SamplePair access, are refused
A feature's values are a sequence its multiplicity constrains (KerML 1.0 §7.3.4.1 Features Overview, §7.4.12 Multiplicities, §8.3.3.3 Features), so a [0..*] member holding one value denotes that value wherever one value is taken, and one binding rule serves every such place: an operator's operand (+, -, <, ==, not, unary -), a [1] parameter of a user calc or of a library function, and a cast or classification operand (as, @, istype, hastype) each take a one-element collection as its element; a collection of several stays what it is, refused as before (a type mismatch operand, a multiplicity violation argument), and an empty one still holds no value. SampledFunctions::SamplePair::domainValue :>> key states no multiplicity and so inherits KeyValuePair::key's [0..*] (§7.3.4.5), which is why s.samples#(1).domainValue - 1.0 and the library's own interpolateLinear read a one-element sequence; the same held for q.zs + 1.0 with zs : Real[0..*] = (2.5), no inheritance involved. Both now bind the element, and a member redefined [1] reads as it did runtime/collections.go soleElement; runtime/eval.go valueOperand, boolOperand, evalEquality, evalTypeClassification; runtime/cast.go evalCast; runtime/invoke_calc.go calcMemberDecl.check; runtime/instance.go Context.admitted; the scalar readers of runtime/library_functions.go, library_conversions.go, vector_functions.go conformance calc_sample_pair_arithmetic (domainValue/rangeValue arithmetic, interpolateLinear), calc_singleton_member_binds_scalar (q.zs through every operand kind), calc_two_valued_member_refused_scalar, calc_inherited_member_redefined_scalar (redefined [1], redefined without a multiplicity, genuinely two-valued still a sequence), robustness_test.go:two_valued_member_in_scalar_context; refereed against the pilot with tools/referee/exec on the q.zs singleton and two-valued cases ✅ Faithful
Boolean operators evaluated at runtime (and, or, xor, implies, short-circuiting where they can) eval.go evalLogical calc_boolean_operators.sysml ✅ Faithful
Identity (===, !==), null coalescing (??, lazy) and remainder (%) evaluated at runtime eval.go evalIdentity/evalNullCoalesce/evalArithmetic calc_identity_operators.sysml, calc_null_coalesce.sysml, calc_modulo_operator.sysml ✅ Faithful
Value classification evaluated at runtime (istype, hastype, and x @ T with a value subject and an ordinary type, the third ClassificationTestOperator of the KerML 1.0 ClassificationExpression grammar beside hastype and istype; KerML 1.0 §7.4.9.2: istype and hastype hold when every value of the operand is classified, @ when at least one is; SysML v2 8.4.4.2 ClassificationExpression) eval.go evalTypeClassification, valueHasType, directValueType, runtime/classification.go classifyValue (the one reading of "the value is of type T" that as and a feature write share), and evalOperator routing OpAt there through classifiesValue — x @ T is a value test when a subject is written and T resolves to a type that is not a metadata definition or metaclass (semantics.IsMetadataType), and the metadata test of the next row otherwise — a value's direct type is derived from what the value already holds (a scalar constant's representation — an integer is an Integer, a finite real a Rational whatever number it holds, an infinity a Real, a Complex a Complex on the real axis or off it (representationPrim; KerML 1.0 §8.4.4.9.2: only the rationals have a finite literal, so a LiteralRational is classified in Rational) — a string's String, an object's classifier, a selected variant, an enumeration literal's enumeration, a quantity's numeric value), so no type field was added to runtime.Value; istype is classifyValue by any type (byAnyType) — the operand's representation and declared types conforming to the target or classifying it as a composed type does, the same relation a cast and a feature write ask — and hastype is classifyValue by the value's own type alone (byOwnType), so rat : Rational = 4 answers rat hastype Integer true and rat hastype Rational false; a target the value's types leave open (Natural against a bare integer, Even :> Integer against 5) is false for istype, since nothing states the value is one — unless the target is an enumeration, whose enumerated values are the only instances it has (SysML v2 §8.3.7 EnumerationDefinition: "An EnumerationDefinition is an AttributeDefinition all of whose instances are given by an explicit list of enumeratedValues. This is realized by requiring that the EnumerationDefinition have isVariation = true, with the enumeratedValues being its variants"), so membership is decided by equality with them (classifyNarrower → enumeratedValue, over semantics.Model.LiteralsOf and each literal's evaluated value): with enum def Level :> Integer { low = 1; high = 3; }, 3 istype Level and 3 @ Level are true, 2 istype Level false, and a plain enum def Color { red; green; blue; } classifies by identity with its literals (3 istype Color false, Color::red istype Color true, no other enumeration's literal is a Color). hastype does not infer an enumeration for a bare scalar: §7.4.9.2 reads the value's direct type ("just directly", the pinned reference's release notes), a bare 3 is directly an Integer and nothing else, so 3 hastype Level is false and 3 hastype Integer true; an enumeration literal's own type is its enumeration, so Level::high hastype Level, lvl hastype Level for lvl : Level = Level::high and (3 as Level) hastype Level are true and Level::high hastype Integer false (Integer is a supertype, so Level::high istype Integer is true) — a scalar-valued literal evaluates to its assigned scalar (eval.go enumLiteralValue, so Level::high == 3 and Level::high + 1 == 4) carrying the literal's identity in its payload (value.go Value.ofLiteral/EnumerationLiteral, no field added), and valueTypes reads that identity before the library scalar lookup, as do the readers of a literal as an occurrence — a chain through it (chainMemberValue, assign_chain.go chainObject: Level::high.n) and the element a metadata test classifies (elementDenotedBy: Level::high @ Hot) — so a scalar-valued literal answers them as an unvalued one does — and identity (===, valueIdentical) tells the literal from the bare scalar it equals and from another enumeration's literal of that value, as it tells an Integer from an equal Real (Level::high === 3 is false, Level::high == 3 true); a value equal to none of an enumeration's literals whose literal values cannot be evaluated is that error, not false — and a sequence or set is classified elementwise — valuesClassified asks every element for istype and hastype and any element for @, so an empty value is true under the first two and false under @ (§7.4.9.2) eval_operator_test.go:TestTypeClassificationOperators (the reference's 20-case truth table case by case), :TestTypeClassificationFollowsSelectedVariant, conformance/w7d_type_classification.sysml + .expected.json (the same table over the library scalar types), robustness_test.go:type_classification_unresolved_type, :type_classification_undetermined_value_type, :enumeration_typed_feature_holding_an_unenumerated_value, :enumeration_whose_literal_value_cannot_be_evaluated, classify_test.go:TestEnumerationClassifiesByItsEnumeratedValues, :TestEnumerationTypedFeatureAdmitsOnlyEnumeratedValues, conformance/enumeration_value_classification.sysml + .expected.json (istype, hastype, @ and as over Level, Grade :> Real, Color and Even, a bare scalar, a literal, a feature holding one and a cast), pilot-exec-diff enumeration_classification.cases (23 cases, adjudicated per case in pilot-execution-referee.md: the twelve on a bare scalar's direct type, a literal's Integer supertype and value, (3 as Level) hastype Level and the plain Color agree; the eight the pilot answers otherwise — 3 istype Level false, and Level::high istype Level false because it folds the literal to its Integer — contradict §8.3.7 and the pilot's own cast-hastype-level, and the three casts draw no output), conformance/value_classification_at.sysml + .expected.json (n3 @ Integer, n3 @ Real, n3 @ String, seqInt @ Integer, car @ Vehicle beside the same subjects under hastype and the metadata forms belt @ Safety, belt @@ SysML::PartUsage), conformance/value_classification_shared_rule.sysml + .expected.json (istype, hastype, @ and as over an integer, a whole real, a quotient, a quantity, an enumeration literal, an object, a sequence, a mixed sequence, an empty value and a declared Even, each answering by the one rule), pilot-exec-diff w6d:istype-*, :hastype-*, at-*, real-at-integer, seq-at-integer, mixed-at-*, mixed-istype-integer, empty-at-integer, car-at-*, and the 27 scalar_classification.cases (whole-istype-integer, whole-hastype-rational, quotient-istype-integer, intdiv-hastype-rational, rational-feature-hastype-integer, infinity-hastype-positive, empty-istype-integer, …) ✅ Faithful — externally refereed: all 20 cases agree with the pinned reference, which they did not before (they were ours-error; the execution report moved from 31 to 51 agreeing of 94 cases), and the ten @ cases agree too (65 agreeing of 104): a : Integer = 3 answers a @ Integer and a @ Real true, a @ String false, and car : Car answers car @ Vehicle true, where before a @ Integer was reported as classifying no element. The referee settles what the direct type is: nat3 : Natural = 3 answers hastype Integer true, so it is the value's type and not the declaring feature's, w : Real = 4.0 answers w istype Integer false, w hastype Rational true and w hastype Real false, so a real is judged by its representation and never by its magnitude, and 6 / 3 istype Integer is false, a quotient being the Rational IntegerFunctions::'/' returns (§9.4.11.1), while car : Car answers istype Vehicle true and hastype Vehicle false. The one scalar case the pilot answers otherwise is nat : Natural = 7 under nat istype Natural — true here, false from the pilot, which reads the literal's type alone; a feature's values are instances of all its types (§8.3.3.3.4), so the declared type counts and the verdict stays (adjudicated in pilot-execution-referee.md). A mixed sequence (1, 2.5, 3) is false under istype Integer and true under @ Integer, and an empty one true under istype and false under @, as the reference answers (AtFunction tests any value, IsTypeFunction every one; mixed-at-integer, mixed-istype-integer, empty-at-integer, empty-istype-integer of scalar_classification.cases). A type operand that does not resolve is ErrUnresolvedType and a value whose direct type cannot be determined is ErrUndeterminedValueType — reported, never answered false. One local rule the referee does not cover: a feature declared [0..1] with no value classifies as the empty collection on an instantiated object, and at model level by what its declared types and count settle (cast.go classifyUndetermined: none : Integer[0..1] answers istype Integer true, @ Integer <undetermined>, @ String false)
Cast expressions evaluated at runtime (x as T; KerML 1.1 8.3.4.9 CastExpression, whose result is the values of x that T classifies, so it selects values and converts none — ToInteger and its siblings stay the library functions that convert) runtime/cast.go evalCast, castValue, castKeeps, declaredOperandTypes over runtime/classification.go classifyValue — the one classification istype, hastype, @ and a feature write (write_conformance.go valueConforms) answer from, so no two of them can judge the same value and target differently — and semantics/cast.go Model.ClassifiesTypes: the types a value is of decide the cast where they are enough, and where T is narrower than all of them the value's own content does (classifyNarrower): a scalar by its representation (representationClassifies, representationPrim: an integer is an Integer, a finite real a Rational whatever number it holds, an infinity a Real, a Complex a Complex on the real axis or off it — KerML 1.0 §8.4.4.9.2 — so nothing is judged by magnitude), a quantity by whether its unit is commensurable with the dimension T fixes, an object and an enumeration literal by the types they carry, a structured value (an array, a vector, a vector or tensor quantity, a measurement reference, a coordinate frame, a coordinate transformation) by the shape, units and frame reading write_conformance.go valueConforms already applies to a value written to a feature of that type — so a vector quantity of three axes is not a ScalarQuantityValue; Natural and Positive mark no evaluated value — no evaluation yields a value whose own type is either — so their bounds (§9.3.2.2.4, §9.3.2.2.7) refuse a negative integer, and zero for Positive (positiveScalar), and leave an in-bound integer no declaration types so undecided, exactly as Even :> Integer against 5 — where an enumeration target is decided by equality with its enumerated values (SysML v2 §8.3.7, the classification row above), so 3 as Level is the value Level::high, held with the literal's identity (castKept over Context.asEnumerated), 2 as Level is () and (1, 2, 3, 4) as Level is (1, 3); the ScalarValues type a scalar is of is read from the library rather than resolved by name in the scope reading it (scalarLibraryType), so a declaration wearing one of those names changes no cast's verdict; the type a value's own feature is declared with is a type it is of as well (declaredOperandTypes; §8.3.3.3.4, a feature's values are instances of all its types), so a custom scalar subtype and a scalar-valued enumeration keep the values declared with them, and a written sequence is cast entry by entry (castEntries), each entry judged by the types its own expression is declared with and none by another's, at any nesting depth and whatever number of values an entry holds, while Base::Anything — which every declaration implicitly specializes — states nothing and is left out (semantics.IsAnything); an expression written as a value is of the evaluation type the model reads it as (semantics/valuetype.go Model.ExprResultType, so a boolean body is a BooleanEvaluation); and a quantity type stating a measurement reference of its own measures every value of its dimension while one narrowing lengths by something else does not (semantics/dimension.go Model.FixesMeasurementReference); every type an operand is declared with counts and not only the first (semantics/operator_conformance.go Model.ExprResultTypes), so a cast to a feature's second type and a cast of a selection keep their values; a type composed of others classifies as those types do (KerML 1.0 §8.3.3): the values of a union are those of any of the types it unions, of an intersection those of every type it intersects, of a difference those of the first that are none of the rest, at any nesting depth and cycle-safely (semantics/cast.go Model.Classifies over classifiesComposed, semantics/model.go UnioningTypes/IntersectingTypes/DifferencingTypes, applied alike by the cast, by the static cast check Model.CastConformance and by an object's write conformance runtime/classify.go instanceConforms, so a cast to a composed type neither warns nor is refused by the feature it is written to); a composed target weighs all the types a value is of together (Model.ClassifiesTypes over instanceConforms and runtime/eval.go valueHasType), so an object held as a type a difference subtracts is none of its values and istype answers so too, whether that difference is the target, a type it specializes or one an intersection of it reaches (excludes), while hastype stays on identity with one of them; the static check asks whether the two types may share a value at all (Model.MayShareValues, which reads a source difference as the values of the first type that are none of the rest), weighing every type the operand is declared with together, so an operand typed by both a difference's first type and one it subtracts is called unrelated, so casting a union-typed operand to one of its members is not called unrelated either, while casting to a type composed of one the operand relates to — a member of a union, a type an intersection intersects, at any nesting depth — is not called unrelated; a composed target that the types a value is of leave open is read through its operands by the classification itself (classifyComposed), so a bare quantity is kept by the union operand whose measurement reference its unit matches and dropped by one that fixes another, and an operand the value settles nothing about is reported as undecided only where no other operand excludes the value outright, whatever order the operands are written in; and a complex value arithmetic left on the real axis stays the Complex ComplexFunctions return, so as Real drops it and re of it is the Real conformance calc_cast_scalar_values, calc_cast_sequence_elementwise, calc_cast_enumeration, calc_cast_quantity, calc_cast_structured, calc_cast_instances, calc_cast_qualified_target, calc_cast_declared_types, calc_cast_expression_value, calc_cast_complex_real_axis; semantics/cast_classification_test.go:TestClassifiesComposedTargets, :TestSubtractedTypeExcludesADeclaredValue, :TestMayShareValuesOfComposedTypes; passes/typecheck_operator_test.go:TestCastConformanceRelatedTypes, :TestCastConformanceUnrelatedTypes; runtime/eval_operator_test.go:TestClassificationWeighsEveryTypeOfAnObject; robustness_test.go:cast_to_an_unresolved_type, :cast_undecided_by_the_value, :cast_of_a_quantity_to_a_constrained_subtype, :difference_typed_feature_holding_a_subtracted_object; passes/w8d_metadata_usage_test.go:TestW8DMetadataClassificationValuesAreModelLevelEvaluable; conformance value_classification_shared_rule (the same values under as, istype, hastype and @); pilot-exec-diff integer-as-real, integer-as-natural, fraction-as-integer, whole-as-integer, sequence-as-integer, car-as-vehicle, car-as-car, int-as-rational, real-as-real and the scalar_classification.cases that fix the rule through istype/hastype ✅ Faithful (a value T classifies is answered unchanged, one it does not is the empty sequence, and a sequence is filtered element-wise in order — 4.0 as Integer and 2.5 as Integer are () — a finite real is a Rational whatever number it holds (§8.4.4.9.2), a cast converts nothing and RealFunctions::ToInteger is what converts — 4 as Rational is 4, (1, 2.5, 3) as Integer is (1, 3). A target no value's type and no value content settles — a user-defined specialization of a scalar or quantity type, such as Even :> Integer or RoomLength :> LengthValue, whose membership a bare 5 or 5 [m] does not state — is ErrUndecidedClassification, reported rather than answered as the empty sequence; read from a feature declared with that type (attribute e : Even = 4, attribute room : RoomLength = 4 [m]) the same value is kept, the declaration being what states which of the supertype's values it is. Classifying a value is model-level evaluable, so x = 1 as Integer in a metadata body is accepted (semantics/evaluable.go evaluableOperator reads the named type instead of folding the operand, as it does for istype/hastype), while an unresolved target or an operand that is not evaluable there is still refused. The cast keeps exactly the values istype affirms, read from the same classifyValue: r : Real = 4.0 answers r istype Integer false and r as Integer (), n : Integer = 7 answers n istype Natural false and n as Natural the typed ErrUndecidedClassification — no evaluation yields a value whose own type is Natural, so the bound alone can refuse (-1 as Natural is ()) but not affirm, as with 5 as Even — while nat : Natural = 7 answers nat istype Natural true and nat as Natural 7, the declaration stating it. The pinned reference draws no output at all for a cast but evaluates istype and hastype, and the 26 agreeing scalar_classification.cases fix the scalar rule the cast borrows (w : Real = 4.0 is no Integer and is a Rational; 6 / 3 is no Integer), so the cast is refereed through them. A scalar's own type is read from the library (castTypes, scalarLibraryType), so a cast decides the same way in a scope that writes ScalarValues::Integer out in full and imports nothing. An empty result is a result: it keeps the unit the source's elements measure in (sequenceFrom), so sum((5 [m], 2 [m]) as DurationValue) is 0 [m]; a feature the cast may drop everything from needs multiplicity [0..1], and return : Integer = r as Integer reports a multiplicity violation for r = 2.5 because a lower bound of 1 is unsatisfiable by ())
Extent expressions evaluated at runtime (all T; KerML 1.0 §7.4.9.2 Operator Expressions — the extent operator takes a type name and "evaluates to a sequence of all instances of the named type"; §8.2.5.8.1 ExtentExpression, Table 5 mapping all to BaseFunctions::'all', "Type extent", model-level evaluable "No"; §7.3.2.1 "the set of things classified by a type is the extent of the type"; §8.3.4.8.17 OperatorExpression; §9.4.2 abstract function 'all' { return : Object[0..*]; }). The typer gives all T the element type T and the multiplicity [0..*], so attribute xs = all T; is a collection an #, size or a chain reads (an alias resolves to its target; a usage's extent is typed by the usage's types), and judges its value element by element as an instance of T, as it judges any collection, where a collection binds (attribute flags : Boolean[*] = all Flags;); a condition all T is refused for every T (constraint expression must be Boolean, found the extent of Color, a sequence), a Boolean-typed T included, since the extent is a sequence and never the one Boolean the run-time condition reader takes, and so is an extent as the operand of a Boolean operator (not all Flags, all Flags and true, if all Flags ? 1 else 2), while an operation reducing it (size(all Flags) == 2) is judged by its own result; and all Car as String warns that the cast selects nothing. The runtime materializes objects on request, so no run holds the extent the spec's Object semantics describes in the abstract; the extent an evaluation answers is the run's: what the run has materialized and what its context reaches, in declaration order, less what destroy ended — a destroyed object is released from the extent while a feature still naming it keeps that value (see the object-lifecycle row of the Instantiation map). For a variation usage or definition it is the variants it declares, each the object that variant stands for (the object a selection of it would bind, materialized once per owner and reused by a later selection); for an ordinary definition or usage it is every object of the run classified by it — the objects materialized so far, the object usages every namespace of every document of the model declares — library packages included, so all Clock answers Time::universalClock — that may hold one, in document-name then declaration order (the order .metadata gives cross-file annotations), and the nested usages it types reachable from those, materialized as the extent is taken; the extent is of the type, not of what the expression's namespace imports or sees, so a usage in an unrelated, un-imported package counts as one beside the expression does — only the nested usages whose type may hold an object of T are materialized for it, the rest contribute what they already hold, so all Garage leaves the garage's tools unread and a failing read of them cannot end it (an object linked into a recursive composition is walked through the links a run wrote, and each object on the way has every feature that may hold a T read but the one that would create another object of a declaration already on the path — settled by the value's possible types where they agree, else by what reading it makes, a read making one being undone — so a composition recursing through one declaration is not materialized under itself again while the wheel of every Node a link reaches is, and a value choosing at run time between another Node and a Wheel contributes the wheel; one making both at once, (new Wheel(), new Node()), is undone and refused with ErrExtentUnavailable naming it where the wheel may lead to a T, since it can be kept neither whole nor in part); the result is charged to the run's element budget like any collection; for an enumeration it is its declared literals, each with its declared value; for any other data type — a scalar domain (all Integer, all String) or a structured one (attribute def Point { attribute x : Real; }, all Point) — it is a typed ErrUnboundedExtent, not an empty sequence and not the values the run's attributes happen to hold: a data value is not created by a run (KerML 1.0 §7.4.2: data types "classify things that do not exist in time or space", distinguished only by their feature values, so a Point is one for every Real its x may hold), and the value a run's origin : Point holds is one value of the type, not its extent. A nested usage the walk cannot materialize (one exceeding the element budget, say) ends the extent with that usage's typed error, never an extent short of it. A namespace-level object usage given a value (ref part car : Car = new Car();, part fleet = new Truck();, ref part alias : Car = spare;) is bound to that value for the run: a FeatureValue binds its feature to its expression's result through a BindingConnector (KerML 1.0 §7.4.11 Feature Values, §8.3.4.10.2 FeatureValue — "the result of the valueExpression is bound to the featureWithValue using a BindingConnector", §8.4.4.11 Feature Value Semantics), so the value is read once and every read of the usage — and all Car, before any read of it — yields the one object it denotes; a default value binds nothing at its declaration but is realized for any individual of the featuring type given no other value (§8.3.4.10.2), which at namespace level is the run alone, and an initial value (:=) binds at the start shot, which at namespace level the run's start is, so both are held for the run the same way, while a default nested in a definition stays what each object built from it reads at construction. A valued usage is reached for every type its value may yield — its declared type, its value's static types, or a subtype of either (part fleet = new Truck(); for all Car, not for all Boat; ref part lead : Car = cars#(1); for all Truck). A usage bound to an extent of its own type (ref part cars : Car[*] = all Car;) stands for no object while its value is being bound, and a usage whose value depends on it (ref part lead : Car = cars#(1);) for none yet, so the extent binds to the objects there are; a value reaching back to its own usage (ref part loop : Car = loop;) is refused as a cyclic feature value, and so is an extent that would read it. A binding is made whole or not at all: a value refused after constructing objects (ref part car : Car = new Boat();) leaves none of them nor their behaviors behind, however often the usage is read, so no extent counts them. A namespace-level object usage of several occurrences and no value (part wheels : Wheel[2];, item links : Link[3];, part hubs : Hub[1..*];) denotes its lower bound of objects for the run (§7.3.4.3 Multiplicities — a feature of multiplicity [2] has exactly two values; a [1..*] usage denotes one, a [0..*] or [0..1] usage holds nothing of itself and denotes none), materialized once and memoized as the [1] case is, through the same member materializer a collection nested in an object is filled with, so all Wheel reaches them in declaration order, before any read of the usage, a usage of exact count reads as the sequence (or set, as declared) of those objects, and wheels#(1), wheels.radius, size(wheels) read them, while one of open count (hubs, size(hubs), hubs.wheel) reads undetermined of that count, as a nested collection of open count does, since the run holds only its lower bound. A usage whose bound the model does not evaluate (part wheels : Wheel[2..n];, part hubs : Hub[n]; over a valueless n) fixes no count, so it denotes nothing — neither one object nor its lower bound: an extent that may reach it is refused with ErrExtentUnavailable naming the usage and its bounds (wheels declares [2..?] occurrences, a count the model does not fix), and a read of it stays undetermined of the bounds the declaration does fix, as a nested collection of unknown count does; a valued one (part wheels : Wheel[2] = (new Wheel(), new Wheel());) is bound to its value instead, so no anonymous object is made beside the bound ones, and a value whose count violates the declared multiplicity is refused with ErrMultiplicityViolation naming the usage, binding nothing. The lower bound is charged to the element budget before any object is made, so part many : Wheel[10000]; is refused with ErrMultiplicityViolation (the materialized-lower-bound cap) and a bound within the cap but over OPENSYSML_MAX_ELEMENTS with ErrElementLimitExceeded, each naming the usage; a member that cannot be constructed (its classifier behavior failing on entry, say) is that typed error naming the usage, and every case leaves no object, behavior or occurrence record behind, so a later read makes the collection anew or fails the same way, never an extent short of it. Known limitation: a package-level port (port link : Link;) denotes no object the runtime reaches — a port stands for an interaction point of the object holding it, and a namespace holds no object — so an extent it may contribute to (all Link) is refused with a typed ErrExtentUnavailable naming the usage rather than answered short; a port nested in a part is reached like any object, and an extent no such usage may contribute to is exact. A name resolving to nothing is ErrUnresolvedType; a name resolving to an element that is no type (a package, a dependency, a comment) or an operand that is no name is ErrTypeMismatch. all T is never model-level evaluable, so a filter or a metadata body value built on it is diagnosed rather than evaluated semantics/extent.go IsExtentExpr, ExtentTypeName, Model.ExtentOperand, Model.ExtentType, IsType, Model.extentTypes, ExtentRange; semantics/evaluable.go evaluableOperator (all is never model-level evaluable); semantics/valuetype.go ExprResultType, semantics/operator_conformance.go resultTypes, semantics/collection.go sourcesOf/valuesHeldBy/sourcesElements/judgeSources (an extent is a collection source of its own, judged by Model.instanceConformance and castTypesConformance over its instance types, so typing it never re-enters the type it names), semantics/valuetype.go operatorConformance (all judged through collectionConformance); runtime/extent.go EvalContext.evalExtent, literalValues, variantValues (over Context.variantValue), Context.objectsOf (over Context.heldObjectsOf in runtime/condition.go, which reads only the features that may hold a target, Context.mayHold, and would not create an object of a declaration on the path, Context.createdTypes, takes what the others already hold, and returns a feature that cannot be read as the error it is) over EvalContext.extentRoots (the objects the run holds and the model's namespace usages that may hold one: Context.modelUsages walks the index's documents once per Model into a usageCensus, Context.extentCandidates judges it once per run and type, namespaceObjectUsage, EvalContext.boundObjects, Context.mayHold over Context.givenValue; Context.denotedObjects over Context.occurrenceOf for one occurrence and Context.occurrencesOf for several — Context.namesObjects, Context.lowerBoundCount, Context.materializeMembers, the same helpers materializeIntrinsic fills a nested collection with, recorded in Context.occurrences as the ordered objects each usage denotes; a port at namespace level that may hold one refuses the extent, Context.undenotedUsage), Context.isOf; runtime/instance.go Context.denotedValue (a read of the usage), runtime/undetermined.go Context.declaredCountRefusal (an object usage's value judged against its declared count); runtime/eval.go EvalContext.declaredValue (Context.bindingNamespace, CyclicBindingError), Context.bindNamespace (Context.namespaceBindings, one binding per run, undone with a probe, dropped with an abandoned object by classifier_behavior.go abandonInstancesBetween, carried into a re-analysis with the object by adopt.go adopter.carryDerived while everything the value read still reads as it did — each declaration's text, each name looked up by the declaration it now denotes, each type judged by its hierarchy and members, and the census of namespace usages an extent walked (binding_reads.go noteCensusRead), so a nearer declaration shadowing a name, a supertype added in another document or an object usage declared in any document reads the binding again); runtime/errors.go ErrUnboundedExtent, ErrExtentUnavailable semantics/extent_test.go:TestExtentExpressionTypes, :TestExtentExpressionConformance; semantics/evaluable_test.go:TestModelLevelEvaluable; passes/typecheck_expr_test.go:TestExprExtentConditionMustBeBoolean (conditions, guards and Boolean operators alike), passes/typecheck_operator_test.go:TestCastConformanceExtent; passes/filter_test.go:TestFilterExtentIsNotModelLevelEvaluable, passes/w8c_metadata_annotation_test.go:TestMetadataBodyValueRejectsAnExtent; conformance extent_variation_variants.sysml, extent_definition_objects.sysml, extent_enumeration_values.sysml, extent_unbounded_data_type.sysml (all Integer, all String, all Point beside an origin : Point the run holds), extent_declaration_order.sysml + .trace.golden (an enumeration's values and a variation's variants in declaration order, whatever order names them elsewhere), extent_namespace_objects.sysml + .trace.golden (package-level usages materialized as the extent is taken, roots then nested objects in declaration order, another package's usage among them), extent_enclosing_namespaces.sysml (usages of every enclosing package and of a sibling package reached before any run read them), extent_imported_and_unimported_packages.sysml (a usage in an imported package and one in an un-imported package both count), extent_library_clock.sysml (all Clock and all Item answer Time::universalClock; the library's [0..*] collections neither count nor refuse), extent_across_documents.sysml + .depot.sysml + .trace.golden (a usage of another document reached, and first, its document name sorting before the expression's), extent_linked_objects.sysml (objects of one declaration linked by a run's assignments walked along the links), extent_linked_repeated_declaration.sysml (a wheel under every one of the linked objects reached, in depth-first feature declaration order), extent_value_chosen_at_run_time.sysml (a value choosing a wheel over another Fork at run time contributes it; one choosing the Fork is left unread), robustness extent_through_a_value_recursing_and_not (a value making a wheel and another Fork at once is refused for all Wheel, undone, and left unread for all Seat), extent_bound_namespace_objects.sysml + .trace.golden (valued usages bound once: car === car, all Car reaching them before any read), extent_namespace_collection.sysml + .trace.golden ([2], [3], [1..*] and [0..*] usages: all Wheel counting the two beside a [1] usage, the usage read directly, indexed, chained through and sized, members created in declaration order once and identical on every read), extent_bound_namespace_collection.sysml + .trace.golden (a valued [2] usage bound to its two objects, no third made; a ref part bound to its members denoting those), extent_bound_namespace_collection_count.sysml (a [2] usage whose value yields three refused with ErrMultiplicityViolation); adopt_test.go:TestAdoptRebindsAnExtentWhenItsNamespaceChanges (a usage added to the extent's own document or to another rebinds it); extent_test.go:TestNamespaceBindingDenotesOneObject (=, an untyped value, an alias, default and := at namespace level each read twice as one object, a nested default read at construction), :TestNamespaceBindingProbeIsUndone, :TestNamespaceBindingFailureLeavesNoBinding, :TestNamespaceBindingFailureLeavesNoObject, :TestNamespaceBindingToAnExtent, adopt_test.go:TestAdoptCarriesANamespaceBinding; element_budget_test.go:TestElementBudgetBoundsExtents; robustness_test.go:extent_of_an_unresolved_or_unbounded_type (an unresolved name, a scalar and a structured data type, a package, a dependency, a comment), extent_reaching_a_namespace_collection (a [2] usage's two objects counted beside a nested one, the usage read as those two objects and a [1..*] usage read undetermined, a [0..*] usage contributing none, the ErrExtentUnavailable refusal for a port at namespace level, and the same objects on a second evaluation), namespace_collection_that_cannot_be_constructed (a [2] usage whose members' classifier behavior fails on entry is that typed error naming the usage, leaving no object, behavior or occurrence record, however often it is read), namespace_collection_over_budget ([10000] refused by the lower-bound cap, [5] over the element budget, each leaving nothing behind), extent_over_an_object_that_cannot_be_read (a nested usage over the element budget ends the extent with ErrElementLimitExceeded, and is not read for the extent of a type it cannot hold), extent_reaching_a_far_usage_that_cannot_be_read (a usage of another document whose value its type refuses ends the extent with ErrTypeMismatch naming it and is not read for a type it cannot hold, while a [2] collection of another document is counted), extent_over_far_usages_under_the_element_budget (usages of two other documents materialized in document order, the second ending the extent with ErrElementLimitExceeded naming it and its collection; a budget covering both answers), extent_over_recursive_composition (a part of its own type, a constructor of it and two types holding each other each end, every object created having its own wheel read); cmd/sysml/run_trade_study_corpus_test.go:TestRunTradeStudyPilotTradeOff; compiled: repl/compile_test.go Refused::Extent ⚠️ Approximate — the spec's extent is every instance of the type; this evaluator answers the instances the run holds or reaches — every namespace-level object usage of the loaded model, the objects those hold, and what the run made — so a usage nested in a definition nothing instantiates (part def Garage { part car : Car; } with no Garage usage or object) contributes no Car, a document not loaded into the index contributes nothing, two runs materializing different objects answer different extents of the same definition, and the order is document-name then declaration order, which the spec leaves to the sequence (adjudicated in pilot-execution-referee.md). Behavior change: the extent used to reach only the usages of the namespaces lexically enclosing the expression, so size(all Car) in a package importing Gaps under-counted Gaps::car; it now reaches the whole model. Behavior change: a namespace-level valued object usage used to be evaluated anew on every read, so car read twice was two Cars; it now denotes one object for the run, as the binding the spec makes of its value. The variation, enumeration and unbounded cases are the finite extents the model itself fixes and are ✅ faithful. Behavior change: a namespace-level usage of several occurrences used to denote no object, so an extent it might contribute to was refused with ErrExtentUnavailable and a chain through it was undetermined; it now denotes its lower bound of objects, as a collection nested in an object does. An extent a package-level port may contribute to is still refused, not answered, until the runtime denotes what such a port stands for. The library types the result Object[0..*]; an enumeration's literals are answered as its extent because they are the only instances it has. Native code generation refuses all with a typed operator 'all' refusal (native-compilation.md)
Meta-cast expressions evaluated at runtime (x meta T; KerML 1.0 §7.4.9.2 MetaCastExpression, the shorthand for x.metadata as T, so the result is the metadata of the element x names — not of its values — that T classifies: its metadata annotations whose type conforms to T, in model order, then its reflective metaobject when the element's own metaclass conforms to T (§8.3.4.8.15, §8.4.4.9.7); the empty sequence when neither does, so seatBelt meta SysML::PartDefinition is () for a part usage). The metaobject is a value of its own kind (ValMetaobject): the element together with the metaclass that classifies it — KerML.kerml's and SysML.kerml's reflective metaclasses — not a runtime object of that metaclass, so no derived feature is fabricated. Its identity is the element's: (x meta KerML::Type) === (x meta KerML::Feature), == likewise, and two evaluations answer one metaobject. A feature of the metaclass read off it (.declaredName, .qualifiedName, .ownedFeature, .ownedMember, .type, .direction, .isAbstract, and every side-table property an element filter classifies by — name, shortName, documentation, isComposite, isDerived, isEnd, isOrdered, isUnique, isVariable, isConstant, isPortion, isSufficient, …) is derived from the element's declaration through the ordinary member-access path, shaped by the feature's declared multiplicity (one value under [0..1]/[1], a sequence otherwise), an element-valued one answering metaobjects (Element::documentation is Documentation[0..*], so each doc comment is a metaobject whose own Comment::body and Comment::locale are the strings, the locale unquoted; qualifiedName of an unnamed element such as a doc comment is (); Dependency::client and Dependency::supplier are the from and to elements in declaration order, and TextualRepresentation::representedElement — declared subsets owner — is the owning element, with language and body its strings) and direction a FeatureDirectionKind literal; a metaobject cast to a supertype is read by that supertype's names even where a SysML metaclass redefines them (definition for a usage's type). A feature the metaclass declares but the runtime does not derive (ownedRelationship, …) is ErrReflectiveFeatureUnsupported naming the feature and the element, as is Comment::body of a model never given its notation (SetSourceText) rather than an empty string; a name the metaclass does not declare is the ordinary missing-member error; a subject that is a value rather than an element (1 meta KerML::Feature) is semantics.ErrFilterUnevaluable; an unresolved x or T is ErrUnresolvedReference. Metaobjects cross ===/==, set membership and canonical set order, trace and FormatTraceValue (meta(test::seatBelt : SysML::Systems::PartUsage)), carry and adoption (both symbols rebound in the adopting model) and the gRPC boundary (the metaobject_values row under gRPC boundary); native compilation refuses them (docs/project/native-compilation.md). @@, @ and the static SemanticMetadata::baseType reading of a meta cast are unchanged runtime/metaobject.go evalMetaCast, metaCastSubject, reflectiveMetaobject, metaobjectFeature, reflectiveResult, metaclassFeature, reflectivePropertyNames; runtime/value.go ValMetaobject, NewMetaobject, MetaobjectText; runtime/eval.go (OpMeta dispatch, elementDenotedBy, chainMemberValue); runtime/value_equality.go, set_order.go, describe.go, trace.go, adopt.go, carry.go, classification.go; semantics/annotations.go MetaclassOf, ReflectiveElements, ReflectiveDirection, ReflectiveFeatureValues, reflectiveCommentBody; semantics/documentation.go commentBody; semantics/members.go MembersOfIncludingRedefined, LookupMember conformance meta_cast (annotation then metaobject, () for a non-instance, every listed feature, identity across casts, documentation metaobjects with .body — "" for a blank doc /**/, since Comment::body is String[1..1] — .owner, .qualifiedName and .locale; a dependency and a rep read through .metadata and meta KerML::Element, with .client, .supplier, .language, .body and .representedElement), meta_cast_trace + meta_cast_trace.trace.golden; value_kinds_test.go (every exhaustive dispatch holds a metaobject sample; a set orders metaobjects by element, as equality does); robustness_test.go:meta_cast_failure_modes; repl/compile_test.go (MetaCast refused); tools/referee/exec metadata_access.cases refereed against the pilot (docs/project/pilot-execution-referee.md) ✅ Faithful
An operator with no runtime evaluation (bitwise complement, and the one the runtime evaluates from its own expression node) reports why eval.go unimplementedOperators (ErrUnsupportedOperator) eval_operator_test.go:TestUnimplementedOperatorReportsWhy ⛔ Deliberate divergence (bitwise complement: KerML 1.0 §8.2.5.8.1 Table 5 marks ~ "Undefined" and not model-level evaluable — DataFunctions::'~' is abstract and no library the runtime applies specializes it, the pinned pilot leaves OperatorExpression ~ unevaluated, and answering -6 for ~5 would invent a two's-complement semantics nothing states; the runtime keeps the typed refusal and the checker warns on every use (undefined-operator, row below) — adjudicated in bitwise-complement.md. OpIndex is evaluated from an IndexExpression rather than as an operator, so reaching it as one says that. Classification istype/hastype, metadata @/@@, the as cast, the meta cast and the all extent are evaluated — see the rows around this one)
Metadata classification evaluated at runtime (p @ Safety, p @@ SysML::PartUsage, in a constraint, a calc body or an %eval; SysML v2 7.9.4 metadata, 8.4.4.2 ClassificationExpression) eval.go EvalContext.evalClassification — reached by @@, by @ with no subject, and by x @ T when T is a metadata definition or metaclass (semantics.IsMetadataType), since only an annotation has such a type; x @ T with an ordinary type is the value test of the row above — over semantics/filter.go Model.EvalClassification (the same compiled predicate and Model.AllSupertypes conformance an element filter is decided by, so the two paths cannot disagree); eval.go classifiedElement settles the element the subject denotes — the object being evaluated for an implicit subject or self, the element a name names, or an object's classifier, a selected variant or an enumeration literal runtime/eval_classification_test.go (TestEvalClassificationOverNamedSubjects, TestEvalClassificationMetaVersusAnnotation, TestEvalClassificationInACalcBody, TestEvalClassificationOfTheObjectBeingEvaluated, TestEvalClassificationAgreesWithAnElementFilter — the verdicts of the two paths pinned equal); conformance/filter_classification_annotation_forms.sysml, filter_classification_meta_versus_annotation.sysml, filter_classification_implicit_subject.sysml, view_exposed_element_classification.sysml; robustness_test.go classification_outside_the_evaluable_subset; semantics/classification_test.go (the shared predicate itself) ✅ Faithful (@T holds for metadata annotating the subject in every form the parser accepts, inherited conformance included, and for the subject's own metaclass; @@T holds for the metaclass alone. A subject that denotes no element, or a metadata type that does not resolve, is semantics.ErrFilterUnevaluable — reported, never answered false)
lower..upper is the ordered sequence of integers the library declares it to be (IntegerFunctions::'..' returns Integer[0..*] ordered, and SequenceFunctions::subsequence maps over it), so every sequence operation, index and for applies to it unchanged. A descending range is empty, a bound that is not an Integer is a type error, and each element generated costs a step and an element of the budgets, so a range wider than either — including one whose width overflows — is ErrStepLimitExceeded or ErrElementLimitExceeded rather than an allocation runtime/range.go evalRange/rangeSequence/rangeBound/builtinIntegerRange (ErrTypeMismatch), registered in builtins.go calc_integer_range.sysml, range_test.go:TestIntegerRange, :TestIntegerRangeSequenceOperations, :TestIntegerRangeNonIntegerBound, :TestIntegerRangeSpendsTheStepBudget, :TestIntegerRangeExtremeBounds, :TestForOverIntegerRange, robustness_test.go:testRangeBoundIsNotAnInteger, :testRangeSpendsTheStepBudget ✅ Faithful
Unary operators (not, -, +) eval.go evalUnary calc_unary_operators.sysml ✅ Faithful
Type coercion (Integer→Real) eval.go:344 toReal calc_type_coercion.sysml ✅ Faithful
Qualified names (A::B::C) eval.go + resolve/ calc_qualified_names.sysml ✅ Faithful

A calc usage with several out features, a usage nested in a part or in a calc def's body, and a chain into one (c.a) are all existing productions — a directed feature member of a usage body and FeatureChainExpr — so multi-output calc usages added no grammar and no golden AST fixture of their own. calc_defaults_and_invocation.sysml (a typed calc usage binding an inherited parameter in its body) and calc_statement_body.sysml lock the parse structure they reuse.

Analysis Case (SysML v2 §7.22 Analysis Cases, §7.21 Cases; Systems Library/Cases.sysml, AnalysisCases.sysml)

An analysis case is a Case, a Case is a Calculation, and a Calculation is an Action, so running one is running an action whose result is its out and return parameters. The runtime runs a case through the calc-usage machinery (calcShape/calcRun) with the body's action nodes lowered as one lower.Block over the ActionGraph they state and performed by the action executor (calcStmtHost with performances), the subject as an in parameter, and the objective evaluated afterwards by the requirement engine. No analysis-specific executor exists, and none for trade studies either: TradeStudies::TradeStudy runs through the same path by evaluating the library's own expressions (see Trade studies below). %optimize reads the same declarations through analysis.go and answers a different question, the boundary stated under Objective optimization.

Semantic Rule Implementation Test Case Status
An analysis definition or usage is a performable calculation: RunAnalysis runs it and answers its outputs; -calc/%calc/EvaluateCalc refuse it by kind, saying to run it as an analysis runtime/analysis_run.go Context.RunAnalysis, Context.analysisRun; runtime/analysis_run.go Context.RequireAnalysisCase, runtime/errors.go ErrNotAnAnalysis; runtime/calc_usage.go isCalcUsageSymbol, Context.checkCalcTyping analysis_subject_bound_in_usage.sysml, analysis_robustness_test.go:not_an_analysis, repl/analysis_test.go:TestAnalysisErrors, grpc/analysis_test.go:TestRunAnalysisFailures ✅ Faithful
subject s : T; is an in parameter of the case: a usage binding it (subject s = ship;) supplies the object, the object a run is asked on supplies it otherwise, and a case run with neither is refused naming the subject — never run with an empty one runtime/analysis_run.go subjectDeclaration, Context.subjectParameter, calcShape.subjectParameter, calcShape.analysisArgs, calcShape.unboundSubject analysis_subject_bound_in_usage.sysml, analysis_subject_at_run_time.sysml, analysis_robustness_test.go:unbound_subject, :subject_of_the_wrong_type, repl/analysis_test.go:TestAnalysisDefinitionOnAnObject ✅ Faithful
A nested case binding no subject of its own runs on the enclosing case's subject (§7.22 AnalysisCase, the pilot's 10a-Analysis.sysml massAnalysisCase comment) runtime/analysis_run.go Context.enclosingSubject, enclosingBehavior analysis_nested_default_subject.sysml ✅ Faithful
in parameters are bound positionally or by name from the arguments, or from their declared defaults; the subject is never a positional parameter; a missing value, a surplus argument and an unknown name are typed refusals runtime/analysis_run.go calcShape.analysisArgs; runtime/invoke_calc.go bindCalcParameter analysis_in_parameter_positional.sysml, analysis_in_parameter_named.sysml, analysis_robustness_test.go:missing_in_parameter, :too_many_arguments, :unknown_named_argument ✅ Faithful
The body's action nodes — owned action usages, perform steps and nested analysis usages — are the steps of the case, lowered as one Block over the ActionGraph they state: then successions, first, forks, joins, decisions and merges through ToActionGraph (Block.Stated), and declaration order where the body states no succession, as a calc body orders its steps lower/case_body.go PerformsSteps, caseSteps, CaseFlowStart; lower/calc_body.go CalcBody; lower/block_graph.go IsCaseNode, lowerBlockFlowWith, recordNodeScope analysis_steps_then_sequenced.sysml (+ .trace.golden), analysis_steps_first_then_reorders.sysml (+ .trace.golden), analysis_step_perform.sysml, analysis_step_typed_action.sysml, analysis_nested_step_then.sysml ✅ Faithful
Each step is a subperformance run by the action executor, and a later step reads an earlier one's output by step.pin; a nested analysis usage runs as a step through the same dispatch, its outputs read as features of it runtime/calc_statements.go calcStmtHost.attachPerformances, calcStmtHost.performNode, calcStmtHost.runFlow, calcStmtHost.assignAround; runtime/action_statements.go performances.performNode; runtime/action_executor.go (token traversal, data flow, deadlock detection, step budget) analysis_steps_then_sequenced.sysml, analysis_nested_step_then.sysml, analysis_nested_default_subject.sysml, analysis_robustness_test.go:failing_step, :step_budget, :deadlocked_body, :cyclic_successions, :flow_with_no_start ✅ Faithful
A step waiting on the clock (action sail accept after 60.0 [s];, accept at) advances the run's time as an action's does: a case run on its own (RunAnalysis, RunVerification, -analysis) drives the clock through its flow, which is on the clock for the run and off it afterwards, whether the run ends or fails; a case an action body performs as a step (verification run : Scene;) pauses that body, whose executor lists the case's waits among its own and resumes the step when the instant comes, so a wait for a message nothing posts deadlocks the performing action (ErrAcceptDeadlock) and a wait under a behavior holding the clock is refused (ErrStateBehaviorWaits). An expression reading a case's output (assign x := station.voyage.total;) takes the case whole, so a wait under it is the typed ErrCaseReadWaits naming the wait, the clock left where it was runtime/calc_usage.go Context.runCalcUsage (a run with no body to pause puts the flow on the clock), Context.finishCalcUsage, Context.caseReadWaits; runtime/case_step.go performances.performCase, caseStepFrame; runtime/action_body_run.go bodyWait.waiter, Token.pausedWaiter, Token.hostedFlow; runtime/action_executor.go ActionExecutor.pausedWaiters, hostedFlows, armedWaits, visibleArmedWaits, hasDuePausedWork; runtime/snapshot.go executorCaptures.captureBody conformance analysis_steps_wait_on_clock.sysml + trace golden, action_case_step_waits_on_clock.sysml + trace golden; robustness_case_clock_wait_test.go:TestRuntimeRobustnessCaseClockWait ✅ Faithful
return x = expr;, the body's trailing result expression, and out p : T = expr; are evaluated in the case's frame after the steps complete, reading the subject, the in parameters, the steps' outputs and calling calc defs; units are preserved; an output never bound, one reading itself and outputs reading each other are typed refusals runtime/analysis_run.go calcRun.outputValues; runtime/calc_usage.go calcRun.value, calcRun.output, calcShape.resultOutput analysis_return_vs_out.sysml, analysis_units_preserved.sysml, analysis_robustness_test.go:output_never_bound, :usage_reading_its_own_output, :outputs_reading_each_other ✅ Faithful
The objective is a requirement the case frames, evaluated after the body: its require/assume constraints are checked against the case's results by the requirement engine and reported as satisfied, not satisfied (with the condition) or undecided (with the reason), never executed as a step; an assert constraint in the body is checked the same way, after the body runtime/analysis_run.go Context.analysisVerdicts, Context.analysisVerdict, assertionName, calcRun.bindings, VerdictStatus, AnalysisVerdict; the condition checks of runtime/requirement.go analysis_objective_satisfied.sysml, analysis_objective_not_satisfied.sysml, analysis_objective_undecided.sysml, analysis_assert_constraint.sysml ⚠️ Approximate — the objective's own, its body's and its inherited definition's conditions are checked (ObjectivesOf), and every asserted condition of the case is checked once the whole body has completed, against the values the run bound, rather than at the body position it follows; the two differ only where a later step reassigns a case local the assertion reads
An objective typed by a requirement def is a requirement usage (§8.3.22, the RequirementUsage an ObjectiveMembership owns): its own members bind the def's subject before its conditions are checked, through the requirement engine's member binding — by keyword alone (subject = ship;, an *ast.SubjectMember), by name (subject s = ship;) or by redefinition (subject :>> s = ship;) — and the binding reads the case's subject, in parameters, locals and its steps' outputs, an action step's pins (weigh.m) included; a binding that cannot be evaluated leaves the objective undecided naming it runtime/analysis_run.go Context.objectiveBindings; runtime/context.go Context.memberBindings (the enclosing bindings the case run supplies); semantics/redefinition.go behaviorLike (an objective's subject implicitly redefines the def's) analysis_objective_subject_keyword.sysml, analysis_objective_subject_keyword_violated.sysml, analysis_objective_subject_action_step_output.sysml, analysis_objective_binding_test.go:TestObjectiveSubjectBinding, :TestObjectiveSubjectBindsAnActionStepOutput, :TestObjectiveBindingFailureIsUndecided, :TestObjectiveSubjectBindingOnAnObject, cmd/sysml/run_test.go:TestRunAnalysis ✅ Faithful
An objective subject nothing binds holds the case feature the library's objective states for it, read through the redefinition chain the objective's subject folds (obj :>> Case::obj, its subj redeclared by the requirement's own subject): in an analysis case that is the case's result, since Cases::Case::obj declares objective obj : RequirementCheck { subject subj default Case::result; } (§7.22, Cases.sysml) and no redefinition restates it — the objective checks the value the case returns; a result of another type is undecided naming the mismatch (subject s defaults to the case's result (Cases::Case::obj): type mismatch: 1000.0 (a Real) is not a Ship), one that breaks the subject's multiplicity (Ship[2] given one Ship, or two given Ship) is undecided as a multiplicity violation — a redeclaration stating none (subject :>> pair;) keeps the multiplicity and type it redefines (KerML §7.3.4.5) — and a case returning nothing leaves the subject unbound, the verdict saying to bind it or return a result. Once the result passes, its objects are classified by the subject as a declared feature value's are (KerML §7.3.4.1, classifyHeld), so a Ship returned to a subject t : Tanker answers t.cargo; a subject or actor bound by an expression (subject = ship;) is admitted and classified the same way, in an objective as in a requirement usage — a value the member cannot hold refused as a type mismatch, more or fewer values than it declares (folded along what it redefines, subject :>> pair;) as a multiplicity violation runtime/analysis_run.go Context.objectiveBindings, Context.unboundObjectiveSubject, Context.statedSubjectValue, Context.statedCaseMember, statedAsDefault; runtime/calc_usage.go calcShape.memberName, Context.calcMemberNames; runtime/context.go Context.memberBindings, Context.holdBound, Context.holdAs; runtime/invoke_calc.go calcMemberDecl.admits, Context.boundMemberDecl; runtime/errors.go UnboundSubjectError (kind objective) analysis_objective_subject_default.sysml, analysis_objective_subject_default_mismatch.sysml, analysis_objective_subject_default_multiplicity.sysml, analysis_objective_subject_redeclared_multiplicity.sysml, analysis_objective_subject_classified.sysml, analysis_objective_subject_bound_multiplicity.sysml, analysis_objective_subject_bound_pair.sysml, analysis_objective_subject_default_over_case_subject.sysml (an analysis case with a subject of the requirement's type still checks its result), analysis_objective_binding_test.go:TestObjectiveSubjectDefaultsToTheResult, :TestObjectiveSubjectDefaultHonoursMultiplicity, :TestObjectiveBindingKeepsErrorIdentity, :TestBoundSubjectIsClassifiedByItsType, :TestBoundSubjectHonoursMultiplicity ✅ Faithful
A case's result parameter is a feature of the case named by qualified name: MassAnalysisCase::result (§7.22, the OMG examples' objective : MassAnalysisObjective { subject = MassAnalysisCase::result; }) denotes the run's result whether the case names it (return m : Real = ...) or not (a trailing expression, return : Real = ...), and equally Cases::Case::result inherited from the library; in an objective's subject binding, an assert constraint in the body, and as inner.result read from the case performing inner as a step; the qualifier decides whose run it is — the running case or one it specializes reads that run, while a sibling definition's Other::result never reads the running case's value and a sibling usage's light::result is the sibling's own run runtime/eval.go EvalContext.frameFeatureValue (a qualified feature reads the run whose calc the qualifier names or is specialized by); runtime/calc_usage.go calcShape.qualifiedBy, calcShape.memberName, Context.calcMemberNames (declared member → the name the run binds; the library's result → the run's designated or synthetic result), calcShape.anonymousResult; runtime/frame.go frame.owner analysis_objective_subject_qualified_result.sysml, analysis_objective_subject_qualified_result_violated.sysml, analysis_nested_step_implicit_result.sysml, analysis_objective_subject_sibling_result.sysml, analysis_objective_binding_test.go:TestObjectiveSubjectBindsTheQualifiedResult, :TestQualifiedResultNamesItsOwnCase, cmd/sysml/run_test.go:TestRunAnalysis ✅ Faithful
A case usage owned by a part definition is a feature of every object of the type, as a calc usage owned by a part is: holder.inner.total runs the case on first read, memoized per activation and invalidated when a value it read changes; an attribute redefined from an analysis output (attribute :>> x = a.result;) evaluates through the same path; a package-level usage's outputs are read the same way (An::shipCost.total) runtime/calc_usage.go EvalContext.evalCalcUsageMembers, Context.calcUsageRun, Context.forgetCalcUsage, Context.enterCalcUsage; runtime/analysis_run.go Context.RunAnalysis (usage-bound path) analysis_read_from_attribute_redefinition.sysml, analysis_subject_bound_in_usage.sysml, cmd/sysml/run_test.go:TestRunAnalysis, repl/analysis_test.go:TestAnalysisUsageRuns ✅ Faithful
A case that runs itself, directly or through a step, is refused naming the case rather than recursing; a case recursing without bound through a nested analysis step, or a calc def through a calc usage member typed by itself, hits the depth limit with one line collapsing the repeated frames to a count (analysis An::Rec::again: … 9999 frames: …), as a calc def calling itself does, and still errors.Is the limit runtime/calc_usage.go Context.enterCalcUsage, ErrCalcUsageRecursion, Context.runCalcUsage and calcRun.value (framed by calcFrame); runtime/errors.go CalcFrameError, calcFrame; runtime/invoke_calc.go ErrCalcRecursionLimit analysis_robustness_test.go:self_recursion, :recursion_through_a_step, :recursion_through_a_calc_usage, :usage_reading_its_own_output ✅ Faithful
%analysis <name>[(<args>)] [<object>] and the repeatable -analysis run a case, printing its outputs with units and each verdict; a usage owned by a part runs as a feature of the session's object for that part; the exit status is 0 satisfied, 1 not satisfied, 2 undecided or failed repl/analysis.go Session.doAnalysis, Session.analysisVerdict, Session.runAnalysis, splitAnalysisArgs, parseAnalysisArgs; repl/lookup.go Session.owningInstance; repl/run.go Session.RunAnalysis; repl/meta.go; cmd/sysml/check.go (the -analysis loop), cmd/sysml/usage.go; packaging/man/man1/sysml.1 repl/analysis_test.go (all), cmd/sysml/run_test.go:TestRunAnalysis, cmd/sysml/usage_test.go, make man-check ✅ Faithful
RunAnalysis RPC: symbol_id, optional subject_symbol_id, positional arguments and named_arguments; answers outputs, verdicts (satisfied / not satisfied / undecided as holds + error), the subject's instances, and a failure_reason on refusal (WRONG_KIND, AMBIGUOUS_SUBJECT, EVALUATION); exposed in the Connect adapter, the Go client (Client.RunAnalysis, Subject, Arguments, Argument) and the Python client (Model.run_analysis, AnalysisResult) grpc/analysis.go Service.RunAnalysis; grpc/connect.go; client/opensysml/analysis.go, client.go, inprocess.go, remote.go; client/python/opensysml/model.py, connection.py, verdict.py; api/proto/sysml.proto (generated code by make proto-buf, make python-proto, make proto-ts, make proto-rust) grpc/analysis_test.go (all), grpc/capability_test.go, conformance/scenarios/13-run-analysis.json (8 scenarios over grpc, connect-json, pkg, pkg-connect), client/python/tests/test_analysis_integration.py, client/python/tests/test_verification.py ✅ Faithful — over the RPC a case owned by a part has no session object to belong to, so it runs against a fresh object of that part; the REPL and CLI run it on the object %instantiate/-instantiate created
A verification case body shares the grammar and runs the same way: RunAnalysis, %analysis and -analysis accept a verification definition or usage and run it through the same lowering, subject and in binding, steps and objective checks, reporting in addition the verdict its body produced (see the Verification Case map) lower/case_body.go PerformsSteps, lower/block_graph.go IsCaseNode; runtime/analysis_run.go Context.runCase; runtime/verification_run.go Context.RunVerification conformance verification_verdict_pass, verification_verdict_fail (+ verification_verdict_pass.trace.golden), runtime/verification_verdict_test.go, repl/verification_verdict_test.go:TestAnalysisRunsAVerificationCase, cmd/sysml/verification_test.go:TestRunVerification, grpc/verification_verdict_test.go:TestRunAnalysisRunsAVerificationCase ✅ Faithful

Trade studies (SysML v2 §7.22, §8.3.22; Domain Libraries/Analysis/TradeStudies.sysml)

The library writes a trade study's semantics as ordinary expressions over a case's features, and the runtime evaluates those expressions as written rather than recognizing TradeStudy by name: subject studyAlternatives : Anything[1..*] is the case's subject; abstract calc evaluationFunction : EvaluationFunction is the calc a model redefines with a body; tradeStudyObjective : TradeStudyObjective { subject :>> selectedAlternative; in ref :>> alternatives = studyAlternatives; in calc :>> eval = evaluationFunction; } binds the calc as a function value; MinimizeObjective/MaximizeObjective bind best = alternatives->minimize {in x; eval(x)} (maximize) and require constraint { eval(selectedAlternative) == best }; and the case returns studyAlternatives->selectOne {in ref a {} tradeStudyObjective(selectedAlternative = a)}. The rules below are the general ones the library expressions needed and the runtime lacked; each is stated as a rule of the language, and none tests for the trade-study library.

Semantic Rule Implementation Test Case Status
A domain library's calc (TradeStudies::TradeStudyObjective::eval, EvaluationFunction) is executed from its text as a model's calc is; only the semantic libraries' metamodel frame and the Kernel functions the runtime implements natively are realized rather than executed runtime/invoke_calc.go Context.frameDeclared; symbols/library_tier.go LibraryTier.Semantic analysis_trade_study_minimize.sysml, analysis_trade_study_maximize_definition.sysml ✅ Faithful
in calc :>> eval = evaluationFunction; binds a calc-typed parameter to a calc held as a value — the case's own evaluationFunction, abstract or bodied — and eval(x) in an expression the objective inherits applies it; the objective records the calc eval is bound to hold (Objective.Evaluates) runtime/analysis.go Context.objectiveOf, Context.calcHeldBy, Objective.Evaluates; runtime/function_value.go EvalContext.functionValueOf, EvalContext.invokeFunction; runtime/invoke_calc.go Context.calcInterfaceOf, calcShape.Uncomputed analysis_trade_study_minimize.sysml, analysis_trade_study_abstract_evaluation.sysml, robustness_test.go:trade_study_with_an_abstract_evaluation_function ✅ Faithful
An inherited expression reads a feature as the running behavior inherits it: through the redefinition where the behavior, or a behavior enclosing it, redefines the feature the library expression names (KerML §7.3.4.5) — studyAlternatives and evaluationFunction in the library's bindings answer to the model's subject and calc :>> evaluationFunction, and tradeStudyObjective(selectedAlternative = a) in the library's selectOne body applies the model's objective usage runtime/eval.go EvalContext.runningBehavior, Context.inheritedFeature, EvalContext.evalNameGeneral, EvalContext.invocationTarget; runtime/frame.go frame.running; semantics/masking.go Model.maskingRedefinedFeatures (a subject or objective redefining by role masks what it redefines) analysis_trade_study_minimize.sysml, analysis_trade_study_maximize_definition.sysml, analysis_trade_study_case_parameter.sysml ✅ Faithful
A requirement usage is applied as a predicate: tradeStudyObjective(selectedAlternative = a) binds the subject — a case's or requirement's first input parameter (§7.19.1, §7.20.1) — and answers whether its require constraints hold, the body's frames flattened so the objective's inherited bindings (alternatives, eval, best) are read in the applying case's run; a call may omit the subject when its declaration, or one it redefines, gives it a default or a multiplicity admitting none runtime/invoke_predicate.go EvalContext.invokePredicate, Context.predicateShapeOf, isPredicateDecl, flattenFrames; semantics/invocation.go Model.signatureOf, Model.signatureParameterOf, Model.OptionalParameter analysis_trade_study_minimize.sysml, analysis_trade_study_tie.sysml, analysis_trade_study_minimize.trace.golden, calc_predicate_defaulted_subject.sysml, passes.TestInvocationOverloadDefaultedSubjectIsOptional ✅ Faithful
An objective's inherited require constraint { eval(selectedAlternative) == best } is seen and checked as the objective's own condition, its parameters (eval, best, alternatives) visible to the condition through the objective's inherited declarations; the library's condition is reported by the requirement engine as any objective condition is runtime/analysis.go Context.objectiveConditionsOf; runtime/condition.go Context.conditionFeatures, Context.parameterFeatures analysis_trade_study_minimize.sysml (objective tradeStudyObjective: satisfied), analysis_trade_study_maximize_definition.sysml, repl/trade_study_test.go:TestAnalysisOfATradeStudyReportsEachAlternative ✅ Faithful
->minimize {…}/->maximize {…} over a body applying a function value evaluate the body once per element in collection order and answer the least/greatest value; a body answering a feature that holds no value is a typed NoValueError naming the feature, not a comparison of nothing runtime/collections.go EvalContext.applyValueBody, EvalContext.extremum; runtime/instance.go Context.noValueError, runtime/errors.go NoValueError analysis_trade_study_minimize.sysml, analysis_trade_study_maximize_definition.sysml, robustness_test.go:trade_study_whose_alternatives_read_an_unbound_feature ✅ Faithful
->selectOne {…} is the library's select {…}#(1): the first element the predicate holds for. Two alternatives scoring best are both matched by eval(a) == best, the first is selected, and the later is reported tied so the tie is visible; the library states no tie-breaking beyond #(1) and none is invented runtime/collections.go builtinControlSelectOne, EvalContext.filter; runtime/evaluation_log.go evaluationLog.evaluations analysis_trade_study_tie.sysml, cmd/sysml/run_trade_study_test.go:TestRunTradeStudy, grpc/case_evaluations_test.go:TestRunAnalysisReportsCaseEvaluations ✅ Faithful
A redefining feature that states no multiplicity inherits the redefined feature's (KerML §7.3.4.5): subject : Engine = (a, b); in a TradeStudy is [1..*] from studyAlternatives, so two alternatives are not a violation of [1]; a subject that lists none, or one redeclared [1] and bound to several, is a multiplicity violation before any alternative is evaluated runtime/shape.go Context.featureMultiplicity, Context.inheritedMultiplicity analysis_trade_study_subject_empty.sysml, analysis_trade_study_subject_one.sysml, robustness_test.go:trade_study_with_an_empty_subject, :trade_study_with_a_single_valued_subject, cmd/sysml/run_trade_study_corpus_test.go:TestRunTradeStudyTrainingExample ✅ Faithful
Each application of a case's own calc as a value is reported with the run — AnalysisResult.Evaluations, one AnalysisEvaluation per distinct argument in first-evaluation order (subject order), with the result or the error, Selected for the one whose argument selectOne picked and the case returned (a result that merely equals an argument, with no selectOne picking it, selects nothing) and Tied for a later one computing the same — and the trace shows the same order: the subject bound, each alternative scored under minimize, best read, then selectOne checking each alternative runtime/evaluation_log.go evaluationLog, Context.beginEvaluationLog, evaluationLog.record, evaluationLog.pick, evaluationLog.evaluations; runtime/collections.go builtinControlSelectOne; runtime/analysis_run.go AnalysisResult, AnalysisEvaluation analysis_trade_study_minimize.trace.golden, analysis_trade_study_tie.sysml, analysis_evaluations_result_coincides.sysml, conformance_test.go (evaluations expectations) ✅ Faithful
An evaluation that fails for one alternative — division by zero, a feature with no value, a calc with no return expression — is reported for that alternative with its error, the earlier ones keep their results, nothing is selected, the objective is undecided naming the failure and the run fails with it; a swept run keeps the same on its failed row runtime/analysis_run.go Context.undecidedVerdicts, Context.analysisRun; runtime/sweep.go Context.RunSweep, SweepRow.Evaluations analysis_trade_study_alternative_fails.sysml, analysis_trade_study_abstract_evaluation.sysml, robustness_test.go:trade_study_whose_evaluation_fails_for_one_alternative, repl/trade_study_test.go:TestSweepOfATradeStudyKeepsEvaluationsOnAFailedRow, grpc/case_evaluations_test.go:TestRunSweepKeepsEvaluationsOfFailedRow ✅ Faithful
An output the case cannot compute — a later out after an earlier one succeeded — leaves every objective and asserted constraint undecided naming that failure, the one reading only the computed output included, while the outputs computed before it and the evaluations made stay reported and none is selected; the run fails with the output's error runtime/analysis_run.go Context.runCase, Context.undecidedVerdicts analysis_output_fails_verdicts_undecided.sysml ✅ Faithful
An application is one evaluation per distinct binding of the calc's parameters, spelled by parameter position — a named argument at its parameter's position, null for a parameter the application leaves to the calc before a later one — so the same values bound by name to different parameters are two evaluations and a value bound by position and then by name to the same parameter is one runtime/evaluation_log.go evaluationLog.record, calcShape.argumentsByPosition, evaluationLog.key analysis_evaluations_named_arguments.sysml ✅ Faithful
%analysis/-analysis print each evaluation beneath the verdicts (evaluationFunction(Trade::b (object #2)) = 10.0 [selected]), -json carries them as each check's evaluations, %sweep/-sweep as an evaluations column and each JSON row's evaluations; RunAnalysisResponse.evaluations and SweepRow.evaluations carry them as CaseEvaluation under the case_evaluations capability, an alternative as an instance_id resolving in instances, and the Python (CaseEvaluation, AnalysisResult.evaluations, .selected, AnalysisRunError.result), Node, Java and Rust clients read them repl/analysis.go evaluationOf; repl/sweep.go; cmd/sysml/report.go caseEvaluations; grpc/analysis.go verifyContext.caseEvaluations, verifyContext.runRoots; grpc/sweep.go; api/proto/sysml.proto CaseEvaluation; client/python/opensysml/verdict.py, client/node/src/core/capabilities.ts, client/java/.../Capabilities.java, client/rust/opensysml/src/connection.rs Connection::call repl/trade_study_test.go, cmd/sysml/run_trade_study_test.go:TestRunTradeStudy, :TestSweepTradeStudy, grpc/case_evaluations_test.go, client/python/tests/test_analysis_integration.py, client/node/test/client.test.ts, client/java/.../ApiIntegrationTest.java, client/rust/opensysml/tests/client.rs ✅ Faithful
The OMG models run through this path: the training corpus's Trade Study Analysis Example.sysml (subject : Engine = (engine4cyl, engine6cyl);, inheriting [1..*]) binds both alternatives and fails at the first with a typed no result expression naming evaluationFunction::powerRollup, whose rollup calcs the model leaves bodyless; the pilot's TradeStudyTest.sysml declares evaluationFunction with no body and fails the same way naming it; both leave the objective undecided rather than fabricating a pick runtime/invoke_calc.go calcShape.Uncomputed cmd/sysml/run_trade_study_corpus_test.go:TestRunTradeStudyTrainingExample, :TestRunTradeStudyPilotSimpleTest ✅ Faithful — the models state no way to score their alternatives
subject : Engine[1..*] = all engineChoice; (the pilot's 10b-Trade-off Among Alternative Configurations.sysml): the subject is the extent of the variation engineChoice — its variants '4cylEngine' and '6cylEngine', in declaration order — so both alternatives are bound, the first is evaluated and fails with a typed no result expression naming evaluationFunction::powerRollup, whose rollup calcs the model leaves bodyless (as the training corpus's trade study does, the row above), nothing is selected and the objective is undecided naming that failure runtime/extent.go EvalContext.evalExtent, variantValues; runtime/invoke_calc.go calcShape.Uncomputed cmd/sysml/run_trade_study_corpus_test.go:TestRunTradeStudyPilotTradeOff ✅ Faithful — the extent binds and the alternatives are evaluated; the objective stays undecided because the model computes no evaluation, not because anything is refused

State-Space Dynamics (Domain Libraries/Analysis/StateSpaceRepresentation.sysml; OpenSysML Libraries/StateSpaceIntegration.sysml, an OpenSysML extension)

The domain library declares a protocol and no way to run it: StateSpace, Input and Output are vector quantities, GetNextState, GetOutput, GetDerivative, Integrate and GetDifference are abstract calcs, StateSpaceDynamics holds stateSpace, input and output beside getNextState and getOutput, ContinuousStateSpaceDynamics adds getDerivative and getNextState's integrate, DiscreteStateSpaceDynamics adds getDifference, and ZeroCrossingEventDef names the event a crossing raises. The runtime runs a model that specializes one of the two dynamics as a fixed-step run of the action executor: StateSpaceIntegration (a bundled OpenSysML library, not part of the OMG release) supplies the concrete integrators Euler :> Integrate and RK4 :> Integrate, the FixedStepDynamics mixin that states timeStep, stopTime and time, and the ZeroCrossing :> ZeroCrossingEventDef whose guard and terminal a model binds. The action's own getDerivative, getOutput, getDifference and getNextState are ordinary calcs the evaluator applies, so an inherited or redefined one resolves as any redefinition does.

Semantic Rule Implementation Test Case Status
An action typed by (or specializing) ContinuousStateSpaceDynamics or DiscreteStateSpaceDynamics is dynamics: the classification is semantic, over the bundled library's own declarations (a model's action fall : ContinuousStateSpaceDynamics, FixedStepDynamics and a usage :> of one both qualify), and everything the run needs is lowered once — the kind, the stateSpace/input/output features (own, inherited or redefined), the concrete getDerivative or getDifference and getOutput (the library's abstract declarations do not count), a bodied getNextState, the integrator bound to integrate, timeStep/stopTime/time and, for continuous dynamics (where the library declares them), every event occurrence specializing ZeroCrossingEventDef with its guard and terminal expressions and their scopes. A shape the runner cannot run — a protocol calc left abstract, a crossing binding no guard, an integrate bound to a calc that is neither Euler nor RK4 — is the typed ErrUnsupportedStateSpace at initialize(), naming the action and what is missing; the executor still constructs lower/state_space.go StateSpaceKindOf, ToStateSpaceDynamics, StateSpaceDynamics, ZeroCrossing, StateSpaceModel, providedCalc, lowerNextState, lowerIntegrator, lowerCrossings, ErrUnsupportedStateSpace; runtime/state_space.go stateSpaceModel, Context.stateSpaceKindOf, ActionExecutor.initializeDynamics; action_executor.go hasFlow, initialize lower/state_space_test.go:TestStateSpaceDynamicsLowering, :TestStateSpaceDynamicsIntegratorChoice, :TestStateSpaceDynamicsUnsupportedShapes; state_space_robustness_test.go:TestStateSpaceRobustness/derivative_left_abstract, /integrator_the_runtime_does_not_provide; tests/parser/testdata/parse/state_space_dynamics.sysml (golden AST) ✅ Faithful
Continuous dynamics step by the integrator getNextState's integrate is bound to: Euler advances the state by timeStep times getDerivative(input, stateSpace) at the start of the step; RK4 evaluates the derivative at the start, twice at the midpoint and at the end — with time reading t, t + h/2, t + h/2 and t + h at those stages, so a non-autonomous derivative is integrated as the scheme requires, and reading the settled instant again once the step commits — and advances by their 1:2:2:1 weighted mean; a model naming neither runs under RK4, the documented default. A getNextState the model bodies itself is applied as written instead, whatever integrate it also binds. The state, input and derivative are VectorFunctions vectors (NumericalVector, VectorQuantity); the derivative's unit is the state's over time, so a quantity state stays a quantity across the step runtime/state_space.go stepDynamics, nextState, eulerStep, rk4Step, derivativeAt, derivative, vectorOp, invokeProtocolCalc; lower/state_space.go Integrator, IntegratorUnstated, IntegratorEuler, IntegratorRK4 state_space_euler_first_order.sysml (dx/dt = -0.5 x, x(2) = 0.95^20 = 0.3585 against e^-1 = 0.3679: the first-order error is visible), state_space_rk4_first_order.sysml (the same system under the default, within 1e-6 of the closed form), state_space_accuracy_test.go:TestStateSpaceIntegratorsAgainstClosedForm (both against e^-1: RK4 within 1e-6, Euler more than 1e-3 off), state_space_rk4_lunar_descent.sysml (the hand-rolled calc_rk4_lunar_descent.sysml re-expressed over the protocol; both stay and agree), state_space_rk4_nonautonomous.sysml (dx/dt = t with h = 1 s reaches x(2) = 2 exactly, which stages all reading the step's start would miss), state_space_custom_next_state.sysml (a bodied getNextState beside integrate : Euler runs its body), state_space_euler_first_order.trace.golden ✅ Faithful
Discrete dynamics step by getDifference: each step adds getDifference(input, stateSpace) to the state, output is getOutput over the new state, and the same timeStep/stopTime drive the run runtime/state_space.go nextState (the discrete branch), invokeProtocolCalc state_space_discrete_difference.sysml (compound growth, 10 steps) ✅ Faithful
Time is the shared clock's (Kernel Semantic Library Clocks.kerml, Clock::currentTime): a step is a wait on Context.Clock() due timeStep after the last, so the dynamics park beside accept after/at triggers and state timers, the run's -advance/%advance and RunToCompletion advance them all, time reads the clock, and the dynamics stop at stopTime (a model stating none steps as far as the clock is driven; run to completion alone it stops at the step budget). A state machine exhibited beside the dynamics samples the state a step wrote; when a step and a timed trigger are due at one instant, which runs first is the due order choice point the existing scheduling policies decide (the default policy runs the behavior created later first; the declared policy takes the other order), never an implicit order, and the checker's snapshots carry the run's step count and guard history so a search backtracks through the dynamics as through any behavior runtime/state_space.go stateSpaceRun, parkDynamics, stateSpaceRun.due, dynamicsDue, dynamicsSteps, instantValue, stateSpaceRun.clone; action_executor.go runDue, readiness; snapshot.go actionCapture.dynamics; held_image_behavior.go imagedAction.dynamics (a held image carries the run, so a copy steps on from where the source was imaged); check_moves.go (a step is a moveTrigger), check_reduce.go dynamicsFootprint (conservatively dependent on every other move) state_space_clock_state_interleave.sysml + .trace.golden (the default policy's order), .seed-1.trace.golden and .declared.trace.golden (the other), .check.expected.json (the checker's two outcomes), state_space_zero_crossing_state.sysml (a machine's 10 s timeout driving the clock past the dynamics) ✅ Faithful
Zero crossing: after each step every ZeroCrossing occurrence's guard is evaluated over the action's features (state, input, time); a sign change since the previous step, or the first arrival at an exact zero, posts an event of the occurrence's type to the message bus, where a state machine's accept Touchdown transition takes it as it takes any signal, and a crossing whose terminal is true ends the dynamics at that step; a guard that rests at zero, or leaves it, raises nothing more. The event carries the performance's own event occurrence, so a machine accepting fall.touchdown takes its performer's crossing and not another performer's. The crossing is located to the end of the detecting step: the event carries the step boundary's instant, not a bisected root, and the state the machine reads is the post-step state runtime/state_space.go watchCrossings, crosses, evalGuard, crossingTerminal, evalCrossingExpr, settleStep; runtime/signal.go eventOccurrence, isPerformanceEvent, carriesEvent; lower/state_space.go lowerCrossings, ZeroCrossing (Guard, GuardScope, Terminal, TerminalScope, Event, EventType) state_space_zero_crossing_state.sysml + .trace.golden (event: zero crossing touchdown of fall (t=2.0), then transition: falling -> landed (event: accept Touchdown)), state_space_zero_crossing_stationary.sysml (a guard going 1, 0, 0, -1, -2 raises one event), state_space_zero_crossing_performer.sysml (two performers, each machine taking its own crossing), state_space_accuracy_test.go:TestZeroCrossingPredicate, state_space_robustness_test.go:TestStateSpaceRobustness/guard_that_is_not_a_number ⚠️ Approximate (the crossing is located to the step boundary; no bracketing or bisection within the step, so a guard that crosses and recrosses inside one step is missed, and the event's instant is late by up to one timeStep — see the known limitation)
The run's (t, x, y) is a trace and a result: each step records state: <action> t=<instant> x=<state> y=<output> in the execution trace (-trace, %trace on, the trace goldens), the initial sample included — getOutput runs once per sample, the runner's initial sample standing in for the inherited output default — a crossing records event: zero crossing <name> of <action> (t=<instant>), and the action's outputs — stateSpace, output, time — are the final values -action, %action, ExecuteAction and the analysis result tables already report; no new format and no wire change runtime/trace.go TraceRecorder.RecordStateSpaceStep; runtime/state_space.go settleStep, stateSpaceRun.ownsFeature; action_executor.go initializeAttributes state_space_euler_first_order.trace.golden, state_space_zero_crossing_state.trace.golden, state_space_clock_state_interleave.trace.golden; every state_space_*.expected.json reads stateSpace/output as outputs ✅ Faithful
Every shape the runner cannot run truthfully is a typed error, never a silent wrong result: a stateSpace, input, derivative, difference or output that is not a vector, or that the action does not bind, is ErrStateSpaceValue; a timeStep that is absent, not a duration, zero, negative or not finite, or whose next instant is past what the clock can hold, is ErrStateSpaceStep (refused before the token parks, so the clock stays finite); a step that leaves a component non-finite, or whose arithmetic overflows, is ErrStateSpaceDiverged naming the step and instant; a guard or terminal that is not a number or Boolean is ErrStateSpaceValue; a dynamics action declaring a return parameter is ErrActionResultParameter before its run begins, as for any action runtime/state_space.go ErrStateSpaceValue, ErrStateSpaceStep, ErrStateSpaceDiverged, initializeDynamics (checkResultParameters), readStep, parkDynamics, stateVector, inputVector, checkVector, checkFinite, divergence state_space_robustness_test.go:TestStateSpaceRobustness (state_that_is_not_a_vector, state_without_an_initial_value, derivative_left_abstract, difference_left_abstract, derivative_that_is_not_a_vector, output_that_is_not_a_vector, step_not_stated, step_that_is_zero, step_that_is_negative, integrator_the_runtime_does_not_provide, divergent_state, step_past_the_last_instant, guard_that_is_not_a_number, return_parameter) ✅ Faithful
StateSpaceRepresentation as an analysis library: the OMG example State Space Representation Examples/EVSample1.sysml parses and validates, and a model of its shape — an action specializing ContinuousStateSpaceDynamics with its own getDerivative and getOutput, an initial stateSpace and an input — runs through the runner above once it also states a timeStep (through FixedStepDynamics or a timeStep : DurationValue of its own), the library itself stating no step as above state_space_rk4_lunar_descent.sysml, state_space_zero_crossing_state.sysml; model/pilot_corpora_test.go (the example in the pilot ratchet) ⚠️ Approximate (the OMG example itself declares no step size and no stop, so it is run only once a model adds them; StateSpaceItem and StateSpaceEventDef beyond ZeroCrossingEventDef are parsed and not executed)

Dispatch during an entry the model does not run to completion

When a state entry is not run to completion, the specification leaves open whether a dispatch due at the same instant happens before the unfinished entry or after it. Both orders are admissible: dispatching first can leave the entered composite without visiting its later state, while completing the entry first can visit that state before the dispatch transitions away. The conformance fixture state_run_to_completion_false_self_signal records both outcomes and its trace goldens record each permitted order. The scoped fixture state_run_to_completion_scope_sibling_region records the two permitted orders when an entry boundary in one region overlaps a dispatch owned by its sibling; its default twin state_run_to_completion_scope_sibling_region_default proves that the un-redefined machine retains its single-step behavior.

Verification Case (SysML v2 §7.23 Verification Cases, §9.3.2; Systems Library/VerificationCases.sysml)

The specification defines the notation of a verification case and its library declarations — VerificationCase :> Case with return verdict : VerdictKind :>> result, enum def VerdictKind and calc def PassIf — but says the evaluation of a verdict is intentionally not specified normatively (§9.3.2). What OpenSysML does is therefore tool-defined and stated here: it runs the case body exactly as it runs an analysis case body, and reports the VerdictKind that run produced, computed by the library's own PassIf calculation where the body calls it. The verdict is reported beside the requirement-satisfaction verdicts the same surfaces already give, never in place of them: a failing verification body does not make a satisfied requirement violated.

Semantic Rule Implementation Test Case Status
A verification def/verification usage is run the way an analysis case is — the same lowering of the body over one ActionGraph, the same subject/in binding, the same step execution, the same objective and assert constraint checks afterwards — through one shared case path rather than a second executor; a symbol that is not a verification case is a typed refusal runtime/analysis_run.go Context.RunAnalysis, Context.runCase; runtime/verification_run.go Context.RunVerification, IsVerificationCaseSymbol, Context.RequireVerificationCase, runtime/errors.go ErrNotAVerification; runtime/invoke_calc.go calcShape.isCase; runtime/calc_usage.go isCalcUsageSymbol; lower/case_body.go PerformsSteps, lower/block_graph.go IsCaseNode, lower/calc_body.go CalcBody conformance verification_verdict_pass (+ .trace.golden), verification_verdict_fail, runtime/verification_verdict_test.go:TestVerificationBodyVerdicts, robustness_test.go:verification_of_a_symbol_that_is_not_a_case, :verification_with_an_argument_the_case_does_not_take ✅ Faithful to the case-execution semantics it reuses
A body whose result is a VerificationCases::PassIf(...) call answers pass or fail as that library calculation computes it — the calc is invoked through the ordinary calculation machinery and its VerdictKind result read, not a truth table restated in Go runtime/verification_run.go Context.bodyVerdict, Context.verdictOf, Context.isVerdictKind; runtime/invoke_calc.go (the calc invocation the body's expression makes); libs/stdlib/Systems Library/VerificationCases.sysml calc def PassIf conformance verification_verdict_pass, verification_verdict_fail, runtime/verification_verdict_test.go:TestVerificationBodyVerdicts (test::passing, test::failing) ⚠️ Tool-defined — the spec leaves the evaluation non-normative; the pass/fail truth table itself is the library's own text and is executed rather than reimplemented
A verification case's objective checks the case's subject, not its verdict (§7.23 — "the subject of the objective of a verification case is the subject of the verification case"): the library redefines the objective with a bound, not defaulted, subject — VerificationCases::VerificationCase::obj :>> Case::obj { subject subj = VerificationCase::subj; } — and the runtime honours the redefining feature's stated value over the redefined Case::obj's default Case::result, as a language rule rather than a verification one: the stated VerificationCase::subj is resolved to the run's subject binding through the redefinition index (subj :>> Case::subj, the usage's own subject lander redefining it). So an objective typed by a requirement def whose subject has another name (subject lander : Lander against the library's subj) evaluates that requirement against the verification subject with no binding of its own — whether the usage restates the subject without a value (subject :>> subj; ahead of its inputs, the shape the pilot accepts since a usage's owned parameters redefine its definition's by position) or leaves it to the library — an objective whose requirement declares no subject is decided the same way, in limit = limit; still binds the requirement's parameter, and the objective is decided (satisfied/not satisfied) beside the body's pass/fail on every surface — -analysis, -requirement, -satisfy, %analysis, %requirement, %satisfy, gRPC RunAnalysis/VerifyRequirement — and in every row of a -sweep. A requirement subject the verification subject cannot be is undecided naming both (subject rover is bound to the case's subject (VerificationCases::VerificationCase::obj): type mismatch: Lander #1 (scout) is not a Rover); a verification whose own subject nothing binds is the case's UnboundSubjectError naming that subject; a usage restating the objective's subject (objective : R { subject lander = other; }, subject = other;, subject :>> subj = other;) is refused as overriding the library's fixed = value runtime/analysis_run.go Context.objectiveBindings, Context.unboundObjectiveSubject, Context.statedSubjectValue, Context.statedCaseMember, statedAsDefault, calcShape.memberRole; runtime/calc_usage.go Context.calcMemberNames (redefined features indexed under the run's name), calcShape.memberName; runtime/subsetting.go Context.redefinedFeatures; runtime/condition.go Context.conditionFeatures (the effective feature values along the chain); runtime/library_functions.go Context.libraryDeclared; passes/feature_value_overriding.go (the = refusal) conformance verification_objective_subject (+ .trace.golden), verification_objective_subject_violated, verification_objective_subject_unstated, verification_objective_subject_unbound, analysis_objective_subject_default_over_case_subject; robustness_test.go:verification_objective_subject_of_another_type, :verification_objective_subject_left_unbound, :verification_objective_subject_rebound; cmd/sysml/verification_test.go:TestVerificationObjectiveDecidesOnEverySurface, cmd/sysml/sweep_test.go:TestSweepVerificationThroughCLI, repl/verification_verdict_test.go:TestVerificationObjectiveDecidesAgainstTheCaseSubject, grpc/verification_verdict_test.go:TestRunAnalysisDecidesAVerificationObjectiveAgainstTheCaseSubject ✅ Faithful
A body binding verdict (or its result) to a VerdictKind literal reports that literal, recognized by the enumeration the value belongs to rather than by the spelling of the name. The verdict is read from the case's result parameter only — the name it declares, the library's verdict, or an unnamed result — so an out parameter of the same enumeration is not mistaken for the case's answer runtime/verification_run.go Context.bodyVerdict, verdictOutputNames, Context.verdictOf, Context.isVerdictKind, verdictKindName, verdictOutputName; runtime/analysis_run.go calcRun.resultName runtime/verification_verdict_test.go:TestVerificationBodyVerdicts (test::stated, test::aux) ⚠️ Tool-defined — reported as the library declares the literal
A body that produces no verdict value — no PassIf, no verdict binding — is inconclusive, saying so, rather than defaulted to a pass or a fail runtime/verification_run.go Context.bodyVerdict (VerdictInconclusive) conformance verification_verdict_inconclusive, runtime/verification_verdict_test.go:TestVerificationBodyVerdicts (test::silent) ⚠️ Tool-defined
A body whose run fails with a typed error — an unbound subject, a failing step, a feature holding no value — is error carrying that error's own text, and the failure is a verdict rather than a refused request; a malformed request (a symbol of the wrong kind, an unknown named argument) stays a typed error from the call runtime/verification_run.go Context.RunVerification, badRequest; runtime/errors.go UnboundSubjectError (kind verification) conformance verification_verdict_error, robustness_test.go:verification_body_that_cannot_run, :verification_body_step_that_fails, runtime/verification_verdict_test.go:TestVerificationBodyVerdicts ⚠️ Tool-defined
A verification case performed as a step of another (verification sub : Case;, the library's subVerificationCases :> subcases) has its own verdict read from the step's performance and is reported on its own, marked as a subcase: the library declares the containment but states no roll-up rule — no calculation combining subcase verdicts into the parent's — so none is invented: a parent whose body binds no verdict of its own stays inconclusive beside its subcases' verdicts, and a parent whose own body passes still reports and exits as a pass beside a subcase that failed. A subcase the run did not perform — the case on the branch a decision did not select — answered nothing and is reported by neither, rather than being reported as an error. A subcase that could not run at all is a step that failed, which ends the performing case's run as any failed step does: that case's verdict is the error naming the subcase and the reason, and no verdict is reported for a body that did not finish runtime/verification_run.go Context.subcaseVerdicts, Context.subcaseVerdict, verdictOutputNames; runtime/case_step.go performances.performCase; VerificationVerdict.Subcase conformance verification_verdict_subcases, verification_verdict_subcase_branch, runtime/verification_verdict_test.go:TestVerificationSubcaseVerdictsAreReportedOnTheirOwn, runtime/robustness_test.go:verification_subcase_that_cannot_run, repl/verification_verdict_test.go:TestAnalysisKeepsACaseStatusApartFromItsSubcases ⚠️ Tool-defined — reported individually, with the reason stated here
A requirement's verdict is unchanged: %requirement, %satisfy, -requirement and -satisfy still report what the requirement engine decided, and add one line per verification case verifying that requirement (a case's objective { verify r; }, direct or inherited), the assertion's own verdict deciding the exit status as before runtime/verification_cases.go Context.VerificationsOf, Context.VerifiedRequirements (over the objectives the case states as it runs them, so an objective a usage redeclares verifies what the redeclaration says and not also what the inherited one said), Context.VerificationVerdictsIn (every document of the model, so a case verifying a requirement is found wherever it is written, and a case reached twice runs once), Context.VerificationVerdicts, Context.VerifyingCase; repl/verification.go Session.withVerifications, verificationLine, verificationStatus; repl/meta.go, repl/analysis.go; cmd/sysml/report.go (verifications in the JSON report) runtime/verification_verdict_test.go:TestVerificationVerdictsForRequirement, :TestVerificationsOfRequirement (test::q, the redeclared objective), repl/verification_verdict_test.go, cmd/sysml/verification_test.go:TestVerificationVerdictsBesideRequirements ✅ Faithful — the satisfaction semantics are untouched
The RPCs carry the body verdicts in added fields rather than overloading existing ones: VerificationVerdict (case_id, kind, detail, subcase, requirement_id) repeated on VerifyRequirementResponse, VerifySatisfactionResponse and RunAnalysisResponse, advertised as the verification_verdicts capability, so a client built before it reads the responses as it did. Each verdict names the requirement it was reported for, and a satisfy verdict names the requirement it asserts satisfied (Verdict.requirement_id), the one it references or, for satisfy requirement r by p, the requirement it declares itself, so a satisfaction response covering several requirements is read per requirement rather than as one undifferentiated list; a case run for itself names none api/proto/sysml.proto VerificationVerdict, Verdict.requirement_id; grpc/service.go CapabilityVerificationVerdicts; grpc/verify.go Service.VerifyRequirement, Service.VerifySatisfaction, verifyContext.requirementVerifications, verifyContext.assertionVerifications, verifyContext.associateRequirement, verificationVerdictsOf; runtime/satisfy.go SatisfyAssertion.AssertedRequirement; grpc/analysis.go Service.RunAnalysis; client/opensysml/verify.go VerificationVerdict, verificationVerdictsFromProto, verificationsOf, analysis.go; client/python/opensysml/verdict.py VerificationVerdict, connection.py _verifications_of, _verifications_for, capabilities.py; client/node/src/core/capabilities.ts; client/java/.../Capabilities.java; client/rust/opensysml (generated protos) grpc/verification_verdict_test.go (all), client/python/tests/test_verification.py:test_verify_requirement_reports_the_body_verdicts_beside_satisfaction, :test_verify_satisfaction_reports_the_body_verdicts_of_the_run, :test_run_analysis_of_a_verification_case_reports_its_body_verdict, :test_a_service_without_body_verdicts_reports_none, :test_verify_satisfaction_gives_each_verdict_only_its_own_requirements_cases, :test_a_service_without_requirement_association_reports_no_satisfaction_cases, grpc/verification_verdict_test.go:TestVerifySatisfactionAssociatesBodyVerdictsWithTheirRequirement, client/opensysml/verification_verdict_test.go (all), client/node/test/client.test.ts, client/java/.../ApiIntegrationTest.java, client/rust/opensysml/tests/client.rs, grpc/verification_multidoc_test.go ✅ Faithful — additive fields, absent when the capability is not advertised

Parameter sweeps and samples

A sweep is tool-defined orchestration, not spec semantics: the specification says nothing about running a case repeatedly, and nothing about how a case executes changes. Each row of a table is one ordinary RunAnalysis/calc invocation with the swept parameter bound to that row's value and every other argument as given, so the rules above are the ones a row obeys. The bundled library states no probability distribution of any kind (no Random, Distribution, Uniform or Normal definition under internal/workspace/libs/stdlib/), so sampling is uniform over a range and a distribution asked for by name is refused naming what is missing rather than approximated.

Semantic Rule Implementation Test Case Status
A sweep plan enumerates one binding set per run: a range steps from <from> towards <to>, inclusive where the step lands on it; a range between whole numbers with no step steps by one, up or down as its endpoints direct, and a range with a fractional endpoint and no step is refused; several ranges run their cartesian product in lexicographic order, the first given varying slowest; endpoints and step carry the argument syntax and its units, converted to the first endpoint's unit; a step of zero, a step whose sign never reaches <to>, incompatible units, a range naming no parameter the target declares, one the arguments already bind, a plan naming no range at all, and a plan asking for more runs than its budget allows are typed refusals that make no run runtime/sweep.go Context.RunSweep, SweepPlan, SweepRange.bounds/endpoints/enumerate, sweepBounds.descends, Context.sweptBindings, Context.ResolveSweepPlan, ErrSweepRange/ErrSweepParameter/ErrSweepEmpty/ErrSweepBudget runtime/sweep_test.go:TestSweepIntegerRangeStepsByOne, :TestSweepIntegerRangeDescends, :TestSweepStepIncludesEndpointItLandsOn, :TestSweepRealRangeReachesItsEnd, :TestSweepSeveralRangesRunTheirCartesianProduct, :TestSweepFractionalRangeWithoutAStepIsRefused, :TestSweepZeroStepIsRefused, :TestSweepStepAwayFromTheEndIsRefused, :TestSweepNonNumericEndpointIsRefused, :TestSweepEndpointsMustBothCarryAUnit, :TestSweepWithoutARangeIsRefused, :TestSweepParameterSweptTwiceIsRefused, :TestResolveSweepPlanRefusesUnknownAndBoundParameters ⚠️ Approximate (tool-defined: the spec states no sweep, so the ranges, their order and their refusals are this tool's contract)
A range's values are produced in the type of the parameter it sweeps, not the type its literals spell: the plan is resolved against the target's effective parameter declarations — inherited and redeclared ones included — and each range carries its parameter's type; an Integer, Natural or Positive parameter (or an attribute def specializing one) takes Integers however the endpoints are written, so 1.0..3.0:1.0 binds 1, 2, 3, and an endpoint or step that is not an Integer, or a value below what Natural/Positive holds, is refused before any run naming the parameter and its type; a Real or Rational parameter (or an attribute def specializing one) takes reals however the endpoints are written, so 1..4:1 binds 1.0, 2.0, 3.0, 4.0 and every surface shows them so; a quantity-typed parameter is typed through its num (Number for the library's quantities, so as written; Integers where a model redefines num : Integer) and its unit is the first endpoint's, the parameter's own dimension refusing an incommensurable or bare range and its num refusing a magnitude it cannot hold — a negative one under num : Natural, zero under num : Positive, any under a num holding no number; a Number-typed parameter and one declaring no type take the range as written — Integers between Integer literals, reals otherwise — the untyped one noted under the table; a Boolean, String, enumeration or non-scalar parameter refuses a numeric range naming its type; each endpoint is admitted as an argument to the parameter would be, so a plan whose rows could not run is refused before any of them is; a range read as reals takes an Integer endpoint or step only where a Real holds it without rounding (float64, so every Integer up to 2⁵³ in magnitude), and steps only where the reals tell consecutive rows apart, so a Real parameter swept over 2⁶⁰..2⁶⁰+3 is refused naming the endpoint and the parameter rather than collapsed onto one row, and one swept by one over 2⁶⁰..2⁶⁰+512 is refused for rows that would repeat; sampling over such a range draws as ever runtime/sweep.go Context.ResolveSweepPlan, Context.sweepTypeOf, sweepNumbersOf, Context.positiveScalar, Context.quantityNumber, SweepType (Numbers, Untyped, Positive, Declared), SweepType.integer/notAnInteger/exactReal/admit/admitMagnitude, SweepRange.exactRealEndpoints, sweepInteger, realHolds, Context.numTypeText, SweepRange.endpoints/bounds/enumerate (the repeated-row check), sweepBounds.value/valueInt/valueReal, SweepTable.Types; repl/sweep.go untypedNotes; grpc/sweep.go RunSweep (a resolved plan) runtime/sweep_test.go:TestResolveSweepPlanTypesEachRangeByItsParameter, :TestSweepIntegerParameterTakesIntegersHoweverTheRangeIsWritten, :TestSweepIntegerParameterRefusesFractionalEndpointsAndSteps, :TestSweepNaturalAndPositiveParametersRefuseWhatTheyCannotHold, :TestSweepRealParameterTakesRealsHoweverTheRangeIsWritten, :TestSamplesDrawInTheParameterTypeNotTheLiteralType, :TestSweepOverANonNumericParameterIsRefused, :TestSweepOverAnUntypedParameterFollowsTheLiterals, :TestSweepOverANumberParameterFollowsTheLiterals, :TestSweepOverAQuantityParameterConvertsToTheFirstEndpointsUnit, :TestSweepOverAQuantityParameterIsBoundedByItsNum, :TestSweepRealRangeRefusesWhatARealCannotHoldApart; robustness_test.go:sweep_over_a_boolean_parameter, :sweep_over_a_parameter_typed_by_a_part, :sweep_over_an_integer_parameter_by_a_fraction, :sweep_over_a_real_parameter_by_integers_no_real_holds; repl/sweep_test.go:TestSweepBindsInTheParameterType, :TestSweepOverAnUntypedParameterIsReadAsWritten, :TestSweepErrors; cmd/sysml/sweep_test.go:TestSweepBindsInTheParameterTypeThroughCLI, :TestSweepRefusalsThroughCLI; grpc/sweep_test.go:TestRunSweepBindsInTheParameterType, :TestRunSweepFailures ⚠️ Approximate (tool-defined: the sweep is this tool's contract, but its values respect the model's types — a range is one more way of writing an argument, and takes the values the parameter's declaration admits. The write check that admits an integral Real into an Integer feature (semantics.PrimTypeOfValue; the row A constant is a value of every scalar type that holds it records it) is a deliberate, tested rule and is unchanged here; the sweep converts up front instead of relying on it, so an Integer parameter's rows carry and print Integers. The pinned pilot is no referee of that rule: its validator raises Bound features should have conforming types for a String or Boolean bound to an Integer feature but stays silent for 2.0 and for 1.5, and its evaluator returns 9.0 for Sq(3.0) and 2.25 for Sq(1.5) against in x : Integer, admitting every Rational where this tool's analysis refuses both and its runtime admits the integral one)
A run that fails is a row carrying its typed error rather than an abort, the runs after it are still made, and each row carries the wall time of its own run runtime/sweep.go Context.RunSweep, SweepRow, SweepTable runtime/sweep_test.go:TestSweepFailedRunIsARowAndTheTableGoesOn, repl/sweep_test.go:TestSweepFailedRunIsARowOfTheTable, cmd/sysml/sweep_pilot_test.go:TestSweepPilotDynamicsAnalysis (the pilot's division by zero at zero speed) ⚠️ Approximate (tool-defined)
Samples draw n values per range instead of running every value of it — uniformly, in draw order, from math/rand/v2's PCG seeded from the given seed alone, so one seed draws one sequence on every platform; a parameter taking Integers draws them inclusively over [from, to] and one taking reals draws from [from, to), however the endpoints are written, so an Integer parameter sampled over 1.0..4.0 draws the Integers 1 to 4 and a Real one sampled over 1..4 draws reals; a sampled range needs no step and is refused with one; a seed is required rather than taken from the clock; a distribution asked for by name is refused naming the missing library runtime/sweep.go NewSampleSource, Context.sampledBindings, sweepBounds.draw, ErrSweepSamples, ErrSweepDistribution; repl/sweep.go distributionCall runtime/sweep_test.go:TestSamplesDrawOneRowPerDraw, :TestSamplesAreReproducibleFromTheirSeed, :TestSamplesDrawnValuesArePinned (the same seed over an Integer and over a Real parameter), :TestSamplesDrawInTheParameterTypeNotTheLiteralType, :TestSamplesOverIntegersIncludeBothEndpoints, :TestSamplesOverRealsStayWithinTheirRange, :TestSamplesNeverDrawTheEndOfARealRange, :TestSamplesWithAStepAreRefused, :TestSamplesWithoutADrawAreRefused, :TestNewSampleSourceIsSeededFromTheSeedAlone, repl/sweep_test.go:TestSweepErrors (a distribution by name), :TestSweepBindsInTheParameterType, cmd/sysml/sweep_test.go:TestSamplesThroughCLI, grpc/sweep_test.go:TestRunSweepBindsInTheParameterType ⚠️ Approximate (tool-defined; uniform is the only distribution the model can state, since the library defines none)
The number of runs one plan may make is bounded by OPENSYSML_MAX_SWEEP_RUNS (default 1000), counted before the first run and refused naming the count asked for and the bound; each run still has the runtime bounds of its own runtime/budget.go Budgets.MaxSweepRuns, BudgetsFromEnv, DefaultMaxSweepRuns; runtime/context.go runtime/sweep_test.go:TestSweepBudgetIsRefusedBeforeRunning, :TestSweepBudgetBoundsTheProduct, :TestSamplesBeyondTheBudgetAreRefused ✅ Faithful to the bound it states (see Runtime bounds)
-sweep <param>=<from>..<to>[:<step>] (repeatable) with -samples <n> -seed <s> sweep an -analysis case or a -calc, printing the table as text and, under -json, as rows inside the check the sweep ran; %sweep and %samples do the same in the REPL, on the session's object where the case takes a subject, and a named argument holding = is not mistaken for a range; a sweep or sample flag without an -analysis/-calc, more than one of them, -samples without a range or a seed, and -seed without -samples are refused cmd/sysml/check.go (the sweep flags and their validation), cmd/sysml/report.go checkRow, cmd/sysml/usage.go; repl/sweep.go Session.RunSweep, Session.RunSamples, splitSpecs; repl/meta.go; repl/query.go VerdictRow; packaging/man/man1/sysml.1 cmd/sysml/sweep_test.go (all), cmd/sysml/sweep_pilot_test.go (all), repl/sweep_test.go (all), make man-check ⚠️ Approximate (tool-defined)
RunSweep RPC: the RunAnalysis request plus repeated ranges and an optional samples/seed pair, answering one row per run — inputs, outputs, verdicts, elapsed_micros and a per-row error/failure_reason — with the swept parameters, sampled and the seed echoed; a refused plan answers an error and no rows; exposed in the Connect adapter, the generated Go, Python, Node, Java and Rust stubs, and the Python client (Model.run_sweep, SweepTable, SweepRow) grpc/sweep.go Service.RunSweep; grpc/connect.go; client/python/opensysml/connection.py, model.py, verdict.py; api/proto/sysml.proto grpc/sweep_test.go (all), client/python/tests/test_sweep_integration.py ⚠️ Approximate (tool-defined; the Go client and the conformance scenarios keep the single-run RPCs — a sweep over the wire is the generated stub or the Python client)

Analysis libraries, measured (Domain Libraries/Analysis/, VectorFunctions, OccurrenceFunctions)

The rows above and in the Function Library map say what the analysis libraries do case by case; the table below says how much of them runs, and is measured rather than written. TestAnalysisLibraryCensus (internal/exec/runtime/library_census_test.go) enumerates every public callable declaration each bundled library package makes — calc and function definitions, the calc usages a definition holds, action and event definitions, and the constraint, requirement and objective usages the runtime applies as predicates (a function's invariants and preconditions, a trade study's objective) — from the standard library's own symbol scopes, so a declaration the library gains or loses moves the count without anyone editing a list. Each declaration has one representative probe (library_census_probes_test.go): a small model that specializes or calls it the way the library's own text says to, evaluated through internal/exec/runtime with the value checked against the library's text or a hand-computed result. The verdicts are:

  • Evaluated — the invocation produced a value and the value passed its check. Parsing, resolving or dispatching the declaration does not count; only a checked value does.
  • Refused — the runtime answered with one of its typed errors (ErrNoValue, ErrNotAFunction, ErrInvalidActionFlow, …) or an undetermined value instead of a value. Each is listed by name with the error it was refused by. A refusal typed by no known error fails the test, so nothing is refused silently.
  • Wrong — the invocation produced a value and the value failed its check. Each is listed by name with what the check reported.

The test writes what it measured to analysis-library-census.json and fails when the committed file disagrees, so the figures cannot go stale against the runtime; make docs-counts renders the block from that file and go run -C tools ./cmd/doc-counts -check fails when the block disagrees with it. Regenerate with go test ./internal/exec/runtime -run TestAnalysisLibraryCensus -update-library-census followed by make docs-counts.

AnalysisTooling declares metadata definitions only, no callable declaration, and is listed with zeros. StateSpaceRepresentation is abstract by design — GetNextState, GetOutput, GetDerivative, Integrate, GetDifference and the dynamics actions holding them state their parameters and leave the body to a specialization and a runner stepping it through time — so its probes specialize the declarations as a model would and record what the runtime answers: an inherited calc body evaluates where the specialization states its result, a ContinuousStateSpaceDynamics and a DiscreteStateSpaceDynamics stating a timeStep run one step of the state-space runner (see "State-Space Dynamics" above) and their output is checked, while the base StateSpaceDynamics, the event definitions, the calc usages the dynamics hold read as functions and the integration through an abstract derivative are refused by name. Those refusals are the honest measure of that library; none of them is worked around here. TradeStudy::evaluationFunction invoked directly on a study usage is refused (the case usage is read as a computation, not as the holder of a callable member) although the study itself evaluates it through its objective; that is a runtime limitation of member calc invocation on case usages, not of the library. A VectorFunctions invariant is probed where its parameters are bound — from the result expression of a specialization of its function — so the preconditions and the invariants an implication discharges on the inputs evaluate, while an invariant that reads the function's result is refused with ErrNoValue (or ErrTypeMismatch where the result feeds an operator) because the result is not yet bound when the expression computing it runs; a postcondition checked after the result is bound is not a form the runtime offers, and none is added here.

Measured by go test ./internal/exec/runtime -run TestAnalysisLibraryCensus -update-library-census, which writes analysis-library-census.json; make docs-counts renders this block from that file and go run -C tools ./cmd/doc-counts -check fails when they disagree.

Package Declarations Evaluated Refused Wrong
AnalysisTooling 0 0 0 0
SampledFunctions 5 5 0 0
TradeStudies 7 6 1 0
StateSpaceRepresentation 17 6 11 0
VectorFunctions 39 30 9 0
OccurrenceFunctions 8 6 2 0
Total 76 53 23 0

Refused, by name (the typed error the runtime answered with):

  • TradeStudies::TradeStudy::evaluationFunction — ErrNoValue: no value: calc usage study computes output features (selectedAlternative); read one of them
  • StateSpaceRepresentation::StateSpaceEventDef — ErrInvalidActionFlow: initialize action: invalid action flow: no initial node found in action run
  • StateSpaceRepresentation::ZeroCrossingEventDef — ErrInvalidActionFlow: initialize action: invalid action flow: no initial node found in action run
  • StateSpaceRepresentation::StateSpaceDynamics — ErrInvalidActionFlow: initialize action: invalid action flow: no initial node found in action Plant
  • StateSpaceRepresentation::StateSpaceDynamics::getNextState — ErrNotAFunction: not a function: plant.getNextState is undetermined, not a function
  • StateSpaceRepresentation::StateSpaceDynamics::getOutput — ErrNotAFunction: not a function: plant.getOutput is undetermined, not a function
  • StateSpaceRepresentation::Integrate — ErrNoResultExpression: calc test::Euler: evaluating the returned expression: no result expression: calc test::Euler::getDerivative has no return expression: the result parameter binds no value; write the result as the trailing expression of the body, or bind it with return : StateDerivative = <expr>;
  • StateSpaceRepresentation::ContinuousStateSpaceDynamics::getDerivative — ErrNotAFunction: not a function: damper.getDerivative is undetermined, not a function
  • StateSpaceRepresentation::ContinuousStateSpaceDynamics::getNextState — ErrNotAFunction: not a function: damper.getNextState is undetermined, not a function
  • StateSpaceRepresentation::ContinuousStateSpaceDynamics::getNextState::integrate — Undetermined: damper has no value in the model
  • StateSpaceRepresentation::DiscreteStateSpaceDynamics::getDifference — ErrNotAFunction: not a function: spring.getDifference is undetermined, not a function
  • StateSpaceRepresentation::DiscreteStateSpaceDynamics::getNextState — ErrNotAFunction: not a function: spring.getNextState is undetermined, not a function
  • VectorFunctions::+::commutivity — ErrNoValue: calc test::Probe: evaluating the returned expression: constraint VectorFunctions::+::commutivity: constraint commutivity: require condition evaluation failed: no value: condition is undetermined: u has no value in the model
  • VectorFunctions::-::difference — ErrTypeMismatch: calc test::Probe: evaluating the returned expression: constraint VectorFunctions::-::difference: constraint difference: require condition evaluation failed: type mismatch: operator '+' is not defined for a vector and an undetermined VectorValue
  • VectorFunctions::scalarVectorMult::scaling — ErrNoValue: calc test::Probe: evaluating the returned expression: constraint VectorFunctions::scalarVectorMult::scaling: constraint scaling: require condition evaluation failed: no value: condition is undetermined: w has no value in the model
  • VectorFunctions::scalarVectorMult::zeroLength — ErrNoValue: calc test::Probe: evaluating the returned expression: constraint VectorFunctions::scalarVectorMult::zeroLength: constraint zeroLength: require condition evaluation failed: no value: condition is undetermined: w has no value in the model
  • VectorFunctions::inner::commmutivity — ErrNoValue: calc test::Probe: evaluating the returned expression: constraint VectorFunctions::inner::commmutivity: constraint commmutivity: require condition evaluation failed: no value: condition is undetermined: x has no value in the model
  • VectorFunctions::norm::squareNorm — ErrNoValue: calc test::Probe: evaluating the returned expression: constraint VectorFunctions::norm::squareNorm: constraint squareNorm: require condition evaluation failed: no value: condition is undetermined: l has no value in the model
  • VectorFunctions::norm::lengthZero — ErrNoValue: calc test::Probe: evaluating the returned expression: constraint VectorFunctions::norm::lengthZero: constraint lengthZero: require condition evaluation failed: no value: condition is undetermined: l has no value in the model
  • VectorFunctions::angle::commutivity — ErrNoValue: calc test::Probe: evaluating the returned expression: constraint VectorFunctions::angle::commutivity: constraint commutivity: require condition evaluation failed: no value: condition is undetermined: theta has no value in the model
  • VectorFunctions::angle::lengthInsensitive — ErrNoValue: calc test::Probe: evaluating the returned expression: constraint VectorFunctions::angle::lengthInsensitive: constraint lengthInsensitive: require condition evaluation failed: no value: condition is undetermined: theta has no value in the model
  • OccurrenceFunctions::removeOld — ErrNotABehavior: performed action go of object #1: action node work: not a behavior: removeOld is not an action (kermlType)
  • OccurrenceFunctions::removeOldAt — ErrNotABehavior: performed action go of object #1: action node work: not a behavior: removeOldAt is not an action (kermlType)

Constraint

Semantic Rule Implementation Test Case Status
Assert evaluation (boolean satisfaction) context.go:81 EvaluateConstraint constraint_literal.sysml ✅ Faithful
Assume evaluation (trusted precondition) context.go:81 (same path) constraint_assume.sysml ✅ Faithful
Bare expression as invariant context.go:81 constraint_literal.sysml ✅ Faithful
Unresolved feature reference resolve package + eval.go robustness_test.go:testConstraintMissingFeature ✅ Faithful
Negated constraint (assert not) eval.go:483 evalNeg constraint_negation.sysml ✅ Faithful
A constraint a type carries is evaluated against an instance of it, so it reads that object's feature values rather than declared defaults context.go EvaluateConstraintOn, eval.go NewEvalContextIn/selfFeatureValue instance_constraint_binding.sysml, repl/instance_test.go:TestConstraintBindsToInstance ✅ Faithful
A condition on a type reached only through a nested redefinition (part top : Top { part :>> mid { part :>> leaf { attribute :>> value = 99.0; } } }) is checked against that nested object, at any depth and across part boundaries, so it agrees with %features instead of answering from the declaration; a sibling the object does not redefine keeps its declared value condition.go conditionSubject, carriersUnder, nestedObjects conformance nested_redefinition_two_levels, nested_redefinition_inherited_leaf, runtime/subject_value_test.go:TestNestedRedefinitionIsTheConditionSubject, TestNestedSubjectUnderSuppliedObject ✅ Faithful
With no object instantiated, such a condition still answers about the declaration condition.go conditionSubject conformance nested_redefinition_no_object, runtime/subject_value_test.go:TestNestedRedefinitionIsTheConditionSubject ✅ Faithful
Two objects redefining the same nested feature differently make the subject a question, reported as ErrAmbiguousSubject naming the carriers rather than answered from either; the same declaration materialized twice is one object, the latest, and the objects a namespace-level collection usage denotes (part wheels : Wheel[2];) occur as one declaration, so they are one candidate as the members of a nested collection are, while a second usage of the definition is a distinct carrier condition.go ErrAmbiguousSubject, conditionSubject, rootInstances, carriersUnder, rootPath runtime/subject_value_test.go:TestNestedSubjectAmbiguous, TestSubjectOfANamespaceCollection, robustness_test.go:testNestedConditionSubjectIsAmbiguous, testDuplicateObjectsOfOneDeclaration ✅ Faithful
An object a caller instantiated directly is a subject in its own right — a definition nested in another (part def Outer { part def Big :> Inner { ... } }) and a nested usage (%instantiate Top::leaf) alike — while an object a feature value holds, or one materialized only to read a nested declaration through, is reached through its holder condition.go rootInstances over heldObjectIDs and readThrough runtime/subject_value_test.go:TestSubjectOfANestedDefinition, TestSubjectOfADirectlyInstantiatedNestedUsage ✅ Faithful
An object a caller instantiated under a usage is the object that usage denotes from then on — a name or feature chain evaluated afterwards (-e "ctx.recv.got", a prompt line, %eval in P::ctx : recv.got, %eval in #1 : …) reads it, not a fresh materialization — and creating an object materializes and runs the nested parts whose types exhibit or perform behaviors with it, so the whole runs to quiescence once rather than part by part in the order of first reads instance.go Instantiate (registers the occurrence; Instance.explicit keeps it a root in condition.go readThrough), classifier_behavior.go materializeBehavingParts/runsBehaviors from startBehaviorsOfAll; repl/meta.go evalIn over objectRef conformance instance_nested_parts_run_to_quiescence, repl/evalin_test.go:TestReadsAfterInstantiateSeeTheRunObject, :TestEvalInObjectFormErrors, cmd/sysml/unset_feature_value_test.go:TestEvaluationAfterInstantiateReadsTheRunObject ✅ Faithful
A check reports the object it was evaluated against, so a verdict is labelled with that object rather than with the one supplied when the two differ context.go CheckResult, CheckConstraintOn, CheckRequirementOn, satisfy.go CheckSatisfactionOn, grpc/verify.go subjectOf runtime/subject_report_test.go:TestCheckReportsTheChosenSubject, TestCheckWithNoObjectReportsNoSubject, grpc/verify_subject_test.go:TestVerifyConstraintNamesTheNestedSubject ✅ Faithful
A satisfaction assertion (assert satisfy Inner::lim by big;) chooses the object its requirement's conditions read by the same rule the requirement does, so by naming an object carrying the requirement nested answers about that nested object, and an ambiguous one is reported rather than picked. An assertion stating its own conditions (satisfy requirement fits { require … }) resolves against the usage itself, so it too answers about the object carrying it satisfy.go CheckSatisfactionOn, condition.go checkSubject runtime/subject_report_test.go:TestSatisfactionRoutesThroughTheSubjectRule, TestSatisfactionOfItsOwnConditionsRoutesThroughTheSubjectRule, satisfy_nested_subject.sysml, robustness_test.go:testSatisfactionSubjectIsAmbiguous ✅ Faithful
An object is validated as a whole: every assertion about it and the objects it holds — each assert constraint (an AssertConstraintUsage, SysML v2 §8.3.19.2, an Invariant over the type's features) its type declares or inherits, each requirement usage it carries, and each satisfaction assertion (SatisfyRequirementUsage, SysML v2 §8.3.21.10) whose by subject is in its tree — is evaluated against the concrete object carrying it, nested parts and every element of a multi-valued feature included, one verdict per assertion per object labelled by the path from the root (wheels[2], one-based). A verdict is holds, violated (the condition evaluated false) or undecided (it could not be evaluated, carrying the reason), and the object is valid only when every verdict holds and the walk was complete: a walk that met an object graph without end within the runtime's bound is reported bounded and not valid. A constraint declared without assert states no invariant and is not swept. An object two features hold — a reference bound to a composite — is validated once, under the path it was first reached by, and is the subject of a satisfaction naming either feature, a chain through either included. The sweep is one runtime function, shared by %validate <object>, -validate=<object>, the ValidateInstance RPC, the Go client (Client.ValidateInstance answering a Validation) and the Python client (Model.validate_instance answering a Validation) runtime/validate.go Context.ValidateObject, validationWalk.walk, Context.carriedVerdicts, Context.satisfactionVerdicts, ValidationReport.Valid/Status; analysis/ask.go ValidationAnswer, ValidationReason; repl/validate.go; cmd/sysml/check.go; grpc/validate.go Service.ValidateInstance conformance instance_validate_nested_tree, instance_validate_same_type_parts, instance_validate_shared_object, instance_validate_undecided, instance_validate_bounded (conformance_test.go validation expectations), repl/validate_test.go, cmd/sysml/check_test.go:TestValidateObjectThroughCLI, grpc/validate_test.go, client/opensysml/surface_test.go:TestValidateInstance*, conformance/scenarios/09-verify.json, client/python/tests/test_verification.py, test_model_surface_integration.py ✅ Faithful
The by operand of a satisfaction assertion may be a feature chain (satisfy r by config.child;, assert not satisfy r by assembly.config.child;): the chain is kept whole from parse to verdict, resolved member by member to the nested feature, and evaluated as the expression it is, so the object of config is materialized and the one its child holds is the subject; the verdict and each diagnostic spell the chain as written, and a segment resolving to nothing reports the full chain parser/defusage.go parseRelationshipTarget (ast.FeatureChainExpr under ast.RelSubject); runtime/satisfy.go resolveRelationship (resolve.Resolver.ResolveTarget), targetText, chainRoot, SatisfySubject, chainSubject (over EvalContext.Eval); repl/satisfy.go chainNotation; repl/meta.go subjectInstance, keepSubject, subjectName parse/satisfy_chain_subject.golden; conformance satisfy_chain_subject.sysml (depth 2, depth 3, through a part typed by a def with its own nested part, a failing nested case), satisfy_chain_unresolved.sysml; repl/satisfy_test.go:TestSatisfyChainedSubject ✅ Faithful
A verdict about a nested object is labelled with that object as a reader can type it back — the object the search started from plus the features walked to it (A::o::b), ending in the declaration it materializes as an ambiguity's labels do — rather than with the object the command named context.go CheckResult (SubjectRoot, SubjectPath), condition.go carriersUnder, carrierFeatures, repl/query.go reportedSubject repl/subject_label_test.go:TestVerdictNamesTheNestedSubject, runtime/subject_report_test.go:TestReportedFeaturesEndInTheDeclaration ✅ Faithful
An ambiguity names each carrier by the features walked to it, ending in the declaration it materializes (Bolt #3 (front::bolt) vs Bolt #5 (rear::bolt); Component #2 (small) vs #3 (large) where one collection gathers both), so carriers are told apart however they were reached condition.go carrierLabels, carrierPath, carriersUnder runtime/subject_value_test.go:TestAmbiguousCarriersAreNamedByTheirPath, robustness_test.go:testPartsSubsettingOneCollection ✅ Faithful
Over gRPC an ambiguous subject is a typed reason (FAILURE_REASON_AMBIGUOUS_SUBJECT), so a client tells it apart from a violated condition without reading the message grpc/verify.go failureReason, api/proto/sysml.proto FailureReason grpc/verify_subject_test.go:TestVerifyConstraintReportsAnAmbiguousSubject ✅ Faithful
A requirement's subject/actor bindings are evaluated against the same object its conditions read context.go CheckRequirementOn over memberBindings runtime/subject_report_test.go:TestSatisfactionRoutesThroughTheSubjectRule ✅ Faithful
A subject or actor is bound under its name and short name, and a redefining one under every name of the features it redefines too — explicitly (subject renamed :>> truck), by short name (:>> t), implicitly by role (SysML v2 §8.3.21), or through a redefinition of a redefinition (KerML 1.0 §7.3.4.5) — so a condition inherited from the general reading truck or t sees the value; a redefinition valuing nothing reads the redefined binding, a satisfy … by subject overrides the declaration under all those names, and an unbound subject is reported under each. One written without a name of its own (subject <s> :>> x;) takes the redefined name as any usage does, so s and x are one member runtime/context.go memberBindings, memberNames, unboundSubjectNames, runtime/subsetting.go redefinedFeatures; ast/behavior.go SubjectMember.NamingFeature, DeclNamingFeature, symbols/builder.go namingIdent, resolve/document.go redefinesUnderItsName runtime/subject_short_name_test.go, runtime/subject_redefinition_test.go (TestRedefiningSubjectBindsTheRedefinedNames, TestRedefiningSubjectInheritsTheRedefinedBinding, TestUnboundRedefiningSubjectIsReportedAsUnbound, TestSatisfactionBindsRedefiningSubjectUnderRedefinedNames), symbols/requirement_member_short_name_test.go:TestBuildRequirementMemberShortNameEffectiveName, resolve/requirement_member_short_name_test.go ✅ Faithful
One object stands for each declaration reached in that search: the objects of a part held with a multiplicity (part wheels : Wheel[4]), those of a part nested inside it, and an object left behind by an earlier materialization of its holder are each one candidate, while two declarations feeding one collection (part small : Component :> subsystem) are two condition.go carriersUnder over occurrenceOf, rootInstances over heldObjectIDs robustness_test.go:testNestedPartHeldWithAMultiplicity, testPartNestedInsideARepeatedPart, testPartsSubsettingOneCollection, testDuplicateObjectsHoldingAPlainPart, testDuplicateObjectsOfOneDeclaration ✅ Faithful
The search for that object descends a declaration once per path, so composition naming its own kind (part def Node { part next : Node; }) terminates instead of materializing objects until the step budget is spent condition.go carriersUnder robustness_test.go:testRecursiveCompositionSubjectSearch ✅ Faithful
A false assertion is a verdict, not a malfunction (ErrViolated), and is distinguishable from an evaluation failure errors.go ErrViolated, context.go EvaluateConstraintOn repl/instance_test.go:TestConstraintEvaluationErrorIsNotAViolation ✅ Faithful
A constraint usage inherits its conditions from the definition it is typed by (constraint limit : MassLimit;) context.go chainMembers over semantics.Model.AllSupertypes instance_inherited_constraint.sysml ✅ Faithful
A parameter a typed usage binds (constraint limit : MassLimit { in m = mass; }) is visible to the conditions it inherits, and masks both the declaration it redefines and a same-named member of the object carrying the usage — including the usage's own name condition.go conditionFeatures over Model.MembersOf, eval.go evalFeatureReference instance_constraint_bound_parameter.sysml, instance_constraint_parameter_name_collision.sysml, runtime/condition_test.go:TestConstraintUsageBindsInheritedParameter ✅ Faithful
The conditions of a nested constraint (assert constraint [name] { <expr> }) are the conditions of the member stating it parser/behavior.go tryParseNestedConstraint, condition.go appendConditions parser/behavior_require_member_test.go:TestConstraintMemberNestedBody ✅ Faithful
A constraint body that declares parameters (constraint c { in x : Real; assert x >= 0; }) states conditions like any other, including conditions that start with a keyword (assert true, assert not false, assume null != x, assert if …); a condition missing its expression is a diagnostic parser/behavior.go atConstraintCondition, parser/expr.go exprStartKeywords parse/constraint_parameterised_conditions.golden, parser/constraint_condition_test.go, parser/negative_test.go:constraint_params_assert_no_condition ✅ Faithful
A constraint carrying no condition yields no verdict (ErrNoConditions) rather than a vacuous pass errors.go ErrNoConditions, context.go EvaluateConstraintOn/EvaluateRequirementOn runtime/constraint_test.go:TestConstraintWithoutConditionsIsNotAVerdict ✅ Faithful
A constraint body's action statements (assign, if, while/loop/for, send, terminate, perform — SysML v2 7.20, whose constraint body is a CalculationBody), action nodes and successions parse, but a constraint stating one yields no verdict (ErrStatementNotExecuted) and no solver query rather than one that ignored the step; a case's own steps are its procedure and are not refused condition.go appendConditions/statementKeyword, analysis.go CaseConditionsOf, solve/translate.go translator.condition runtime/constraint_test.go:TestConstraintBodyStatementIsNotAVerdict/TestConstraintBodyPerformIsNotAVerdict/TestConstraintBodyActionFlowIsNotAVerdict, solve/translate_test.go:TestBodyStatementRefuses/TestPerformedActionRefuses/TestActionFlowRefuses, solve/objective_test.go:TestCaseStepsAreNotBodyStatements ⚠️ Approximate (the statements are not executed before the conditions are evaluated)

Instantiation and Feature Values (SysML v2 §7.6 Feature Values, KerML §8.3)

Semantic Rule Implementation Test Case Status
A literal default is folded at instantiation instance.go Instantiate instance_derived_slots.sysml (folded) ✅ Faithful
A default that reads sibling features is derived per instance, evaluated against that object's feature values on demand instance.go GetFeatureValue/evalFeatureValueDefault, eval.go selfFeatureValue instance_derived_slots.sysml (doubled) ✅ Faithful
A default reaching through a nested part reads that part's own derived values eval.go evalFeatureChain (via GetFeatureValue) instance_derived_slots.sysml (total) ✅ Faithful
A default expression resolves declarations in the scope that declared the feature, while instance feature values take precedence shape.go EffectiveFeature.DeclScope, eval.go EvalContext.self instance_derived_slots.sysml ✅ Faithful
Mutually dependent defaults report a cycle rather than recursing to the step budget context.go derivingSlots, errors.go ErrCyclicFeatureValue robustness_test.go:cyclic_derived_slot ✅ Faithful
A value written with = holds at all times, not once: a FeatureValue with isDefault = false binds the feature to its expression's result (KerML 1.0 §7.3.4.5), and a binding requires the same values at both ends (KerML 1.0 §7.4.9), so once a feature the expression read changes, the derived value is derived again from what the object holds now — attribute a : Integer default 3; attribute d : Integer = a * 2; attribute dd : Integer = d + 1; reads d as 6 and dd as 7, then 18 and 19 after a is written 9, transitively and through a part or a binding the expression read through, and a default null collection a classifier's subsetter later supersedes re-derives the roll-up that read it folded. Recomputation is lazy — the write unmaterializes what read the feature, the next read derives it — and only a value nothing assigned tracks: one a run assigned keeps the assignment (d := 100 then a := 1 reads d as 100, dd as 101), a probe or transaction that wrote what a derived value read is undone with it, edges and materialization alike, and a value derived from itself is still ErrCyclicFeatureValue dependents.go (deriveFeatureValue opens the recording frame around the = evaluation only, noteRead lists the derived value on each feature value it reads and the feature value among what it read, forgetReads delists it before it is derived again so a branch it no longer takes cannot unmaterialize it, beforeWrite/afterWrite/invalidate unmaterialize what read a feature that changed, forgetEdgesOf drops every edge into or out of an object a failed creation abandons), instance.go SetFeatureValue, materializeFeatureValue, unfoldSubsettedDefaults, binding.go assignBindingEndpoint, classify.go refineFeatureValue, subsetting.go fillOptionalSubsetters, adopt.go commit, classifier_behavior.go forgetBehaviorWrites conformance derived_value_follows_assignment, derived_invalidation_test.go:TestDerivedValueRecomputesWhenWhatItReadChanges, :TestWrittenDerivedValueKeepsWhatWasAssigned, :TestWriteOfTheSameValueLeavesDerivedValues, :TestClassifierSubsetterRecomputesWhatReadTheFoldedDefault, :TestDerivedValueFollowsAWriteThroughAPartAndABinding, :TestProbeRollbackRestoresDerivedValuesAndTheirDependencies, :TestProbeRollbackForgetsDependenciesRecordedInIt, :TestDerivedValueForgetsTheBranchItNoLongerReads, :TestAbandonedObjectsLeaveNoDependencyEdges, :TestWriteUnderADerivationDerivesItAgain, :TestSelfReferentialDerivedValueStaysACycle, :TestNoDependencyIsRecordedOutsideADerivation, robustness_test.go:write_into_cyclic_derived_feature_values ✅ Faithful (object-level = values only: the in parameters of a calc or action usage are bound once per invocation and stay bound while its outputs are read — the calc-usage rows under Calculation (Calc) above — and a compiled calc reads parameters and library constants alone, so it has no object feature value to track)
A default over an undeclared feature fails naming the feature value instance.go evalFeatureValueDefault robustness_test.go:derived_slot_over_missing_feature ✅ Faithful
A multi-valued feature holds its default's contents; a single value written on it is the collection's one element instance.go GetFeatureValue runtime/instance_test.go:TestMultiValuedDefaultMaterializes, repl/instance_test.go:TestCollectionFeatureValuesShowTheirContents ✅ Faithful
A nested part usage with a body of its own is instantiated as that usage, so what its body declares wins over what its type declares, and an untyped nested part (part engine { ... }) still materializes instance.go compositeType, GetFeatureValue instance_nested_usage_body.sysml, instance_unnamed_redefinition.sysml, runtime/instance_test.go:TestNestedUsageBodyOverridesItsType, TestUntypedNestedPartMaterializes ✅ Faithful (both the named form and an unnamed :>> power = 250.0;, which takes the name of what it redefines — see the KerML 7.3.4.5 row above)
A single-bound multiplicity is both bounds, unless it is unbounded: [*] is 0..*, so a [*] feature materializes empty like [0..*] (KerML 1.0 §8.2.5.11, confirmed by OMG issue KERML11-204) semantics/multiplicity.go multiplicityRange semantics/multiplicity_test.go:TestMultiplicitySingleBoundStar, conformance multiplicity_unbounded_single_bound, parse/multiplicity_unbounded_and_subsetting.golden, passes/constraint_test.go:TestConstraint_RedefinitionUnboundedMultiplicity (a [*] redefinition keeps an inherited 0..* and loosens an inherited 1..*) ✅ Faithful
A required lower bound is materialized eagerly, so an unbounded one ([*..*]) or one past the materialization bound reports a multiplicity violation instead of allocating instance.go GetFeatureValue (maxMaterializedLowerBound, ErrMultiplicityViolation) robustness_test.go:multiplicity_infinite_lower_bound, :multiplicity_lower_bound_too_large, pilot-exec-diff w6d:lower-bound-three, :lower-bound-unbounded, :star-only, :held-lower-three-no-value ⚠️ Approximate (a lower bound above 1000 is refused rather than materialized lazily. Unrefereeable: the pinned artifact answers the declaration node rather than a value for every valueless required-lower-bound feature above, so the eager materialization has no reference answer to be measured against)
The values of a subsetting feature are values of the feature it subsets, so a nested part declared part a : Sub :> subsystem or part a : Sub subsets subsystem is one of the objects subsystem holds and a roll-up over subsystem sums over it (KerML 1.0 §7.3.4.4); a default is only what the feature holds where nothing else populates it (KerML 1.0 §7.3.4.5), so part subcomponents : C [*] default null; with part a : Leaf :> subcomponents; holds a, an inherited or redefined (:>> a) subsetting member keeps its membership, totalMass = mass + sum(subcomponents.totalMass) rolls up recursively through nested stacks, a default null collection nothing subsets stays empty, an explicit non-default binding stays authoritative, more members than the multiplicity admits is ErrMultiplicityViolation, and features subsetting each other (xs :> ys default null; ys :> xs default null;) report ErrCyclicFeatureValue rather than recurse subsetting.go subsettingContributions/relatedFeatureNames, instance.go GetFeatureValue (valueBinds + DefaultIsFallback, then holdContributed), shape.go EffectiveFeature.DefaultIsFallback conformance feature_chain_rollup_over_subsets, cubesat_mass_rollup, instance_empty_aggregate_subsetting_rollup (stack of two leaves is 210 [kg], a tower over the stack 311 [kg], the cycle), subsetting_test.go:TestDefaultNullCollectionHoldsTheMembersSubsettingIt (type-level, usage-level, inherited, redefined, nested, explicit binding, unsubsetted), :TestDefaultIsFallbackOnlyWhereWrittenDefault (a scalar default null with one subsetter holds it; two are a multiplicity violation), robustness_test.go:mutually_subsetting_features, :cyclic_subsetting_of_default_collections, queryexec/derived_test.go:TestExecuteProjectsEmptyQuantitySumsAndDefaultedSubsettedParts, docrender/markdown_test.go:TestMarkdownRollupReport, pilot-exec-diff w6d:subsetting-rollup (the reference rolls up the same 1.0), :subsetting-defaulted-count, :subsetting-two-count, :subsetting-none-count (disagree, unrefereeable: the pilot counts an undefaulted [*] collection as 1 whether two parts subset it or none does, and folds a default null one to 0, so it reads the feature as one value rather than as the objects it holds — see pilot-execution-referee.md) ⚠️ Approximate (self-assessed, the reference deciding no membership; contributions are the subsetting features declared on the same object; the subsetted collection is read-only with respect to them, and a subsetting feature declared elsewhere for the same object contributes nothing; the members' order in the collection is declaration order over the flattened type, which the spec leaves unordered)
A bracket multiplicity is the population, not a factor: part cell : Component[4] holds four objects, each carrying its own feature values, so a roll-up such as sum(cell.basicMass) counts each object once and a per-object value multiplied by the same 4 double counts. The subsetting features are among the population and anonymous objects make up the rest of the required lower bound instance.go GetFeatureValue (subsettingContributions, then mult.Lower.Value - len(contributed) anonymous objects), collections.go sum conformance multiplicity_rollup_counts_each_instance_once (a [4] collection, and a [3] one whose named subsetting part is joined by two anonymous objects), feature_chain_rollup_over_subsets, cubesat_mass_rollup ✅ Faithful
A redefining feature is the feature it redefines, so part subsystems : Component[*] :>> Subsystems makes both names read one collection, and a chain of redefinitions (dry :>> own :>> mass) reads one feature value under every name even when a usage restates the redefinition (part sat : Sys { attribute :>> own = 10.0; }) (KerML 1.0 §7.3.4.5) subsetting.go aliasRedefinedFeatureValues, redefinitionGroups, sharedRedefinitionName, redefinedNames, isFeatureOf conformance cubesat_mass_rollup, redefinition_restated_in_a_usage, redefinition_multilevel_base_name, redefinition_value_under_either_name, redefinition_valued_under_two_names, robustness_test.go:one_feature_valued_under_two_names ✅ Faithful (the shared feature value is the one the most specific declaration writing a value created, whichever name it wrote — the redefining name, the base name, or a name in between, including where the value is written in an abstract part usage a configuration specializes; one declaration valuing two names of the feature is ErrConflictingRedefinition rather than a silent pick)
A usage of any kind whose body restates or adds features is instantiated as that usage, so a struct-typed attribute takes its value from a nested body (attribute :>> material { attribute :>> v = 3.0; }) at any depth, and a renaming redefinition that restates no type is typed by the feature it redefines (KerML 1.0 §7.4.7) instance.go CompositeTypeOf, declaresFeatures, shape.go extractType, declaredType conformance attribute_nested_value_body, parse/nested_value_body.golden, shape_test.go:TestFeaturesOf_TypeInheritedThroughRedefinition ✅ Faithful
A feature bound to a value takes its own features from that value, so a body of the same declaration valuing one of them (attribute :>> ringCost = 400.0 { attribute :>> v = 9.0; }) states two values for it and is reported; a body that only re-declares features (the stdlib's item :>> edges : Ellipse = shape { attribute :>> Shell::edges::innerSpaceDimension, Ellipse::innerSpaceDimension; }) states no second value and reads the bound one instance.go restatedInValuedBody, restatedValueInBody, GetFeatureValue (ErrValuedFeatureRestated) robustness_test.go:valued_feature_restated_in_a_body, conformance attribute_nested_value_body (valuedWithReDeclaredFeatures) ✅ Faithful (the two values are equally specific here, neither one governing, so the model is reported rather than a value picked; a body over a value the redefined declaration wrote is the more specific declaration and governs instead — see the row below)
A body on a redefining declaration valuing features the value it inherits would supply governs over that value, so part def Ring { attribute cost : Cost = template; } re-opened as part r : Ring { attribute :>> cost { attribute :>> v = 11.0; } } reads r.cost.v as 11.0: the more specific declaration of a feature is the one that holds (KerML 1.0 §7.3.4.5, FeatureValue in KerML.kerml) instance.go valueBinds, bodyGovernsInheritedValue, restatedValueInBody, valuesAFeature (a value stated anywhere in the body, at any depth, is what makes it govern), read by CompositeTypeOf, Instantiate, materializeFeatureValue and adopt.go derivedFeatureValue conformance attribute_body_over_inherited_value (incl. deepReDeclarationKeepsInheritedValue, valueStatedAtDepthGoverns), attribute_body_over_inherited_value_chain, runtime/instance_test.go:TestBodyGovernsAnInheritedValue, pilot-exec-diff w6d:body-governs-valued (11.0), :body-governs-unvalued (2.0), :inherited-value-template, :inherited-value-no-body; conformance structured_attribute_own_features, inherited_feature_value_no_body, inherited_feature_value_redefined ✅ Faithful (all four cases match the pinned artifact: the body supersedes the inherited value rather than merging with it, so a feature the body does not value takes its type's own default — 2.0 — and not the bound value's, which is what a FeatureValue binding a feature as a whole implies. Reading such a default through the usage itself (template.v) or through an instance with no body of its own (plain.cost.v) is the same rule and now answers the same values: a structured attribute usage names one object of its own — instance.go namesOneObject, namesStructuredValue — so a chain reads its features rather than a value it does not have)
A check made without materializing an object reads the same values one built from the declaration holds, so a condition naming a feature a body governs over reports it uninitialized rather than judging the superseded value, and an edit confined to a governing body changes the type's shape a carried-over object is admitted by condition.go conditionFeatures, adopt.go writeShape, ShapeDigest, read by context.go memberBindings runtime/instance_test.go:TestConditionsDoNotReadAGovernedOverValue, runtime/adopt_test.go:TestShapeFollowsAGoverningValueBody ✅ Faithful (the two readings agree; a feature the body governs is read from the object, so a check without one names it uninitialized rather than answering from a value the model replaced)
A redefining feature that declares no value holds the value the redefined declaration wrote, evaluated in the scope that wrote it, so attribute grossMass :>> mass reads the inherited default under either name (KerML 1.0 §7.3.4.5) shape.go redefinedDefault, EffectiveFeature.DefaultScope, instance.go evalFeatureValueDefault conformance instance_redefined_attribute_default, subsetting_test.go:TestRedefiningFeatureHoldsTheRedefinedDefault ✅ Faithful
A multi-valued feature given a default holds that default's contents whether or not it is also typed, so attribute volumes : Real[0..*] = subsystem.volume is the chain's values rather than an empty typed collection instance.go GetFeatureValue, CompositeTypeOf conformance feature_chain_rollup_over_subsets, feature_chain_nested_multivalued, instance_test.go:TestTypedMultiValuedDefaultHoldsItsContents ✅ Faithful
A value type classifies values, not objects (Base::DataValue is abstract datatype DataValue specializes Anything, "entities that are values"), and a Real declares no features, so a valueless feature of one holds no value: every surface that reports a value reports it as <unset> — -instantiate/-e, %features, the JSON report, and pb.Value.unset / opensysml.UNSET on the wire — rather than as the empty object materialization creates for it runtime/instance.go Context.HoldsNoValue, UnsetText, isValueTypeSymbol; repl/meta.go formatValue/formatSlot/nestedInstances, repl/run.go namedValues/renderResults (which cmd/sysml/report.go renders as JSON); grpc/convert.go ValueToProtoIn, ProtoToValueIn (ErrUnsetNotAccepted); client/python/opensysml/values.py UnsetType/value_to_python conformance instance_valueless_value_typed_attribute, robustness_test.go:expression_over_a_slot_holding_no_value, repl/unset_feature_value_test.go, cmd/sysml/unset_feature_value_test.go:TestUnsetFeatureValueReadsTheSameOnEverySurface, gRPC conformance instantiate_unset_slot, grpc/unset_feature_value_test.go, client/python/tests/test_unset.py, pilot-exec-diff w6d:valueless-value-type ⚠️ Approximate (unrefereeable: the pinned artifact answers the declaration node rather than a value for a valueless Real, so it states no rendering either. The spec settles neither what materialization creates for such a feature nor a rendering for it, so the empty object stays and only the reporting is unset; a valueless 1..1 feature draws no diagnostic, since the multiplicity check bounds values a model binds and this one binds none)
Only a conforming multi-valued default is honoured: a default whose element count is within the declared multiplicity becomes the feature's value — a collection literal, one value, (), an expression's result, the objects a composite part[n] default names, quantities with their units — while one outside it is a multiplicity violation rather than a value broadcast to the lower bound, truncated to the upper one or dropped instance.go GetFeatureValue, checkDefaultCount (ErrMultiplicityViolation), semantics/multiplicity.go Range.CountViolation conformance multiplicity_default_merged, multiplicity_default_composite, multiplicity_default_nonconforming, multiplicity_default_redefinition, instance_test.go:TestCompositeMultiValuedDefaultHoldsTheNamedObjects, instance_test.go:TestNullDefaultHoldsNoElements, robustness_test.go:default_not_conforming_to_multiplicity, semantics/multiplicity_test.go:TestMultiplicityWithANonEvaluableBound (a bound that is not constant leaves that side unknown — the known side still bounds the count), :TestMultiplicityInfiniteLowerWithFiniteUpper ✅ Faithful
A feature that declares no multiplicity holds exactly one value, so a default is bound by the assumed 1..1 — attribute x : Real = (1.0, 2.0) is the multiplicity violation Real[1] = (1.0, 2.0) already is (KerML 1.0 §7.4.5) semantics/multiplicity.go AssumedRange, Model.EffectiveMultiplicityOf, used by runtime/shape.go extractMultiplicity and runtime/instance.go checkDefaultCount semantics/multiplicity_test.go:TestEffectiveMultiplicityAssumesOne, :TestMultiplicityOfANonUsage (a definition and a nil symbol declare none and take the assumption), conformance multiplicity_default_assumed, robustness_test.go:default_against_an_undeclared_multiplicity, cmd/sysml/materialize_test.go:TestCheckReportsMaterializationDiagnostics, pilot-exec-diff w6d:undeclared-multi, :undeclared-multi-typed, :held-undeclared-multi ⚠️ Approximate (the reference bounds it nowhere: it reports no static diagnostic for attribute xs = (1.0, 2.0) and answers both values through an instance, where our materialization reports the violation — w6d:held-undeclared-multi. The assumption bounds a default where its value count is known — when the feature value materializes; the static tier still bounds only a declared multiplicity, so a multi-valued default on an undeclared one is reported at materialization rather than by passes.checkValueCount)
Materializing an object is part of a run, so -instantiate reports every feature value that did not materialize and -validate reports no errors only for a run that found none: an invalid model exits 2 rather than 0 cmd/sysml/check.go runChecks, cmd/sysml/report.go reporter.finding, clean, status, over runtime/materialize.go Context.MaterializationErrors (read through repl/instantiate_report.go Session.InstantiateReport) cmd/sysml/materialize_test.go:TestCheckReportsMaterializationDiagnostics (exit code plus the diagnostic, for a clean model, a scalar default against [3], and a multi-valued default on an undeclared multiplicity), :TestCheckReportsMaterializationDiagnosticsAsJSON ✅ Faithful for -instantiate/-validate, documented in docs/reference/cli.md § Exit status
The prompt surface keeps the same rule: a meta-command that rendered a feature value it could not materialize — a %features listing carrying <error: …>, the same listing's JSON form carrying it as the API's error field, or an %eval of such a feature value, pinned to a context or not — answered nothing about it, so the failure is carried as the runtime's typed error in the session and a non-interactive (piped / non-TTY) run exits 2, while at a terminal the failure is reported at the prompt and the session goes on repl/run.go Session.HasErrors, MaterializationFailures, noteMaterializationFailure, noteIfMaterializationFailure, hasAnalysisErrors, recorded by repl/meta.go doFeatures/slotWalk, repl/features.go featuresJSON, doEval and doEvalLine, read by cmd/sysml/main.go sessionStatus repl/materialize_status_test.go:TestFeatureValuesCarriesMaterializationFailureIntoStatus, :TestFeatureValuesJSONCarriesMaterializationFailureIntoStatus, :TestEvalCarriesMaterializationFailureIntoStatus, :TestPinnedEvalCarriesMaterializationFailureIntoStatus, :TestFeatureValuesOfAConformingModelLeaveNoFailure, :TestPromptContinuesAfterAMaterializationFailure, cmd/sysml/materialize_test.go:TestPipedSessionExitsOnAMaterializationFailure, :TestSessionStatusAtATerminal ✅ Faithful
A feature value that could not be materialized is marked as such by the runtime that read it, so a surface tells it from any other failure to evaluate without matching rendered text, whatever expression it surfaced through; naming no feature value of the object is not such a failure runtime/errors.go FeatureValueError, ErrFeatureValueMaterialization, marked at runtime/instance.go GetFeatureValue over materializeFeatureValue, tested by repl/run.go noteIfMaterializationFailure repl/materialize_status_test.go:TestPinnedEvalCarriesMaterializationFailureIntoStatus, :TestEvalOfAnUnknownFeatureValueIsNoMaterializationFailure, :TestFeatureValuesCarriesMaterializationFailureIntoStatus (the marked error still unwraps to ErrMultiplicityViolation) ✅ Faithful
The check reads what it can: a walk that spent its budget, met a part deeper than it descends, or met a kind already being expanded above it reports what it did not read rather than that there were no errors, and being no model error it stays 0 runtime/materialize.go materializeWalk.walk (bounded), cmd/sysml/check.go runChecks, cmd/sysml/report.go reporter.warn (runtime.materialize.bounded) runtime/materialize_test.go:TestMaterializationErrorsBoundsAWideModel, :TestMaterializationErrorsOfAConformingObject, cmd/sysml/materialize_test.go:TestCheckDoesNotReportCleanWhenNestingWasElided ✅ Faithful
A feature of a value type that nothing values holds no value (attribute mass :> ISQ::mass; with no binding, reported <unset> as stated above), and a derived feature of the value it does not hold is nothing to read: the check descends into the objects a feature value holds, not into the object standing for an unset one, so Quantities::ScalarQuantityValue's dimensions = mRef.dimensions is not evaluated over an mRef no value bound and an unset quantity attribute is reported clean, as %features already lists it <unset>; a bound quantity still derives its dimensions and a default that genuinely fails, beside an unset quantity or nested under a part carrying one, is still reported runtime/materialize.go heldInstances over runtime/instance.go Context.HoldsNoValue conformance instance_unset_quantity_attribute_materializes, instance_set_quantity_attribute_derives_dimensions, instance_failing_default_beside_unset_quantity, runtime/materialize_test.go:TestMaterializationErrorsPassOverAnUnsetQuantity, cmd/sysml/materialize_test.go:TestCheckReportsMaterializationDiagnostics (an unset quantity attribute exits 0) ✅ Faithful
The multiplicity a redefining feature does not restate is the one it redefines, so a default it adds is bound by the redefined declaration's multiplicity (KerML 1.0 §7.3.4.5); an abstract feature that restates none is bound by the intersection of the features it redefines and subsets (KerML 1.0 §8.4.4.12.1), so abstract action decisions :> controls is bound by controls[0..*] and nothing subsetting it is no violation, while a concrete subsetting feature keeps the assumed 1..1 shape.go buildFeatures, inheritedMultiplicity, semantics/multiplicity.go Range.Intersect, passes/typecheck_value.go effectiveRange conformance multiplicity_default_redefinition, runtime/optional_feature_test.go:TestAbstractFeatureInheritsMultiplicityFromWhatItSubsets, semantics/multiplicity_test.go:TestRangeIntersect, passes/typecheck_value_test.go:TestValueCountAgainstRedefinedMultiplicity ✅ Faithful
A relationship target that resolves outside the object names no feature of it, so attribute totalmass :> ISQ::mass specializes the library feature and contributes nothing to a same-named feature of the object; a target the object carries under its name — including one a restating declaration masks — is a feature of it, and an unqualified target the declaring scope cannot see is looked up among the object's members subsetting.go relatedFeatures/isFeatureOf subsetting_test.go:TestSubsettingIgnoresALibraryFeatureOfTheSameName, conformance cubesat_mass_rollup ✅ Faithful
An object carries the features the Systems, Domain and OpenSysML libraries declare for its kind — Items::Item::voids, isSolid = isEmpty(voids), shape :>> spaceBoundary, subitems, Parts::Part::ownedPorts, Requirements::RequirementCheck::subj/actors/stakeholders/assumptions/constraints, the Geometry ShapeItems fields — with their defaults, derived expressions and multiplicity, masked by a model's own :>> like any inherited feature, while the Kernel Semantic Library frame every object restates (Anything::self, Occurrence::portions, timeSlices, snapshots, startShot, endShot, the transfer features) stays out of the shape. Which is which is the library tier the loader records for each library document (Kernel Semantic, Kernel Data Type, Kernel Function, Systems, Domain, OpenSysML), kept through the symbol cache, not a path the runtime inspects symbols/library_tier.go LibraryTier (Frame()), symbols/index.go MarkLibraryTier/LibraryTier, libs/loader.go TierOf, libs/snapshot.go (format 3, carrying each library document's tier and text digest); runtime/library_frame.go frameFeature (a frame tier, a member a value-held type's value carries itself — semantics/shape.go HeldByValue, the library records staying objects; Structured values below — a directed or result parameter, or a Systems/Domain member that redefines or subsets a frame root), runtime/shape.go buildFeatures; runtime/adopt.go writeShape names a library type with the index's LibraryIdentity — a digest of every library document's name, tier and text — rather than expanding it, so the digest stays bounded and two contexts loaded over libraries that differ do not agree on it libs/library_tier_test.go (tiers, identity through the snapshot, identity moved by an edited document), symbols/library_tier_test.go, runtime/library_shape_test.go (shape, classification, inheritance and masking, stable order, no extra objects, digest, typed error, subject/objective aliasing, redefinition through masked targets), runtime/adopt_test.go (TestAdoptRefusesASameNamedLibraryTypeOfAnotherLibrary, TestShapeDigestExpandsALibraryOfUnknownText), runtime/connector_test.go:TestOptionalConnectorLinksNothingOfItsOwn, conformance instance_library_geometry_box, instance_library_item_part_features, instance_library_requirement_features, instance_library_requirement_subject_binding (subject v : Vehicle = car; binds v and the inherited subj, through shape.go extractDefaultValue reading a SubjectMember's binding), repl/library_features_test.go (%features, %eval box.isSolid, box.voids), grpc/library_feature_values_test.go ⚠️ Approximate (the features materialize and evaluate — box.isSolid is true, box.voids and box.shape empty, box.faces six objects, box.edges their twenty-four edges, rect.e1.length the rectangle's own length — and a model inheriting nothing from these libraries keeps its shape digest. One residual: the Kernel frame roots a Systems member may restate (Anything::self, Occurrence::timeSlices, incomingTransfers, outgoingTransfers) are named in library_frame.go frameRoots rather than derived from the library, since nothing in the library marks them. The Geometry graph's own rules — a listed item classified by the collection it is listed in, a nested usage reading Rectangle::length from its enclosing object, faces.edges collected across the faces — are the five rows below. Unrefereeable: the pinned pilot artifact answers declaration nodes, not instance values, for these features)
An optional composite feature fills to its lower bound like a collection: part spare : Wheel[0..1] holds no object of its own and reads as the empty sequence, holds the object a feature subsetting it holds, and an abstract feature holds only what subsets it (KerML 1.0 §7.3.3.1), so a library's shape :>> spaceBoundary [0..1] or voids [0..*] materializes no anonymous object; a required abstract feature whose contributions fall short of its lower bound is ErrMultiplicityViolation. A required feature holding nothing is still ErrUninitializedFeatureValue when read runtime/instance.go materializeFeatureValueIntrinsic, holdContributions, FeatureValue.ReadValue; runtime/eval.go selfFeatureValue, evalFeatureChain, emptyDeclaredFeature runtime/optional_feature_test.go, runtime/library_shape_test.go:TestInheritedLibraryFeaturesMaterializeNoObjects, conformance instance_library_geometry_box (voids, shape) ⚠️ Approximate (a [0..1] part used to materialize one object; the lower bound is now the population as it already was for [0..*] and [n..m]. Unrefereeable: the pinned artifact answers the declaration node for a valueless optional part)
The values of a feature are classified by every type of that feature, and a feature value binds the feature to its value's, so an object written into a typed feature is classified by the feature's type rather than refused: item e1 [1]; listed in item :>> edges [4] : Line = (e1, e2, e3, e4) makes the e1 object a Line and it carries Line's features (vertices, length) from then on, and its relationships too — the binding connectors, subsettings, connections and connectors a classifier declares hold for the object as its declared type's do, in type order and each once. A classifier redefining a feature the object already carries refines it: the object reads the redefinition's default, type and multiplicity, keeps what it held where the redefinition admits it (a written value, an object now classified by the narrower type) and is refused whole where it does not, while a usage's own redefinition (item raw : Car { :>> doors default = 3; }) is not refined by a classifier redefining what it redefines (KerML 1.0 §7.3.4.5 Redefinition). Two comparable classifiers declaring one name without redefinition (A { attribute n : Real = 1.0; }, B :> A { attribute n : Real = 2.0; }) read the declaration of the narrower type — it masks the wider type's as it does on an object created as that type (KerML 1.0 §7.3.2.1 Type Membership) — whichever classified the object first, so what the object holds under the name is settled by its types alone; a held value the governing declaration does not admit refuses the classification and the object stays as it was. A classifier's renamed redefinition of a carried feature (grossMass :>> mass = …) refines it the same way, and the walks over an object's feature values — MaterializationErrors, the nested objects a constraint's subject holds, %features, signal routing — cover the features every type of the object declares, the declared type's first then each classifier's in declaration order, each shared feature value once. A collection is classified whole: one object refused leaves every object of it as it was, the objects the classifications' behaviors made abandoned with them. The object a selected variant materialized is held as any object is: a typed feature holding a variation's value (item tallied : Tallied = car.engine) classifies that object by the feature, which keeps its identity as the variant's object. A classifier renaming a behavior the object already runs (exhibit state fancyModes :>> modes), or one whose behavior a running one renames, starts no second execution: the one behavior answers to every name redefinition gives it through any type of the object (KerML 1.0 §7.3.4.5), and a classifier's behavior writes the features the classifier itself declares, the performer being the object under every type of it. istype and hastype judge an object by every type of it too — the object a selected variant materialized included, and a type the object already conforms to is still recorded as a direct type when a feature of it holds the object, so hastype answers alike in either classification order — hastype when one is the stated type itself, istype when one conforms to it — and a name subject to them is read as any name is: a feature of the enclosing object reads that object's value, not a fresh occurrence of the declaration (KerML 1.0 §7.4.9 Classification Expressions). Object identity is kept — rect.e1 and rect.edges#(1) are the one object — and a classification that cannot hold is still the write's typed error: an Integer into edges : Line, or an object of a type disjoint by specialization from the feature's, is ErrTypeMismatch (KerML 1.0 §7.3.4.3 Feature Typing, §7.4.11 Feature Values, §7.4.6.3 Binding Connector Declaration) runtime/classify.go Context.classify, canClassify, instanceConforms, comparableTypes (comparable: neither type refuses the other along the specialization graph), classifyHeld (one journal over the value, every object of it, a selected variant's included), refineFeatureValue (a redefining or masking declaration of the classifier governs a carried value), subsetting.go aliasRedefinedFeatureValuesOf (a carried value refined by the redefinition renaming it), outer_feature.go Context.FeaturesOfObject (the features of an object under every type of it, read by materialize.go, condition.go nestedObjects, signal.go, repl/meta.go), declaredBy/bindingsOf/connectionsOf/anonymousConnectorsOf/subsettingFeaturesOf/subsettedNamesOf (relationships over every type of an object, read by binding.go, subsetting.go, routing.go, connector.go); runtime/write_conformance.go valueConforms (an object the feature's type can classify conforms, Value.Object naming a variant's object too); runtime/classifier_behavior.go runsBound (a member bound under a redefinition name runs once), BehaviorNamed (redefinition names over every type of the object), namesPerformerFeature (a body's name denotes a feature of the performer under any type of it); runtime/eval.go directValueTypes (an object's declared type then the type of each feature holding it), valueHasType; runtime/instance.go Instance.classifiers, CompositeTypeOf (an untyped occurrence usage names one object), Context.declaresFeatures (a redefining usage materializes the features the redefined usage's body declares); runtime/holders.go holdingFeatures/mentionedFeatures/passedFeatureReferences/materializeHolders (a feature whose objects another object-holding feature's value may answer as its own — listed, chosen by a condition or an index, an argument a calc's returns pass on, directly or through the locals its body declares, assigns or iterates with (calc def choose { in x; attribute y = x; return y; }), the calc a call through a feature chain applies (picker.pickChosen(lead, trail)) resolved from the chain, which is the callee rather than an argument (returnedArguments/chainTarget) — any argument of a function whose body the model does not write — a body's result — is classified before its own features are read, whichever is read first; a chain's values, a condition tested, an operand computed from, an argument no return passes on and an attribute's data hold nothing; a holder that cannot materialize — too few values for its multiplicity, a type its object is refused by — holds nothing, and its error is reported when it is read, so the held feature reads alike in either order) runtime/classify_test.go:TestListedValueIsClassifiedByTheFeatureType, :TestIncomparableValueIsRefusedByTheFeatureType, :TestCalcLocalsPassArgumentsOnToTheReturn, :TestFailingHolderDoesNotFailTheFeatureItWouldHold, :TestRelationshipsComeFromEveryTypeOfTheObject, :TestRefusedClassificationUndoesWhatItsBehaviorsDid, :TestRefusedCollectionClassificationUndoesTheEarlierObjects, :TestRefusedCollectionClassificationAbandonsWhatItsBehaviorsMade, :TestComputedHoldingIsClassifiedWhicheverIsReadFirst, :TestOnlyFeaturesPassingObjectsOnHoldThem, :TestArgumentNotReturnedIsNotHeldByTheCall, :TestChainCallReturnedArgumentsAreHeldByTheCall, :TestNarrowerClassifierRefinesTheCarriedFeatures, :TestSameNameFeaturesOfClassifiersReadTheNarrowerDeclaration, :TestObjectWalksCoverTheFeaturesClassifiersAdd, :TestClassifierBehaviorsWriteTheFeaturesTheClassifierAdds, :TestTypePredicatesSeeTheClassifiersOfAnObject, :TestTypePredicatesSeeTheTypesOfASelectedVariantObject, :TestHoldingByAWiderTypeRecordsItAsADirectType, :TestSelectedVariantObjectIsClassifiedByTheFeatureHoldingIt, runtime/classifier_behavior_test.go:TestBehaviorNamedFollowsRedefinitionByAClassifier, conformance instance_binding_classifies_value, function_value_chain_holder (lead, returned by picker.pickChosen, is a Tallied; trail is not), instance_classifier_relationships (an untyped raw made a Site sends through Site's connection and reads its binding and subsetting) (model-owned Segment/Loop/Square mirror, Broken binding 3 into edges : Segment stays type mismatch), instance_library_geometry_rectangle (rect.edges four Lines, rect.e1.vertices two objects), instance_library_geometry_triangle, instance_library_geometry_box ✅ Faithful (unrefereeable: the pinned artifact answers rect.edges with the four usage nodes e1…e4 unevaluated and stack-overflows on rect.e1.length; the rule is the spec's, not the pilot's)
Dynamic object creation: an object created while a behavior runs is a first-class occurrence of the run — it has an identity of its own (Instance.ID), begins its life where it is made (create: T #n in the trace), is classified by the type it is created as and by the type of every feature later written to hold it, starts the behaviors its type exhibits or performs as an object materialized from a declaration does, and is reached by all T, by feature chains through the feature holding it, by %features, by isDuring/istype, and by routing and addressing over its value. The spellings that create one are the ones the specifications define: the instantiation expression new T(args) (KerML 1.0 §7.4.9 InstantiationExpression, a ConstructorExpression when it binds features by args, SysML v2 §7.6 over occurrence definitions), evaluated where any expression is — an assignment assign cars := (cars, new Car(1));, a feature value part car : Car[0..1] = new Car(3);, an argument addNew(cars, new Car(n)), a send new Data(…) — so a loop creates one object per iteration, and two new Car(…) in one context are two objects (=== is false, size(all Car) counts both); and the library functions OccurrenceFunctions::create/addNew/addNewAt, which begin an occurrence the argument's evaluation materialized. SysML v2 offers no other textual constructor for an occurrence of a definition — no create keyword, no T() without new, and an occurrence usage declared in a definition (part cars : Car[*]) populates only to its lower bound — so a second object of one usage is created by an expression and held by the feature it is written into: writing an object into an object-valued feature classifies it by the feature's type (the row above) and, where the feature is composite (a part, not a ref part) makes it a portion of the owner, ended with it by destroy — of every whole whose composite feature holds it, as a binding makes two hold one port, and the first to hold it becomes its home (Instance.Owner); an object already homed, or one that would be home to its own home, keeps its home, and one its home's composite feature drops (assign cars := ()) moves home to another composite feature still holding it (the lowest object's, its first feature by name) or, where none does, is released, so the composite feature it is written into next becomes its home; either way, every composite feature still holding it ends it. An ended whole (a completed performance, a terminated part) takes no portion live or ended after it: a composite write giving it one is ErrOccurrenceLifetime before anything is held, a portion's life lying within its whole's, while a reference to the live object and a portion ended no later than the whole are held (a portion that began before the whole is admitted: the runtime records where a life began and ended, not where it was held, so the begin boundary is not enforced — ⚠️) (classify.go adoptWritten, releaseDropped, otherHomeOf; robustness_object_lifecycle_test.go an_object_its_home_drops_is_rehomed_to_the_whole_still_holding_it, a_composite_write_giving_an_ended_whole_a_live_portion_is_refused). A new of a data type (attribute def) constructs a data value, not an occurrence, as KerML's InstantiationExpression over a DataType does. Under explore, creation and destruction are ordinary moves: identities are allotted in run order, so every linearization of two branches each creating an object reaches one outcome, and a destroy racing a read reaches exactly the two outcomes the two orders give; a snapshot carries created objects with their behaviors. The RDF mapping (-convert ttl) is of the model, and no surface exports the objects of a run, so nothing there changes runtime/signal.go evalConstructor and buildConstructedMessage (send new T(…)), both through EvalContext.constructObject (arguments, materialization, then startClassifierBehaviors under one beginJournal: a construction failing at any step leaves nothing — the objects its arguments made are abandoned, and what its behaviors wrote or sent to other objects is rolled back); runtime/instance.go Context.materialize (identity, created), SetFeatureValue (admits, then holdWritten, then stores), materializeFeatureValue and materializeIntrinsicValue (the binding endpoints binding.go inspects), all under Context.storedBeforeStarting (classifier_behavior.go: the behaviors a hold starts are attached but run only once the feature holds the value, so one reading the feature reads the object it started for, and the hold's journal stays open over their run — a start failing undoes hold and store together, the trace records the run made included — snapshot.go markJournal/rollbackJournal capture and restore the recorder, so a rolled-back store or constructor reports no step the run does not show — while an older behavior failing in answer is its own, the store kept; constructObject writes a constructor's arguments under Context.storedTogether, one such boundary over all of them, so a behavior the first argument starts reads the later ones stored); runtime/classify.go Context.holdWritten (refuse, as ErrOccurrenceLifetime, a composite write that would make the holder a portion of itself — adoptWritten, isPortionOf — then adopt an ownerless object into the composite feature, then classify it by the holding feature, so the behaviors the feature adds start on a part that knows its whole; one journal, beginHoldJournal, so a refused write leaves no adoption behind), holdDeclared (the same adoption for a default's, a binding's or a contribution's value: materializeDerived, assignBindingValue, holdContributed), ownsHeld (a composite usage per SysML v2 §7.6.2), releaseDropped, holdsItself; runtime/extent.go Context.objectsOf, runtime/condition.go heldObjectIDs (a live object only a destroyed holder refers to stands as a root, so all T keeps it), rootInstances, carriersUnder (a destroyed object is no subject of a check, whether standalone or the stale part a live whole retains); runtime/lifetimes.go destroy (ends the object and its portions, ends the behaviors they perform, and drops the messages addressed to them, or routed to a port among them, from the bus — forgetMessagesTo — which the journal restores when the destruction is rolled back); runtime/builtins_occurrence.go builtinOccurrenceCreate, builtinOccurrenceAddNew conformance object_created_by_constructor + trace golden, object_destroyed_at_runtime + trace golden; robustness_object_lifecycle_test.go:TestRuntimeRobustnessObjectLifecycle (two_objects_of_one_usage_are_distinct_and_held, creation_in_a_loop, a_part_moved_between_wholes_ends_with_the_new_whole, send_new_starts_the_message_objects_behaviors, a_failed_constructor_leaves_no_argument_object, destroying_a_holder_leaves_what_it_referred_to_in_the_extent, a_destroyed_object_is_no_subject_of_a_check, a_failed_constructor_rolls_back_what_its_behaviors_wrote, a_refused_write_leaves_no_adoption_for_an_outer_rollback, a_rolled_back_store_or_constructor_leaves_no_trace_of_what_it_undid, a_failed_constructor_revives_what_its_behaviors_destroyed_whole, a_part_declared_or_bound_to_a_new_object_ends_with_the_whole, an_object_two_wholes_hold_composite_ends_with_either, behaviors_a_write_starts_run_once_the_feature_holds_the_object, explore_creating_objects_is_deterministic, and the destruction cases listed at destroy); held_image_test.go:TestHeldImageRoundTrip over both fixtures ✅ Faithful (the addressed send … to <expr> that picks the second object is Track E's routing item, not this row)
A qualified name naming a feature of an enclosing type, read from an expression nested in a usage of that type, is that feature of the enclosing object: item :>> e1 { attribute :>> length = Rectangle::length; } reads the rectangle's length, a level deeper RectangularCuboid::length inside tf : Rectangle reads the cuboid's, and e3.length = e1.length is the sibling feature of the same enclosing object. A qualified name that does not name a feature of an object on the evaluation's ownership chain still resolves as a declaration (a type, a calc, a library constant), and imports, aliases, visibility and a model's own shadowing are untouched, since the name is resolved first and only its referent is looked up among the enclosing objects (KerML 1.0 §7.4.9.3 Primary Expressions — a feature reference is evaluated relative to the object featuring the referenced feature; §7.2.5.2 Namespace Declaration, qualified names) runtime/outer_feature.go EvalContext.outerFeatureValue (walks self and its owners, maps the resolved feature symbol to the effective feature name each object carries, reads its value), called from runtime/eval.go evalNameGeneral and evalFeatureChain (sibling chain heads) runtime/classify_test.go:TestQualifiedOuterFeatureReadsTheEnclosingObject, conformance instance_nested_usage_outer_feature (model-owned Frame::span, sibling bar.len, bar.len + Frame::gap), instance_library_geometry_rectangle (rect.e1.length = 4 [m], rect.e3.length = 4 [m]), instance_library_geometry_box (box.tf.length = 2 [m], box.slf.length = 1 [m] — the side faces read RectangularCuboid::height) ✅ Faithful
A feature chain valued over a collection collects across the collection: item :>> edges [24] = faces.edges is every edge of every face, in face order, and a chain end that is itself a collection (edges.vertices) flattens the same way; a binding whose end path crosses a collection (bind [0..*] base.edges = [0..*] be) visits every object the path reaches. A collection valued this way is checked against the feature's multiplicity and type like any other value, and a required lower bound the named subsetting features fall short of is filled from an optional subsetting feature with spare upper bound before an anonymous object is made up for it — CuboidOrTriangularPrism::srf : Quadrilateral [0..1] is the sixth face of a Box, not an anonymous Polygon beside it (KerML 1.0 §7.3.4.6 Feature Chaining, §7.3.4.4 Subsetting, §7.4.12 Multiplicities) runtime/eval.go evalFeatureChain (collection-valued heads), runtime/binding.go resolveBindingLocation, bindingEndpointValue (collection-valued paths), runtime/subsetting.go subsettingContributions, fillsFromSubsetted, fillOptionalSubsetters, materializeSubsettedCollections (guarded by context.go readingSubsetted, so mutually subsetting features stay ErrCyclicFeatureValue) runtime/classify_test.go:TestChainValueCollectsAcrossTheCollection, :TestOptionalSubsetterFillsTheCollection, conformance instance_chain_valued_subsetting (model-owned Panel.sides, corners = sides.corners), instance_library_geometry_box (box.edges twenty-four, box.faces six), instance_library_geometry_rectangle (rect.vertices eight from edges.vertices), robustness_test.go:mutually_subsetting_features ✅ Faithful (unrefereeable: the pinned artifact answers box.edges with the unevaluated edges usage node and box.tf.length with its [ operator node)
A binding connector's end multiplicities bound how many values of each end take part, so bind [0..*] a.edges = [0..*] b equates the two collections whole — two whole bindings of one collection agree or are ErrBindingConflict — while binding [1] bind [0..1] tf.edges = [0..1] tfe links one unspecified value of each end: the model states that tfe is some edge of tf, not which, so reading tfe — and tflv, tfe.length, and vertices, subsetted by tflv = tfe.vertices — is the typed ErrBindingEnd naming the binding and both of its ends (box.tfe is bound by … which makes some value of tfe a value of tf.edges without saying which value of either; the model does not state what tfe holds) rather than a witness the runtime picked. Each partial binding is read on its own: the runtime does not solve the conjunction of several partial bindings of one feature, so a feature they would jointly pin down ([1] bound [0..1] to two collections sharing exactly one value) is reported the same way. A partial binding determines nothing by itself, so it never decides a feature bound whole as well: every binding of the feature is read, a whole binding — or a written value or default — answers whatever order the bindings are declared in, and the partial-binding error is reported only where no whole binding does. An end whose path crosses a collection (bind [0..*] groups.items = [0..*] allItems, groups multi-valued) reaches that feature on every object the collection holds, in the collection's order, and holds their values together: allItems is every group's items in group order, an end multiplicity counts those values as one sequence ([4] is met by two groups of two, [5..*] is ErrMultiplicityViolation over the four), and a step of the path holding a non-object, a feature the reached objects lack, a destroyed object or an unmaterialized one is ErrBindingEnd. The union determines none of its objects' parts: a group's items keeps what it holds on its own — a default, a write, or the objects it materialized — and one holding nothing of its own is the typed ErrBindingEnd naming the collection (Group.shares is bound by … through every object groups holds) rather than a partition the runtime picked, whichever end is read first. The connector's own multiplicity is how many links it declares, so binding [1] bind [0..*] base.edges = [0..*] be links one value of each end: an end whose feature may hold more is partially bound — one whose declared lower bounds already exceed the links (be [2]) is decided so before its value is read, since it can never be linked whole — and so is the other end, which is some unspecified value of it; an end holding a value of its own (Disc::edges = shape) keeps it, the binding only relating it to the other end, and one holding nothing of its own is ErrBindingEnd. binding [2] over two [2] ends is a whole binding, and bind a = b with no connector multiplicity is whole as before; a whole binding's count is checked against the connector's own range too — binding [2] identifying one value is ErrMultiplicityViolation — and binding [0] declares no links, so it assigns nothing to either end. The connector multiplicity is quoted in the diagnostic (binding [1] bind …). The end multiplicity is carried from the syntax to the runtime rather than re-read from the declaration, and bind [m] a = [m] b states m as the first end's multiplicity, as binding [1] bind [m] a = [m] b does — bind declares no connector for a multiplicity of its own (KerML 1.0 §7.4.6.2 Connector Declaration — end multiplicities; §7.4.6.3 Binding Connector Declaration; §7.3.4.6 Feature Chains — a chain's values are those of its last feature on every value of the preceding ones; SysML v2 §8.2.2.6 BindingConnectorAsUsage) ast/defusage.go ConnectorEnd.Multiplicity (each of Usage.ConnectorEnds); parser/defusage.go parseBindingEnds, parseConnectorEnd (the bind shorthand's leading multiplicity is end 0's); lower/binding.go lowerBinding, BindingEnd.Multiplicity, Binding.Multiplicity; libs/record.go (cache format 26); runtime/binding.go resolveBindingSet, partialBinding, endpointRequiresMoreThan, endpointAdmitsMoreThan, bindingText, wholeBindingCounts, bindingLinksNothing, attemptBinding (multiplicity bounds read through semantics.RangeIn, so a bound naming a valued feature evaluates in the binding's scope), resolveBindingLocations, bindingEndpoint.spread, bindingEndpointValue, readBindingEnds, heldOnItsOwn, UndeterminedBindingError.Across parse/binding_indexed_ends.golden, parse/keyword_as_name.golden, robustness_test.go:binding_multiple_collection_contributors (partial → ErrBindingEnd, whole_unequal → ErrBindingConflict, whole_equal succeeds, whole_beside_partial in either declaration order), :binding_multiple_scalar_contributors, classify_test.go:TestBindingEndAcrossACollection (the union in either read order, members undetermined by the union in either order, the union against its own value, end multiplicity over the union, a collection holding no object, a non-object step, a missing feature), binding_connector_multiplicity_test.go:TestConnectorMultiplicityBoundsLinks (both ends of binding [1] over a [1] and a [2] feature undetermined, an end with a value of its own kept), :TestWholeBindingWithoutConnectorMultiplicityStillChecksCounts, :TestConnectorMultiplicityWideEnoughIsWhole, :TestConnectorMultiplicityDeclaresLinkCount (binding [2] over one link, binding [0] assigning nothing), robustness_partial_binding_test.go:TestRuntimeRobustnessPartialBinding (a lower bound above the links, mutually partial bindings, a chain through the undetermined end), runtime/shape_items_limits_test.go (box.tfe, box.tfe.length, box.tflv, box.vertices; cyl.base.edges, cyl.af.edges answer), conformance instance_library_geometry_box (box.tfe, box.tfe.length, box.tflv, box.vertices errors), instance_library_geometry_cylinder, instance_library_geometry_box_void ⚠️ Approximate (the partial case is reported, not resolved: nothing in the spec, the library or the pinned artifact — which answers box.vertices, box.tfe and box.tflv with the unevaluated usage node — determines which edge each [0..1] binding links — the bindings of tfe on tf.edges and ff.edges pick one member each from disjoint collections, MatesWith holds for every choice and size(edges) counts faces.edges — so box.vertices, box.tfe…box.urre and box.tflv…box.brrv stay ErrBindingEnd. A rule choosing a witness would be an invention; the library's own size(vertices) == size(edges) is unsatisfiable beside faces.vertices subsets vertices, see omg-issues.md. Solving a conjunction of partial bindings that does determine a value is not implemented)
A feature whose lower bound is zero and which holds no value is the empty sequence, on every surface: %eval box.shape, %features box and -instantiate all answer [] for shape :>> spaceBoundary [0..1], while a required feature holding nothing is still ErrUninitializedFeatureValue, and a valueless value-typed feature is still <unset> (the row above) (KerML 1.0 §7.3.4.1 Features Overview — a feature's values are a sequence; §7.4.12 Multiplicities) runtime/instance.go FeatureValue.ReadValue, repl/meta.go formatFeatureValue (reads through ReadValue) repl/library_features_test.go:TestFeaturesListsInheritedLibraryFeatures (shape = [], not <unset>), conformance instance_library_geometry_box (box.shape, box.voids), repl/unset_feature_value_test.go (a valueless Real stays <unset>) ✅ Faithful
A usage declared with no kind keyword is a reference usage (doubled = span * 2.0; inside a part def), so it is a feature of the object that materializes and evaluates like any attribute — Triangle::base { length = Triangle::length; } values the base's length — while the same name = value; inside an enum def is an enumeration literal and no feature of anything (SysML v2 §7.6 Usages — DefaultReferenceUsage in SysML.xtext; §7.7 Enumerations) parser/defusage.go parseBodyMember (a bare name is an enumerated value only under inEnumBody, an attribute-kinded usage elsewhere), semantics/shape.go IsShapeFeature conformance instance_default_reference_usage (beam.doubled = 8.0, beam.end.offset = 4.0 through Beam::span), instance_library_geometry_triangle (tri.base.length = 4 [m]) ✅ Faithful
What ShapeItems declares beyond these rules fails as a typed error, never as an empty collection, a guessed value or a panic: matingOccurrences and spaceBoundary are Kernel Semantic frame features (Occurrences.kerml) and so not features of the object — ErrNoSuchFeature — which is also why the assert constraint { isClosed == vertices->forAll{ … includes(p1.matingOccurrences, p2) } } bodies of Path/Polygon are not evaluated as part of materializing a shape; Cylinder/Cone bind cf : Surface whose edges are the Kernel StructuredSpaceObject's, so be/ae are ErrBindingEnd ("cf.edges": feature edges not found) and edges = faces.edges/vertices = faces.vertices are ErrNoSuchFeature over cf, while base.edges and af.edges — each Disc's own edges = shape, related to be by the one link binding [1] bind [0..*] base.edges = [0..*] be declares — answer the ellipse (the row above), as do faces (three), height and a Cylinder nested as a Box's voids; RightTriangle::hypotenuse.length = (Triangle::length^2 + Triangle::width^2) is the library's squared length and is reported as the dimension mismatch it is (ErrTypeMismatch, 25 [m**2] into a LengthValue) rather than rewritten; an Ellipse has one edge and no vertices, both of which evaluate runtime/errors.go ErrNoSuchFeature (a member read, chained read or write naming no feature of the object), runtime/eval.go evalFeatureChain, runtime/instance.go GetFeatureValue/SetFeatureValue, runtime/library_frame.go frameFeature runtime/shape_items_limits_test.go:TestShapeItemsUnsupportedExpressionsAreTypedErrors, conformance instance_library_geometry_triangle (tri.hypotenuse.length error), instance_library_geometry_box (box.self, box.timeSlices, box.startShot errors) ⚠️ Approximate (what the library determines from the dimensions — every Box/Rectangle/Triangle/Ellipse face, edge and vertex collection not routed through a [0..1] group, a Cylinder's/Cone's faces and their edges — evaluates; the vertex-mating constraints, matingOccurrences, spaceBoundary, the curved face's edge graph through cf : Surface and the [0..1]-bound groups are the typed errors named here, none an empty collection)

Redefinition in a Specialization (KerML §7.4.7 Redefinition, SysML v2 §7.6)

Semantic Rule Implementation Test Case Status
A redefining feature's declared type need not conform to the redefined feature's type: a redefinition is a subsetting (KerML 1.0, formal/2026-03-01, §8.3.3.3.6), so the redefining feature is typed by its own typings and the redefined feature's types (§8.3.3.3.4), and neither §8.3.3.3.6 nor SysML v2 §8.3.x declares a type-conformance constraint (the normative redefinition constraints are validateRedefinitionDirectionConformance, validateRedefinitionEndConformance, validateRedefinitionFeaturingTypes and validateRedefinitionMultiplicityConformance). The pinned pilot validator is silent on part :>> p : B under part p : A with A, B unrelated, and on attribute :>> q : String under q : Integer, and ShapeItems.sysml relies on it (item :>> faces : Polygon and item :>> faces : PlanarSurface under faces : StructuredSurface). OpenSysML still reports the unrelated-type case as redefinition-type-mismatch, an extension, because a redefinition typed by two unrelated types is almost always a slip — but as a warning, so no conforming model is rejected passes/constraint.go checkRedefinition passes/constraint_test.go:TestConstraint_RedefinitionTypeMismatch, :TestConstraint_RedefinitionConformingTypeStaysSilent, :TestConstraint_ShapeItemsRedefinitionsAreNotErrors; passes/constraint_unions_test.go ⚠️ approximate (advisory warning where the specification and the reference have no rule)
A chain redefinition written as a member of a type or usage (attribute :>> mid.leaf.value = 99.0;) applies below every composite feature the chain walks: the chain parses to a feature hosting it — the chain determines the host feature's featuring type and featured type (KerML 1.0, formal/2025-12-01, §7.3.4) — and the host is redefinable (§8.3.3.3), so the object of an affected member behaves as if the chain had been written as nested redefining usages, carrying the member's declared value (= or default =), type, multiplicity and body, evaluated in the declaring body's scope, inherited through the type's generals and applied per element of a multi-valued intermediate. A nested-body redefinition declared by the chain's owner or something specializing it wins; the child's type's own redefinition and bodies in types the owner specializes lose, as with the nested-body form — and a chain crossing a ref/port/subject owns nothing below it and is never applied (an error, redefinition-through-reference). The pinned pilot evaluator (0.62.0) accepts the notation but reads the original value; recorded as a pilot-evaluator gap semantics/nested_redefinition.go NestedRedefinitionsOf (own members, chain targets resolved), IsReferenceUsage/IsSubjectUsage; runtime/nested_redefinition.go pendingNestedRedefinitions, applyNestedRedefinitions, redefinitionContext/blocksChain; passes/nested_redefinition.go NestedRedefinitionPass semantics/nested_redefinition_test.go, passes/nested_redefinition_test.go, runtime/robustness_nested_redefinition_test.go:TestRuntimeRobustnessNestedRedefinition, runtime/nested_redefinition_test.go, conformance nested_redefinition_chain, nested_redefinition_chain_equiv, nested_redefinition_precedence ✅ Faithful
A usage that redefines an inherited usage (part derived :> base { part :>> inner { … } }) specializes what it redefines, so it keeps every nested member the redefined usage declared and overrides only what it restates semantics/model.go NewModel (attaches the model to resolve.Resolver, so a redefinition target reachable only through inheritance resolves and the redefining usage gains it as a supertype), consumed by runtime/shape.go FeaturesOf over Model.MembersOf redefinition_inherited_nested_values.sysml, ballandchain_variant_configuration.sysml, robustness_test.go:deep_specialization_chain_of_redefinitions, conflicting_redefinitions_at_several_levels ✅ Faithful (multi-level chains, a redefinition of a redefinition, and conflicting restatements where the innermost wins; the merge is the inherited-member view, not a feature value-level merge in the instantiator)
A union's instances are exactly those of its unioning types (KerML §8.3.3), so a type declared classifier MyWheel unions MyWheel1, MyWheel2 conforms to every type all of its unioning types conform to, and feature redefines rollsOn : MyWheel redefining rollsOn : Wheel is well-formed. Unioning is not a generalization edge — a union inherits nothing from its members — so it is resolved separately from DirectSupertypes semantics/model.go Model.Conforms → unionConforms, UnioningTypes passes/constraint_unions_test.go:TestConstraintRedefinitionConformsThroughUnion, :TestConstraintRedefinitionUnionMemberDoesNotConform, :TestConstraintRedefinitionUnionCycleTerminates ✅ Faithful (conformance only: a union's members are not computed. intersects and differences are read for classification rather than conformance — see the cast row — so a type is not made to conform through them)
The type a redefinition must inherit the redefined feature from is the feature's featuring type where it declares one (member feature CC1_snapshots :>> Occurrences::Occurrence::snapshots featured by CC1; is featured by CC1, not by the feature it is written inside — KerML §7.4.5, §8.3.4.3), and a bare feature owned by a package has no featuring type, so nothing can inherit it and the rule does not apply; a target that is not an inherited member may still be accessible through the featuring context — a context conforming to the target's own featuring context, or one that redefines a common feature whose own contexts conform (the variable-feature snapshot encoding; the pilot checks accessibility, FeatureUtil.canAccess, not inherited membership) passes/constraint.go checkRedefinition over featuringOwners (the declared featured by targets, else the lexical owner), isInheritedMember, isPackageLevelFeature, and the accessibility fallback redefinedAccessible/featuringContexts/featuredWithin/featuringContextConforms passes/constraint_test.go:TestConstraint_RedefinitionUsesFeaturingType, :TestConstraint_PackageLevelRedefinitionHasNoInheritedOwner, :TestConstraint_PackageLevelUnfeaturedRedefinitionExemptsNoInheritedRule, passes/f100_redefinition_featuring_test.go (a featured by context inheriting the target, the snapshot-style pair, and the unrelated-context/no-common-target/unrelated-typing negatives) ⚠️ Approximate (TimeVaryingCarDriver.kerml:93 is accepted, an unrelated featured by context still rejected; the accessibility walk approximates the pilot's canAccess — only a declared featured by is read, the featuring a nested feature implies is not computed. The package-level exemption is decided by the absence of a featured by relationship, so a package-level feature that declares one is still checked)
A redefinition removes the redefined feature from the type owning the redefining one, so none of its names — primary, short, or an alias binding it — is visible there, transitively along a redefinition chain; masking is keyed by element, so an inherited namesake nobody redefines keeps its name, a redefinition taking the redefined name masks nothing, and a member the type declares itself is never masked (KerML §7.4.7, §8.3.3.3) semantics/masking.go RedefinedFeatures, redefinitionMask, buildMask, InheritanceMasked; semantics/members.go MembersOf / MembersOfDeclaring (the redefinition-anchor view, KerML §8.3.3.3.6) / MembersOfIncludingRedefined (the unmasked view the runtime shape needs so a redefinition shares its target's feature value), consumed by model/scope_names.go semantics/w8b_masking_test.go, model/scope_names_test.go (redefinition-anchor cases), pilot-xpect scope class extra-names 31 → 3 on the merged tree ⚠️ Approximate (masking governs enumeration: x.a still resolves through resolve.lookupMember's unmasked LookupContributedMember fallback, and under multiple inheritance a qualified path binds the masked namesake; visibility, 8A's filter, composes after the masked view in the same enumeration)
A subsetting feature's declared type need not conform to the subsetted feature's type, and no diagnostic reports one that does not: a feature's types are derived from its own typings and the types of the features it subsets (KerML 1.0, formal/2026-03-01, §8.3.3.3.4; the 1.1 draft the pinned 2026-07 pilot implements states the same), so feature f : B subsets g; under feature g : A; makes f typed by both A and B — subsetting "adds additional feature types" (KerML §7.3.4.4) and the co-domain rule (§8.3.3.3.10) holds by construction. The OMG training corpus relies on this (Model Library Example.sysml: occurrence causes : Cause[*] nonunique :> situations; with situations : Situation[*], Cause unrelated to Situation), and the pinned pilot validator reports nothing for the unrelated-type case. Neither §8.3.3.3.10 nor §8.3.3.3.8 declares a type-conformance constraint (the normative subsetting constraints are validateSubsettingConstantConformance, validateSubsettingFeaturingTypes and validateSubsettingUniquenessConformance; multiplicity conformance is a warning). Redefinition, a kind of subsetting, derives a feature's types the same way, so redefinition-type-mismatch is an OpenSysML extension reported as an advisory warning, never an error (see the redefinition row below) passes/constraint.go checkRedefinition (no subsetting counterpart, by design) passes/constraint_test.go:TestConstraintSubsettingUnrelatedTypeOK, :TestConstraintSubsettingOccurrenceUnrelatedTypeOK, :TestConstraintSubsettingConformingTypeOK; training-corpus gate over Model Library Example.sysml ✅ Faithful (no rule, matching the specification and the reference)
A redefining feature's direction must be compatible with the redefined one's — undeclared takes the redefined direction and a redefined inout admits either, through conjugation — and a subsetting or redefining feature may be neither nonunique where its target is unique nor variable where its target is (transitively) constant (KerML §8.3.3.3); the target's uniqueness is its effective one (Model.IsUnique, the row on uniqueness through redefinition), so :>> rs nonunique over a middle :>> rs that inherits nonunique from its base is legal semantics/redefinition_conformance.go ConformanceViolations, directionViolations, restrictionViolations, isConstantFeature, directionThrough; diagnostics in passes/w8b_redefinition_conformance.go passes/w8b_redefinition_conformance_test.go (direction, conjugation, inout, uniqueness, constancy, cyclic subsetting), semantics/uniqueness_test.go:TestUniquenessConformanceUsesEffectiveUniqueness, pilot-reject xpect/p04-nonunique-subsets-unique.kerml both-reject ✅ Faithful (the invalid/BindingConnector_redefine.sysml.xt binding-connector rules are still silent)
A metadata annotation body restates features of the annotated metadata type, so a name that type does not offer redefines nothing even when the surrounding namespace binds it (KerML §8.3.3.3) semantics/metadata_body.go MetadataBodyViolations over LookupMember; body-local child scopes in symbols/builder.go; resolve.Resolver.resolveMetadataPrefix resolves body values through the annotation scope and records implicit redefinitions without treating unknown body names as references; diagnostic metadata-owning-type-feature in passes/w8b_redefinition_conformance.go passes/w8b_redefinition_conformance_test.go:TestW8BMetadataBodyMustRedefineOwningTypeFeature; symbols/metadata_test.go, resolve/metadata_body_test.go, and semantics/metadata_body_test.go cover private/nested scopes, inherited lookup, body-value resolution, and missing-feature silence ✅ Faithful
A feature bound with = may not be given another value by a feature that redefines it, directly or through further redefinitions — every feature the featureWithValue redefines must carry only default values; a default = value may be overridden, a default = redefinition of a binding is itself an override, and an initial value (:=) is the separate initial-value constraint's (KerML 1.0 §8.3.4.10.2 validateFeatureValueOverriding) passes/feature_value_overriding.go checkFeatureValueOverriding (diagnostic feature-value-overriding, registered in passes/constraint.go) over semantics/masking.go Model.AllRedefinedFeatures (explicit redefinitions plus the implicit parameter, connector-end and case-role ones, transitively); the parser records the value operator on ast.Usage, ast.SubjectMember, ast.AssumeMember and ast.RequireMember (ValueOperatorSpan, ValueIsDefault, ValueIsInitial); a require/assume constraint declaration is a constraint-usage member of its requirement, named (c), named by its lone redefinition (:>> c) or anonymous (require constraint { … }, :>> c1, c2) (symbols/builder.go buildRequirementConstraint, ast.OwnedConstraintOf), so :>> c resolves, its value is checked against every feature it redefines, its declaration is held to every constraint-usage rule (passes/feature_decl.go featureDeclOf: one typing, value conformance, declared and inherited multiplicity), and the runtime checks it by qualified name and through its requirement like any constraint usage, a redefining one owning none (;, { }, docs or nested assert constraint { … } only) inheriting the result expression it redefines, and one stating a second result expression refused rather than evaluated (runtime/context.go RequireConstraint; runtime/condition.go appendMemberConditions, appendOwnedConditions, Condition.Conflict) passes/feature_value_overriding_test.go (direct usage and definition overrides, transitive chains, default = and no-value bases silent, := untouched, implicit parameter redefinition, nested and renamed forms, named and anonymous owned constraints), passes/typecheck_owned_constraint_test.go (owned and ordinary declarations report alike), parser/feature_value_operator_test.go, symbols/owned_constraint_test.go, resolve/constraint_body_test.go, lsp/owned_constraint_test.go, repl/owned_constraint_check_test.go, tools/referee/reject/testdata/negative/semantic/s46-feature-value-overriding.sysml (both reject; the pinned pilot reports Cannot override a binding feature value at the same two positions) ✅ Faithful (the solver reads an objective's value from its redefinition of the library's eval, so no shipped example or fixture restates the bound TradeStudies::…::best; one that does is reported here and refused by the solver)

Variation and Variant (SysML v2 §7.20 Variant Modelling)

Semantic Rule Implementation Test Case Status
variation and variant are recorded on the declaration they modify, in every position they may appear (variation attribute/part/interface, nested or top-level) parser/defusage.go applyFeatureMods, atKindPrefix, ast/defusage.go Usage.IsVariation/IsVariant, round-tripped by export/rdf_out.go/rdf_in.go tests/parser/testdata/parse/variation_and_variant.golden, parser/negative_test.go (variation_no_declaration, variation_attribute_no_name, variant_unclosed_body, variant_selection_no_variant_name) ✅ Faithful
A variation is an abstract classifier of its variants: a variant specializes its variation and so carries the variation's type and features semantics/model.go DirectSupertypes via semantics/variation.go VariationOwning semantics/variation_test.go:TestVariationAndVariantModifiers, TestVariantsOfInheritedThroughRedefinition ✅ Faithful
A variant is reachable through the variation feature's name (cut::cutIdeal), including through a feature chain (engagementRing.nesting::nestingTrue) and through a feature that redefines or specializes the variation semantics/variation.go Model.IsVariationFeature, Model.VariantsOf, Model.VariantOf, runtime/variation.go EvalContext.variantSegment, runtime/eval.go evalFeatureChain variation_attribute_selection.sysml, variation_part_selection.sysml, semantics/variation_test.go:TestIsVariationFeatureThroughSpecialization ✅ Faithful
Binding a variation usage to one of its variants selects that variant, with its nested values and its own nested features, whether the variation is read through an object's feature value or through its declaration (EvaluateConstraint, EvalWithScope, REPL %eval) runtime/variation.go Context.bindVariation, variantValue, EvalContext.bindVariationOf, runtime/instance.go GetFeatureValue (variation feature values resolve before ordinary defaults), runtime/value.go ValVariant variation_attribute_selection.sysml, variation_part_selection.sysml, variation_interface_selection.sysml, robustness_test.go:variation_read_through_its_declaration ✅ Faithful
A variation feature compares equal to the variant it is bound to (x == x::variantName), and unequal to any other variant — the form an asserted configuration constraint uses runtime/eval.go equality over ValVariant, runtime/value_equality.go variation_attribute_selection.sysml, variation_interface_selection.sysml, variation_interface_mismatch.sysml, ballandchain_variant_configuration.sysml ✅ Faithful
A variation with no variant selected is a typed error, never a silently wrong value, and a variation is no occurrence of itself, so a chain through an unselected variation part reports the same rather than reading an object of the variation runtime/errors.go ErrVariationUnselected, runtime/instance.go GetFeatureValue, runtime/eval.go evalFeatureReference, runtime/calc_usage.go occurrenceOperand variation_unselected.sysml, robustness_test.go:variation_without_a_selected_variant, chain_through_an_unselected_variation_part ✅ Faithful
Selecting what is not a variant of the variation, or selecting two variants at once, are typed errors naming the variants available runtime/errors.go ErrNotAVariant/ErrMultipleVariants, runtime/variation.go bindOneVariant, variantSummary, runtime/eval.go (a missing member under a variation feature) robustness_test.go:variation_bound_to_what_is_not_a_variant, variation_bound_to_two_variants, semantics/variation_test.go:TestSelectsVariantOfRejectsForeignVariant ✅ Faithful
A variant whose owner is not a variation offers no choice, so it stays an ordinary feature of its owner and the idle variant keyword is reported; an owner that is a variation point by specialization still offers its variants as choices passes/constraint.go checkVariantOutsideVariation (warning variant-outside-variation), runtime/shape.go buildFeatures and runtime/eval.go (only a variant of a variation point is a choice rather than a feature value, via semantics/variation.go Model.VariationPointOwning over IsVariationFeature, which semantics/model.go DirectSupertypes also uses so a variant specializes such a point and inherits its type, and VariantsOf uses so the choices offered are the choices a selection accepts) variant_outside_a_variation.sysml, variant_under_an_inherited_variation.sysml, robustness_test.go:variant_outside_a_variation, variant_under_a_redefined_variation, passes/constraint_test.go:TestConstraintVariantOutsideVariation, TestConstraintVariantInsideVariationOK, TestConstraintVariantUnderInheritedVariationOK, semantics/variation_test.go:TestVariantsOfExcludesAMisplacedInheritedVariant ✅ Faithful
The object a selected variant stands for belongs to the selection that made it: two owners, or two variation points read through their declarations, each get their own object runtime/variation.go variantObject (keyed by owning object, variation point and variant), variantValue robustness_test.go:two_owners_selecting_one_variant, two_ownerless_selections_of_one_variant, repeated_reads_of_a_variant_object ✅ Faithful
variation interface and its variant interface … connect … members parser/defusage.go (interface usages take the same modifiers), selection as above; the selected variant's connection is realized by runtime/variation.go variantInstance over runtime/connector.go materializeConnector, and routing follows it through runtime/routing.go routableConnections/realizedConnections/selectedVariant over lower/connection.go Connection.Variation/Variant/Owner and ToObjectConnections, with the object performing the behavior carried by runtime/context.go ExecuteActionPerformedBy/ExecuteStatePerformedBy variation_interface_selection.sysml, variation_interface_mismatch.sysml, ballandchain_interface_connected.sysml, ballandchain_interface_disconnected.sysml, ballandchain_variant_configuration.sysml, signal_test.go:TestRoutingHonorsTheSelectedVariantConnection, lower/connection_test.go:TestLowerVariantConnectionsCarryTheirVariation, :TestLowerObjectConnectionsAreOwnedByTheObject, variant_connection_per_owner.sysml, signal_test.go:TestRoutingIsPerOwnerVariantSelection ✅ Faithful (the variant is selected and compares equal, so a configuration constraint over interface variants evaluates, and the connection that variant declares is a real runtime connector whose ends are the connected features, so port communication follows the selected variant and not the variants left unselected; a connection an object declares routes for that object, so two objects of one type selecting different variants each route their own)

⚠️ Variant selection is not ordering-sensitive — a variation feature value resolves to one variant per instance — so no golden execution trace accompanies these rows.

Requirement

Semantic Rule Implementation Test Case Status
Require expression evaluation, in a requirement definition body as well as a usage parser/behavior.go parseRequirementBody (both parseDefinition and parseUsage paths), condition.go conditionsOf requirement_literal.sysml, requirement_def_body_require.sysml, parser/behavior_require_member_test.go:TestRequirementConditionForms ✅ Faithful
A condition stated through an anonymous nested constraint (require constraint { <expr> }, the form the OMG Domain Libraries use) is evaluated, and every condition of that body is kept parser/behavior.go parseNestedConstraintConditions, condition.go appendConditions requirement_nested_constraint.sysml, parser/behavior_require_member_test.go:TestRequireMemberRetainsConditions ✅ Faithful
A requirement's conditions see the requirement's own features — declared, inherited, or rebound by a typed usage (attribute :>> maxVerticalSpeed = 1.5;) condition.go conditionFeatures, eval.go evalFeatureReference requirement_own_attribute.sysml, requirement_nested_constraint.sysml, runtime/condition_test.go:TestRequirementConditionSeesOwnAttributes ✅ Faithful
A feature a condition names but which carries no value reports that (ErrNoValue) rather than being unresolved; a condition checked at model level that comes out <undetermined> is the same ErrNoValue, naming the open feature, never a verdict errors.go ErrNoValue, eval.go evalFeatureReference, condition.go conditionHolds runtime/condition_test.go:TestRequirementConditionWithoutValueIsNotUnresolved, :TestConstraintOverUndeterminedOperandIsNotDecided ✅ Faithful
The same holds for any declared feature read by name — a multi-valued part wheels : Wheel[4], a chain through it (wheels.radius), an attribute with no default, qualified or not: the name resolves, so it is never ErrUnresolvedReference, which is reserved for a name nothing declares. Read where an object, element or running behavior features it, it is a NoValueError naming the feature (a kind of ErrNoValue); read at model level, where nothing features it, it is the <undetermined> value of the Expression Evaluation rows below. The REPL's declaration-scope %eval in <part> : <feature> is a model-level read and answers <undetermined>; %features and an object-pinned %eval read the object and answer <unset> runtime/eval.go resolvedWithoutValue, withoutValue, modelLevel, reached from evalName, evalQualifiedName and Context.EvalDeclaredValue; repl/meta.go evalIn runtime/eval_no_value_test.go:TestDeclaredFeatureWithoutValueIsNotUnresolved, robustness_test.go:accept_payload_read_before_it_is_bound, :object_feature_without_a_value, repl/evalin_test.go:TestEvalInDeclarationScopeReadsValuelessFeaturesAsUndetermined, :TestEvalQualifiedValuelessFeatureIsNotUnresolved ✅ Faithful
A violated condition names the condition that failed, not only the element stating it errors.go ViolationError, condition.go conditionText requirement_violated.sysml, runtime/condition_test.go:TestRequirementConditionSeesOwnAttributes ✅ Faithful
A requirement verification (verify r;) belongs in the objective of a verification case, definition or usage; anywhere else it is an error (validateRequirementVerificationMembershipOwningType) passes/w8d_verification.go W8DVerificationPass passes/w8d_verification_test.go; see the SysML-half validation section ✅ Faithful
Subject binding evaluation context.go:148 EvaluateRequirement (Pass 1) requirement_subject.sysml ✅ Faithful
Actor binding evaluation context.go:148 EvaluateRequirement (Pass 1) requirement_actor.sysml ✅ Faithful
Assume expression evaluation context.go:148 EvaluateRequirement (Pass 2, doesn't fail) requirement_assume.sysml ✅ Faithful
A false required condition is a verdict, not a malfunction (ErrViolated), like a false assertion context.go EvaluateRequirementOn, errors.go ErrViolated repl/instance_test.go:TestRequirementViolationIsAVerdictNotAnError ✅ Faithful
A requirement usage inherits assume/require conditions from the definition it is typed by, and the values it rebinds are the ones those conditions see context.go chainMembers, condition.go conditionFeatures requirement_nested_constraint.sysml, requirement_violated.sysml ✅ Faithful
A subject may redeclare the one it inherits (subject subj : View[1] :>> RequirementCheck::subj;) parser/behavior.go parseSubjectMember, resolve/document.go, passes/typecheck.go checkSubjectMember parser/behavior_require_member_test.go:TestRequirementConditionForms, libs/stdlib_conformance_test.go (Systems Library/Views.sysml) ✅ Faithful
A requirement's subject, assume constraint and require constraint declarations take a short name like any usage — subject <s> x : T;, assume constraint <a> ac : C;, require constraint <r> : C; — since SubjectUsage and RequirementConstraintUsage reach UsageDeclaration → Identification (('<' declaredShortName=NAME '>')? declaredName=NAME?, pinned SysML.xtext). The short name resolves (Req::s, :>> s, an expression naming s), is held to the same distinguishability rule as an ordinary short name, is sysml:declaredShortName in RDF, and a short-name-only member is displayed and renamed by it; a malformed one (subject <> x;, subject <s x;) is a diagnostic ast.SubjectMember/AssumeMember/RequireMember Ident (ast.Identification, shared with ast.OwnedConstraint), parser/behavior.go parseSubjectMember, parseOwnedConstraintDecl, symbols/builder.go defineIdent, symbols/ident.go DeclIdent, passes/w9c_owned_name_and_library.go w9cIdentOf, export/rdf_out.go ident, repl/render.go nameOrAnon parse/requirement_member_short_names.golden, parser/requirement_member_short_name_test.go, parser/negative_test.go (subject_empty_short_name, subject_unclosed_short_name, and the assume/require forms), symbols/requirement_member_short_name_test.go, resolve/requirement_member_short_name_test.go, passes/w9c_rules_test.go (requirement-member short-name duplicates), export/requirement_member_short_name_test.go (source-text-stripped round trip), lsp/requirement_member_short_name_test.go, repl/requirement_member_short_name_test.go ✅ Faithful (the pinned pilot batch validator accepts all four forms and rejects the two malformed ones at the same positions)
The subject or objective a case, requirement or usage of either declares implicitly redefines the same role of each of its generals (SysML v2 §8.3.21, §8.3.17), so a role declaring no type of its own is typed by the role it redefines: requirement r : Req { subject truck = loaded; } reads truck.payload through the definition's subject truck : Truck semantics/roles.go ImplicitRoleRedefinitions, reached from semantics/model.go DirectSupertypes, so the redefined role contributes members and conformance as any supertype does semantics/roles_test.go:TestBoundSubjectInheritsTheRedefinedSubjectsMembers, solve/translate_test.go:TestBoundSubjectReadsTheSubjectsFeatures ✅ Faithful
An actor or stakeholder a case, requirement or usage of either declares implicitly redefines the general's effective actor at its position (KerML §7.4.7.3, a parameter after the subject; SysML v2 §8.3.20 ActorMembership), keeping the inherited name or not, unless it carries a :>> clause of its own; a general presents its own actors first, then the inherited ones none of them redefines. The redefining actor takes the redefined one's type and multiplicity, so requirement r : PilotedLimit { actor pilots = pilot; } is held to the definition's actor pilots : Pilot[2] — one pilot is refused as a multiplicity violation, two buoys as a type mismatch, two pilots satisfy — and an objective binding one such actor is undecided with that detail semantics/roles.go ImplicitRoleRedefinitions, positionalActorRedefinitions, effectiveActors; reached from semantics/model.go DirectSupertypes and runtime/subsetting.go collectRedefinedFeatures, so runtime/context.go holdBound folds the declaration the value is checked against semantics/roles_test.go:TestActorsRedefineByPosition, TestActorsThroughLayeredDiamondsAreLinear, runtime/analysis_objective_binding_test.go:TestImplicitActorIsHeldToTheInheritedDeclaration, conformance analysis_objective_actor_implicit_redefinition, analysis_objective_actor_implicit_refused ✅ Faithful
A view's render member implicitly redefines the library Views::View::viewRendering and nothing else (SysML v2 §8.3.26 RenderingUsage), so render rendering s : AsTable; in a view specializing one that declares render rendering r : AsTree; leaves r inherited and resolvable, and render r; in a view whose general also renders r redefines the same library feature, so it replaces the inherited r rather than duplicating it (KerML 7.4.3) semantics/roles.go ImplicitRoleRedefinitions, viewRenderingRedefinition semantics/roles_test.go:TestRenderMemberRedefinesOnlyTheLibraryViewRendering, passes/nameres_test.go:TestRenderOfAnotherNameLeavesInheritedRenderingResolvable, TestRenderReferenceToOwnRenderingIsNoConflict ✅ Faithful (the pinned validator, 2026-07, resolves r.a in both views and reports nothing)
Nested requirements context.go:148 EvaluateRequirement (recursive) requirement_nested.sysml ✅ Faithful
satisfy <name> is an OwnedReferenceSubsetting of an existing usage, not a typing (SysML v2 §8.3.21.10 SatisfyRequirementUsage) parser/defusage.go parseDefUsage (ast.RelSubsets) tests/parser/testdata/parse/satisfy_reference.golden ✅ Faithful
referencedFeatureTarget().oclIsKindOf(RequirementUsage) — satisfy/verify may only reference a requirement usage (incl. viewpoint/concern usages and an objective); a feature chain is judged by its last feature, and the referent may be inherited, redefined, aliased or nested passes/typecheck.go compatMessage, referentKindMessage, referentKind, isRequirementUsageKind, chains through typeChecker.checkChainReferenceKind passes/typecheck_test.go TestTypeCheckSatisfyRequirementUsageOK, TestTypeCheckSatisfyViewpointUsageOK, TestTypeCheckSatisfyNonRequirementUsageError; passes/typecheck_satisfy_reference_test.go TestSatisfyReferenceToRequirementIsSilent, TestSatisfyReferenceToNonRequirementRejected (every referent kind in the census row, refereed against the pinned pilot) ✅ Faithful
validateAssertConstraintUsageReference (Must reference a constraint., SysML v2 §8.3.19.2 AssertConstraintUsage): assert c, assert not c and assert constraint c reference a constraint usage — a requirement usage included — the last feature of a feature chain deciding, an inherited constraint accepted, the reference stated as a usage or as a member of a constraint body alike; a definition, a package or an unresolved name is the earlier tier's error passes/typecheck.go referenceKindMessage (the referent-kind query satisfy and assert share), checkChainReferenceKind for a chained referent, checkBehaviorMember for a constraint-body member passes/typecheck_assert_reference_test.go; semantic/s04-assert-references-non-constraint.sysml is rejected by both tools ✅ Faithful
validateAssignmentActionUsageReferent (An assignment must have a referent., SysML v2 §8.3.16.2 AssignmentActionUsage::referent): the target of assign x := v names a feature; a definition, a package, a datatype or another namespace is rejected with what it is declared as, an unresolved target stays the name-resolution error, and only a feature reaches the time-varying rule passes/w8d_assignment_referent.go assignmentReferentChecker.check over symbols.SymbolKind.IsFeature passes/w8d_assignment_referent_test.go (TestAssignmentReferentNonFeatureRejected, TestAssignmentReferentFeatureOrUnresolvedIsNotReported); semantic/s43-assign-to-non-feature.sysml is rejected by both tools ✅ Faithful
assert satisfy <requirement> by <part>; is a verdict of its own: the assertion is evaluated as the requirement usage it is (SatisfyRequirementUsage, SysML v2 §8.3.21.10), with the requirement's subject parameter bound to the object the by feature supplies, so the conditions read that object's values; that object is held to the subject's declaration as an expression-bound one is (KerML §7.3.4.1) — classified by the subject's type (a Ship for a Tanker subject answers t.cargo), refused as a type mismatch where the type cannot hold it, or as a multiplicity violation where one object is too few (Ship[2]) runtime/satisfy.go SatisfyAssertionsIn, EvaluateSatisfactionOn; runtime/context.go memberBindings, holdBound; repl/meta.go doSatisfy satisfy_subject_binding.sysml, satisfy_inherited_conditions.sysml, satisfy_subject_classified.sysml, satisfy_subject_refused.sysml, runtime/satisfy_test.go, runtime/analysis_objective_binding_test.go:TestSuppliedSubjectIsHeldToItsDeclaration, repl/satisfy_test.go:TestSatisfyVerdicts ✅ Faithful
An assertion may be negated (assert not constraint { … }, assert not satisfy … by …; Invariant::isNegated, SysML v2 §8.3.21.10), and holds exactly when the conditions it denies do not ast/defusage.go Usage.IsNegated, parser/defusage.go applyFeatureMods, runtime/condition.go evaluateConditions tests/parser/testdata/parse/assert_negated.golden, satisfy_negated.sysml, runtime/negation_test.go ✅ Faithful
A negated element states no condition it can deny when its body holds only assumptions, which are trusted rather than checked, so it reports no condition to evaluate rather than a violation naming none runtime/condition.go evaluateConditions runtime/condition_test.go TestNegatedConstraintWithOnlyAssumptionsIsNotAVerdict ✅ Faithful
A negation denies the conditions of the constraint it is written on together — not (a and b), not not a and not b — so it holds as soon as one of them fails runtime/condition.go appendConditions, conditionHolds runtime/condition_test.go TestNegatedNestedConstraintNegatesTheConjunction, constraint_negated_group.sysml ✅ Faithful
An ObjectiveMembership's ownedObjectiveRequirement is a RequirementUsage (SysML v2 §8.3.22.4), so an objective is typed by a requirement definition or a specialization of one, never by a structural definition passes/typecheck.go compatibleTyping, isRequirementDefKind passes/typecheck_kinds_test.go TestTypeCheckObjectiveTypedByRequirementDefOK, TestTypeCheckObjectiveTypedByConcernDefOK, TestTypeCheckObjectiveTypedByPartDefError, TestTypeCheckObjectiveTypedByActionDefError ✅ Faithful
A SubjectMembership's ownedSubjectParameter is an unconstrained Usage (SysML v2 §8.3.21), so a definition of any kind types a subject — including the port def and action def the OMG training models use — and the rule applies however the requirement body is written, not only when the subject happens to parse as a usage passes/typecheck.go checkSubjectMember, compatibleTyping passes/typecheck_subject_test.go TestTypeCheckSubjectIsCheckedWhateverPrecedesIt, TestTypeCheckRequirementUsageSubjectIsChecked, TestTypeCheckSubjectWithoutResolvableTypeIsNotATypeError; typecheck_kinds_test.go TestTypeCheckSubjectTypedByAnyDefKindOK, TestTypeCheckSubjectTypedByUsageError ✅ Faithful
A definition specializes a definition of a comparable kind: a PartDefinition is an ItemDefinition (SysML v2 §8.3.9.2), so individual item def Alice :> Person names a part definition legally, while disjoint kinds — an occurrence and a data value (§8.4.5.1) — are refused passes/typecheck.go defKindParents, defKindSpecializes, defKindsComparable passes/typecheck_kinds_test.go TestTypeCheckSpecializeComparableKindOK, TestTypeCheckNewKindSpecializeCrossKindError; model/training_examples_test.go (28. Individuals/Individuals and Time Slices) ✅ Faithful
Every definition implicitly specializes the library definition of its kind, so the features that base declares resolve in its body — Items::Item declares start and done, which Parts::Part redefines semantics/implicit.go implicitDefinitionBases, implicitBase model/implicit_typing_test.go, model/training_examples_test.go (27. Occurrences/Time Slice and Snapshot Example) ✅ Faithful
A usage nested in a type implicitly subsets the collection of its owner its kind nests under (SysML v2 §8.3): a part in an item one of its subparts, an item one of its subitems, a port one of its ownedPorts, an action one of its subactions, a state one of its substates, and likewise for connections, interfaces, allocations, calculations, cases, constraints, requirements, views and renderings — so subparts->size() counts the nested parts without a written :> subparts semantics/nested.go ImplicitSubsettings (the owner feature each usage kind subsets, judged by the owner's metaclass kind, with the occurrence, portion and KerML step fallbacks); runtime/subsetting.go implicitSubsettingNames (which gathers the implied collections when a feature value is read) semantics/nested_test.go, conformance nested_subparts ⚠️ Approximate (the semantic relationship is derived for every usage; the runtime materializes the implied collections for model-declared usages only — library-declared usages keep their declared subsettings)
The nested* properties of SysML::Usage and the owned* properties of SysML::Definition (nestedPart, nestedUsage, ownedPort, ownedPart, …) are derived on reflective meta values: the owned members conforming to the metaclass the suffix names, in declaration order — (vehicle meta SysML::PartUsage).nestedPart is its nested parts, (PartDef meta SysML::PartDefinition).ownedPort its owned ports semantics/reflective_usages.go reflectiveOwnedUsages, ownedUsageMetaclasses, reached from semantics/annotations.go ReflectiveElements semantics/nested_test.go:TestReflectiveOwnedUsagesRejects, conformance meta_nested_usages ✅ Faithful
A quantity expression (1.5 [m/s]) evaluates to a magnitude and the measurement reference it is written in (Quantities::ScalarQuantityValue is num + mRef), so a condition comparing values written with units reaches a verdict runtime/quantity.go evalIndexExpr, value.go ValQuantity requirement_quantity_same_unit.sysml, runtime/quantity_test.go:TestQuantityEvaluation, tests/parser/testdata/parse/quantity_expression.golden ✅ Faithful
Name in the unit position of a quantity expression. x [u] invokes Quantities::'['(num, mRef), so u is an ordinary operand expression and its name is resolved by ordinary name resolution: resolution returns the nearest declaration the name reaches (KerML 8.2.3.5.3 Local and Visible Resolution, 8.2.3.5.4 Full Resolution), and the position's expected type (ScalarMeasurementReference) only decides whether what resolved conforms (KerML 8.2.3.5.1). A sibling named m therefore shadows an imported SI::m — resolution does not continue outward looking for a unit — and the quantity is rejected with a diagnostic naming the declaration, the namespace declaring it, and the qualified spelling of the unit it hid. One routine implements this for every evaluator (part feature value default, action/state attribute default, calc return, condition) semantics/units.go unitTermOfName, ShadowedUnitError, unitOutside; runtime/context.go chainMembers (a condition evaluates in its own body scope, as the other paths do) unit_shadowed_by_sibling_slot.sysml, unit_shadowed_by_sibling_action.sysml, unit_shadowed_by_sibling_calc.sysml, unit_shadowed_by_sibling_constraint.sysml, unit_shadowed_by_local_unit.sysml, unit_undeclared.sysml, robustness_test.go:quantity_unit_shadowed_by_sibling ✅ Faithful
A violated assertion renders a quantity operand as it was written (1.0 [m] > 500.0 [m]), since the bracket form is a quantity and not a sequence index runtime/condition.go conditionText (ast.IndexExpr) runtime/condition_test.go:TestViolationRendersQuantityOperands ✅ Faithful
Commensurable units convert before a comparison or a sum, through MeasurementUnit::unitConversion and unit-defining expressions reduced to base units — 1.5 [m/s] <= 5.4 [km/h] is true, exactly, at its boundary (a conversion factor is kept as a ratio, not evaluated) semantics/units.go UnitTermOf, Scale, ConvertMagnitude requirement_quantity_converted_unit.sysml, constraint_quantity_sum.sysml, semantics/units_test.go:TestScaleStaysExact ✅ Faithful
A unit is composed by the operation over quantities (10 [m] / 2 [s] is a quantity of dimension L·T^-1), and a ratio of like quantities is a number of no unit. The composition is kept in canonical form (UnitProduct) — a product of powers of the named units the operands were written in, powers of one unit merged, cancelled factors dropped, factors sorted, the denominator grouped once — so 3 [m] * 3 [m], (3 [m]) ** 2 and (3 [m] * 3 [m]) / 3 [m] compose m**2, m**2 and m; (m/s) * (kg/s) composes kg*m/s**2; and a named derived unit stays a factor as the user wrote it (N*m, km/h**2). A unit the notation quotes keeps its quotes, so it stays one unit under composition — 'A/m'*m, 'A/m'**2, never the quotient A/m*m — and the text reads back to the same units (UnitNameText). This canonical product is what a result prints only where the row below finds no coherent unit for it: a product no library unit measures (2 [kg] * 3 [K] is 6 [K*kg]), one holding a dimension-one unit (2 [m] * 3 [rad] is 6 [m*rad]) or an opaque unit the wire carried, and one whose exponent is not a number, which stays the error it was. The reduction to base units (UnitTerm) stays the one authority for commensurability and conversion; the named product is display only, and Quantity.String, the trace, the REPL, and the gRPC unit field all render the one text. A quantity arriving over gRPC carries only its unit text and reduction, so ProtoToQuantity reads the named product back out of the text against the model (SI::m/SI::s times SI::s is SI::m remotely as locally), a name written short under an import (m) being looked for in the namespaces of the base units the reduction names (m times SI::m is m**2); text that is no unit expression is one opaque named unit, quoted ('metres per second') so a product it is in reads back as itself; text the model cannot read as a whole — a name no unit bears, or two units bear, or a reduction the whole contradicts — is read name by name, the units it does declare staying factors of their own ('metres per second'*SI::s divided by SI::s after a round trip is 'metres per second' again); text that reads as units but reduces to another dimension or scale than the unit_term beside it is ErrUnitTextMismatch, never a quantity whose label and reduction disagree semantics/unit_product.go UnitProduct (Times, DividedBy, Pow, normalizeProduct, String), runtime/quantity.go scaleQuantities, powQuantity, composedQuantity, grpc/convert.go unitProductOfText constraint_quantity_quotient.sysml, calc_quantity_ratio.sysml, instance_quantity_calculations.sysml, runtime/quantity_test.go:TestComposedUnitCanonical, TestUnitProductRendering, runtime/quantity_functions_test.go:TestQuantityPowerAndProductAgree, grpc/quantity_test.go:TestQuantityFromWireComposesAsWritten, TestOpaqueUnitFactorsSurviveTheWire, semantics/units_test.go:TestNamedUnitProductSpellsOneName, TestQuantityFromWireRejectsUnitTextItsReductionContradicts ✅ Faithful
A computed quantity is reported in the coherent unit of its dimension, not in the expression it was composed by (KerML/SysML Quantities and Units Domain Library: a DerivedUnit is the product of its unitPowerFactors, a ConversionByPrefix/ConversionByConvention a scale over its reference unit, and a coherent derived unit has conversion factor one over the system's base units): 9.80665 ['m⋅s⁻²'] * 311 [s] is 3049.86815 [SI::'m/s'], 10 [N] / 2 [kg] is 5.0 [SI::'m⋅s⁻²'], 3 [m] * 3 [m] is 9 [SI::'m²'], (3.986E14 ['m³⋅s⁻²'] / 6563 [km]) ^ (1/2) is 7793.229127559948 [SI::'m/s'] — the kilometre's 10^3 folded into the magnitude, so the same expression over 6563000 [m] answers the same value. The reduction is derived from the library's MeasurementReferences data alone — a unit's unitPowerFactors (a user-declared DerivedUnit reduces like SI's own), its unitConversion's conversionFactor and referenceUnit, the prefixes — into a power vector over the system's base units and a scale; the scale is folded into the magnitude by the exact factor (ConvertMagnitude, never a rounding of the spelling), and the display unit is chosen among the measurement units the model and the libraries declare whose reduction matches: the coherent one (SI::'m/s', not a model's synonym), the declared type of the feature or parameter the value is bound to deciding between same-dimension units that measure different kinds (EnergyValue takes SI::J, TorqueValue SI::'N⋅m', a product bound to no kind the base-unit spelling SI::'kg⋅m²⋅s⁻²'), and the reduced base-unit product otherwise. A quantity written as one named unit is kept as written (3 [km] stays 3 [km]; 1 [km] + 500 [m] is 1.5 [km]), a dimension-one result is a number (6 [km] / 3 [km] is 2.0), and a magnitude changes only by the reduction's scale. The rule is applied once, in the quantity layer — Context.coherentResult over every product, quotient, power, root, aggregate, vector and tensor scaling and library operator, and Context.checkWriteType where a value is bound to a feature typed by a quantity kind — so -calc, -eval, -analysis, -requirement/-satisfy, -instantiate, the REPL, the trace, queries and the gRPC unit field all report the one spelling semantics/coherent_quantity.go Model.CoherentQuantity, Model.CoherentSpelling, coherentUnitOf, coherentSynonym, definedOverBaseUnits; semantics/units.go powerFactorsUnitTerm, unitPowerFactor (a DerivedUnit's unitPowerFactors); semantics/coherent_unit.go Model.CoherentUnit; runtime/quantity.go Context.coherentResult, scaleQuantities, powQuantity, sqrtQuantity; runtime/write_conformance.go Context.checkWriteType, spellForDeclared; runtime/collections.go (aggregates), vector_functions.go, tensor_functions.go, library_operators.go; queryexec/computed.go conformance/instance_quantity_coherent_units.sysml + .expected.json (products, quotients, fractional powers, prefixed inputs, a user-declared DerivedUnit, a dimensionless ratio, a product no unit measures, a dimension-one factor kept, a non-numeric exponent), calc_quantity_coherent_result.sysml, calc_quantity_coherent_mismatch.sysml (an L·T^-1 result declared an acceleration stays the dimension mismatch), semantics/coherent_quantity_test.go:TestCoherentQuantitySpellsTheLibraryUnit, :TestCoherentQuantityFallsBackToBaseUnits, :TestCoherentQuantityKeepsWhatItCannotReduce, :TestCoherentSpellingFollowsTheDeclaredType, runtime/quantity_test.go, runtime/quantity_functions_test.go, queryexec/quantity_test.go, grpc/quantity_test.go, repl/runtime_commands_test.go, cmd/sysml/run_test.go:TestRunCalcQuantity ✅ Faithful (the pinned pilot evaluator computes quantity magnitudes but spells no composed unit, so the coherent spelling is judged against the library declarations themselves: every unit chosen is one SI declares with conversion factor one over the base units)
A quantity raised to a constant exponent raises its unit with it, and its magnitude comes from the one ** implementation the folder and the runtime share — so (0.0 [m]) ** -1.0 and an overflowing magnitude are the same typed errors as for a bare number rather than an infinity carried in a unit, and (2 [m]) ** 3 keeps an Integer magnitude. A product of quantities keeps the magnitude kind the same arithmetic gives bare numbers, so l1 * l1 and l1 ** 2 agree (9 [m**2], both Integer) runtime/quantity.go powQuantity, scaleQuantities; semantics/quantity_eval.go PowQuantity, ScaleQuantities, MagnitudeArith, semantics/eval.go Pow, semantics/units.go UnitTerm.Pow runtime/quantity_test.go:TestQuantityExponentiation, TestQuantityExponentiationReports, runtime/quantity_functions_test.go:TestQuantityPowerAndProductAgree ✅ Faithful
QuantityCalculations computes unit-aware: sqrt takes the root of the unit with the magnitude (sqrt(9 [m**2]) is 3.0 [m], sqrt(4 [m**2/s**2]) is 2.0 [SI::'m/s']), and a unit some base unit of which is raised to an odd power has no root — sqrt(9 [m]), sqrt(8 [m**3]) — and is a typed error (ErrUnitRoot) naming the unit and the offending power, never a magnitude in a fractional unit; a root the named units cannot spell at whole powers is taken over the base units instead (sqrt(4 [km*m]) is 63.24… [m]), except that a dimension-one unit at an odd power has no base unit to fall back to and is ErrUnitRoot too — sqrt(9 [rad]), sqrt(9 ['°']), sqrt(9 [rad*m**2]) — while sqrt(9 [rad**2]) is 3.0 [rad] and a cancelled ratio sqrt(9 [m/m]) is the number 3.0; no rad**0.5 is ever made; abs, floor, round act on the magnitude and keep the unit; max/min convert to compare and answer the winning operand as written (max(1 [m], 200 [cm]) is 200 [cm]), incommensurable operands being ErrIncommensurableUnits; sum/product fold in the first element's unit ((1 [km], 500 [m])->sum() is 1.5 [km], (2 [m], 3 [m])->product() is 6 [SI::'m²']); '+', '-' (both arities), '*', '/', '**', '^', '<', '>', '<=', '>=', '==', isZero, isUnit, ToString, ToInteger, ToRational, ToReal delegate to the same quantity code the operators use (ToInteger truncates the magnitude toward zero as RealFunctions::ToInteger does: ToInteger(2.5 [m]) is 2); ToDimensionOneValue(2.5) is the number 2.5, which is how the runtime holds every quantity of dimension one and no unit (a bare number multiplies, adds and compares with quantities as one — ratio * 3 [m] is 7.5 [m]), so it binds to a DimensionOneValue feature and computes on from there; conversely the ScalarQuantityValue parameters of these functions take a bare number as that dimension-one quantity, and answer a bare number back (abs(-3) is 3, ToString(2) is "2", 2 + 1 [rad] is 3, never a made-up 3 [1]) runtime/quantity_functions.go registerQuantityCalculations, quantityArg, runtime/quantity.go inUnit, runtime/quantity.go sqrtQuantity, runtime/errors.go ErrUnitRoot instance_quantity_calculations.sysml, instance_quantity_calculation_failures.sysml, runtime/quantity_functions_test.go:TestQuantityCalculations, TestQuantityCalculationsReport, robustness_test.go:quantity_calculation_that_has_no_value ✅ Faithful
import QuantityCalculations::*; — which the ISQ examples do — leaves (1 [m], 2 [m])->sum() computing 3 [m]: the runtime's registration answers the library declaration before its vendored body, whose private zero the library constrains but never values, is reached; without that import the bare sum is whichever sum the model does import (NumericalFunctions::sum sums quantities too), and a model importing neither is told the name is unresolved, as the checker tells it runtime/quantity_functions.go registerQuantityCalculations, runtime/library_functions.go libraryFunctionFor calc_quantity_sum_imported.sysml, calc_quantity_sum_unimported.sysml, runtime/quantity_functions_test.go:TestQuantitySumWithAndWithoutImport ✅ Faithful
An empty collection of quantities sums to the zero of its declared kind: sum(subcomponents.totalMass) over no subcomponents is 0 [kg] where totalMass :> ISQ::mass, sum(lengths) over none is 0 [m], so mass + sum(subcomponents.totalMass) on a leaf is mass. NumericalFunctions::sum is declared over ScalarValue[0..*] and QuantityCalculations::sum folds from a zero : ScalarQuantityValue it constrains to isZero but never values, so the identity's kind is the runtime's decision, and it takes the collection's declared kind. The zero is in the kind's coherent SI unit — the SI::si.baseUnits raised to the dimension's exponents — and is an Integer magnitude from sum and a Real one from RealFunctions::sum; it adds to and compares with any quantity of that dimension by the ordinary unit conversion (500 [g] + sum(masses) is 500.0 [g], sum(lengths) == 0 [mm]). A product of none stays the number 1 (no unit is a multiplicative identity), size/# stay counts, sum0/product1 keep the identity written, and nothing else relaxes: 10 [kg] + 0 [m], 10 [kg] + 5, 10 [kg] + sum(lengths) and 10 [kg] + sum(counts) are incommensurable as before. The kind is read statically: an empty feature, chain or default-null collection is an empty sequence carrying its declared element unit (Sequence.ElementUnit, set by eval.go declaredElements from semantics.Model.DimensionOfExpr + CoherentUnit), select/reject of nothing keep it, and collect/.{…} of nothing take the unit the body's result declares in the body's scope (emptyMapping) runtime/collections.go aggregate (the ElementUnit branch), typedZero, filter, emptyMapping; runtime/eval.go declaredElements, Context.emptyOfDeclared; runtime/value.go NewEmptySequenceOf, Sequence.ElementUnit; semantics/coherent_unit.go Model.CoherentUnit, systemBaseUnits runtime/empty_aggregate_test.go (TestEmptyQuantityAggregateKeepsTheDeclaredKind over masses, lengths, forces, speeds, temperatures, chains, filters, mapped bodies, sum0/product1; TestEmptyQuantitySumMagnitudeKind; TestEmptyAggregateIdentityIsNotLenientAddition; TestEmptySequenceKeepsItsElementUnitOnlyWhileEmpty), quantity_functions_test.go:TestQuantityCalculations (none->sum() is 0 [m], none->product() is 1), conformance instance_empty_aggregate_subsetting_rollup, queryexec/derived_test.go:TestExecuteProjectsEmptyQuantitySumsAndDefaultedSubsettedParts, docrender/markdown_test.go:TestMarkdownRollupReport, grpc/instance_graph_test.go:TestInstantiate_RollsUpMassesOverDefaultedSubsettedParts, pilot-exec-diff w6d:sum-empty-quantity, :add-empty-quantity-sum (pilot-unevaluated: the reference answers the unevaluated InvocationExpression sum/OperatorExpression +, as for every unit-carrying value, so the kind and unit of an empty quantity sum have no reference verdict), :sum-empty (still agrees: sum(()) is the number 0) ⚠️ Approximate (self-assessed; the kind is the declared one, so a collection whose element type fixes no dimension — Real[*], ScalarQuantityValue[*], a body parameter written without a type, ->collect{in l; l * l} — still sums to the number 0; a body written ad hoc at the REPL prompt owns no scope in the index, so its parameters are not typed there either, and its empty mapping is a bare 0 where the same body declared in the model is typed)
TrigFunctions::sin/cos/tan/cot accept an angle quantity, converting through the unit's declared scale to radians — sin(90 ['°']), cos(0 [rad]) — and reject a quantity of another dimension (sin(90 [m]), ErrTypeMismatch) or of dimension one that is no angle — sin(1 [bit]), tan(1 [sr]), cot(1 [MeasurementReferences::one]) — the unit being asked whether it conforms to ISQSpaceTime::AngularMeasureUnit (Model.IsAngularMeasureUnit); a ratio that has cancelled to a number (sin(1 [m] / 2 [m])) is radians, as a bare number is. Only these four angle functions do: every other numeric library function (arcsin, IntegerFunctions::abs, RealFunctions::sqrt, OpenSysMLMathFunctions::exp, …) declares a number and rejects a quantity, dimension one included (IntegerFunctions::abs(1 [rad]) is ErrTypeMismatch). sin(30 ['°']) is 0.49999993…, not 0.5, because the vendored SI::'°' declares conversionFactor = 1.745329E-02 (its own comment says it "should become pi/180") runtime/quantity_functions.go registerAngleFunction, angleScalars, angleArgument, isAngleUnit; semantics/units.go IsAngularMeasureUnit; runtime/library_functions.go numericScalars instance_quantity_trigonometry.sysml, runtime/quantity_functions_test.go:TestQuantityCalculations, TestTrigFunctionsTakeAngles ✅ Faithful
VectorCalculations over the vector value the Kernel VectorFunctions answer (ValVector; a sequence of numbers written for one is read as one, a sequence with a unit-bearing element is ErrTypeMismatch, never a vector stripped of its units): isZeroVectorQuantity, isUnitVectorQuantity, '+', '-', scalarVectorMult, vectorScalarMult, vectorScalarDiv, inner, norm, angle compute — over vector quantities too, where inner/norm/angle answer the Number they declare (return : Number[1], the checker's type as well): the magnitude in the unit the axes compose by the scalar rule (inner(⟨1.0, 2.0⟩ [m], ⟨3.0, 4.0⟩ [m]) is 11.0, in m²; norm(⟨3.0, 4.0⟩ [m]) is 5.0, in m), the unit implied by the operands rather than carried, as QuantityCalculations::ToReal strips it; the quantity-scaled forms scalarQuantityVectorMult, vectorScalarQuantityMult, vectorScalarQuantityDiv — and the *// operators between a scalar quantity and a vector — answer a vector quantity (ValVectorQuantity, ⟨2.0, 4.0⟩ [m]) whose unit each component composes by scaleQuantities, the scalar rule (2 [m] * ⟨1.0, 2.0⟩ [m] is ⟨2.0, 4.0⟩ [m**2], ⟨2.0, 4.0⟩ [m] / 2 [s] is ⟨1.0, 2.0⟩ [m/s]), a division by zero reported; TensorCalculations::'[', '+', '-', scalarTensorMult, TensorScalarMult, scalarQuantityTensorMult, TensorScalarQuantityMult, isZeroTensorQuantity and isUnitTensorQuantity compute over a tensor quantity (ValTensorQuantity; Structured values) and accept a scalar or vector quantity where they declare a TensorQuantityValue, answering the operands' rank; '[' over a coordinate frame ((1.0, 2.0, 3.0) [spatialCF]), transform and the two CoordinateFrame operators compute over the frame value (Structured values below); outer, the three tensor products and TensorCalculations::transform report themselves by name (ErrUnevaluableLibraryFunction) with the reason — no contraction convention, a result type no outer product inhabits, no law for a transformation over a tensor's indices runtime/quantity_functions.go registerMeasurementRefCalculations, registerVectorCalculations, registerTensorCalculations; runtime/vector_functions.go scaleVectorQuantity, scaleVectorByQuantity; runtime/tensor_functions.go runtime/quantity_functions_test.go:TestVectorCalculations, TestQuantityCalculationsReport, TestQuantityCalculationsAreAllDispatchable (every declaration of the four packages either computes or names itself, parameters by effective name in declared order: an in : Type parameter takes the name of the general's parameter it implicitly redefines, and with no general is anonymous), TestAnonymousLibraryParametersBindByPositionOnly (an anonymous parameter binds by position only, a named call is ErrUnknownParameter, LibraryFunctionParams publishes no name for it) ⚠️ Approximate (see the domain-library rows under KerML Function Library)
An execution trace of a unit-carrying value renders the magnitude and the unit (5.0 [m/s]), as the REPL prints a quantity runtime/trace.go FormatTraceValue action_quantity_assign.trace.golden, runtime/quantity_test.go:TestFormatTraceValueQuantity ✅ Faithful
Incommensurable units are a typed error (ErrIncommensurableUnits), never a comparison of bare magnitudes that would equate 1.5 [m/s] with 1.5 [km/h] runtime/errors.go ErrIncommensurableUnits, quantity.go convertTo runtime/robustness_test.go quantity_incommensurable_comparison, runtime/quantity_test.go:TestQuantityIncommensurable ✅ Faithful
A quantity against a bare number. A number naming no unit is a quantity of dimension one (Quantities::ToDimensionOneValue), so length + 1 and length > 1 are incommensurable like length + 1 [s], while length * 2 and length / 2 scale it. The one exception is a bare zero in a comparison: zero is the null quantity of every dimension, so xoffset > 0, x == 0, 0 < x read it in the quantity's unit — the idiom ShapeItems.sysml writes over a LengthValue — whereas length + 0 and max(length, 0) stay incommensurable (their result would have to name a unit) and 0 [s] states a measurement of its own. Operator notation and the library's QuantityCalculations::'<'/'==' forms agree semantics/quantity_eval.go CompareMagnitudes, adoptZeroUnit, isBareZero (shared by runtime evalEquality/comparisonValues and QuantityCalculations, model-level folding QuantityBinary and element filters); passes/typecheck_dimension.go isBareZero semantics/quantity_eval_test.go:TestBareZeroComparesInTheQuantitysUnit, runtime/quantity_test.go:TestQuantityAgainstBareNumber (the full >/</>=/<=/==/!=/+/-/*// matrix, zero and non-zero, either side, and another dimension), quantity_functions_test.go:TestQuantityCalculations, conformance constraint_quantity_against_zero ⚠️ Approximate (self-assessed: the pinned pilot evaluator prints no value for any of these — x > 0, x + 1, x * 2 alike — so it referees neither side; the specification types QuantityCalculations::'<' over two ScalarQuantityValues and gives no reading of a bare number against one, and the zero rule is this implementation's, chosen so the static check and evaluation agree on the library's idiom)
Statically detectable incommensurability is diagnosed before evaluation. SysML v2 §9.8.9.1 requires addition and subtraction operands and result to have the same quantity dimension and top-level quantity type, and says an invalid operation should raise a warning or error. A comparison or a sum whose operands' quantity dimensions are both statically determined and incommensurable (mass < 1000.0 [m]) is reported as a warning at the type tier, naming both units and both dimensions; the dimension comes from the stdlib QuantityDimension power factors (ISQ's L, M, T, …) and commensurability from the same UnitTerm.Commensurable the runtime applies, so the two cannot drift. Evaluation remains the hard error, and a warning changes no exit code semantics/dimension.go DimensionOfExpr, dimensionOf; passes/typecheck_dimension.go checkDimensions model/dimension_check_test.go, model/typecheck_expr_corpus_test.go; differential row Analysis Examples/Turbojet Stage Analysis.sysml:25 ⚠️ Approximate (static only — see below; the specification-grounded Turbojet warning is an adjudicated divergence from pilot silence)
A quantity on a measurement scale is a point, not a magnitude in a ratio unit. MeasurementReferences::MeasurementScale declares a unit its magnitudes are in and an optional quantityValueMapping; an IntervalScale is also a CoordinateFrame whose offset from another interval or ratio scale is defined through that mapping (its documentation) or through its CoordinateFramePlacement, so 26.85 [SI::'°C_abs'] and 5.0 [Time::UTC] are points on an affine scale and only their differences are magnitudes. The operators follow ConvertQuantity: point ± difference and difference + point are the point moved by the difference converted to the scale's unit (warm + 10.0 [SI::'°C'] is 36.85 ['°C_abs'], 5.0 [Time::UTC] + 3.0 [s] is 8.0 [UTC], a scale placed on another scale converting through the chain); point − point is a difference in the left scale's declared unit — warm - 10.0 [SI::'°C_abs'] is 16.85 ['°C'], 5.0 [Time::UTC] - 3.0 [Time::UTC] is 2.0 [s], the unit TimeScale fixes as s — the right point carried onto the left scale first, so two anchored scales subtract across; ==, !=, <, <=, >, >= carry the right operand onto the left operand's reference through the same anchors, so 300.0 [K] == ConvertQuantity(300.0 [K], SI::'°C_abs') is true and 300.0 [K] < 30.0 [SI::'°C_abs'] is true, while different dimensions stay ErrIncommensurableUnits and a scale with no anchor (Time::UTC) compares with itself only; min/max pick between points as comparisons do; point + point, k * point, point * q, point / q, q / point, magnitude − point, point ** n, sqrt(point), -point and sum/product over points are ErrScalePoint naming the scale and the operation, never a scaled point; a scale is no unit factor, so UnitTermOf refuses it in any composition (['°C_abs'*s] cannot be produced) and the wire refuses a reduction composing one. An OrdinalScale, CyclicRatioScale or LogarithmicScale is a point too, but declares no interval, so moving, subtracting or comparing its points against anything is ErrScalePoint naming the scale kind. The static dimension check accepts what evaluation accepts and warns where a literal makes the refusal certain; a feature typed by a quantity value type may hold a point or a magnitude, so it is neither accepted nor refused statically. The constant folder leaves a point unevaluated for the runtime, which alone reads the anchors; a point crosses the API as the scale by name and as the one factor of its reduction, and reads back as the same point, its unit text read as a unit's is (a qualified name of another scale or unit over the reduction is ErrUnitTextMismatch); a set read from the wire for a runtime judges its members as that runtime does, and two sets are equal when each member of either is a member of the other as the judging runtime tells them apart runtime/scale_arith.go pointOf, addQuantities, offsetPoint, subtractPoints, alignForComparison, compareQuantities, equalQuantities, negateQuantity, refusePoints; quantity.go scaleQuantities, powQuantity, sqrtQuantity; quantity_functions.go quantityExtremum, quantityAdditive; collections.go aggregateQuantities; scale_conversion.go toRatioReference, onScale; errors.go ErrScalePoint; semantics/units.go unitTermOfName, MeasurementScaleOf; semantics/dimension.go Dimension.IsPoint, MaybePoint; semantics/quantity_eval.go; passes/typecheck_dimension.go; grpc/convert.go protoToUnitTerm, ErrScaleNotAFactor, unitProductOfText, measurementRefOf, protoToSet; runtime/value.go setsEqual conformance/calc_quantity_scale_affine, calc_quantity_scale_compare, instance_time_scale_arithmetic, instance_measurement_scale_kinds; runtime/robustness_test.go testCoordinateFrameFailureModes ("point added to a point" … "point on a logarithmic scale moved by a magnitude"); runtime/scale_arith_test.go; semantics/quantity_eval_test.go TestMeasurementScaleIsNoUnitTerm, TestPointsOnScalesDoNotFold, TestPointDimensions; passes/typecheck_dimension_test.go TestPointOnMeasurementScale; grpc/quantity_test.go ⚠️ Approximate (a point on an interval scale is faithful; an ordinal, cyclic ratio or logarithmic scale is refused rather than given the arithmetic its kind defines — a cyclic scale's wrap by modulus, a logarithmic scale's mapping through logarithmBase, factor and exponent)

⚠️ The static dimensional check reports only what a declaration determines, and is silent — by design, not by omission — wherever it does not. A feature's dimension is taken from the quantity value type it declares (attribute mass : ISQ::MassValue), never from the value it happens to be bound to, since an assign may replace that value with one of another dimension. So an operand whose dimension comes from an untyped attribute, parameter or calculation result, an unresolved name, or a redefinition not yet bound is undetermined and no warning is raised (a parameter or result that does declare a quantity type, in actual : ISQ::MassValue, return : ISQ::LengthValue, is determined and is checked — a call by the result of the overload its arguments select); the mistake then surfaces at evaluation as it did before. Products and quotients are not checked, having no commensurability requirement, and a dimension the check derives through unit arithmetic (m/s) is compared by dimension alone. A comparison with a bare zero is silent (xoffset > 0 over a LengthValue, as the geometry library writes it and the pinned pilot validator accepts it): zero is the null quantity of every dimension and evaluation reads it in the quantity's unit, whereas any other bare literal is dimensionless, so length > 5 warns as length + 5 does — evaluation rejects both as incommensurable (A quantity against a bare number above).

⚠️ QuantityCalculations::ConvertQuantity(x, targetMRef) converts (ConvertQuantity(3 [km], SI::cm) is 300000.0 [cm]), the target unit being a measurement reference value (ValMeasurementRef, Structured values below), and ErrIncommensurableUnits when the dimensions differ. QuantityCalculations declares no sum0/product1; over quantities those resolve to the Kernel NumericalFunctions::sum0/product1, whose vendored bodies fold with '+'/'*' the runtime does not yet dispatch, so sum0((1 [m], 2 [m]), 0 [m]) reports unresolved reference rather than 3 [m] — write ->sum() (the empty collection is the zero of the collection's declared kind, 0 [m] over a LengthValue[*]) until the Kernel Function Library dispatch covers them. A quantity's num and mRef read by name (q.mRef == SI::m; structuredFeature), the unit carried on the runtime value; the declaration's other members (q.mRef.quantityDimension, q.mRef.unitConversion.conversionFactor) read from the object the named unit's declaration materializes as (Structured values below), and report themselves by name only for a unit composed at runtime. The gRPC value schema does carry a quantity: Value.quantity holds the magnitude as written, the unit as written, and the reduced unit term (scale and base-unit factors by FQN), so a Python caller reads a quantity feature value as opensysml.values.Quantity, sends one as an action input or a calc argument (opensysml/values.py Quantity.to_pb, connection.py _python_to_value), and a round trip preserves both magnitude and unit (internal/frontend/grpc/convert.go QuantityToProto/ProtoToQuantity/ProtoToValueIn). Sequence indexing (speeds#(3)), which the parser represents with the same node, is evaluated as the index it is (see Sequence Indexing and Collection Operations below): the two forms are told apart at the node (ast.IndexExpr.Bracket), so an index is never read as a magnitude in a unit, nor a quantity as an index.

⚠️ A requirement feature that carries no value of its own is read from the satisfying object's feature of that name, which is how a requirement stated over the values it checks (attribute verticalSpeed; compared against a limit) reaches a verdict from by. The spec supplies a subject's values to a requirement through the subject parameter (subject lander : Lander; then lander.verticalSpeed) or an explicit binding, not by matching names, so this fallback — the same one %requirement applies on an instance — is an approximation, and a requirement whose unbound feature happens to share a name with an unrelated feature of the subject would be checked against it. A requirement whose value comes from neither its own binding nor the subject (the lunar lander model's actualVerticalSpeed, produced by an analysis) still has no value to check and reports ErrNoValue.

Action (SysML v2 Actions — Systems Library/Actions.sysml, over KerML Performances)

No external referee: the pinned artifact evaluates expressions but executes neither actions nor state machines headlessly, so every row in this section is self-assessed against the specification text, the normative library and our own goldens. The fUML referee (tools/cmd/fuml-referee) is a second opinion from outside the project, not a referee of these rows: it runs the fUML reference implementation's own test activities through the action executor where they have a SysML v2 spelling and checks the output values against the reference's, so it can catch an executor defect but cannot vouch for a SysML v2 rule — where fUML and SysML v2 differ, the row below and the alignment note say which text the runtime follows.

Semantic Rule Implementation Test Case Status
Initial node token placement action_executor.go:425 initialize action_control_flow.sysml ✅ Faithful
Final node termination action_executor.go:512 stepFinalNode action_control_flow.sysml ✅ Faithful
One feature space per performance: a succession is a HappensBefore link (Kernel Semantic Library Occurrences.kerml) that orders occurrences and carries no values, so the steps of an action — concurrent ones included — read and write the features of the one action they belong to; and each nested action node is a performance of its own (Actions::Action :> Performance, subactions :> subperformances), holding the parameters and attributes it declares and those of the action it performs in a frame of its own, so same-named pins on two nodes do not collide, node.pin reads and bind/flow ends address that frame (two bindings at one input pin must agree, else ErrBindingConflict; a binding at an undirected attribute of a node is kept at both ends: the node reads the other end as it begins and carries back what it changed as it ends; an end that chains through an object, bind add.sum = holder.inner.mark, writes the feature of the object the chain reaches, typed as an assignment through it is), a body-local in a = 3; on a typed node and the positional or named arguments of action n = Callee(3, 4) seed the callee's inputs by the callee's own parameter order and names, and an untyped n read as a value is the callee's result; a typed or invoked node's subactions are those of the action it performed, so call.inner.v reads through it; a pin holding an object is chained through like any feature, so pick.target.mark reads a member of the object at the pin; a read of p.v in a branch is of that branch's p where the other branch declares one too; a feature a node declares with a sibling node's name shadows that node, so pick.mark in the node reads its own object-valued pick; a nested body still resolves the enclosing action's features lexically and writes them in place runtime/action_frame.go actionFrame, beginPerformance, seedDeclaredValues, performInvocation/adopt, nodesNamed/subaction, bindInputPins/bindOutputPins, deliver, collect; runtime/action_executor.go ActionExecutor.root, stepNestedAction, Results/Data; runtime/eval.go lookupSubaction/evalSubactionPath; runtime/invoke_action.go bindArgumentList; lower/action_graph.go ActionGraph.Features/Scopes/Bindings, Feature, PinBinding, lowerFeatures, lowerPinBindings, lowerInheritedPinConnections (over resolve.ActionGeneralBodies); runtime/action_executor.go deliverFlow conformance/action_fork_branches_share_features.sysml + trace golden, action_executor_test.go:TestActionExecutor_ForkNode_SharedFeatureSpace; conformance/action_node_pins_isolated + trace golden, action_node_pins_two_levels + trace golden, action_node_typed_body_inputs, action_node_invocation_positional, action_node_invocation_named, action_node_dependent_default, action_node_bind_input, action_node_bind_input_agreeing, action_node_bind_overrides_default, action_node_arguments_read_caller, action_node_default_reads_calc_per_performance, action_node_bind_output, action_node_bind_undirected_attribute, action_node_bind_output_through_chain, action_node_bind_undirected_through_chain, action_node_body_writes_enclosing, action_flow_between_same_named_pins, action_node_concurrent_performances + trace golden, action_node_bind_nested_to_enclosing, action_node_concurrent_nested_bindings + trace golden, action_node_pin_read_before_performed (ErrNodeNotPerformed), action_block_flow_sibling_pins + trace golden, action_block_flow_loop_node_frames, action_block_flow_nested_pins, action_block_flow_if_branch + trace golden, action_block_flow_nested_action + trace golden, action_block_flow_if_branch_bindings, action_block_flow_loop_bindings + trace golden, action_node_typed_nested_pins, action_block_flow_else_branch_same_name, action_block_flow_alternating_branch_nodes, action_node_pin_object_member, action_node_feature_shadows_sibling_node, action_inherited_node_bindings; lower/action_node_frame_test.go, lower/block_graph_test.go, lower/action_inherited_test.go:TestToActionGraphInheritedPinConnections; robustness_test.go:node_pin_of_a_node_not_yet_performed, :node_pin_the_node_does_not_declare, :block_node_pin_of_a_node_not_yet_performed, :block_node_pin_the_node_does_not_declare, :else_branch_node_read_before_it_performs (ErrNodeNotPerformed), :typed_node_pin_of_a_node_the_callee_does_not_declare, :node_read_as_a_value_without_a_result (ErrNodePin), :node_pin_member_through_a_scalar_pin, :node_invocation_too_many_arguments, :node_invocation_too_few_arguments (ErrActionArity, ErrUnboundParameter), :node_invocation_unknown_named_argument (ErrUnknownParameter), :node_binding_to_a_non_parameter, :node_binding_output_to_an_unknown_feature, :node_binding_output_through_a_scalar_chain, :node_binding_output_through_a_chain_violates_target_type (ErrBindingEnd), :node_undirected_binding_carried_to_a_non_parameter (ErrNodePin), :node_pin_bound_to_unequal_values (ErrBindingConflict), :node_output_bound_to_a_nested_node_that_never_runs (ErrBindingEnd), :block_node_binding_to_a_non_parameter (ErrBindingEnd), :block_node_binding_names_a_node_without_a_pin, :inherited_binding_names_a_node_without_a_pin, :block_node_pin_bound_where_nodes_are_not_performed, :node_flow_into_a_pin_the_target_does_not_declare (ErrNodePin), :performed_action_input_bound_by_nothing, :state_entry_action_input_bound_by_nothing (ErrUnboundParameter) ⚠️ Approximate (self-assessed: the pinned OMG pilot implementation executes no actions. A node's frame is a runtime frame, not a materialized occurrence, so it has no identity a send could address and Results() reports it as p.v, the latest performance of the node standing for it; a node reached from two fork branches is one performance that follows both, holding at each of its pins the one delivery the flow into that pin carried and sending its outputs on once (action_node_concurrent_performances; two flows into one [1] pin of one performance are a model conflict the runtime does not yet refuse — it keeps the earliest delivery, a limitation, not a rule); a pin holds, in order of precedence, what a flow delivered, what a bind at it gives, then the value the node's own declaration states, and a declared value written in terms of another pin reads what that pin holds. A pin of an untyped action n = Callee(args) is read as n — the callee's result — while n.pin on it is refused by name resolution, which does not type n by the invocation; write it as a typed usage action n : Callee to read n.pin. An action declared in an if branch or a loop body is a node of that block's own flow (lower/block_graph.go lowerNestedNode, ActionGraph.BlockNodes) and a performance of its own like any other node, begun by the statement engine (runtime/action_statements.go performNode) with the block's locals — a loop variable — in reach, so a sibling in the branch reads its pins as p.v and Results() reports them under its path, and a bind or flow written in the block at one of its nodes' pins is lowered into the block's own flow (lower/block_graph.go lowerBlockConnector) and applied per performance, so bind dbl.a = i in a loop body seeds each iteration's node from that iteration's variable; a loop performs the node once per iteration and the latest performance stands for it; a debugger breakpoint on such a node pauses the run before each performance of it (runtime/action_body_run.go runPausable/pauseAt, ActionExecutor.NodeNames over lower.BlockFlows; debug_api_test.go:TestBreakpointPausesBeforeABranchNode, :TestBreakpointPausesOnEachLoopIteration, :TestBreakpointPausesInsideABlockNodesOwnFlow, repl/runtime_commands_test.go:TestBreakpointOnABlockNodePausesEachIteration). A binding end naming a pin two levels down, bind leg.inner.w = x, carries the whole path (PinBinding.Path), so it addresses inner's pin and not one of leg; and a binding between a nested pin and a pin of the node around it, or of another node under that node — bind leg.inner.v = leg.v, bind leg.inner.v = leg.rest.n — holds within the one performance of leg the nested node runs in, the performance that follows both fork branches feeding leg's pins, so an inner's output is never queued for a performance yet to come (runtime/action_frame.go otherEnd; action_node_bind_nested_to_enclosing, action_node_concurrent_nested_bindings) (action_node_bind_nested_pin_path, lower/action_node_frame_test.go:TestActionBindingAtANestedNodePin, :TestActionBindingAtANodePinThroughAChain, robustness_test.go:nested_pin_binding_into_a_node_performing_another_action, :nested_pin_binding_at_an_undeclared_pin (ErrBindingEnd), :flow_reaching_into_a_nodes_own_flow; a binding reaching into a node that performs an action of its own, and a flow end reaching past one node into its own flow — a flow joins pins of the nodes of one flow — are refused when the graph is lowered). A bind or flow a general action states at a pin of a node the derived action inherits applies to that node's performance too, evaluated in the general action's scope and once per declaring action however many generalization paths reach it, while one at a node the derived action does not sequence lowers to nothing. Such a connector follows its node's declaration, not its name: where the derived action declares a node of its own under the inherited node's name, the general's connector lowers to nothing rather than attaching to the replacement's same-named pin, while one redefining the inherited node (action add :>> add, directly or through another redefinition) takes it; a binding between two of the general's nodes holds at both ends or at neither, so one whose other end names a node the derived action replaced lowers to nothing rather than reading the replacement's pin by name (lower/action_graph.go inheritedNodeLookup over resolve.ActionNodeOfBody/RedefinesActionNode, bindsReplacedNode; action_inherited_node_masked.sysml, action_inherited_node_redefined.sysml, action_inherited_node_binding_other_end_replaced.sysml, lower/action_inherited_test.go:TestToActionGraphInheritedPinConnectionsFollowDeclarationIdentity, robustness_test.go:inherited_binding_does_not_reach_a_masking_node, inherited_binding_does_not_reach_through_a_replaced_other_end). A perform in statement form and a state's entry/do/exit action are invocations too (runtime/invoke_action.go invokeAction): an in without a default that no argument or same-named caller value binds is refused before the callee runs (ErrUnboundParameter). For compatibility with the flat feature space this replaces, a bare typed usage action call : Callee; with no binding at a pin still reads an unbound in from the same-named enclosing feature — an invocation Callee() passes nothing and lets the callee's defaults apply, which are evaluated in declaration order after the supplied inputs are bound, so a default may read an earlier input — and every invocation form still returns its out values into same-named enclosing features that exist once the node's own body has run, so a body that rewrites an output returns what it wrote (action_invoked_node_body_writes_output) — a bind or flow at the pin is the spelled form)
A binding end at a node of a performed action is a statement of the action's body: a simple name there resolves in the body's scope first — a parameter of the enclosing action before a same-named feature of the part performing it (KerML 1.0 §8.2.3.5 name resolution outward through owning namespaces), so bind noting.n = level under perform action relaying { in level : Integer[0..1]; … } binds the parameter, given none, not the part's level — and reaches the performer's features only through names resolving to them. A pin valued by its own name (inout log = log, inout n = n) names what the pin masks: the pin and the parameter it redefines are one feature (KerML 1.0 §7.3.4.5 Redefinition), so the lookup passes them over and reads the feature of that name around the usage owning the pin, never itself (no cyclic feature value dependency). runtime/action_frame.go bindingEndContext, pinSymbol; runtime/eval.go EvalContext.valuing, lookupName, namesValuedPin; runtime/classifier_behavior.go performerHoldsFeature conformance/performed_action_binding_end_names_parameter.sysml, robustness_binding_end_names_test.go:TestRuntimeRobustnessBindingEndNames, robustness_call_results_test.go:TestRuntimeRobustnessCallResults ✅ Faithful
Fork node (1→N parallelism): SysML v2 §7.17.3 rule 4 requires target multiplicity 1..1 on every outgoing succession; Actions::ForkAction has no inherent behavior and duplicates control, not values action_executor.go:654 stepForkNode conformance/action_fork_branches_share_features.sysml + .expected.json + trace golden; robustness_test.go:fork_without_a_successor (ErrInvalidActionFlow) ✅ Faithful
Concurrent performances are unordered: HappensBefore links (Kernel Semantic Library Occurrences.kerml) order only the occurrences a succession joins, so two tokens steppable in one step are ordered by nothing in the library action_executor.go Step (tokens step in reverse index order, the executor's own rule); action_choice.go beginStepOrder, offeredMessage, stepTokenNoting, tokenActed, noteTokenOrder (the tokens that acted in one step — moved, retired, forked, joined, resumed, took a message or failed — and every accept a message in flight at the step's start would have answered had another token not taken it first are a choice point naming each as id@node and the one stepped first; not one held at an accept nothing in flight answers or an unready join, nor one the step itself created; a step that fails still records the order it took before failing); action_subflow.go (a nested flow's step records its own) conformance/action_choice_fork_token_order.sysml + .expected.json (one outcome; .trace.order admits the tokens either way) + trace golden; conformance/action_choice_shared_message_accept.sysml + .expected.json (outcomes: either accept may take either message) + .trace.order + trace goldens under the default, declared and seed:1 (one message two parked accepts answer to: the choice names the accept left waiting as well as the recipient); choice_test.go:TestSharedMessageAcceptIsAChoice; every fork/join trace golden carries the choice step N: tokens … lines its concurrency implies (action_explicit_succession_fork_join, action_join_waits_for_slowest_branch, action_nested_flow_in_fork_join, …); choice_test.go:TestTokenOrderIsReportedWhenALaterTokenFails; repl/choice_test.go:TestStepReportsChoicePoints, :TestContinueReportsChoicePointsBeforeFailure; grpc/choice_test.go:TestExecuteAction_ChoicePointDiagnostics, :TestExecuteAction_ChoicePointDiagnosticsOnFailure ✅ Faithful (the order is tool-defined and stable — deterministic replay is kept — and every step that had one to make says so in the trace, on the response's diagnostics and in the debugger's summary line)
Join node (N→1 synchronization): SysML v2 §7.17.3 rule 3 requires source multiplicity 1..1 on every incoming succession; Actions::JoinAction (Systems Library Actions.sysml: "Join behavior results from requiring that the source multiplicity of all incoming succession connectors be 1..1") synchronizes control and merges no values; each incoming succession is a HappensBefore link (Kernel Semantic Library Occurrences.kerml; TransitionPerformances.kerml TransitionPerformance transitionLink: HappensBefore[0..1]) whose later occurrence is the one join performance, so the join follows exactly one performance of every source — one per succession, not one per token lower/action_graph.go ActionEdge (Source, Target, Guard, Decl — a comparable identity for every succession, implicit block-flow edges included); runtime/executor_common.go Token.Via (the edge a token travelled, zero for a token no succession delivered), Token.travel; runtime/action_executor.go synchronize (from stepToken, before the node kind is dispatched), arrival, awaitedSuccessions, Awaiting, stepJoinNode (passes the one token on) conformance/action_fork_branches_share_features.sysml + .expected.json + trace golden; conformance/action_join_waits_for_slowest_branch.sysml, action_join_one_token_per_incoming_succession.sysml, action_join_same_succession_twice.sysml, action_join_three_two_arrive_together.sysml, each + trace golden and derived from the library text in the semantic oracle; action_explicit_succession_fork_join, action_nested_flow_in_fork_join, state_fork_join_pseudostate + trace goldens; robustness_test.go:deadlock_join_starvation, :deadlock_join_same_succession_twice (ErrActionDeadlock); repl/runtime_commands_test.go:TestTokensShowHeldJoinArrivals; debug_api_test.go:TestBreakpointPausesOncePerSynchronizedPerformance, :TestBreakpointOnALoopedNodePausesEachPass, :TestBreakpointOnANestedFirstNodePausesWhileOthersSynchronize, repl/runtime_commands_test.go:TestBreakpointOnAConvergingNodePausesOnce (a breakpoint on a join or a plain node several successions reach pauses once per performance, with every arrival in) ✅ Faithful (a token records the succession it travelled, and a join fires when one token has arrived over each of its incoming successions: the earliest token per succession collapse into one fresh token that performs the join, and a second token over an already-delivered succession stays held for the join's next firing rather than standing in for another succession — action_join_one_token_per_incoming_succession completes with log = 12, action_join_same_succession_twice with log = 1212. A held token is progress-neutral like an accept-parked one, so a join one of whose successions no token can travel deadlocks the run (ErrActionDeadlock) rather than firing on a token count; the REPL's %tokens says which succession a held token arrived over and which it awaits. A step moves each token at most once — ActionExecutor.Step numbers its sweep, and a token that sweep moved, created or sent into a nested flow (enterSubflow) takes its next step once it ends — so the last arrival's step leaves the held tokens at the node and the next performs it, and a debugger breakpoint on the node stops there once, before the performance, not once per arriving token)
Concurrent branches assigning the same feature action_executor.go stepForkNode (branches write the shared space in step order); action_choice.go noteWrite, stepWriteLedger.noteChoices (several tokens writing one destination — the object and the shared FeatureValue written, so a feature and one redefining it are one destination, whether the action's own feature, the performer's via assignPerformerFeature, or a chained object's via writeThroughChain — in one step are recorded once the step is complete as one choice point naming the feature, every token's last write and the one that stood) conformance/action_fork_branches_write_one_feature.sysml + .expected.json + trace golden (the semantic oracle derives that both branches run and both writes precede the join, and pins the runtime's pick of x = 1 as tool-defined); conformance/action_choice_same_step_write_conflict.sysml + .expected.json (both outcomes listed as admissible) + trace golden; conformance/action_choice_chained_write_conflict.sysml + .expected.json (both outcomes) + trace golden; conformance/action_choice_performer_write_conflict.sysml + .expected.json + trace golden; choice_test.go:TestWriteConflictChoice, :TestWriteConflictOnOneObjectThroughTwoChains, :TestAliasWritesAreOneDestination, :TestThreeWritersAreOneChoice, :TestRepeatedWritesByOneTokenListItsLast; robustness_test.go:fork_branches_assigning_the_same_feature ✅ Faithful (branches out of a fork are unordered by the spec, so a same-feature write from each leaves the value open between the two; the runtime steps tokens in a fixed order and the last write in that order stands, and it reports the pick as a choice trace line and an informational diagnostic — writes x := 1 by token 2, x := 2 by token 3 (unordered; x := 1 by token 2 stood) — so the outcome is one of the admissible ones and is labelled as a tool-defined order rather than passed off as the derived one)
Merge node (N→1 non-blocking): SysML v2 §7.17.3 rule 1 gives incoming successions source multiplicity 0..1; Actions::MergeAction (Systems Library Actions.sysml: "a MergePerformance that selects exactly one incoming HappensBeforeLink. Incoming succession connectors to a MergeAction must have source multiplicity 0..1 and subset the incomingHBLink feature inherited from MergePerformance") is a MergePerformance (Kernel Semantic Library ControlPerformances.kerml: incomingHBLink: HappensBefore[1] — "For each instance of MergePerformance, the incomingHBLink is an instance of exactly one of the Successions, ordering the MergePerformance as happening after an instance of the source of that Succession"), so every arrival is its own merge performance following its own one source — a loop re-enters the merge on every pass, and each of a fork's branches traverses it — where a join (JoinAction, source multiplicity 1..1 on every incoming succession) or a plain node follows one performance per succession; the loop's exit is the DecisionPerformance's (outgoingHBLink "is an instance of exactly one of the Successions") and its termination the guard's; a guarded succession out of the merge is a DecisionTransitionAction (Systems Library Actions.sysml: "the base type of TransitionUsages used as conditional successions in action models"), a NonStateTransitionPerformance (Kernel Semantic Library TransitionPerformances.kerml: in feature transitionLinkSource: Performance[1], binding transitionLink.earlierOccurrence = transitionLinkSource, succession [1] transitionLinkSource then [1] Performance::self) whose guard constrains the HappensBefore link through TPCGuardConstraint (constrainedHBLink / constrainedGuard, inv { allTrue(constrainedGuard()) }) — so the merge performance, body included, is complete before its guard is read, and a false guard drops the link to the successor, not the merge's performance runtime/action_executor.go stepMergeNode (checks the one outgoing succession, runs the merge's body, then evaluates the succession's guard and forwards the token or retires it — per arrival, no per-run traversal record), synchronizes (exempts MergeNode from the multi-incoming synchronization stepToken applies to every other node kind); chargeActionStep (ErrActionStepLimitExceeded) bounds an unguarded loop conformance/action_decision_merge_guarded_branch.sysml + .expected.json + trace golden; conformance/action_merge_loop_reenters.sysml + trace golden (the specification's ChargeBattery loop: monitor reads level at 0, 50 and 100, the third decide takes endCharging, level = 100, passes = 3), action_merge_loop_three_passes.sysml + trace golden (count = 3, merged = 3: one merge body run per pass), action_merge_fork_branch_and_loop.sysml + trace golden (a merge reached directly from a fork, through the fork's other branch and twice more through a guarded loop: merged = 4, worked = 4, passes = 4), f63_merge_body_runs_on_traversal.sysml + trace golden (a fork's direct arrival performs the merge with the guard false and is retired, the second arrival performs it again and passes: mergeRuns = 2, passed = 2), action_merge_body_flips_own_guard.sysml + trace golden (the merge body's own increment is what its outgoing guard reads: arrivals = 3, continued = 2), all derived from the library text in the semantic oracle; action_executor_test.go:TestActionExecutor_MergeNode, :TestActionExecutor_MergeNode_ControlOnly (two fork branches into one merge yield two downstream tokens), :TestActionExecutor_GuardedSuccession_PrunedMergeStaysOpen; robustness_test.go:merge_without_a_successor (ErrInvalidActionFlow), :unguarded_loop_through_a_merge (start → m → a → m with no exit: RunToCompletion returns ErrActionStepLimitExceeded, and direct Step calls keep the one token circling until RunToCompletion reports the same); repl/runtime_commands_test.go:TestActionDebuggerStepsAnUnguardedMergeLoop (%step walks the loop, %continue reports the step budget) ✅ Faithful (a merge passes each arriving token, runs its body once per arrival before its outgoing guard is read, and never blocks; two arrivals from a fork are two merge performances and two downstream tokens — collapsing them is a join's job, not a merge's)
An action node reached over several successions is one performance that follows all of them: a step with no declared multiplicity holds one value (KerML 1.0 §7.4.5), and each succession into it is a HappensBefore link (Kernel Semantic Library Occurrences.kerml) whose later occurrence is that performance runtime/action_executor.go synchronize — the join row's one gate, applied from stepToken to every node kind but a merge (synchronizes; Actions::MergeAction passes each arrival on) — with awaitedSuccessions, reachableFrom and leaves deciding which successions a plain node awaits; runtime/action_subflow.go Token.positionIn (a token in a nested flow stands at the node performing it) conformance/action_node_with_two_incoming_successions_runs_once.sysml + trace golden, action_nested_node_two_successions_per_performance.sysml + trace golden (two performances of an action holding such a node each perform it once), action_node_concurrent_performances + trace golden and action_node_concurrent_nested_bindings + trace golden (a flow-owning node reached from two fork branches performs once, each flow at its own pin), all derived in the semantic oracle; action_node_converges_after_decision + trace golden (a plain node behind a decision's two branches performs once for the branch taken, no deadlock) and action_node_loop_back_reperforms + trace golden (a plain node a loop re-enters performs once per pass), with action_decision_else_done, action_guard_reads_calc_usage, action_succession_guard_fork_branch_pruned and f63_control_node_body pinning the same at final nodes and under fork guards; robustness_test.go:deadlock_join_starvation, :deadlock_join_same_succession_twice pin that a join, unlike a plain node, awaits an unreachable source (ErrActionDeadlock) ✅ Faithful (hits = 1: the tokens arriving over the node's successions collapse into one performance, per performance of the owning action. A join awaits every incoming succession, its sources being 1..1; a plain node awaits a succession only once it has delivered or while some token of the activation, other than one held at the node, can still reach its source without passing through the node or through a join that node must feed — a decision branch whose guard did not hold, or a succession back from a node downstream of this one, orders no performance before this one, so a loop through a plain node re-performs it once per iteration rather than deadlocking)
Decision node (guarded branching): SysML v2 §7.17.3 rule 2 gives outgoing successions target multiplicity 0..1; Actions::DecisionAction selects exactly one outgoing HappensBeforeLink action_executor.go stepDecisionNode, probeGuard (guards are evaluated in order until one holds, as enabledSuccessions evaluates them out of any other node; the ones after it are read in a beginProbe preview that restores the budget, the trace, every effect and the object identities the preview took; the first that holds is taken, an unguarded succession is the fallback when none holds; a previewed guard that fails to evaluate is no alternative and no error, recorded as a guard-unevaluable note by noteUnevaluableGuard), action_choice.go noteDecisionBranches (several holding guards are a choice point naming the branches by position and target) conformance/action_decision_merge_guarded_branch.sysml + .expected.json + trace golden; conformance/action_choice_decision_overlapping_guards.sysml + .expected.json (both outcomes listed as admissible) + trace golden; conformance/action_choice_unevaluable_guard.sysml + .expected.json + trace golden; choice_test.go:TestChoicesResetPerRun, :TestLaterGuardErrorIsNotAChoiceNorAFailure, :TestLaterGuardIsProbedWithoutCost; grpc/choice_test.go:TestExecuteAction_UnevaluableGuardDiagnostics; repl/choice_test.go:TestStepReportsUnevaluableGuards; robustness_test.go:decision_no_satisfied_guard, :decision_all_guards_false (ErrNoEnabledSuccession) ✅ Faithful (the library selects exactly one link but does not say which when several guards hold, so the pick is the executor's and is reported as one)
Which linearization a run takes where the library orders nothing is tool-defined, so it is a named scheduling policy rather than one fixed rule: reverse (the default — reverse token-index order, first holding guard, first enabled transition — so every run recorded before policies were selectable replays unchanged), declared (tokens in spawn order, guards and transitions in declaration order) and seed:<n> (every pick drawn from a PCG sequence the seed fixes, so one seed replays one run on every platform and two seeds may take two linearizations). The policy decides every choice point of the row above and the rows below — token order in a step, the holding guard a decision follows, the enabled transition that fires, the order the orthogonal regions one event enables react in, whose same-step write stands (it follows from the token order) — and nothing else: every choice point a run reaches is reported under every policy, and the took … of each is what the policy took (another linearization may reach other choice points, so their count is not fixed across policies). A spelling naming no policy (seed, seed:, seed:-1, seed:abc, an unknown name) is ErrInvalidSchedulePolicy before anything runs, on every surface: sysml -schedule, %schedule, and the schedule field of ExecuteActionRequest, ExecuteStateRequest and RunAnalysisRequest (INVALID_ARGUMENT; the schedule capability advertises the field). Guards read in a preview leave the seeded sequence where it was, so reporting a choice does not change which alternative the run takes; a decision branch picked past the first was only previewed, so the run reads its guard once more before taking it (what evaluating it materialized or derived is then the run's), as fireTransition reads a transition's guard again before it fires; an executor a debugger drives call by call keeps the scheduler its run started with when another run under another policy is driven to completion in between, and Decide previews the transition that run would fire from that same scheduler, putting its draw back; and a composite state every leaf of its orthogonal regions reaches is asked for its transition once per dispatch or change poll, so the choice among its enabled transitions draws once (one candidate, one took …), a composite state a nested state outranks draws nothing, the pick among a candidate's enabled transitions being made only once it survives conflict resolution, so the run's next reported choice takes the seed's next draw; and a token parked at a join its other branches have not reached or at an accept no message in flight answers keeps its place in the step's order, so a seed draws only among the tokens able to act and steps taken while every token is parked draw nothing runtime/scheduler.go SchedulePolicy, ParseSchedulePolicy, SchedulePolicyError, scheduler.orderTokens, scheduler.pick, scheduler.mark; runtime/context.go SetSchedule, scheduling, beginExecutorRun (a run under way keeps the scheduler it started with, across the other runs driven while it is paused), previewExecutorRun (Decide previews under it); action_executor.go scheduleTokens, parked (the tokens Step and a nested flow in action_subflow.go move, in the policy's order, a seed ordering only those able to act), stepDecisionNode; state_executor.go enabledTransitions, selectCandidates, chooseTransition, chooseRegion (the next region to react, drawn from the same scheduler.pick and reported whenever two or more can); state_change_trigger.go risenChangeTransitions; cmd/sysml/main.go schedulePolicy flag; repl/schedule.go doSchedule; grpc/service.go, grpc/analysis.go (CapabilitySchedule); client/opensysml/execute.go WithSchedule, analysis.go Schedule; client/python/opensysml/connection.py schedule= scheduler_test.go:TestParseSchedulePolicy, :TestParseSchedulePolicyRejectsUnknownSpellings, :TestReverseAndDeclaredOrders, :TestSeededSchedulingIsReproducible, :TestSeededTransitionChoiceMatchesTheRun, :TestProbeLeavesTheSeededSequenceInPlace, :TestPickedGuardIsReadByTheRun, :TestDrivenRunKeepsItsSchedulerAcrossOtherRuns, :TestDecidePredictsTheDrivenRunsTransition, :TestSharedAncestorChoiceDrawsOnce, :TestOutrankedChoiceDrawsNothing, :TestParkedTokensDrawNothing, explore_test.go:TestExploreSiblingRegionOrder (reverse and declared take region declaration order and report the choice; seed:1 and seed:8 reach the two effect orders, each replaying), choice_test.go:TestNotesOfATransitionBlockedBeforeFiringAreDropped; conformance_test.go:TestExecutionConformance (the whole suite under the default, every .expected.json and .trace.golden unchanged), :TestExecutionConformanceUnderPolicies (the whole suite under declared and seed:1: no error, panic or hang, and every case pinning no policy and listing no outcomes produces its default outputs), trace_test.go:TestExecutionTrace (<case>.declared.trace.golden and <case>.seed-1.trace.golden for every outcomes case); conformance/action_choice_shared_message_accept lists outcomes (a = 2, b = 1 and a = 1, b = 2, derived in behavior-semantic-oracle.md § Two accepts of one type racing for two sends) with a .trace.order and declared/seed-1 trace goldens; send_identity_same_named_ports runs unpinned (it pinned "schedule": "reverse" while the via-less accept Ping over-matched a transfer addressed to alpha.inPort; with the accept held to the receiver the transfer reaches, waiting has one enabled transition under every policy); cmd/sysml/run_test.go:TestRunActionUnderSchedule; repl/schedule_test.go; grpc/schedule_test.go, grpc/capability_test.go; client/opensysml/schedule_test.go, schedule_internal_test.go; client/python/tests/test_schedule.py; conformance/scenarios/06-behavior.json (schedule on the wire, in-process and over every transport) ✅ Faithful (the three driven policies each take one linearization per run and say which at every choice point; explore, the bounded exhaustive replay of the row below, enumerates them all, and the harness checks every listed outcome is reachable and no unlisted one is, so a fixed order is never passed off as the only one. Same-step write order is not a pick of its own: it follows the token order, so under a policy that orders the writing tokens differently the other write stands, which is what the write-conflict goldens under declared and seed:1 pin)
Every linearization the library admits is a valid run, so a model with choice points has a set of outcomes, not one: the explore[:runs=N,depth=D] policy enumerates it by replay — one run records the alternative each choice point took; each later run is a fresh context on the same lowered model (its own instances, message bus, clock, object behaviors, calc memo and notes — nothing of one run is seen by the next; every run of one exploration is built over that exploration's own resolver and semantic model, an analysis.Worker over the shared frozen index, so two explorations on one model, or one beside a gRPC request or a REPL completion, share nothing that memoizes) that follows the recorded prefix and takes the next untried alternative at its end, the prefixes run in plan order — every one departing from the first run at one choice before any departing at two, earliest choice first, so a runs budget of one more than the first run's choice points varies each of them once — until no alternative is untried or a budget is hit (a choice point past depth takes its first alternative in every run and is never varied). An action step under explore is one token advancing one node — the fixed policies move every steppable token once per step — so the tokens able to act are picked among afresh after each move, a branch of several nodes can be overtaken by a concurrent one between any two of them, and a complete exploration covers every interleaving of the nodes the library leaves unordered, at body granularity (a body's statements are not interleaved). Runs agreeing on the observables the conformance harness compares — an action's outputs; a state machine's final state, states visited and values; an analysis case's outputs and verdicts — are one canonical outcome, counted by the linearizations reaching it and witnessed by one run's choice sequence (an object is compared by its type and feature values through the run's own context, never by the id that run gave it, so equal objects with different ids are one outcome and different objects under one id are two); a run that fails is an outcome of its own (error: …), not the end of the exploration; a behavior with no choice point explores in exactly one run; the same model explores to the same sorted table every time. The budget (1024 runs and 64 choice points per run by default) is never exceeded silently: incomplete: <budget> budget <limit> hit after N runs names each budget hit, runs before depth. explore is not a policy one context runs under (ErrExploreUndriven from SetSchedule), so a step-by-step debugger cannot explore: %schedule explore is a typed error at the prompt while sysml -schedule explore (-action, -state, -analysis, -calc; -engine explore is the same selection) tables the outcomes, exit status 2 when incomplete, and the wire answers outcomes and exploration on ExecuteActionResponse, ExecuteStateResponse and RunAnalysisResponse under the schedule_explore capability. A malformed spelling (explore:, explore:runs=0, explore:depth=-1, explore:bogus, an option twice) is ErrInvalidSchedulePolicy on every surface. The harness explores every case listing outcomes and fails when a listed outcome is unreachable or an unlisted one is reached, naming the outcome and a witness, and when the budget is hit, telling the author to raise exploreBudget; cases without outcomes are not explored by default runtime/explore.go Explore, ExploreBudget, DefaultExploreBudget, ExploredOutcome, Exploration.Status, ChoiceTaken, FormatChoices, exploreRun.pick/beginStep/resolve/nextPrefix (the replayed prefix and the frontier), exploreStep.next/acted (one move ends the step); runtime/scheduler.go scheduler.oneMove, tokenSchedule.Choice; runtime/action_executor.go Step (an exploring step picks among every token able to act, a paused body whose wait has ended among them — action_body_run.go Token.resumable — where a fixed sweep resumes paused bodies last); runtime/outcome.go Outcome.identity (the canonical identity, every name quoted so no output name can spell another outcome), Outcome.String and Outcome.RenderedOutputs (the rendering), objectSpeller (objects by type and feature values), Context.ActionOutcome, StateExecutor.Outcome, Context.VerifiedOutcome; runtime/scheduler.go parseExploreOptions, ExplorePolicy, SchedulePolicy.Exploration; runtime/context.go beginExploration (the per-run scheduler of an exploring run), SetSchedule (ErrExploreUndriven); action_choice.go (token order and same-step writes through the exploring run); cmd/sysml/main.go, report.go (outcomes, exploration in -json); repl/explore.go ExploreAtPromptError, exploreVerdict; repl/schedule.go doSchedule; grpc/explore.go Service.explore, grpc/service.go, grpc/analysis.go (CapabilityScheduleExplore); client/opensysml/explore.go ExploreAction, ExploreState, ExploreAnalysis; client/python/opensysml/exploration.py, connection.py explore_action, explore_state, explore_analysis explore_test.go:TestExploreThreeWritersReachEveryOutcomeOnce, :TestExploreIsDeterministic, :TestExploreNoChoicePointsIsOneRun, :TestExploreRunsBudgetIsIncomplete, :TestExploreDepthZeroIsIncomplete, :TestExploreErrorIsAnOutcome, :TestExploreDecisionInLoop, :TestExploreStateTransitionConflict, :TestExploreRejectsOtherPolicies, :TestParseExplorePolicy, :TestExploreRunsShareNoState, :TestExploreTellsObjectsApartByWhatTheyAre, :TestExploreEquatesObjectsByWhatTheyAre, :TestOutcomeIdentityQuotesNames, :TestOutcomeIdentityOpensEveryObject (a ring of objects is spelled once around, and objects nested past the rendering's depth still tell outcomes apart), :TestExploreSiblingRegionOrder, :TestExplorePausedBodyDueIsAMove; explore_order_test.go:TestExploreVariesEveryChoiceOfTheFirstRunFirst (plan order: the first run's choices varied earliest first, before any twice); conformance_test.go:TestExecutionConformance (exploreConformanceCase over every outcomes case); conformance/action_explore_three_writers (six linearizations, three outcomes, derived in behavior-semantic-oracle.md § Three concurrent writers of one feature: six orders, three values), action_explore_write_between_branch_nodes (three linearizations, three outcomes, the third reached only when one branch's two nodes both run before the other branch's one; § A write between two nodes of a concurrent branch: three orders, three outcomes), action_explore_performed_and_accept_due_together (six linearizations, two outcomes, the paused performed action and the sibling accept due at one instant each resumed first by three; § A performed action and a sibling accept due at one instant: which resumes first is open), action_explore_early_race_long_tail (20 linearizations, two outcomes, the open write order met before the closed orders of the branches' tails; § Two writers of one feature before a long tail of closed choices: two values), action_explore_decision_in_loop (§ A decision inside a loop: every pass is its own open choice), state_explore_transition_conflict (§ Two transitions out of one state enabled by one event: exactly one fires, which one is open), state_explore_region_order (§ Transitions in sibling regions enabled by one event: each fires, in which order is open), each with a .trace.golden under the default and declared/seed-1 goldens; cmd/sysml/run_test.go:TestRunUnderExplore, :TestJSONReportsExploration, spacecraft_showcase_test.go:TestExploreTablesTheSpacecraftRaceWithinItsBudget (a race past the default depth, tabled within a stated budget at any -jobs); repl/explore_test.go, repl/isolation_test.go (completion answers beside an exploration; a second command waits); grpc/explore_test.go, grpc/evaluate_retention_test.go (concurrent requests on one model); analysis/isolation_test.go (two plans on two goroutines under -race, each on its own worker; a run's context takes the budget's Steps and Memory at construction, a zero field being the context's own); client/opensysml/explore_test.go; client/python/tests/test_explore.py; client/rust/opensysml/tests/client.rs, client/java/.../ApiIntegrationTest.java, client/node/test/client.test.ts (the schedule_explore capability) ✅ Faithful (exploration is over the choice points the executors report, so a linearization two choice points do not distinguish is not run twice; a replay whose choice points differ from its recorded prefix is ErrExplorationDiverged rather than a table nobody can trust)
A witness is a run that can be run again: the replay:<file> policy reads a file of input <feature> = <value> lines — the inputs a solver chose, each as InputTaken.String spells them (a rational exactly, an enumeration or variation-point constructor by its qualified name), any number of them or none, so a stage-1 or check witness without them replays as before — followed by choice lines — each as ChoiceTaken.String spells them, one per line or joined by ;, up to the first blank line, so a checker's witness file with a trace body after its header serves as it stands — fixes the inputs on the action as it starts, before its defaults and ahead of the moves, through the same path a caller's Start values take, refusing one naming a feature the action does not declare or cannot take with the typed WitnessInputError naming it, and resolves the run's choice points in that order, each line having to name the step the run is at and pick among the alternatives it offers, then as reverse picks, one token a step, once the lines are spent; a line the run cannot follow — a pick not offered, a step already passed — or one left over at the end fails the run with the typed ReplayError naming the move, its choice and what the run faced (Context.Unfollowed), never silently running another linearization; a file that is empty or whose lines spell no choice is SchedulePolicyError as the policy is parsed, while a header of no choice points alone is the witness of a run with none and follows it; accepted wherever a policy is spelled (sysml -schedule, %schedule, a conformance case's schedule pin) except the wire, where a request carries no file of the caller's and the spelling is INVALID_ARGUMENT runtime/replay.go ParseWitness, ParseInput, InputTaken, InputParseError, WitnessInputError, ParseChoices, ChoiceParseError, ReplayPolicy, SchedulePolicy.Replay, SchedulePolicy.Witness; runtime/action_executor.go ActionExecutor.fixWitnessInputs, Context.Unfollowed, ReplayError, ErrReplayRefused, replayRun.beginStep, replayRun.choose; runtime/scheduler.go ParseSchedulePolicy (replay:<file>), ReplaySpelling, SchedulePolicyNames; grpc/service.go schedulePolicy (the wire refusal); cmd/sysml/usage.go (the -schedule help) runtime/replay_input_test.go:TestParseInputReadsEverySpelling, :TestParseWitnessReadsInputsBeforeChoices, :TestReplayFixesWitnessInputs; smt/input_engine_test.go:TestEngineWitnessWithInputsReplaysThroughTheStart, :TestEngineWitnessWithoutInputsReplaysAsBefore; runtime/replay_test.go:TestParseChoicesRejectsWhatSpellsNoChoice, :TestParseChoicesStopsAtBlankLine, :TestParseReplayPolicy, repl/checker_test.go:TestReplayStepsAWitnessOfNoChoice, cmd/sysml/check_engine_test.go:TestEngineCheckWitnessOfNoChoiceReplays, :TestReplayFollowsActionWitnesses, :TestReplayFileReproducesTheExploredRun (every witness explore tables replays to its run's trace), :TestReplayFollowsStateWitnesses, :TestReplayRefusesAMoveNotEnabled, :TestReplayFallsBackToReverse, :TestReplayReadsAStepsOrderInEitherPlace, :TestReplayProbeLeavesTheWitnessInPlace, check_loop_replay_test.go:TestCheckWitnessesOfALoopingDoRoundReplay (a do body looping through timed waits past the witness's last line, top-level), :TestCheckWitnessesOfAnInlinePerformanceReplay (an order recorded at the performer's step, resolved in the inner flow), robustness_replay_test.go:TestRuntimeRobustnessReplay (an inner order naming a token of no flow, or mixing two, refused; a witness ending before the run), conformance/state_do_action_loop_timed_exit + .check.expected.json; cmd/sysml/spacecraft_showcase_test.go:TestEngineCheckWitnessesTheSpacecraftRaceAndReplaysEach (every witness -engine check writes replays to its values); grpc/schedule_test.go:TestAReplayScheduleIsInvalidArgument; smt/referee_test.go:TestRefereeCorpus, :TestRefereeInputs (every solver witness, with its inputs, replayed under it) ✅ As designed (an OpenSysML policy, like the others; no run under another policy changes)
A choice point is reported wherever the executor had several enabled alternatives the library does not order, and nowhere else: several steppable tokens in one step (token order), several holding guards of one decision (decision branch), several tokens writing one feature in one step (write order), several enabled transitions out of one state for one event (transition), the next unit among the firings transitions in several regions selected for one event make (region order), the next entry among the regions a composite state, a fork or a history enters (entry order), and the next exit among the regions a state leaves (exit order). Each is one trace line choice <what>: <alternatives> (unordered; took <alternative>), one informational diagnostic with code choice-point at the node, decision, feature or state that made it, and a count on the debugger's summary line; none is an error, and a run under a fixed policy reports the same set another fixed policy would have had to make on the linearization it took choice.go ChoiceKind (ChoiceTokenOrder, ChoiceDecisionBranch, ChoiceWriteOrder, ChoiceTransition, ChoiceRegionOrder, ChoiceEntryOrder, ChoiceExitOrder), ChoicePoint.String (the trace line), ChoicePoint.Describe, ChoicePoint.Diagnostic (SeverityInfo, code ChoiceDiagnosticCode), Context.noteChoice, Context.Notes, Context.Choices, ActionExecutor.Notes/StateExecutor.Notes; action_choice.go noteTokenOrder, noteDecisionBranches, stepWriteLedger.noteChoices; state_executor.go transitionChoice, chooseRegion; repl/trace.go Session.noteSummary (N choice points; %trace on to see them, or the lines themselves under %trace on); grpc/convert.go RunNoteDiagnosticsToProto (the diagnostics of ExecuteActionResponse, ExecuteStateResponse and RunAnalysisResponse) choice_test.go:TestChoicePointRendering, :TestChoicesResetPerRun, :TestTransitionChoiceNamesStateAndEvent, :TestWriteConflictChoice, :TestSharedMessageAcceptIsAChoice, :TestAncestorPriorityIsNotAChoice; explore_test.go:TestExploreSiblingRegionOrder (the region order kind); conformance/action_choice_fork_token_order, action_choice_decision_overlapping_guards, action_choice_same_step_write_conflict, state_choice_transition_conflict, state_explore_region_order, each + .trace.golden carrying its choice line; repl/choice_test.go:TestStepReportsChoicePoints, :TestStepChoiceSummaryWithTraceOn, :TestContinueReportsChoicePoints, :TestAdvanceReportsChoicePoints; grpc/choice_test.go:TestExecuteAction_ChoicePointDiagnostics, :TestExecuteState_ChoicePointDiagnostics, :TestRunAnalysis_ChoicePointDiagnostics; conformance_test.go:TestConformanceDiagnosticsGate (an informational note is no failure of a conformance case) ✅ Faithful (the ancestor-priority case between a substate's transition and its enclosing state's is ordered by SysML v2/KerML and is not reported — state_choice_ancestor_priority_not_reported, state_choice_ancestor_outranked_not_reported)
A guard the executor reads only to report a choice — one after the branch or transition already taken — that fails to evaluate is no alternative and no error: a guard with no result is not true, so its succession is not selected, the run is unchanged, and the failure is an informational guard-unevaluable note, while the same failure at the guard the run does take still fails the run choice.go UnevaluableGuard, UnevaluableGuard.Diagnostic (SeverityInfo, code UnevaluableGuardCode), Context.noteUnevaluableGuard, Context.UnevaluableGuards; action_choice.go ActionExecutor.noteUnevaluableGuard; action_executor.go probeGuard (a beginProbe preview that restores the budget, the trace, every effect and the object identities it took); state_executor.go probeTransition, unevaluableTransition; state_route.go probeBranch (a choice pseudostate's later branch); state_change_trigger.go probeChangeGuard conformance/action_choice_unevaluable_guard + .expected.json + .trace.golden; conformance/state_choice_unevaluable_transition + .expected.json + .trace.golden; conformance/state_choice_unevaluable_branch + .expected.json + .trace.golden; choice_test.go:TestLaterGuardErrorIsNotAChoiceNorAFailure, :TestLaterChoiceGuardErrorIsNotAChoiceNorAFailure, :TestFirstTransitionFailureStillFailsTheRun, :TestLaterGuardIsProbedWithoutCost, :TestLaterChangeGuardErrorIsNotAChoiceNorAFailure, :TestProbedGuardLeavesObjectIdentitiesUntouched; grpc/choice_test.go:TestExecuteAction_UnevaluableGuardDiagnostics; repl/choice_test.go:TestStepReportsUnevaluableGuards; robustness_test.go:decision_no_satisfied_guard (the taken path still fails) ✅ Faithful (SysML v2 §7.17.3 selects a succession whose guard is true; a guard that cannot be evaluated is not true, and reading it changed nothing)
A conformance case that admits several outcomes states the whole set: outcomes lists every admissible outcome in full — never "anything" — and admissible cites the section of behavior-semantic-oracle.md that derives the set; a .trace.order beside it states the partial order the library does fix as earlier < later over trace labels. The harness checks the run's outcome is exactly one listed member, checks the trace against every order constraint, then explores the case and fails on a listed outcome no linearization reached, a reached outcome the list omits, and a hit budget — each naming the outcome and a witness choice sequence conformance_test.go ExpectedOutcome.Outcomes/Admissible/ExploreBudget, admissibleSchemaProblems (an outcomes list needs two or more full outcomes, an admissible title the oracle has, and no single-outcome fields beside it), matchOutcome, runConformanceCase, exploreConformanceCase; trace_test.go checkTraceOrder, parseOrderConstraints, orderViolations; testdata/conformance/README.md (the schema) conformance_test.go:TestAdmissibleOutcomesSchema, :TestMatchOutcomeRequiresExactlyOne, :TestExecutionConformance (exploreConformanceCase over every outcomes case), :TestExecutionConformanceUnderPolicies; trace_test.go:TestTraceOrderViolationFails, :TestExecutionTrace; conformance/action_explore_three_writers (outcomes of three, .trace.order of six constraints; explored in six runs), action_choice_shared_message_accept, action_choice_fork_token_order (one outcome, a .trace.order admitting the tokens either way), state_explore_region_order ✅ Faithful (a case without outcomes is not explored by the harness, so a fixture pinning one linearization of an unobservable openness stays a one-outcome case; the oracle names which fixtures those are)
Exploration is bounded and says so: the budget is runs (1024 by default) and choice points per run (64), a budget hit ends the exploration as incomplete: <budget> budget <limit> hit after N runs naming each budget hit, runs before depth, never as a table that looks complete; the CLI exits 2 on an incomplete exploration, the JSON report carries exploration: {complete, runs, budgetsHit}, the wire carries ExplorationStatus, and the harness tells the author to raise exploreBudget in the .expected.json explore.go ExploreBudget, DefaultExploreBudget, Exploration.BudgetsHit, Exploration.Complete, Exploration.Status; scheduler.go parseExploreOptions (explore:runs=N,depth=D); repl/explore.go explorationVerdict (VerdictUnresolved when incomplete or a run failed), VerdictExploration; cmd/sysml/report.go checkExploration; cmd/sysml/status.go exitUnevaluable; grpc/explore.go Service.explore; conformance_test.go exploreConformanceCase (ExpectedExploreBudget) explore_test.go:TestExploreRunsBudgetIsIncomplete, :TestExploreDepthZeroIsIncomplete, :TestParseExplorePolicy; repl/explore_test.go:TestRunActionExploreReportsTheBudgetHit; cmd/sysml/run_test.go:TestRunUnderExplore, :TestJSONReportsExploration; grpc/explore_test.go; client/python/tests/test_explore.py ✅ Faithful (an incomplete exploration is a verdict nobody decided, so it takes the exit status of a run that decided nothing, the same 2 as an unevaluable verdict)
The set of outcomes is also reached by search rather than by enumeration: the explicit-state model checker takes the schedules of an invocation — the actions and state machines started on one clock, run to a horizon, and the machines of the objects they materialize — one move at a time — one token advancing one node, a body being one move (stepToken's unit, coarser than the interpreter's statement), one event dispatched, one due do behavior stepped — enumerating the enabled moves of a state (each token able to act, a join whose tokens have all arrived, a paused token whose wait has ended, a parked accept whose message has arrived or whose routing fails under a readiness probe, one move per holding guard of a decision; a machine's dispatch, one per event the library leaves unordered at the instant, and its due do behaviors; among the executors due, the one drawn holds the turn until it has no move at the instant, as runDue draws it), snapshotting the run before each choice and restoring it to take the next, so every schedule the library admits is visited and no other. Two moves whose static footprints — the features a node's body reads (an outgoing succession's guard and a parked trigger's condition among them) and writes, the channels it sends on and accepts from, the joins and merges it reaches, computed once per node when the action is lowered; for a dispatch the guards and triggers of the transitions the event can select out of the active configuration, their effects and the activity of the states left and entered, for a do step its statements'; one executor's moves pairwise dependent — are independent commute, so only one order of each such pair is searched (persistent sets with a sleep set; a dynamic assignment target or send target depends on everything), and a state already visited — every executor in invocation order: the token multiset by node and frame path, frames root-first with values in the trace recorder's canonical form, a paused body's statement cursor, frames and wait, a machine's configuration, history, values, queue in dispatch order, deferred events, timers and do progress; objects by their materialization path rather than by Instance.ID, messages, the clock — is not searched again. What the search finds: a named constraint or requirement false at a stable state, a deadlock (ErrActionDeadlock, ErrAcceptDeadlock) or a typed error a body raises (an unbound parameter, a dangling succession, a division by zero, an accept whose via port does not resolve, each on the schedule that reaches it) is a violation; a feature ending with different final values on different schedules is divergent, one witness per value (the features named — finalState a machine's resting configuration, <behavior>.<feature> and <behavior> finalState in a joint invocation — else every attribute of the behaviors and their performing object and every machine's finalState, an action without an object on its own); a machine resting where nothing wakes it is a complete schedule, not a deadlock, and a wait past the horizon is left unreached, the verdict reading exhaustive up to t=D; a bound reached — moves along one schedule (depth, 10 000), distinct states (states, 1 000 000), the plan's clock (time), an executor budget (actionSteps, steps, elements, …) — is named and the verdict is no violation within bounds, never no violation, exhaustive, the one proof, relative to the atomic move and the properties given. A witness is the ChoiceTaken sequence explore records for the same steps, written as its choice lines, a blank line and the run's trace, and is replayed through the scheduler seam (replay:<file>, the policy that follows a witness's choices and refuses where the run departs) before it is reported: the replayed run must reach the state claimed with the same trace, else the witness is not covered with the disagreement as the reason. Where an exploration completes, the final states the check reaches are the outcomes it tabled. A body paused mid-statement — a token suspended at a breakpoint or on the clock, a do behavior waiting — is explicit state (bodyRun: statement cursor, block, loop and flow-node frames, the nested performance, a bodyWait descriptor) a snapshot of the same context captures and restores, so it is searched like any other; a portable HeldImage alone refuses it (ErrSnapshotPausedBody). Statement-level moves and following a signal to an object whose machine the invocation does not run are later stages'. The search is single-threaded — one executor state per stack frame, one visited set — so Jobs divides only the replay of witnesses lower/footprint.go Footprint, Footprints, Footprint.Dependent, footprintOf (Graph.Footprints beside Bodies); runtime/check.go Check, checker.search, checker.take, checker.enter, checker.visit, checker.stabilize, checker.properties, checker.final, CheckReport, CheckVerdict, CheckStopped, ExecutorBounds; runtime/check_reduce.go checker.persistent, checker.childSleep, checker.footprintOf; runtime/check_invocation.go Invocation, Starter; runtime/check_run.go (the executors on one clock, the due order drawn, the horizon); runtime/check_moves.go enabledMove, ActionExecutor.enabledMoves, ActionExecutor.makeMove, StateExecutor.dispatchMoves, checkPolicy; runtime/check_state.go canonicalState, stateSpeller; runtime/action_body_run.go bodyRun, bodyWait; lower/state_footprint.go (transition and behavior footprints); runtime/replay.go ParseChoices (stops at the first blank line), ReplayPolicy, Context.Unfollowed, ReplayError; runtime/check_replay.go Replay, Witness.String, ReplayDisagreement; runtime/action_executor.go acceptMatch (a routing failure kept, not a deadlock); analysis/check.go checkEngine (below) runtime/check_test.go:TestCheckJoinWaitsForSlowestBranch (arrived = 3 on every schedule, nothing divergent), :TestCheckForkBranchesWriteOneFeatureDiverge (x over exactly {1, 2}, leftRan/rightRan agreed), :TestCheckEvaluatesPropertiesAtCompletion, :TestCheckDivergenceOfNamedFeaturesOnly, :TestCheckWitnessesReplay, :TestCheckReplayDisagreesWithATamperedWitness, :TestCheckWitnessesAPerformedActionThroughItsPerformer, :TestCheckPropertyOfThePerformerIsWitnessed, :TestCheckReportsFailuresAsViolations (the robustness failures as violations with a witness), :TestCheckReportsAnUnresolvedViaPortAsTheRoutingError, :TestCheckMergeLoopHitsTheDepthBound (incomplete, depth named, no hang, no exhaustiveness), :TestCheckNamesEachExecutorBudget, :TestCheckStopsWhenCancelled, :TestCheckSettlesTimedBranchesOnTheClock, :TestCheckVisitedStatesCloseALoop; runtime/check_moves_test.go; runtime/check_reduce_test.go:TestCheckReductionIsSound (reduced and unreduced final-state sets equal over testdata/check/por_*.sysml: shared write, guard read, trigger-condition read, send/accept pairing, join convergence, dynamic target), :TestCheckReductionRatchet (testdata/check/reduction_expected.txt, adjudicated on every movement); runtime/check_corpus_test.go:TestCheckConformanceOracles (every action, state and clock case with an admissible set against its .check.expected.json, reduced and unreduced — the eight cases whose default run pauses a body and clock_action_state_due_together among them), :TestCheckAgreesWithExploreOverTheConformanceCorpus (the referee), :TestCheckWitnessesReplayOverTheConformanceCorpus; runtime/check_horizon_test.go (a re-arming timer bounded by the horizon, a property up to it, an action's wait past it, a machine's failures as violations, the caller's deadline); runtime/check_reduce_state_test.go (dispatch and do-step footprints, one executor's moves as a unit); runtime/action_body_run_test.go (a body paused at a breakpoint, on the clock and inside a loop resumed, snapshotted and restored); analysis/check_clock_test.go:TestExploreRefereesCheckOverBehaviorsOnOneClock, :TestChecksOfBehaviorsOnOneClockHaveWorkersOfTheirOwn; runtime/replay_test.go; lower/footprint_test.go, lower/state_footprint_test.go ✅ Faithful for actions and state machines on one clock (an exhaustive verdict is a proof relative to the atomic move — a body one move, a dispatch one move — and the properties named; statement-level moves and signals to machines off the clock are later stages')
The object an explored run performs a behavior on is one the run builds, never one the session holds: a performer, subject or object named to explore, check, smt, sweep or all is a declaration (a part/item usage or definition) each run instantiates, or a declaration-rooted path into what it holds (Comms::pair.ground, Fleet::fleet.rovers[2]) the run walks inside the object it made, the declaration being instantiated once per run however many behaviors name paths under it, so sibling parts share their assembly and its connectors carry their messages; a declaration -instantiate names is given to every run the same way, a machine named alone attaching to the run's one object exhibiting it; the path is checked against the declarations before any run starts (an unknown usage, an index on a single-valued usage, a step through a value) and what only a run can know (a part its recipe left unbuilt, an index past what it built) is that run's error; an id (#2, #2.ground) or a path from an object the session alone holds is refused as naming no recipe; the wire's performer_symbol_id and subject_symbol_id spell the same paths, and a witness the checker writes for a behavior on a nested object replays on it repl/explore.go freshRef (the longest declaration prefix and the path past it), checkFreshPath, freshPathError, planFresh, freshPlan.given, freshPlan.bind (one root per declaration per run), freshObjects.object (the walk, through objref.Walker), freshObjects.exhibitors, freshMachine, freshAction, freshExhibitorsError, UnplannedObjectError, ExploredObjectError; repl/session.go Session.given, givenRoots; repl/instantiate_report.go (the CLI's -instantiate recorded as a given root); repl/carryover.go (a given root dropped with its declaration); repl/meta.go ExhibitorsError.Fresh; objref/objref.go Ref, Segment, LooksLikePath; objref/walk.go Walker.Walk (the one walk the prompt and the runs share, typed at each refusal); runtime/context.go ExecuteActionPerformedBy, ExecuteStatePerformedBy, StateOutcomePerformedBy; runtime/check_invocation.go Invocation.Outcome (the performers' attributes beside the behaviors'); grpc/verify.go verifyContext.performer, subject, objectAt; grpc/service.go (CapabilityPerformer; performer_symbol_id on ExecuteActionRequest and ExecuteStateRequest); cmd/sysml/usage.go (-instantiate, -state, -action); client/opensysml/execute.go PerformedBy; client/python/opensysml/connection.py (performer=) repl/explore_test.go:TestRunForExploresAMachineOnANestedObject, :TestRunForExploresAnActionOnANestedObject, :TestRunAnalysisExploresOnANestedSubject, :TestRunForExploresSiblingsOnOneRoot (one root, two machines, the connector between them; the same table under one and four jobs), :TestExploredRunsAreGivenTheObjectsInstantiated (a machine alone attaches to the given object, a path reuses it, the prompt's %instantiate gives nothing, a given root outlives unrelated submissions and not its declaration's), :TestExploredPathsAreCheckedAgainstTheDeclarations (an id, an unknown usage, an index on one value, a step through a value: refused before any run; a part left unbuilt: the run's error); cmd/sysml/explore_nested_test.go:TestExploreRunsAMachineOnANestedObject (a connector race tables two outcomes, its no-race variant one, byte for byte under -jobs 1 and -jobs 4), :TestExploreRunsSiblingsOnOneAssembly, :TestExploreRefusesPathsItCannotPlan, :TestEngineCheckWitnessesANestedObjectsDivergence (the witness written, replayed under -schedule replay:, and composed under -engine all); runtime/check_test.go:TestCheckWitnessesOfSeveralBehaviorsReplay, runtime/replay_test.go:TestReplayFollowsDoActionWitnesses, :TestReplayFollowsSiblingDoActionWitnesses; grpc/explore_test.go:TestPerformOnANestedObjectOverTheWire, grpc/analysis_test.go:TestRunAnalysisOnANestedSubject, grpc/capability_test.go (performer advertised); client/opensysml/performer_test.go; client/python/tests/test_performer.py ✅ Faithful (the runs' objects are built by the same instantiation and walked by the same objref.Walker as the prompt's, so a path means one thing at the prompt and in a run; the session's objects stay out of every run by construction, the plan being made under the session's lock and the objects inside each run)
Action execution nodes action_executor.go:723 stepActionExecutionNode action_control_flow.sysml ✅ Faithful
Nested action invocation (action call : Callee;, action call = Callee(3, 4);, action call = Callee(a = 3);, perform action call : Callee;) runtime/action_frame.go bindArguments, performInvocation, checkInputsBound; runtime/state_statements.go stateStmtHost.performNode; runtime/invoke_action.go invocationArguments, bindArgumentList, Context.actionParametersOf (over semantics.Model.BehaviorParametersOf, so inherited and redefined parameters keep their effective order); action_executor.go stepNestedAction invoke_action_test.go:TestInvokeActionPassesParametersBothWays, :TestInvokeActionBindsPositionalArguments, :TestInvokeActionBindsNamedArguments, :TestInvokeActionRejectsBadArguments; conformance/action_node_invocation_positional, action_node_invocation_named, action_node_typed_body_inputs, action_node_inherited_parameters, action_node_invocation_empty, action_inherited_typed_node_scope, action_node_arguments_before_defaults, state_block_flow_node_arguments_before_defaults ✅ Faithful (arguments bind the callee's inputs by the callee's parameter order and names, never by what the caller happens to name alike; they bind the node's pins before the defaults it declares are evaluated, so a default an argument replaces is never evaluated and one the node keeps reads the argument's value, in an action's flow and in a state's body alike; the callee is resolved where the node was declared, so a node a derived action inherits still finds a callee visible only to the general action; a surplus, missing, unknown or repeated argument is ErrActionArity, ErrUnboundParameter, ErrUnknownParameter or ErrDuplicateArgument before the callee runs — a surplus one is also reported statically by passes/typecheck_expr.go, so no conformance fixture states it. The row above states the compatibility by-name fallback a bare usage keeps)
Assignment statement in a body (assign x := <expr>) lower/action_graph.go lowerStatement Assign; runtime/action_statements.go execStatement action_send_accept.sysml, lower/action_body_test.go:TestActionBodyLowering ✅ Faithful
A value written to a feature conforms to that feature's declared type, by the relation that governs binding an initial value: Actions::AssignmentAction :> FeatureWritePerformance, Action "updates the accessedFeature of its target Occurrence with the given replacementValues" and FeatureReferencingPerformances::FeatureWritePerformance "assigns the values of a feature on an occurrence to the given replacementValues", so what is written are values of that feature and answer to its type. The values of a feature are instances of all its types (KerML 1.0 §8.3.3.3.4), so a feature holds a value exactly when istype would affirm the feature's type of it — the one shared runtime/classification.go classifyValue, so a write and a classification never judge the same value and type differently. Specialization widens (a subtype value where a supertype is declared), the scalar lattice widens by representation (Integer ⊑ Rational ⊑ Real ⊑ Complex: an integer is held by a Rational or Real feature, a finite real — a Rational, §8.4.4.9.2 — by a Real one, and neither the whole real 2.0 nor the quotient 4 / 2 by an Integer or Natural feature, whatever number it is; RationalFunctions::ToInteger, RealFunctions::ToInteger and IntegerFunctions::ToNatural convert), a target the value's types leave open holds it (Natural = 7, Even :> Integer = 4, Cost :> Real = 250.0 — the declaration being what states which of the supertype's values it is — while the bounds of Natural and Positive refuse -1 and 0), and an untyped or Anything-typed target holds anything. Statically, an expression's type only bounds its values, so the type pass refuses a value only for a disjoint type (String to Integer), when the value is a literal, whose type is exact (2.5 to Natural), or when it is a quotient, a Rational however whole (IntegerFunctions::'/', §9.4.11.1: Integer = 7 / 2, Natural = i / 2, an argument add(4 / 2) to an Integer parameter); a call or a Real feature bound to an Integer is the run time's to judge, since a Real feature may hold an integer, and the run time judges every binding it holds a value for: a default a declaration binds (folded ahead of time or evaluated on first read), a value read through a declaration, an argument or default bound to a calc parameter (inherited or redefined), and a calc's result. A value's element count answers to the applicable multiplicity: a non-parameter feature with none stated has assumed 1..1 (KerML 1.0 §7.4.5), but writes and declarations of an action parameter are judged against its effective parameter range (EffectiveParameterRange, SysML v2 §7.6.3), so a bare action parameter is [0..*] and an explicitly stated [*], [0..1] or [1..2] parameter holds what it declares; action_param_default_scalar_count.sysml now explicitly declares [1], and robustness_performed_action_inputs_test.go:testNestedActionParameterMultiplicity covers bare nested parameters and explicit required inner and outer parameters; a null holds none and so reaches a [0..1] parameter's ?? but not a [1..1] one — and a Complex is one value (ValComplex) against any feature's multiplicity: a Complex feature holds it, a Real feature refuses one off the real axis by type, and a numeric pair (1.0, 2.0) is two values whatever the feature's type passes/typecheck_assign.go assignWalker → passes/typecheck_expr.go exprChecker.checkBoundValue and bindable (the initial-value rule, reached with the target's declaration); runtime/write_conformance.go Context.writeTargetIn (parameter targets use EffectiveParameterRange, non-parameters keep statedMultiplicity), Context.checkWrite/checkWriteType/valueConforms, reached from runtime/instance.go Instance.SetFeatureValue, Context.checkDefault (at materialize for a folded default and materializeFeatureValueIntrinsic for a deferred one), runtime/binding.go assignBindingEndpoint, runtime/eval.go EvalContext.declaredValue, runtime/write_conformance.go Context.checkBodyDeclaration → checkTarget with admitDeclared (a declaration local to a calc, action or constraint body, a { } block or a collection-expression body — lower.Declare in runtime/statements.go, an action node's own feature seeded by runtime/action_frame.go seedDeclaredValues, and the compiled statement tier's runtime/compiled_stmts.go — is judged once when its initial value is bound, by the namespace-level rule: its type with declared admission, so an enumeration-typed local holds the enumerated value; its multiplicity only where the declaration states one, an omitted multiplicity holding the count the initializer has; its uniqueness where it declares more than one value; KerML 1.0 §7.3.4, "the values of a feature are instances of its types"; a local declaring no type holds anything), the statement hosts' assignData/assignOuter, ActionExecutor.setFeature and StateExecutor.assignAttribute; runtime/invoke_calc.go bindCalcParameters and runtime/calc_statements.go calcStmtHost.acceptReturn and runtime/calc_usage.go calcRun.value (an output a declaration binds rather than a body assigns, and a result a bind result = ... binds, which calcShape.designatedOutput keeps the declared result's declaration for) through calcMemberDecl.check (the parameter's effective declaration: calcMemberDecl.redeclaring keeps the type and multiplicity a redeclaration states none of from the parameter, output or result it redeclares, per KerML 1.0 §7.3.4.5; the compiled calc tier's runtime/compile.go scalarCheck.accepts decides the same declaration on the lattice, least being the Positive bound, and hands every value it declines to calcMemberDecl.check) and Context.writeCountRefusal (runtime/write_conformance.go, shared with checkWrite and checkDefault; elementCount in runtime/collections.go counts a scalar, a Complex included, without materializing a sequence); runtime/state_statements.go stateStmtHost.assignStateAttribute for an attribute held in a state's own frame passes/typecheck_assign_test.go (state entry/do bodies, action bodies, calc bodies, transition effects, numeric widening, silence on an unknown type); passes/typecheck_expr_test.go:TestExprQuotientDoesNotBindToWholeNumberFeature (Integer = 4 / 2 refused, Rational = 7 / 2 and Integer = RationalFunctions::ToInteger(4 / 2) accepted), :TestExprInvocationArgumentNarrowerThanExpression (add(7 / 2, 1) refused, add(RationalFunctions::ToInteger(7 / 2), w) accepted), :TestExprBindWiderTypedReferenceToNarrowerFeatureOK, :TestExprBindDecimalLiteralToNaturalRejected, :TestExprBindStringToIntegerAttribute; runtime/value_conformance_test.go:TestDefaultValueMustConformAtMaterialization (whole : Integer = 4 / 2, nat : Natural = 4 / 2, fromReal : Integer = two and computed : Integer = seven - two are ErrTypeMismatch; wholeRational : Rational = 4 / 2 holds 2.0 and wholeConverted : Integer = ToInteger(4 / 2) holds 2), :TestRestatedDefaultConformsByItsOwnValue, :TestDeclaredValueReadChecksItsType, :TestUserDeclaredScalarTypeDefersToTheBinding, :TestCalcParameterAndResultMustConform (Half(4 / 2) and Half(two) refused, Half(ToInteger(4 / 2)) accepted), runtime/compile_test.go:TestCompiledCalcErrorParity (Natural1(-1), Positive1(0) and a Positive or Rank :> Positive result of 0 are the interpreter's ErrTypeMismatch on the compiled tier too), runtime/sweep_test.go (a sweep range over a Natural/Positive parameter admits what the parameter holds, in the interpreter and compiled alike), conformance/value_classification_shared_rule (e : Even = 4, q : Rational = 6 / 3, none : Integer[0..1]), a feature typed by an enumeration admits exactly the enumerated values (SysML v2 §8.3.7, the classification row) — held : Level = three for three : Integer = 3 is held as Level::high (checkWriteType → holdAsEnumerated, so held hastype Level is true) and = two is the write-conformance ErrTypeMismatch, and a calc parameter, output or result declared by an enumeration holds its argument the same way (calcMemberDecl.check → holdForDeclared; runtime/compile.go scalarCheckFor declines an enumeration-typed declaration, so the compiled tier hands it to the same checker), so asLevel(3) hastype Level is true for calc def asLevel { in n : Integer; return : Level = n; }; where the value is constant the checker refuses it at its spelling (passes/typecheck_value.go checkEnumeratedValue: Level = 2 is cannot bind 2 (an Integer) to a feature typed by Level, whose values are Level::low = 1, Level::high = 3, while a value of another kind stays the scalar lattice's one diagnostic) — classify_test.go:TestEnumerationTypedFeatureAdmitsOnlyEnumeratedValues, robustness_test.go:enumeration_typed_feature_holding_an_unenumerated_value, passes/typecheck_value_test.go:TestValueScalarConstantToScalarValuedEnumeration, :TestCalcParameterAndResultMultiplicity, :TestCalcMultiplicityViolationNamesTheBinding, :TestComplexCountsAsOneValue, conformance calc_body_local_declared_type (BodyOnly(3) is 3 with l held as Level::high, BodyOnly(2) is the write's ErrTypeMismatch, attribute xs : Integer[2] = (n, n + 1, n + 2) ErrMultiplicityViolation, attribute xs : Integer[*] = (n, n + 1, n) ErrUniquenessViolation, an untyped local and one stating no multiplicity hold what they are given, a local in an if block and in a ->collect body is judged the same), action_body_local_declared_type, constraint_body_local_declared_type, constraint_body_local_count (a condition reads its body's declaration through eval.go conformBodyDeclared, a stated multiplicity judging the count, determined or open, and an omitted one keeping it — undetermined_test.go:TestBodyLocalKeepsOpenInitializerCount), constraint_param_scalar_count (the unchanged constraint-parameter case), action_param_default_scalar_count.sysml (now explicitly [1], preserving its two-value ErrMultiplicityViolation expectation), robustness_performed_action_inputs_test.go:testNestedActionParameterMultiplicity (bare nested action parameters admit no input, while explicit [1] inner and outer parameters preserve their typed errors), constraint_body_local_redefined_type and constraint_body_local_redefined_count (a local inheriting its initializer from the declaration it redefines is held to the redefinition, shape.go statedMultiplicity walking the same redefinition, positional-parameter and abstract-subsetting chain as the effective multiplicity — undetermined_test.go:TestBodyLocalIntersectsRedefinedMultiplicities, TestBodyLocalInheritsImplicitParameterMultiplicity), constraint_body_local_redefined_unique and action_body_local_redefined_count (the inherited bound also gates uniqueness and the lowered checkBodyDeclaration path), robustness_test.go:body_local_outside_its_declaration, repl/compile_test.go:TestCompiledCalcsAgreeWithInterpreter (PosLoc, Seq::LocM1, Seq::LocN, Seq::LocAny2: the native tier declares the local through codegen/compile.go compileDeclare → bind with the declared range and stated multiplicity, codegen/ir.go Declare.Range, and declines an enumeration-typed local and one redefining or subsetting another feature — TestCompileRefusesWhatItCannotCompile SubsetLocal — not compilable, rather than answer differently), pilot-exec-diff body_local_conformance (the pilot binds a body-local value unchecked, so the accepted case agrees and the refused ones are ours-error against its answer), invoke_calc_body_test.go:TestCalcForLoopOverParameterSequence (in xs[*];), conformance/calc_null_coalesce.sysml (in n : Integer[0..1];), conformance/calc_library_feature_imaginary_unit.sysml (return : Complex = i;), pilot-exec-diff intdiv (deliberately ours-error, now refused by the checker: the reference answers 3.5 for return : Integer = 7 / 2, checking nothing against the parameter's type) and rational-feature-hastype-integer (rat : Rational = 4 holds the Integer 4); conformance/assign_write_conforms_to_target_type.sysml, assign_write_violates_target_type.sysml, assign_write_through_chain_violates_target_type.sysml; robustness_test.go:write_of_a_wrong_typed_value_leaves_the_feature, :state_entry_write_of_a_wrong_typed_value, :chained_write_of_a_wrong_typed_value, :calc_output_write_of_a_wrong_typed_value, :action_local_write_of_a_wrong_typed_value, :action_output_write_of_a_wrong_typed_value, :performer_feature_write_of_a_wrong_typed_value, :performance_occurrence_write_of_a_wrong_typed_value, :calc_output_binding_violates_declared_type; conformance/assign_write_to_state_attribute_violates_target_type.sysml ⚠️ Approximate (self-assessed: the pinned pilot implementation executes no action or state machine, so it cannot referee this. A quantity value carries its unit rather than the quantity value type measured in it, so the specialization relation between two quantity value types is not itself decided; the quantity kind such a target declares is judged dimensionally, by the row below. A model declaring its own scalar types gives the run time no value semantics for them, so there a value is refused only for a disjoint type, the binding rule's either-direction conformance standing in for a judgement by value. A Complex is a Complex on the real axis or off it — rect(2.0, 0.0) is refused by a Real or Integer feature, re of it is the Real — since ComplexFunctions return Complex and the specification fixes no finer classification of such a value. The fixtures that relied on a whole real or a quotient being held by an Integer feature were re-adjudicated by this rule rather than relaxed: an Integer or Natural feature or parameter a quotient or a whole real reaches is declared Rational or Real where the model computes such a value (repl/testdata/compile_calcs.sysml, repl/choice_test.go, grpc/explore_test.go, lsp/library_invocation_test.go, passes/typecheck_value_test.go, runtime/classify_test.go), converts with ToInteger where it means the integer (runtime/value_conformance_test.go, passes/typecheck_expr_test.go), or pins the typed error where the write is what the fixture probes (runtime/value_conformance_test.go, robustness_test.go coordinate-frame failure modes: a number written where a ScalarMeasurementReference or a TranslationOrRotation is declared is the write's ErrTypeMismatch), each by §8.3.3.3.4 and §9.4.11.1 above)
A constant is a value of the scalar type its representation states and of that type's supertypes, whatever number it holds: an integer is an Integer (and so a Rational, Real, Complex, Number) whether written 2 or computed as ToInteger(4 / 2); a finite real is a Rational — 2.0, 4 / 2 (which IntegerFunctions::'/' declares Rational, KerML 1.0 §9.4.11.1) and 3.5 alike, so none is held by an Integer feature and nothing is truncated to become one (§8.4.4.9.2: a LiteralRational is classified in Rational); an infinity is a Real; a Complex is a Complex on the real axis or off it; a NaN, real or complex, states no type at all and so is of none — istype, hastype, @ and as answer nothing for it, a Real or Rational parameter refuses it as ErrTypeMismatch on both calc tiers and its direct type is ErrUndeterminedValueType, never Rational — while * is the Positive exceeding every bound (§8.4.4.6) and is placed on the lattice as one: a subtype a model declares of Positive (or of any type above it) holds it through a declared feature and leaves a bare cast undecided, exactly as Even :> Integer treats 4, while String, Boolean and their subtypes exclude it; a sequence index is still the position a whole value names (Value.WholeNumber), which is arithmetic, not classification runtime/classification.go representationPrim, representationClassifies, scalarLibraryType, isNaN; runtime/eval.go directValueType; semantics/eval.go Value.WholeNumber (a finite real with no fractional part within the Integer range, so NaN, an infinity and 1e19 are no whole numbers); runtime/write_conformance.go valueConforms over classifyValue semantics/eval_test.go:TestWholeNumberRefusesFractionsAndNonFinite; runtime/value_conformance_test.go, :TestNaNIsOfNoScalarType, runtime/compile_test.go:TestCompiledCalcErrorParity (SameReal(NaN), SameRational(NaN)), runtime/infinity_test.go:TestInfinityDirectType, :TestUnboundedAgainstDeclaredSubtype, :TestComplexCountsAsOneValue, runtime/complex_test.go; runtime/eval_operator_test.go:TestTypeClassificationOperators (a model's own Integer/Natural keep their declared relation); conformance value_classification_shared_rule; pilot-exec-diff scalar_classification.cases ✅ Faithful
A bound or written quantity is measured in the dimension its target's declared quantity value type fixes: ScalarQuantityValue declares attribute :>> mRef: ScalarMeasurementReference, ScalarMeasurementReference declares attribute quantityDimension: QuantityDimension[1], and a quantity kind narrows that reference to one unit definition (DurationValue :>> mRef: DurationUnit[1], whose durationPF states quantity = isq.T, exponent = 1), so the type states the dimension its values are measured in and a unit of another dimension is not one of them. Scale is not part of a dimension, so 5 [min] and 250 [ms] conform where 5 [s] does, and a composed unit is judged by the dimension it reduces to (10 [m] / 2 [s] conforms to SpeedValue, not to DurationValue) semantics/dimension.go Model.DimensionOfType (beside DimensionOfFeature, sharing quantityValueTypeIn), compared with Model.DimensionOfUnit by UnitTerm.Commensurable; passes/typecheck_dimension.go exprChecker.checkValueDimension, reached from passes/typecheck_expr.go exprChecker.checkBoundValue and so from passes/typecheck_assign.go assignWalker; runtime/write_conformance.go Context.quantityConforms, reached from Context.valueConforms so every write path inherits it semantics/dimension_type_test.go:TestDimensionOfTypeFixesTheDeclaredKind, :TestDimensionOfTypeThroughAnAlias, :TestDimensionOfTypeMatchesTheFeatureItTypes; passes/typecheck_dimension_test.go:TestBoundQuantityOfAnotherDimension, :TestBoundQuantityOfTheSameDimensionAtAnotherScale, :TestBoundQuantityComposedToItsDimension, :TestAssignedQuantityOfAnotherDimension, :TestBoundQuantityThroughAnAlias, :TestBoundDimensionlessQuantity; conformance/action_quantity_dimension_scaled.sysml, action_quantity_dimension_inferred_mismatch.sysml, instance_quantity_dimension_mismatch.sysml; robustness_test.go:quantity_write_of_another_dimension ⚠️ Approximate (self-assessed: the pinned pilot implementation executes no action or state machine, so it cannot referee this. the rule is stated as the existing type-mismatch error with a dimension-specific message rather than a new diagnostic kind, so -validate, the REPL and the LSP report it identically)
Nothing a declaration does not determine is judged dimensionally: a target typed by a scalar (Real), an untyped target, and ScalarQuantityValue itself — whose mRef is any ScalarMeasurementReference — fix no dimension, and a unit the libraries determine none for is not judged either semantics/dimension.go Model.DimensionOfType (quantityValueTypeIn skips ScalarQuantityValue), Model.DimensionOfUnit (unknown where a factor's dimension is unknown); passes/typecheck_dimension.go exprChecker.checkValueDimension; runtime/write_conformance.go Context.quantityConforms (a scalar target keeps the primitive judgement) semantics/dimension_type_test.go:TestDimensionOfTypeIsSilentWhereNothingIsFixed; passes/typecheck_dimension_test.go:TestBoundQuantityWhereTheTargetFixesNoDimension, :TestBoundBareNumberStatesNoDimension; robustness_test.go:quantity_write_of_another_dimension (a unit the target fixes no dimension against) ✅ Faithful
The collection a write stores satisfies the target's multiplicity, by the rule an initial value's element count answers to, and an empty write or null is decided by the lower bound rather than waved through passes/typecheck_value.go exprChecker.checkValueCount (reached for an assignment by assignWalker); runtime/write_conformance.go Context.checkWrite (semantics.Range.CountViolation, ErrMultiplicityViolation), before any value is stored passes/typecheck_assign_test.go:TestAssignMustSatisfyMultiplicity; conformance/assign_write_violates_target_multiplicity.sysml, assign_write_of_no_value_where_one_is_required.sysml, assign_write_to_state_attribute_violates_target_multiplicity.sysml; robustness_test.go:write_of_too_many_values_leaves_the_feature, :write_of_no_value_where_one_is_required ✅ Faithful
A rejected write leaves the feature as it was: the value is judged before it is stored, so a feature never holds a value it was reported for runtime/instance.go Instance.SetFeatureValue (checks precede the mutation); runtime/write_conformance.go storeBodyValue robustness_test.go:write_of_a_wrong_typed_value_leaves_the_feature, :write_of_too_many_values_leaves_the_feature, :write_of_no_value_where_one_is_required ✅ Faithful
Where a written value's type is not statically known — a chain read, an unbound parameter, a solver result — the type pass stays silent and the run time is the enforcer. The split is the same for a body-local declaration: the pass decides what the expression's spelling settles — a disjoint scalar type (attribute s : String = n;), a literal or quotient bound to a narrower scalar type, a constant that is no enumerated value (Level = 2), an element count or repeat a literal collection fixes — and the run time decides what depends on the value bound — an Integer parameter to an enumeration or bounded scalar type (Level = n, Positive = n - 3), a collection whose count or repeats the arguments fix (Integer[2] = (n, n + 1, n + 2), Integer[*] = (n, n + 1, n)) — so no value is judged by both, and a value one of them refuses is refused by the other wherever it could decide it passes/typecheck_expr.go exprChecker.checkBoundValue (silent on semantics.PrimUnknown; checkValueConformance, checkValueCount, checkValueUniqueness over constant elements only), passes/typecheck_assign.go assignWalker (the same rule over a body's declarations); runtime/write_conformance.go Context.checkWriteType, Context.checkBodyDeclaration passes/typecheck_assign_test.go:TestAssignOfAnUnknownTypeStaysSilent, :TestBodyLocalDeclarationSplitsWithTheRunTime (the literal count and the constant no enumerated value are the checker's, the parameter-derived ones silent); conformance calc_body_local_declared_type (analyses clean, refused at run time); conformance/assign_write_violates_target_type.sysml (analyses clean, fails at run time) ✅ Faithful
An assignment target may be a feature chain: assign a.b := v writes feature b of the object the chain a reaches, the chain resolved at execution time in the scope the statement was written in, to any depth and through a part, a port or an inherited feature. Actions::AssignmentAction :> FeatureWritePerformance, Action "updates the accessedFeature of its target Occurrence with the given replacementValues", and FeatureReferencingPerformances::FeatureWritePerformance "assigns the values of a feature on an occurrence" — the occurrence written is the one the chain reaches, not the performer. The write goes through that object, so it is multiplicity-checked as a direct write is, is visible to later reads and guards of the same run, and is seen through every feature holding that one occurrence lower/action_graph.go assignTarget/flattenChain (Assign.Chain, an AssignTarget carrying base, steps and text, as Send carries TargetPath); runtime/statements.go stmtEngine.execute; runtime/assign_chain.go assignThroughChain/chainCarrier/chainRoot/chainObject (reusing EvalContext.chainMemberValue, the traversal a chain read walks); runtime/action_statements.go and runtime/state_statements.go assignChain conformance/assign_chain_depth_two.sysml + .trace.golden (write then read back), assign_chain_depth_three.sysml, assign_chain_through_port.sysml, assign_chain_inherited_feature.sysml, assign_chain_aliased_object.sysml, assign_chain_state_entry_guard_reads.sysml, assign_chain_state_do_and_exit.sysml, assign_chain_transition_effect.sysml; parse/assignment_chain_target.golden; lower/assign_target_test.go; robustness_test.go:assign_chain_* ⚠️ Approximate (the write itself is faithful; a read-only or derived target is not refused, because no write path in the runtime enforces one — a chained write reaches Instance.SetFeatureValue, the same primitive a direct write uses, so the two behave alike, and enforcing constant/derived is a separate rule for both)
A calculation writes no feature of another object, so a chained target in a calc body is rejected rather than performed runtime/calc_statements.go calcStmtHost.assignChain (ErrCalcExternalAssignment); diagnosed before execution by passes/w8d_assignment_referent.go checkChain conformance/assign_chain_calc_body_rejected.sysml, robustness_test.go:assign_chain_rejected_in_calc_body, passes/w8d_assignment_chain_test.go:TestAssignmentChainInCalcBodyIsReported ✅ Faithful
Static analysis agrees with the runtime on chained assignment targets as far as a declaration determines: a final segment naming no feature of the type the chain reaches is an unresolved member, a chained target in a calculation body and a step whose declared multiplicity admits more than one object are constraint errors. What a declaration does not determine stays a run-time report, and each is typed: a step holding no value (ErrUninitializedFeatureValue), a step that is not an object or holds several at run time (ErrTypeMismatch), a base the body cannot reach (ErrUnresolvedReference), a final segment the reached object does not hold (ErrNoSuchFeature), and a value the target's multiplicity refuses (ErrMultiplicityViolation) passes/w8d_assignment_referent.go assignmentReferentChecker.checkChain/chainSteps (assignment-chain-in-calc, assignment-chain-step-not-one-object); runtime/assign_chain.go for the run-time reports passes/w8d_assignment_chain_test.go (all four, including the model that used to analyse clean and fail at run time), robustness_test.go:assign_chain_* ⚠️ Approximate (honest split: a chain is written on the object reached at execution time, so whether an intermediate holds an object is not decidable from the declarations, and no pass claims it is)
Conditional statement (if <cond> { … } else { … }) lower/action_graph.go lowerStatement/lowerBlock (If); runtime/action_statements.go execIf, execBlock action_if_else_then_branch.sysml, action_if_else_else_branch.sysml, action_if_no_else.sysml, action_nested_loop_if.sysml + trace golden, lower/action_body_test.go:TestActionBodyLoopAndConditionalLowering, passes/typecheck_test.go:TestTypeCheckNonBooleanControlFlowConditions ✅ Faithful (the condition is evaluated outside both branches; each branch body is a namespace of its own, so the names it declares do not reach the enclosing behavior)
Pre-condition loop (while <cond> { … }) lower/action_graph.go lowerStatement (Loop, ast.LoopWhile); runtime/action_statements.go execLoop action_while_loop.sysml + trace golden, action_while_loop_zero_iterations.sysml, parse/action_loop_forms.golden ✅ Faithful (tested before every iteration, so the body may run no times)
Post-condition loop (loop { … } until <cond>;) parser/behavior.go parseLoopAction; lower/action_graph.go (ast.LoopUntil); runtime/action_statements.go execLoop action_loop_until.sysml, action_loop_until_repeats.sysml + trace golden ✅ Faithful (tested after every iteration, so the body runs at least once)
Iteration over a collection (for <x> in <collection> { … }) ast/behavior.go WhileLoopActionNode.Variable/Collection; symbols/builder.go (the variable is a member of the loop's own scope); runtime/statements.go forLoop, forElements action_for_loop.sysml, action_for_over_produced_collections.sysml + trace golden, parse/action_loop_forms.golden, action_for_over_a_part_collection.sysml + trace golden, statements_test.go:TestForElementsOrder, robustness_test.go:for_over_a_value_no_expression_makes_iterable ✅ Faithful (the collection is evaluated once, before the loop is entered. Every collection the expression layer produces is iterated: a sequence in the order the expression built it — a literal sequence as written, a range ascending and a descending range empty (range.go rangeSequence), a filter in the order of the collection it filtered, a collection-valued function's result as returned — a set in its canonical order (set_order.go), since a set has no order of its own, and null, which holds no element, not at all. A for input that is not a collection reports ErrTypeMismatch naming what it was given)
A for input that is not a collection is reported, where a general collection reader coerces one runtime/statements.go forElements (ErrTypeMismatch), deliberately stricter than runtime/collections.go elementsOf, which keeps reading a scalar as the one-element collection KerML makes of it — a for over a scalar is a modelling error, and a single silent iteration hides it, while the general readers (a collection operator's argument, a multiplicity check) legitimately coerce action_for_over_a_scalar.sysml (typed error), action_for_over_a_part_collection.sysml (a valid collection input, nested in a part) + trace golden, statements_test.go:TestForElementsRejectsANonCollection, :TestElementsOfStillCoercesAScalar, robustness_test.go:for_over_a_scalar ✅ Faithful (ruling: for requires a collection; elementsOf is unchanged for its other callers)
A non-terminating loop ends the execution rather than hanging it runtime/action_statements.go execLoop (a step per iteration), context.go incrementStep action_loop_step_budget.sysml, robustness_test.go:non_terminating_loop_exhausts_step_budget ✅ Faithful (reports ErrStepLimitExceeded, the same failure as any other runaway evaluation)
A legitimately long loop runs under a raised budget budget.go BudgetsFromEnv (OPENSYSML_MAX_STEPS) resolved at the REPL/CLI and gRPC entry points budget_test.go:TestRaisedBudgetRunsLongerLoop ✅ Faithful (a 10 000-iteration loop that exhausts a 100 000-step budget completes under the default)
The budget bounds one run, not a session: each run has a state of its own — the steps and elements it spent, what it noted (Notes()), its scheduler and the calc usage evaluations of its open activations — begun fresh at a top-level run and shared by a run nested in it; a run a caller drives call by call — a paused %action/%state debugger — resumes its own state on each call, so a run driven to completion, an evaluation or another driven run in between neither resets its budget nor mixes its notes, scheduler or calc memo into it, and Notes() right after a call reports that run's context.go runState, beginRun/beginExecutorRun (both through enterRun), endExecutorRun (a release ends the run's own activations, the installed run kept), beginProbe; choice.go ActionExecutor.Notes/StateExecutor.Notes (a debugger reads the executor's own) budget_test.go:TestStepBudgetIsPerRun, :TestStepBudgetHoldsAcrossExecutorDrivenRun, :TestStepBudgetIsPerRunForInstancesAndCalcs; run_state_test.go:TestDrivenRunResumesItsOwnStateAcrossAWholeRun, :TestDrivenRunResumesItsOwnStateAcrossAnEvaluation, :TestInterleavedDrivenRunsKeepTheirOwnState, :TestStepBudgetIsTheDrivenRunsOwn, :TestNestedRunSharesTheEnclosingState, :TestPausedActivationKeepsItsCalcUsageEvaluations, :TestReleaseEndsThePausedRunsActivations, :TestReleaseInsideAnotherRunEndsThePausedRunsOwn; repl/run_state_test.go:TestStepAfterEvalKeepsTheActionsOwnBudget ✅ Faithful
A legitimately long action or simulation runs under raised sibling budgets budget.go Budgets (OPENSYSML_MAX_ACTION_STEPS, OPENSYSML_MAX_EVENTS, OPENSYSML_MAX_DO_STEPS), read by action_executor.go and state_executor.go from the context budget_test.go:TestActionStepBudgetIsConfigurable, budget_test.go:TestStateBudgetsAreConfigurable ✅ Faithful (each bound counts its own unit and its error names the variable that raises it)
A member-attached then sequences the members either side of it (action a; then action b;) parser/succession.go (desugared at parse time to the same edge represented by succession first a then b;), lowered by lower/action_graph.go and lower/state_graph.go like any other edge conformance/action_member_then_order.sysml + trace golden (declaration order is the reverse of the execution order), conformance/state_member_then_order.sysml (the same for a state's completion transitions), parser/succession_test.go:TestMemberAttachedThenDesugars, parse/succession_member_then.golden ✅ Faithful (an end with no name to give is bound by position: SuccessionEdge.SourceMember/TargetMember refer to the member itself, so then send Show(x) to screen;, a then after an anonymous member and then loop action { … } all sequence what they are written beside. A then before a member the notation does not admit one in front of, such as an attribute or a definition, is a syntax error)
The source a positional then sequences from is the nearest feature before it that is not a connector or a transition, read past every member that is not a feature (action a; doc /* */ then action b;, action a; part def Inner; then action b;, action a; private import P::*; then action b; and action a; alias X for Y; then action b;, action a; multiplicity m [1]; then action b; and state a; defer Ping; then state b; all sequence from a; action a; connect p to q; then action b; from a; action a; attribute k; then action b; from k; a then with no feature before it — then action b; or a one-name then b;, if g then b;, else b; — is diagnosed, as the pilot grammar admits a target succession only after a node member) — the pilot implementation's UsageUtil.getPreviousFeature rule. Skipping the non-feature members is the literal reading of SysML v2 §7.17.4, which describes the source as the nearest occurrence lexically previous to the then, skipping non-occurrence usages; the connector part follows the pilot where that text is underdetermined, and §8.3.13.6 SuccessionAsUsage states no constraint (OMG issue SYSML21-171) ast/succession.go IsSuccessionSource over ast/defusage.go UsageKind.IsEdge (shared by parser/succession.go bodyBuilder.add and export/rdf_out.go encodeMembers; export/behavior.go isSuccessionSource reads the same rule off a metaclass) parse/succession_then_past_non_features.golden, parse/succession_then_past_connectors.golden, parse/succession_member_then.golden, parser/succession_test.go:TestThenSequencesFromTheNearestFeatureBefore, :TestThenSequencesPastADeferral, :TestThenWithNoFeatureBeforeItIsDiagnosed, :TestOneNameEdgeWithNoFeatureBeforeItIsDiagnosed, conformance/action_then_skips_non_feature_members.sysml + trace golden, conformance/state_then_skips_non_feature_members.sysml, conformance/action_then_after_allocate.sysml, export/behavior_test.go:TestThenComesBackPastTheMembersTheParserSkips, :TestThenIsRefusedWhenTheGraphSequencesFromAnotherMember, :TestThenIsRefusedWhenTheGraphSequencesFromANonFeature, :TestThenIsRefusedWhenTheGraphSequencesFromADeferral ⚠️ Approximate (the pilot keeps a flow or message written with no ends — message m; — as the source, and resolves an alias of a feature to that feature; this implementation reads past both, the alias as §7.17.4 reads)
A succession end with no name is carried by identity, not by name ast/behavior.go SuccessionEdge.SourceMember/TargetMember, ControlFlowEdge.SourceMember/TargetMember; parser/succession.go bindPositionalSource/bodyBuilder.add; lower/action_graph.go (the member is the graph node the edge reaches) parser/succession_test.go:TestSuccessionBindsUnnamedEndsByPosition, :TestPositionalSuccessionEndIsTheMemberItself, conformance/action_standard_loop_until_then_done.sysml ⚠️ Approximate (the RDF mapping names an edge's ends by qualified name, so exporting a model whose succession has a positional end is reported as unsupported rather than written back — see docs/reference/rdf-mapping.md)
Action-body statement items recursively author and lower: accept, send, assign, if/else, while, loop, for and terminate (SysML.xtext:1607 ActionBodyParameter, 1442 AcceptNode, 1499 SendNode, 1535 AssignmentNode, 1596 IfNode, 1615 WhileLoopNode, 1624 ForLoopNode, 1641 TerminateNode; formal/2026-03-02) parser/behavior.go; check/edit/sequence.go; lower/action_graph.go; runtime/statements.go parse/action_body_statement_authoring.golden, edit/sequence_test.go:TestAddActionBodyStatements, lower/sequence_authoring_test.go:TestActionBodyStatementsLowerRecursively, conformance/action_body_statement_authoring.sysml + trace golden, runtime/action_body_authoring_test.go:TestActionBodyStatementsMatchWrittenNotationAndExecution, robustness_action_body_statements_test.go:TestRuntimeRobustnessActionBodyStatements ✅ Faithful (recursive bodies lower to the same statement IR; authored and directly written notation, trace and outcome are compared for executable forms; nested accepts are reported unsupported)
Named flow with explicit ends (flow f from a.out to b.in;, SysML.xtext FlowUsage → PayloadFeatureSpecializationPart + FlowEndMember) parser/defusage.go parseFlowEnds/parseFlowTo (the name, from and feature-chain ends); ast/defusage.go FlowEnds; lower/action_graph.go lowerFlow/flowEnd (ObjectFlow, the flow's name included); runtime/action_executor.go applyDataFlows parse/behavior_flow_named_from.golden, parse/flow_payload_declaration.golden, negative_test.go:flow_from_without_to, :flow_named_from_no_source, conformance/action_flow_named_from.sysml, robustness_test.go:flow_end_naming_no_node, :flow_from_a_node_that_produced_nothing ✅ Faithful (both ends, feature chains included, name a node and its pin, and the value at the source's pin is what the target reads; flow of x from a to b names the pin at both ends. An end naming something that is not a node of the action, and a source pin the node left empty, are reported rather than dropped)
Accept with a trigger expression in an action body (accept when <cond>, accept at <instant>, accept after <duration>; SysML.xtext TriggerValuePart) parser/behavior.go parsePayloadParameter/parseTriggerExpression; lower/action_graph.go Accept.Trigger; runtime/action_executor.go triggerHolds (a change trigger is tested each step), awaitClock, dueNow, timeWaits, parked (a time trigger registers the token's due instant on the context's clock through clock.go Context.dueInstant — after counted from the moment the accept is reached, at taken as read, both converted to the clock's SI::s by timeMagnitude and refused by judgeTimeTriggerType as a transition's are — and parks the token until the clock reaches it; a run driving itself, RunToCompletion, moves the clock to the earliest wait when nothing else can move, and a run driven call by call reports a token waiting only on time as TimeWaits and NextWait rather than as a deadlock) parse/behavior_accept_trigger.golden, conformance/action_accept_when_trigger.sysml, conformance/clock_action_accept_after.sysml + .expected.json + trace golden, conformance/clock_action_accept_at.sysml + .expected.json + trace golden, negative_test.go:accept_when_no_condition, :accept_at_no_instant, robustness_test.go:action_accept_time_waits (after and at fire and leave no wait behind; at an instant already past fires at once; a negative after is ErrNegativeDuration; a mass after after is ErrTimeTriggerType), :action_accept_non_boolean_change_trigger; grpc/runtime_test.go:TestExecuteAction_TimedAcceptRunsTheClock; repl/runtime_commands_test.go:TestAdvanceMovesActionAndStateDebuggersTogether; cmd/sysml/run_test.go:TestAdvanceRunsActionsAndStatesOnOneClock ✅ Faithful (at an instant the clock has passed fires at once: a TimeEvent occurs when the clock reads its instant, and a clock that already reads past it has nothing left to wait for — Kernel Semantic Library Clocks.kerml Clock::currentTime "advances monotonically"; a change trigger is tested each step and suspends the token until it holds)
Accept subsetting a declared event (action interrupt accept :> shutDown;, SysML.xtext PayloadParameter → PayloadFeatureSpecializationPart) parser/behavior.go parsePayloadParameter; lower/action_graph.go subsettingTarget → Accept.SubsetsEvent; runtime/action_executor.go (the subsetted event names the message the accept takes) parse/behavior_accept_subsets.golden, conformance/action_accept_subsets_event.sysml, negative_test.go:accept_subsets_no_event ✅ Faithful (a send shutDown to interrupt of the event feature is taken by the accept subsetting shutDown, as a typed accept takes a message of its type: runtime/signal.go records the feature a send reads its message from in Message.Event)
Send of a constructed signal or an invoked behavioral feature (send new Data(reading) via commPort;, send new Telemetry(frames = 3.0) via antenna;, then send fullyCharged() to self;) parser/behavior.go parseSendStatement; parser/expr.go parseConstructor (positional and named arguments, ast.ConstructorExpr.NamedArgs); runtime/signal.go buildMessage/buildConstructedMessage/buildInvokedMessage/buildTypedMessage/invokesCalc parse/behavior_send_via.golden, parse/behavior_send_new.golden, conformance/action_send_invocation_via_port.sysml, conformance/accept_binds_signal_occurrence.sysml, conformance/send_via_owner_nested_port_path.sysml, export/testdata/convert/send_new.*, negative_test.go:send_via_no_port, :send_no_target ✅ Faithful (new Def(args) constructs the message: the definition types it and its positional or named arguments bind its features, so an accept binds an occurrence of the definition — send new Data(7) is accepted as a Data whose value is 7, never as the 7. Def(args) on an item or attribute definition is an invocation of a non-behavior and is reported by invocation-not-behavior, as the reference reports it; an invocation of a behavioral feature sends the message that feature names, and an invocation of a calc is evaluated as an expression)
Succession to a loop node, and a loop's until condition (then loop action { … } until battery >= 100;, SysML.xtext WhileLoopNode) parser/behavior.go parseLoopAction/parseWhileLoopAction; ast/behavior.go WhileLoopActionNode.Until; lower/action_graph.go (loop body and Until lowered); runtime/statements.go iteration parse/behavior_loop_until_succession.golden, conformance/action_standard_loop_until_then_done.sysml, negative_test.go:loop_until_no_condition, :loop_until_no_semicolon, passes/typecheck.go checkBehaviorMember (the until condition is checked Boolean) ✅ Faithful (loop { … } until c tests after the iteration; while c action { … } until u tests c before and u after)
A loop or branch body written as an action body parameter (loop action [<name>] { … }, for x in c action { … }, SysML.xtext ActionBodyParameter) is the body itself, named or not parser/behavior.go parseActionBodyParameter (marks ast.Usage.IsBodyParameter); lower/action_graph.go lowerStatement (lowered to the block the usage's scope owns); runtime/statements.go block lower/action_body_test.go:TestActionBodyParameterLowersToItsBlock, conformance/action_named_loop_body_parameter.sysml ✅ Faithful (a name only scopes the members it declares — loop action charging { … } until charging.done — so the body runs either way; an empty body parameter runs as an empty body)
then done; — a final node as a successor target parser/behavior.go parseFinalNode (reached by a succession); lower/action_graph.go Finals; runtime/action_executor.go stepFinalNode parse/behavior_loop_until_succession.golden, conformance/action_standard_loop_until_then_done.sysml, negative_test.go:then_done_no_semicolon ✅ Faithful (the token reaching it ends the flow, as for a declared done;)
A flow ending at a node with no outgoing succession (action a; action b; first a then b;) lower/action_graph.go Edges (the node has no outgoing edge); runtime/action_executor.go retireToken, called from stepActionExecutionNode, stepNestedAction, stepStatementNode conformance/action_last_node_without_a_succession.sysml + trace golden, conformance/action_flow_ending_at_a_statement.sysml, action_executor_test.go:TestActionExecutor_NodeWithoutSuccessorsRetiresItsToken, robustness_test.go:action_whose_last_node_has_no_succession ✅ Faithful (Actions::Action gives every action a done snapshot it inherits, so a flow that runs out of successions ends there and the action completes with what its features hold — an explicit done; is one way to write that end, not a requirement. A node reached by a fork retires its own branch; the action completes when the last token does)
A two-ended first a then b; in an action body — with or without a body, any number of times, beside first start;, then chains, succession first a then b;, forks, joins, decisions and merges — is the succession a → b (SysML.xtext ActionBodyItem → InitialNodeMember TargetSuccessionMember, SuccessionAsUsage): it declares no node and marks no start. The one-ended first a; naming a node the body declares starts the flow at that node; first start; (naming nothing declared) is the body's initial node; a body without either starts at its one node no succession leads to lower/action_nodes.go collectActionNodes (only a one-ended first is collected as the initial node; two of them are multiple initial nodes), lower/action_graph.go lowerSuccession (the edge a two-ended first and a succession both lower to), FirstEndReference, resolveFirstNode (the node a one-ended first names becomes Initial); passes/action_endpoint.go (both ends of a two-ended first are checked); semantics/action_succession.go (a two-ended first is an ActionSuccession from the node it names) lower/action_first_node_test.go:TestToActionGraph_FirstThenIsASuccession, :TestToActionGraph_FirstThenBesideFirstStart, :TestToActionGraph_FirstThenWithABody, :TestToActionGraph_FirstNamesADeclaredNode, :TestToActionGraph_FirstDeclaresItsOwnInitialNode, :TestToActionGraph_FirstNamesAFinalNode, conformance action_first_then_succession.sysml, action_first_then_fork_join.sysml (first start; beside a fork and four two-ended first ends, total = 111), action_first_names_a_declared_node.sysml + trace goldens, parse/behavior_first_then_declared_node.golden, robustness_test.go:first_node_with_a_second_succession, :first_beside_an_initial_node, :two_one_ended_firsts, :first_naming_a_final_node, runtime/pilot_first_then_test.go (the OMG 3a-Function-based Behavior-2 provide power, first start; and eight two-ended first ends, runs to its accept) ✅ Faithful (a body states one start, so two one-ended first ends are rejected, as is a one-ended first naming a final node — the flow cannot start where it ends. The body of a two-ended first is a succession's body and lowers only when it holds annotations, as succession first a then b { … } does)
A guard on a succession out of a node that is not a decision (first s1 if c then s2;, SysML.xtext GuardedSuccession) lower/action_graph.go (the guard is carried on the edge); runtime/action_executor.go enabledSuccessions (every guard out of the node is evaluated before a token leaves it) conformance/action_succession_guard_holds.sysml, action_succession_guard_fails.sysml, action_succession_guard_two_branches.sysml, action_succession_guard_fork_branch_pruned.sysml, action_succession_guard_not_boolean.sysml, action_succession_guard_two_hold.sysml, robustness_test.go succession_guard_failure_modes ✅ Faithful (a guard that does not hold prunes the succession, since TransitionPerformance::transitionLink is HappensBefore[0..1]; two consequences the notation leaves open are tool-defined and pinned in the goldens: a node whose every succession is pruned ends its flow, and two guards holding at once out of one node is reported rather than resolved — a fork prunes only the branches whose guard fails)
else branch of a decision in an action flow (if c then a; else b;, SysML.xtext DefaultTargetSuccession) parser/behavior.go (the else clause builds *ast.ControlFlowEdge{IsElse: true}); lower/action_graph.go (the else edge is the guardless alternative); runtime/action_executor.go stepDecisionNode parse/behavior_decision_else.golden, conformance/action_decision_else_branch.sysml, conformance/action_decision_guarded_branch.sysml, negative_test.go:decision_else_no_target ✅ Faithful (the else edge is taken when no guarded branch out of the decision holds)
Qualified succession at namespace level (first part1::action1 then requirement1;) parser/namespace.go (a succession is a namespace member), parser/succession.go parse/behavior_namespace_succession.golden, negative_test.go:namespace_succession_no_target, lower/namespace_succession_test.go:TestNamespaceSuccessionHasNoTokenFlowOrNeighborGraphAttachment ⚠️ Approximate (parsed and carried in the AST with both ends, and pinned as a gap rather than left unobserved: lowering is behavior-owned, a namespace has no token flow to lower such a succession into, and the reproducer asserts the edge is neither dropped into nor attached to a neighbouring behavior's graph — the root cause is that lower/ builds a graph per behavior body, so ordering two namespace members would need an owner the notation does not give)
A statement written directly among an action's own members is reported, not ignored lower/action_graph.go ToActionGraph first pass, statementKeyword robustness_test.go:statement_directly_in_an_action_body ✅ Faithful (a statement runs as part of an action node's body; written beside first/then it has no name a succession could reach, so the execution reports it instead of dropping it)
A body member that is not an executable statement is reported, not skipped lower/action_graph.go Unsupported; runtime/statements.go execute (lower.Unsupported) lower/action_body_test.go:TestActionBodyUnexecutableMemberIsLowered, lower/block_graph_test.go:TestBlockStatingItsOwnEdgeIsLoweredToAStatedFlow, robustness_test.go:loop_body_of_unexecutable_statement, :block_flow_of_unexecutable_member ✅ Faithful (a member of a loop or branch body outside what a block's flow executes — a part declaration fails the execution instead of producing a wrong answer silently; an edge written in a block now states a flow)
An action node that states a flow of its own runs that flow: its nodes are its subperformances (Kernel Semantic Library Performances.kerml — composite step subperformances: Performance[0..*] subsets enclosedPerformances, suboccurrences; Systems Library Actions.sysml — abstract action def Action :> Performance and action subactions: Action[0..*] :> actions, subperformances), so the node completes only when its own flow does and its outgoing succession fires only then lower/action_subflow.go lowerActionNode, statesOwnFlow (ActionGraph.Subflows); runtime/action_subflow.go enterSubflow, leaveSubflow, validateSubflows, actionFrame; runtime/action_executor.go stepNestedAction, retireToken, completeNode conformance/action_nested_flow_writes_value.sysml, action_nested_flow_two_levels.sysml + trace golden, action_nested_flow_in_fork_join.sysml + trace golden, action_nested_flow_guarded_inner_succession.sysml, action_nested_leaf_body_preserved.sysml; lower/action_subflow_test.go:TestActionNodeCarriesItsOwnFlow, :TestActionNodeSubflowsNest, :TestActionNodeWithoutFlowStaysALeaf, :TestActionNodeSubflowCarriesItsFailure; robustness_test.go:nested_flow_without_an_initial_node (a cycle, so no node is unpreceded), :nested_flow_with_a_dangling_succession (both ErrInvalidActionFlow at initialize()), :nested_flow_that_cannot_progress (ErrActionDeadlock), :nested_flow_that_never_ends (ErrActionStepLimitExceeded) ⚠️ Approximate, self-assessed (the pinned OMG pilot implementation executes no actions, so nothing external referees this. Nesting is unbounded and combines with fork/join, decision/merge and guards at either level; an inner flow is a performance of its own — the features its node declares live in that frame and are read as node.pin — while a name it does not declare resolves lexically to the enclosing action's feature, so what it writes there is visible to later nodes and to the performance occurrence where one applies. A node stating no flow keeps its leaf body. Not covered: a node that both states a flow of its own and is typed by an action definition stating one — the node's own flow is run and the definition's is not merged into it)
An action usage stating no body of its own performs the body of the action it names — the definition typing it (Systems Library Actions.sysml — abstract action def Action :> Performance; a usage's features are those of its type, KerML 1.0 §7.4.7) runtime/classifier_behavior.go actionBodySymbol (used by newActionExecutorForOccurrence, so every entry point resolves it the same way classifierBehaviorSymbol already did for perform) repl/runtime_commands_test.go:TestActionDebuggerRunsAPerformUsage (%action <part>::<perform usage>), repl/testdata/action_perform_usage.sysml ⚠️ Approximate, self-assessed (the flow is sourced from the definition typing the usage, which is what makes %action on a perform usage runnable. A usage that both states members of its own and is typed by a definition does not merge the two: its own body wins, and the definition's content is not inherited)
A block has a token flow of its own: a nested action declaration or a perform in a loop or branch body is a node of it lower/block_graph.go blockNeedsFlow, lowerBlockFlow, lowerStatedBlock; runtime/statements.go runBlock; runtime/action_statements.go performNode, performBlockFlow; runtime/action_subflow.go runSubflow; state and calculation hosts delegate stated body flows through the same transparent performance path action_block_flow_nested_action.sysml, action_block_flow_perform_in_loop.sysml, action_block_flow_if_branch.sysml, action_body_flow_succession.sysml, action_body_flow_fork_join.sysml, action_body_flow_branch_in_loop.sysml, action_body_flow_while_attribute.sysml, action_body_flow_terminate_statement.sysml, state_body_flow_succession.sysml, all + trace goldens where marked; lower/block_graph_test.go:TestBlockStatingItsOwnEdgeIsLoweredToAStatedFlow, TestBlockStatingACycleKeepsInitialUnset, TestStatedBlockWithUnsequencedStatementRecordsInvalidFlow, TestStatedFlowInIfBranchInsideLoop; robustness_nested_node_body_test.go:TestRuntimeRobustnessNestedNodeInBody ⚠️ Approximate (declaration-order blocks retain their existing sequential statement semantics; a body that states first, successions, forks, joins, branches or other control nodes is lowered to Block.Graph and run as a transparent performance, recreated for every loop iteration, with body-local attributes seeded per performance and terminate; resolved through the body frame to the enclosing action node. Member-attached then in loop and branch bodies is parsed into the same positional succession edges as an action body. Remaining typed refusals are accept in a declaration-order body, a stated flow in a calculation body, and body succession probability metadata when lowering has no resolver.)
Send statement (message passing) lower/action_graph.go lowerBody (FeaturePath keeps the whole target path); runtime/signal.go buildMessage (carries the resolved signal symbol), valueTypeName, objectSignalSymbol, messageMatches (qualified identity plus subtype conformance over semantics.Model.Conforms), post, postFor (a target led by a name the sending performance binds is delivered to the objects it holds; a target that is neither a name nor a feature chain is evaluated by EvalContext.valuedTargetAddresses/receiverObjects to the objects it yields, lower/action_graph.go Send.TargetExpr carrying the expression), postTo, postAt, resolveAddresses, namedAddresses/qualifiedAddresses, featureAddresses, addressesFrom, EvalContext.boundTargetAddresses, addressOwner, fvObjects, portAddress/receiverAddress/objectAddress, PostMessage (deliveryOf), Message.reaches; runtime/invoke_action.go invokeAction (a performed action runs as the object performing its caller) action_send_accept.sysml, send_identity_same_named_ports.sysml, port_identity_own_port.sysml, send_identity_unroutable_target.sysml, lower/action_body_test.go:TestActionBodyLowering, lower/connection_test.go:TestLowerSendKeepsAddressedPath, signal_test.go:TestActionMessageReachesStateMachine, :TestAddressedSendStaysWithinTheSendingObject, :TestAddressedSendResolvesPortOfNamedObject, :TestAddressedSendDescendsToNestedPort, :TestAddressedSendToUnreachablePortIsTyped, send_identity_addressed_part.sysml, send_identity_performed_object.sysml, signal_test.go:TestDeliveryHoldsAConsumerToTheWholeDestination, :TestPerformedBehaviorRunsAsItsPerformer, :TestAddressedSendToQualifiedElementOfATwinObject, :TestInjectedMessageIsHeldToTheDestinationItNames, signal_test.go:TestSendOfAnItemObjectIsTypedByItsDefinition, :TestSendOfAnItemObjectIsNotTypedByTheUsageSent, :TestAddressedSendFansOutOverAMultiValuedFeature, conformance send_subtype_matches_supertype_accept + .expected.json, send_same_name_distinct_packages + .expected.json, send_to_bound_parameter + .expected.json (a send to an in parameter of the sending action, and a chain from one, reaches the object the caller bound), robustness_test.go:injected_message_names_a_receiver_no_accept_has, robustness_send_target_test.go:bound_target_holds_a_value, :bound_target_holds_nothing (ErrSendTargetNotObject), :bound_target_chain_names_no_feature; migrate/send_target_test.go:TestSendTargetFedByParameterReachesTheObjectItHolds (a migrated v1 SendSignalAction whose target pin an object flow feeds); conformance send_to_second_object_of_usage + .expected.json + trace golden (two objects of one usage, only the one cars#(2) yields hears), send_to_object_held_in_feature, send_to_object_through_chain (garage.cars#(2)), signal_test.go:TestSendToConstructedObjectReachesIt (to new Car()), parse/send_to_indexed_receiver.golden, robustness_send_to_object_identity_test.go:TestRuntimeRobustnessSendToObjectIdentity (a receiver yielding no object or a data value is ErrSendTargetNotObject, a destroyed one ErrOccurrenceDestroyed) ✅ Faithful (a message is typed by what was sent and delivered by object identity: a target is resolved through the instance graph to the object owning it and the port path within it, and the destination is built whole or refused with UnroutableSendError, so a consumer takes a message only by satisfying every part of it — a same-named port or receiver of another object, or a sibling of the sender, never sees it. A behavior an object performs, however deeply, presents that object's identity, so only a behavior no object performs at all has none. A qualified target takes its object from its qualifier, and a message injected from outside the model is held to the destination its fields name. An object sent as a message — an item cmd : Command { … } sent by name — is typed by the definition it materializes, not by the usage sent, so an accept of that definition takes it and reads its features. A target through a multi-valued feature denotes every element it holds (KerML 1.0 §7.3.4.6), so one send delivers one copy per element, each on that element's own identity and port path, duplicates delivered once; a path reaching no object is still UnroutableSendError. A target led by a parameter, pin or local the sending performance binds — send new Go() to recipient under in recipient : Worker — is read from the performance's frame, so the objects it holds are addressed, with any further segments walked through them; a binding holding no object is SendTargetValueError, and a name no frame binds is resolved as a feature of the sender or a name in scope as before. A receiver that is any other expression — an index cars#(2), a chain through one, a new T() — is evaluated in the sending performance and the message is addressed to the identity of every live object it yields (SysML v2 §7.17.7: the receiver is the value the to expression supplies), so a second object of one usage is reached as itself; a plain name naming a usage that occurs once still reaches the occurrence this context holds for it. A message's type is its qualified semantic identity rather than its written name: an accept takes a message whose signal conforms to the type it names — a subtype satisfies a supertype accept — while two same-named definitions in different packages stay distinct)
Accept action (message consumption suspends the action) action_executor.go stepNestedAction accept case (parks the token as Token.Wait), Step (StateWaiting), RunToCompletion, deadlockError; executor_common.go AcceptWait; runtime/signal.go TakeMessage action_accept_suspends_until_message.sysml + trace golden, action_accept_two_waiters.sysml + trace golden, action_send_accept.sysml, action_accept_message.sysml, signal_test.go:TestAcceptParksTokenUntilMessageArrives, :TestParkedAcceptTakesOnlyItsOwnMessage, robustness_test.go:accept_deadlock_never_satisfied, :accept_deadlock_reports_every_waiting_accept, :send_reaches_only_its_addressee, :accept_of_unsent_type, :send_via_unconnected_port ⚠️ Approximate (an accept with no message it can take suspends the action at that node and resumes when one arrives, from a parallel branch or from another executor sharing the context; a run whose every remaining token is parked reports ErrAcceptDeadlock rather than hanging. Suspension is bounded by the executor: a nested action invoked synchronously, and an action driven by RunToCompletion, cannot wait for a message posted after the call begins)
An accept node's payload is visible by simple name to the other nodes of the same action body, and a nearer declaration shadows it (KerML 8.2.3.5.3) resolve/accept_payload.go acceptPayload/acceptPayloadsIn, consulted by resolve/unqualified.go walkUnqualifiedHiding and enclosingLocal for the scope the accept node is declared in and for every scope enclosing the reader, and by resolve/invocation.go unqualifiedCandidates for an invocation argument, so a typed usage's body inside the same action body (action run : Handle { in level = msg.level; }) reads the payload too; runtime/action_executor.go binds the accepted value under the same name resolve/accept_payload_test.go:TestAcceptPayloadVisibleToSiblingNode, :TestAcceptPayloadTwoAcceptsInOneBody, :TestAcceptPayloadVisibleInNestedBody, :TestAcceptPayloadVisibleBeforeDeclaration, :TestAcceptPayloadShadowsOuterFeature, :TestAcceptPayloadUnresolvedStillReported, :TestAcceptPayloadDoesNotEscapeBody, :TestAcceptPayloadShadowsOuterFromTypedUsageBody, :TestAcceptPayloadNotSharedByAPartBody, conformance/accept_payload_nested_body.sysml, accept_payload_shadows_outer_feature.sysml, accept_payload_read_before_accept.sysml, accept_payload_bound_into_typed_action.sysml + trace goldens, robustness_test.go:accept_payload_read_before_it_is_bound ⚠️ Approximate (the payload is a parameter of the accept node, which KerML scoping does not make a member of the enclosing body, so the body's shared feature space is modelled by contributing the payload to the scope the accept node is declared in: every node resolving through that scope reads it, a nearer declaration still wins, and the payload neither escapes the body nor is reachable as A::msg. It is not offered by LSP completion, which lists a scope's own members)
Send through a port (send x via p) routes over the connectors of the behavior, of the part performing it and of the objects holding that part, to the ends that can receive the message: the direction of the port's flow features decides that, conjugated where a ~P types the end (SysML v2 §7.12.2, §7.15, §7.16). An end is joined as the whole path it was written with, so a nested p.q is itself and not a same-named port elsewhere. A connector its owner declares joins two objects, so a send through the port of one arrives at the port of the other on that object's identity, the owner naming each end by the path from itself. A binding connector between a boundary port and a port of a part the owner holds makes them one port for delivery (SysML v2 §7.16, binding connectors): an accept on either takes a message that reached the other, matched by the identity of the port object (a failure materializing that port is the step's error, not a missed match), and a send through either leaves over the connectors joined to the other, the bound paths being consulted at every owner in turn, through any depth of nested assemblies; connectors reaching one port object by two names deliver one copy. A send that reaches no receiving end is a typed error, not a message dropped. send x via p to r names both the port and the receiver, and a message is delivered only where both match — and only to an accept of the object that sent it; a to that is no receiving node of the sender is evaluated to objects, and the routed message is delivered only to the deliveries reaching those objects lower/connection.go lowerConnections, FeaturePath (ends and the scope they resolve in); lower/action_graph.go Send.Receiver/Send.ReceiverPath (the receiver a routed send names, carried losslessly beside the port), Send.TargetSym/Send.Scope (the feature the via resolves to, resolved once at lowering through resolve.FeatureSymbolInScope); runtime/routing.go routableConnections, performerConnections, enclosingPart, ownerDeliveries, connectedDeliveries, endDeliveries, endNamesAStructuralPath, receivingEnds, receivingEndsForMessage, endReceives, portSymbol, UnroutableSendError, UnknownSendPortError, SendPortTypeMismatchError, UnreachableSendReceiverError; runtime/routing.go boundPortPaths, joinsAnyTarget; runtime/signal.go postVia, routedReceiverExists, routedReceiverObjects (a to expression yielding objects, Send.ReceiverExpr), reaches, messageReaches, portInstanceID, Message.PortID, DeliverPortReceiver; semantics/conjugation.go PortFeatures port_direction_conjugation.sysml, port_nested_port_path.sysml, port_interface_typed_connection.sysml, send_no_reachable_receiver.sysml, send_into_outbound_only_end.sysml, action_port_communication.sysml + trace golden, send_bind_relay_inbound.sysml, send_bind_relay_outbound.sysml, send_bind_relay_nested.sysml, send_bind_relay_connector_order.sysml, send_bind_relay_aliased_ends.sysml (connectors to both names of one bound port deliver one message), runtime/robustness_test.go (send_via_bound_boundary_port_joined_to_nothing, send_fan_out_to_a_port_that_fails_to_materialize, accept_via_a_port_that_fails_to_materialize, action_accept_via_a_port_that_fails_to_materialize), runtime/routing_test.go (conjugated end, outbound-only end, unjoined port, nested path, performing part's ports with and without an instance, part-to-behavior connector, interface-typed connection, an owner's connector to a sibling part and to an outbound-only sibling end, a part's own connector delegating inward to a part it holds), conformance/send_via_owner_connection.sysml + .expected.json, signal_test.go:TestSendViaPortReachesConnectedAccept, robustness_test.go:send_via_unconnected_port; for the routed form conformance/w7d_send_via_port_to_receiver.sysml + .expected.json + trace golden (two routed messages through one port to two accepts active at once, neither consuming the other's), lower/connection_test.go (the three send forms lower losslessly, feature-chain receiver included), signal_test.go (port, receiver name and sending object all required to match), routing_test.go:TestRoutedSendKeepsPerformingObjectIdentity, conformance send_connection_multivalued_end_fans_out + .expected.json, send_connection_into_nested_part + .expected.json, routing_test.go:TestSendDelegatesToTheNestedPartItIsConnectedTo, robustness_test.go:send_via_connector_into_an_empty_part, routing_test.go:TestBehaviorLocalPortShadowsThePerformersConnectedPort, parse/connection_multivalued_end.golden, robustness_test.go:routed_send_via_unknown_port, :routed_send_port_type_mismatch, :routed_send_port_type_match, :routed_send_scalar_typed_flow_mismatch, :routed_send_unreachable_receiver, :routed_send_receiver_name_mismatch_deadlock; conformance send_via_to_second_object_of_usage + .expected.json + trace golden (via out to cars#(2) over connect out to cars.p reaches only the second object), robustness_send_to_object_identity_test.go:via_receiver_object_no_connection_reaches (ErrUnreachableSendReceiver) ⚠️ Approximate (routing honors direction, conjugation and the performing part's ports; the routed form's three failure modes are typed — a via naming no port of the sender is ErrSendViaUnknownPort, a receiving port whose typed inward flow features all reject the message is ErrSendPortTypeMismatch, and a receiver name that resolves to nothing reachable is ErrUnreachableSendReceiver, while a receiver that exists but never accepts stays pending and is reported by ErrAcceptDeadlock at the end of the run rather than misrouted; a port declaring no flow features constrains neither direction nor message type, so it receives in either direction and takes any message, and an end whose path this run cannot resolve is treated as able to receive rather than reported here, unless it reaches its port through a part of the sender that holds no object, where nothing is behind the end and the send is reported. An end reached through a multi-valued feature (part units : Unit[2]) denotes every element it holds, so a send over its connector delivers one copy per element, each on that element's own identity, duplicates delivered once. Every receiving end is resolved to the object holding the port it names, so a part joining its own port to the port of a part it holds delegates inward and the copy is held to the nested part's identity rather than to the sender's. A via is matched as the feature its written name resolves to in the send's scope, resolved once at lowering, so a port a behavior declares under the name of one of the performer's connected ports diverts the route: the local port is used and the outer connector receives nothing; a port written in an action body is still reported as not executable, so the shadowing arises for one declared by a state or other behavior definition. The performer is the object performing the behavior, or the part the behavior is declared in when no object performs it, and the connectors of every object holding the performer are consulted in turn, so a part nested several levels down is reached by the path its owner names it with. A routed to clause is not carried across such a connection: a receiver resolving to another object is ErrUnreachableSendReceiver, since a routed send is delivered only to an accept of the sending object. A routed to clause names a declared receiving node on the sending behavior/object, so an inherited library feature with the same name does not shadow it; a to that names no such node is read as an object expression, and the copies routed over the connections are kept only where they reach one of the objects it yields, none reaching one being ErrUnreachableSendReceiver.)
A send … via p written in a behavior running inside a nested part of the sender — a timeslice or suboccurrence among them — resolves p against its ancestors: owner routing starts at the object actually holding the resolved port, so the enclosing part's own connectors carry the message (Occurrences.kerml — a suboccurrence is withinLifeOf its whole, so what the whole is joined to is how its portions reach out) runtime/routing.go viaPortHolder, ownerDeliveries conformance/send_via_owner_port_from_nested_part.sysml + .expected.json ✅ Faithful
A via path names the port the connector ends of the sender's owner write — via this.p is the owner's own port p — and a path rooted at a feature the behavior binds to another object (a ref parameter, a feature chain into a part) leaves or accepts at that object's port — a bound ref port named bare, via p, leaves the port it holds from the object owning it, and a binding holding an object that is no port is refused — the binding shadowing a same-named feature of the owner as it does in any expression, unless the path is written from this, which roots it at the owner — for an action's send or accept and for a state transition's accept … via alike, the transition resolving its path through the machine's parameters, and for the ends of the behavior's own connectors, so a connect ctx.p to snk.local between two bound references carries the send beside the connections of the object holding the port; a part's port is known to the connectors its type inherits under the name the part was declared with before a subtype redefined it (KerML 1.0 §7.3.4.5 Redefinition); a usage declared ref or with a references relationship holds what is bound to it and materializes no object of its own (SysML v2 §7.6.2 Usage::isReference); and a port with no type is a Ports::Port (SysML v2 §7.9.2), materialized so a binding connector can join it to a part's port and a message sent inward over it reaches the part's behavior lower/connection.go ViaPortPath; lower/state_graph.go Transition.ViaSelf; runtime/via_reference.go viaSender, viaHolder, boundEndDeliveries; runtime/routing.go endDeliveries; runtime/state_executor.go transitionReached; runtime/routing.go heldPortPaths; runtime/instance.go CompositeTypeOf, isReferenceUsage, untypedPortUsage conformance/send_via_this_port, send_delegated_port_nested_binding, accept_via_bound_context_port, via_bound_reference_shadows_part, via_this_keeps_performer, state_transition_via_bound_reference, send_via_bound_reference_behavior_connection, send_via_bound_port_reference, behavior_connection_bound_port_end, robustness_via_bound_port_test.go, robustness_transition_via_test.go, robustness_behavior_connection_end_test.go, instance_reference_usage_not_materialized, send_untyped_port_inward_binding (+ trace), robustness_untyped_port_test.go ✅ Faithful
Accept through a port (accept msg : T via p) lower/action_graph.go acceptPort; runtime/action_executor.go stepNestedAction accept case action_port_communication.sysml, lower/connection_test.go:TestLowerAcceptRecordsViaPort, signal_test.go:TestPortRoutedMessageBypassesPortlessAccept, :TestAddressedMessageBypassesPortAccept ✅ Faithful (an accept on a port takes only messages routed to that port, and an accept on none takes only addressed messages)
An accept node is an action node (SysML.xtext ActionNode), so it stands wherever a statement does — a member of an action body, a statement of a loop or branch body, or the member a then sequences (then action engineStopped accept engineOff : EngineOff;) — and the accepting action's name stays distinct from the received payload's (engineStarted vs engineStart), both registered and resolvable parser/behavior.go parseActionMember, startsInlineSuccessionStatement, atAcceptNode, parseAcceptNode (declaration name, payload parameter, optional via port) parse/accept_action_statement.golden, runtime/testdata/conformance/accept_statement_via_port.sysml, parser/negative_test.go (accept_statement_no_payload, accept_statement_no_payload_type, accept_statement_via_no_port, then_accept_no_payload), robustness_test.go:accept_statement_deadlock_in_a_loop, lower/action_body_test.go:TestAcceptInALoopBodyIsLoweredAsUnsupported ⚠️ Approximate (parsed, resolved and executed as an action-body node; an accept written in a loop or branch body would have to suspend a flow that has no token to park, so it is lowered as Unsupported and reported when reached rather than passed over — suspending a block's flow is not implemented)
Object flow, streaming (flow a.out to b.in;; SysML v2 §7.16: "the input and output parameters are streaming unless designated as succession flows", Flows::Flow :> Message, FlowTransfer): each value the source pin takes while the source is being performed reaches the pin of every ongoing performance of the target at once, so a target that reads its pin between two writes sees each; a value written while no performance of the target is under way waits at its pin, and a later write from the same source performance along the same flow replaces it, so the target's next performance begins with the pin's value as it stands (the writes of distinct source performances wait one per target performance, oldest first); each flow declaration is a transfer of its own, so two flows out of one pin into one target pin — two routes of a fUML fork, or a pin named by its inherited and its redefining name — each stage the pin's write, and two performances of the target begun afterwards each take one; the source completing carries nothing more where it streamed, and a source pin never written by the time the source completes is ErrFlowSource; a value written after the target's last performance ended, which no later performance of the target takes, is ErrStreamUnreceived when the enclosing performance completes; a stream to a pin the target does not declare is ErrNodePin at the write; the outputs of an action a node performs (action p : Producer;) stream from the node as the performance writes them; streaming flows that carry a value back to the pin it was written to are ErrStreamCycle; the checker's footprint of a node counts every target pin its writes stream to, through nested bodies and transitively, so a streaming write and a read of the target pin never commute under reduction; the SMT encoding streams each write of a body, declared value or expression result at the write, a queued value the same performance replaces while another's is an overflow, and a value queued after its target's last performance fails the action as it completes lower/action_graph.go FlowKind (FlowStreaming/FlowSuccession), ObjectFlow.Kind, lowerFlow, succeedFlow; runtime/action_frame.go setFrameFeature → streamFrom → streamFlow (ongoing finds the target's performances under way, deliverFlow queues ahead of the next), actionFrame.streamed, actionFrame.unreceived, takeDeliveries, checkStreamsReceived; runtime/action_executor.go applyDataFlows (skips a pin already streamed), ActionExecutor.streamOutput with runtime/action_frame.go streamCalleeOutput (a performed action's output writes reach the node's pin as made, installed by beginPerformed before the callee's declared values are seeded); lower/footprint.go flowTarget, streamsFrom, streams over ActionGraph.Enclosing/EnclosingNode (block_graph.go encloseBlockFlows); smt/encode.go stream, carry, performedName/unreceivedName marks lower/action_flow_kind_test.go, lower/footprint_test.go:TestFootprintStreamingWritesReachTargetPins; conformance action_flow_streaming_producer_consumer.sysml + .expected.json + trace golden (a producer loop writing 1, 2, 3 beside a consumer loop reading each: total 6), action_flow_streaming_before_target_begins.sysml, action_flow_streaming_in_loop_body.sysml, action_flow_streaming_declared_value.sysml, action_flow_streaming_from_performed_action.sysml (a performed Producer writes 1, 2, 3 before its consumer begins; the consumer reads the last: total 3), action_flow_streaming_last_write_before_target_begins.sysml, action_flow_streaming_performed_declared_value.sysml (a performed action's declared output streams as the performance begins), action_flow_streaming_two_flows_one_pin_to_call.sysml (two flows out of one pin feed two performances of a nested action definition with a required input: each is bound), action_flow_streaming_aliased_source_pin.sysml (the inherited and the redefining name of one pin, two flows: two deliveries), stream_stage_test.go:TestStagedStreams, action_output.sysml, action_block_flow_loop_bindings.sysml; robustness_streaming_flow_test.go (source_never_writes, target_completed_before_source_writes, target_pin_not_declared, later_performance_takes_one_of_two_late_values, flows_form_a_cycle); check_state_stream_test.go, check_reduce_test.go (por_streaming_flow: reduced and unreduced search agree), smt/encode_test.go:TestEncodeFlowKindsDiffer (the encoding and the interpreter's exploration agree that a plain flow beside its target may arrive late and a succession flow never does) ✅ Faithful
Object flow, succession (succession flow a.out to b.in;, Flows::SuccessionFlow :> Flow, FlowTransferBefore: the target "cannot begin until the source completes"): the value the source pin holds when the source completes moves to the target pin, and the flow is also the succession source → target lower/action_graph.go lowerFlow (FlowSuccession), succeedFlow (the succession edge); runtime/action_executor.go applyDataFlows → deliverFlow conformance action_flow_succession_producer_consumer.sysml + .expected.json + trace golden (the same producer and consumer in sequence: the consumer reads the last value, 3, three times), robustness_test.go:flow_from_a_node_that_produced_nothing ✅ Faithful
Succession edges lower/action_graph.go:ToActionGraph action_control_flow.sysml ✅ Faithful
Object flow from a pin declared admitting no value (out reading : Real[0..1]; on an action with no body, as a v1 call naming no behavior migrates to) that the performance never wrote: the flow carries nothing, so the target begins with the pin empty rather than the run failing with ErrFlowSource; a read of such a pin by path (measure.reading), or of the performance itself as a value when the pin is its result, is the empty sequence, as a read of its declaration is, and an activity output bound to it is declared admitting no value too; a required pin the performance never wrote stays ErrFlowSource, and a required target pin no value reaches stays NoValueError naming it, so nothing is made up runtime/action_frame.go nodePins.optional, actionFrame.optional (filled by pinsOf/addFeatureDirections from Context.admitsNoValue over each lowered Feature, as lower.Attribute.Optional is), actionFrame.admitsNoValueAt, actionFrame.pin, actionFrame.resultValue; runtime/action_executor.go applyDataFlows conformance stub_action_output_holds_no_value.sysml + .expected.json; robustness_stub_action_test.go (optional_output_flows_no_value, required_output_never_written, required_target_gets_no_value, optional_output_read_by_path, optional_result_read_as_value, required_result_read_as_value); parse/action_declared_parameters_no_body.golden; tests/migrate/testdata/xmi/stub_actions.xmi goldens ✅ Faithful
then terminate; as a node of an action's flow (SysML v2 §7.17.10 Terminate Actions; Actions::TerminateAction :> Action ends "the occurrence that is the performer" of it): the token reaching it ends the performance the flow belongs to — the action itself at top level, the nested node whose flow it is otherwise — with the outputs assigned so far; later nodes do not run, no data flow fires from a node that did not complete, and every other token of that performance is dropped, a forked sibling branch still running or parked at an accept included, in token-id order, each drop written to the trace. For the root the run ends Completed with the features as they are; for a nested node the parent's token takes the node's succession as after a normal completion, and no succession out of the node's inner nodes fires lower/action_nodes.go (the node's body is one Effect{EffectTerminate} with TerminateContaining); runtime/action_terminate.go performances.terminate (a terminated unwinding caught by ActionExecutor.stepToken → endTerminatedFor), endAround, leaveTerminated, dropTokensIn; runtime/trace.go RecordActionTerminate conformance action_terminate_flow_node, action_terminate_fork_drops_sibling + .trace.golden (the sibling parked at an accept is dropped before the action completes) ✅ Faithful
A named terminate action usage (action stop terminate;, SysML.xtext TerminateActionUsage with a declaration) reached by then stop; ends the performance whose flow it is a node of, as then terminate; does; the marker is kept on the usage, printed back and exported as TerminateActionUsage ast/defusage.go Usage.IsTerminate; parser/defusage.go parsePostModifiers, skipPastTerminateMarker (terminate after an action's declaration is the marker, not a reference, and closes the declaration: a clause after it is reported); lower/action_subflow.go lowerTerminateNode (the statements its body declares run as a leaf's, then the terminate, TerminateEnclosing, which runtime/action_terminate.go terminatedUsage also reaches when a terminate of the body ends the usage's own performance first; the pins its body declares are its features, lowerFeatures; a body stating a flow of its own is ErrInvalidActionFlow at initialize), lower/block_graph.go; export/rdf_out.go/rdf_in.go parse/action_terminate.golden, conformance action_terminate_named_usage, action_terminate_joined_usage (two successions synchronize at the usage; trace golden), action_terminate_usage_with_pins (a flow into a pin the usage's body declares), action_terminate_usage_with_body (its body's statements run before it ends the action), action_terminate_usage_body_ends_itself and action_terminate_usage_in_block_names_itself (a terminate in its own body ends the usage's performance there and the usage still ends the action or node it is a step of), robustness terminate_usage_stating_a_flow_of_its_own, export/behavior_test.go (terminate usage round trip); parser/negative_test.go (terminate_repeated, terminate_then_typing, terminate_then_ordered, terminate_then_value, terminate_then_target), TestTerminateMarkerClosesTheDeclaration ✅ Faithful (a target written after the marker, action stop terminate x; — SysML.xtext TerminateNode's NodeParameterMember — is reported rather than dropped; the target form is the statement then terminate x;. examples/sysml-v2-training/19. Terminate Actions/Terminate Actions Example-1.sysml parses with its marker but still stops at initialize, no initial node found in action node performCriticalActivity: no succession leads to "monitorCriticalActivity" or to "criticalActivity"; 'first' names the step the flow starts at (runtime/action_subflow.go noFlowStart): the nested node's body is two performs with no first and no succession between them, so the flow has two possible starts, which the flow-start rule reports — not terminate, which runs in every position the example uses)
terminate; as a statement of a nested action node's body (then action c1 { assign x := 1; terminate; }) ends that node — "not any containing actions" — so the rest of the body does not run, the node's fork branches and paused work are dropped, and the parent continues along the node's succession and observes what the node assigned before it ended lower/action_graph.go lowerBody (*ast.TerminateStatement lowered as Effect{EffectTerminate}, the target resolved at lowering time), lowerStatement; runtime/action_statements.go actionStmtHost.effect → runtime/action_terminate.go performances.terminate; runtime/action_body_run.go usageWork.perform and runtime/action_statements.go performNode (the unwinding of the node's own body ends it normally, dropping the tokens of a flow a node of an if branch or a loop body inside it owns) conformance action_terminate_nested_body, action_terminate_nested_fork + .trace.golden (the node's sibling branch is dropped, the parent's other fork branch completes) ✅ Faithful
terminate <name>; naming an action node of the flow the statement is in or of a flow enclosing it — the node itself (action c1 { terminate c1; }), the node whose body holds the statement's node, or a sibling node of an enclosing flow still running — ends every ongoing performance of that node, the earliest begun first, and every performance a token of that flow is parked at without having begun — a forked sibling the schedule has not reached yet (§7.17.10's MonitoredActivity, whose waitForTimeOut terminates performCriticalActivity whatever it has done), or an accept node waiting for a signal (an ongoing performance that has done nothing) — which end there, their body never run, the token going on along the node's succession (a merge can bring a second token to a node whose earlier performance is still paused): unwinding the body where the statement runs inside it, dropping the performance's tokens and abandoning its paused body in place where it runs beside it — the performance the token at the node holds, never another performance of the same node — after which the parent's token takes the node's succession. A name that resolves to no action node of an enclosing flow is reported (ErrTerminateTarget), as is a node whose every performance already ended (ErrPerformanceEnded) lower/action_graph.go lowerStatement (Effect.Terminates/Effect.Target: TerminateNode, TerminateUnknown); runtime/action_terminate.go terminateTargets (up the frame chain through subactions), performances.ongoingWith, ActionExecutor.ongoing, terminated.then/endAlongside (the performances named after the one the statement runs within end once it has), Token.performing, ActionExecutor.beginPending/endPending (a parked token's performance, actionFrame.heldAt), endOther; runtime/trace.go RecordActionTerminatePending; runtime/errors.go ErrTerminateTarget, ErrPerformanceEnded conformance action_terminate_names_own_node, action_terminate_names_own_node_concurrently + .trace.golden and action_terminate_names_own_node_from_the_earlier + .trace.golden (a performance naming its own node ends the other ongoing performances of it too, the order earliest-first kept across its own unwinding), action_terminate_names_enclosing_node, action_terminate_names_sibling_flow + .trace.golden, action_terminate_names_concurrent_performances + .trace.golden, action_terminate_from_block_node_flow + .trace.golden and action_terminate_names_block_node + .trace.golden (a terminate in the forked flow of a node an if branch declares names the node holding the branch, or the branch node around it: the forked waiter is dropped with the performance named); action_terminate_names_sibling_before_it_begins + .trace.golden (the MonitoredActivity shape: the terminated sibling had not begun, terminate … ended before it began) and action_terminate_names_waiting_accept + .trace.golden (ended waiting; the join after both branches still meets); robustness_terminate_test.go terminate_of_an_ended_performance, terminate_of_an_unknown_name, terminate_of_a_node_of_a_sibling_flow (a node nested inside a sibling branch is out of reach) ✅ Faithful
terminate <expr>; whose target is an occurrence rather than an action node — terminate this; in an action a part performs (an exhibited or performed behavior is a performance the object owns, so this there is the object), a feature chain naming a part (terminate sub.worker;), or an expression evaluating to an object — ends that occurrence at the statement (SysML v2 §7.17.10 "forces the lifetime of the terminated occurrence to end"; Actions::TerminateAction's terminatedOccurrence): its lifetime is recorded as ended, its owned portions with it — a part first read after the whole ended is reached as one that ended with it, its declared values readable and no behavior of it started — and every behavior it performs or exhibits ends where it stands — an action executor of it is Terminated with the writes made so far and its tokens dropped, a state machine it exhibits is terminated with no state exited and its do behaviors abandoned — while a behavior of another object stating the terminate runs on. A behavior whose performer ended between two steps ends the moment it is next run, before doing anything more lower/action_graph.go lowerStatement (TerminateOccurrence, the target expression kept as Effect.TargetExpr with its scope); runtime/occurrence_terminate.go performances.terminateOccurrence, terminateTargetOf (the expression evaluated in the statement's frame to a ValInstance), Context.endOccurrence, endBehaviorsWith, ActionExecutor.endTerminated/StateExecutor.endTerminated, performerEnded (checked by Context.beginExecutorRun before every run of an executor); runtime/lifetimes.go checkLiving (a target that is destroyed, or a performance that already ended, is a typed error), beginLife (a part of an ended whole is born ended), lifeEnded, checkPerformer; runtime/classifier_behavior.go startBehaviorsOfAll (no behavior of an ended object starts); runtime/trace.go RecordOccurrenceTerminate conformance action_terminate_this_ends_part + .trace.golden (the part's exhibited machine is terminated with its action), action_terminate_names_exhibiting_part + .trace.golden (a feature chain to another part: that part's machine ends, the terminating action runs on), state_terminate_this_ends_performer + .trace.golden (terminate this; in a do behavior ends the part exhibiting the machine), action_terminate_owner_before_lazy_part + .trace.golden (a part first read after its whole ended: the value read, its machine never started); robustness_terminate_test.go terminate_of_a_non_action_feature (ErrTerminateOccurrence: no occurrence), terminate_of_a_literal_value, terminate_of_a_qualified_occurrence (a chain that names no object), terminate_of_a_destroyed_occurrence (ErrOccurrenceDestroyed), terminate_of_a_part_reached_after_its_whole_ended (ErrOccurrenceLifetime: it ended with the whole), terminate_of_an_occurrence_expression, terminate_of_an_ended_occurrence_in_a_state_body, behavior_of_an_ended_performer (ErrPerformerEnded: a behavior of an occurrence that ended cannot be started) ✅ Faithful
terminate as a statement of a state's entry, do or exit body, or of a transition effect, ends that behavior at the statement — the containing action of the terminate is the behavior it is written in (StatePerformance's entry/do/exit step, Kernel Semantic Library/StatePerformances.kerml), not the machine — so the rest of the body does not run, the state stays active and the machine keeps dispatching; the machine's exhibiting occurrence ends only when named (terminate this;, the row above). The unit ended is the action the terminate is written in: in the named shapes (entry action a { … }, do action tick { … }, do eff;) the named action; in the braced shapes (entry { … }, do { … }, exit { … }, a transition's do { … }, the machine's own entry { … }/exit { … }) the whole braced block, which is one anonymous ActionUsage with an ActionBody as SysML.xtext reads it — the terminate resolves to the block's own performance, so the statements after it do not run (a do block runs one statement a round, so an accept it would park at next never begins); the steps of a transition's own body (then t { … }) are the one shape lowered one behavior per statement, and a terminate in one ends the steps after it (terminate …: ended before it began trace lines); another member of the same kind beside the block (entry action first; next to entry { … }) and the state's other behaviors run as written — an entry block's terminate still lets the do start, an exit block's or an effect's still lets the transition complete lower/state_behavior.go (a state behavior's *ast.TerminateStatement lowered as Effect{EffectTerminate} with its target, TerminateContaining for the bare form; StateBehavior.Block names the TransitionMember whose body's steps a behavior is one of, nil for every behavior that is a performance of its own), lower/state_graph.go StateGraph.behaviorsIn, lowerBehaviorsFor, transitionEffects, lowerTransitionEdge, lower/state_inheritance.go addMember, cloneStateNode (inherited bodies keep their terminate mappings); runtime/state_statements.go stateStmtHost.effect → runtime/action_terminate.go performances.terminate (the terminated unwinding ends the behavior's own performance, caught where the behavior was started), stateStmtHost.ended (terminated), StateExecutor.executeBehaviors, endedBefore; runtime/state_executor.go stepDoAction; runtime/state_route.go runEffects, runBehaviors; runtime/state_region_entry.go (fork branch effects) conformance state_terminate_entry_do_exit_behaviors + .trace.golden (each of the three named bodies ends at its terminate; the assignments after it do not run, the state is still active for the next event); state_terminate_braced_entry_do_exit_effect + .trace.golden (braced entry, do, exit and transition do blocks each end at their terminate, the machine reaches the target), state_terminate_braced_do_after_accept + .trace.golden (the accept the block would park at next never begins; the state completes and the machine keeps dispatching), state_terminate_braced_entry_among_named (only the braced block is cut short; the named entry actions beside it run), state_terminate_braced_inherited_by_two_usages (the braced blocks of a definition, in two usages of it), state_terminate_braced_do_in_one_region + .trace.golden + .check.expected.json (a sibling region's do block is unaffected, on every schedule); state_terminate_transition_body + .trace.golden (a terminate among the steps of a transition's own body ends the steps after it, ended before it began, and the target is entered); robustness_terminate_test.go terminate_of_an_unknown_name_in_a_state_body, terminate_of_a_value_in_a_transition_effect; robustness_terminate_block_test.go terminate_as_the_only_statement_of_a_block, second_terminate_of_an_ended_block_never_runs, terminate_in_a_loop_ends_the_block_once, terminate_of_a_state_name_in_a_block ✅ Faithful
A transition whose target is a terminate action usage declared in the machine's body or in a composite state's (transition busy accept Abort then stop; action stop terminate;, SysML v2 §7.18.3: "to immediately terminate the containing state performance") ends the state-machine performance where it arrives: the transition's source is exited as any transition's is (its exit behavior runs, since the source is left) and the effect run, the states from the move's boundary down to the usage's owner are entered (their entry behaviors run; a composite's own entry transition never fires), then nothing else is exited, no other exit behavior runs, every do behavior under way — the sibling region's, the enclosing composite's, the machine's own — is abandoned where it stands, and no state is active. The run's Outcome is Terminated with FinalState empty (StateTerminated, distinct from StateCompleted at a final state); the REPL reports State machine terminated (a \terminate` ended its performance short of a final state; no state is active)and%currentExecution state: Terminated; the LSP debugger state isterminated; the trace recordsterminate : …with the do behaviors abandoned. A route through a choice or junction whose taken branch reaches the usage, or out of a join once every source has arrived, ends the machine the same way once the compound transition completes — the sources, and the composite a segment leaves, are exited as for any transition out of it; a transition reaching the usage from another region, or from outside the composite that declares it, exits and enters what a move to a state beside the usage would. A fork branch may not lead into a terminate usage (fork split: branch target must be a state, at lowering): §7.18.3 gives the terminate as a transition's target, and a fork's branches enter one state per region. A machine inherited by a typed state usage owns copies of its terminate usages and pseudostates, nested ones too, so two usages of one definition each terminate through their own |lower/state_graph.goStateGraph.Terminates/TerminateOwner(every terminate action usage with the composite state that declares it;lower.IsTerminateUsage),putCopiedVertex/copyInherited(a transition naming the inherited declaration reaches the usage's own copy),lower/state_inheritance.gocloneStateNode;resolve/transition.go(a terminate usage is a valid transition endpoint);runtime/state_route.goroute.terminate,terminateBoundary,terminateExits,terminateEntries,terminateAlong,terminateAt,mayExit;runtime/state_executor.goterminateMachine,abandonMachine;runtime/executor_common.goStateTerminated,ExecutionState.Ended;runtime/outcome.goOutcome.Terminated;runtime/trace.goRecordStateTerminate;repl/meta.go(the terminated wording);lsp/debug.godebugTerminated| conformancestate_terminate_transition_ends_machine+.trace.golden(the source's exit and the effect run; the machine's do behavior is abandoned,exits = 1),state_terminate_inside_composite+.trace.golden(a terminate declared in a composite state, reached from its substate: the substate is exited, the composite is not),state_terminate_inside_region+.trace.golden(one region's transition ends the machine; the sibling region's state is not exited, its do behavior abandoned),state_terminate_entering_composite+.trace.golden(reached from outside the composite: the composite is entered on the way, its region never started),state_terminate_through_choice+.trace.golden,state_terminate_through_junction+.trace.golden,state_terminate_through_join+.trace.golden(both regions' states and the orthogonal state are exited before the join's effect; the run is terminated, at no final state),state_terminate_inherited_by_two_usages+.trace.golden(the second usage ends the machine through its own copy of a nested inherited terminate);lower/state_inherited_test.go:TestToStateGraphTwoTypedUsagesOwnInheritedTerminateAndPseudostate,lower/fork_plan_test.go:TestToStateGraph_ForkShapeRejected(branch into a terminate action);repl/terminate_test.goTestStateDebuggerReportsATerminatedMachine,TestTerminatingThisEndsThePartsBehaviors; the PSSM referee's *Terminate 003* (pass), *Terminate 001* and *002* (fail` on the region-entry order of alignment finding 9; each reaches an admitted trace whose termination is complete) ✅ Faithful
Deadlock detection action_executor.go:72 Step action_executor_test.go:TestActionExecutor_Deadlock_JoinStarvation ✅ Faithful
The action executor agrees with the fUML reference implementation on every fUML test activity that has a SysML v2 spelling and no fUML-only re-firing: the fUML referee translates each activity of the pinned fUML-Tests.uml by rule into a fuml::<Activity> action definition (an activity parameter node is a nested read or an appending collector of the parameter, an object flow is a flow with an enabling succession beside it where its target has no control predecessor, a called activity a nested action n : fuml::Callee whose same-named parameters are spelled apart, a class a part def with its generalizations and its attributes at their declared multiplicity, CreateObjectAction a new <Class>(), the structural feature actions reads of and assignments to the object's feature that leave what the reference computes, a signal an attribute def specializing its generals, SendSignalAction a send new <Signal>(…) to target and AcceptEventAction an accept node whose result pin is the instance received, a class's owned behavior an action def nested in its part def and its classifier behavior an action classifierBehavior : <Behavior>; member the object starts nothing of at creation, an activity instantiated as an object such a part def around its own body, ReadSelfAction in an owned behavior this, and StartObjectBehaviorAction a perform object.classifierBehavior.start; — an owned behavior's own row is refereed through the activity that starts an object of its owner), explores its schedules and requires the values left in the out parameters to be the reference's, as a multiset where the fUML parameter is unordered; the reference's firing sequence being among the reachable ones is reported, never a verdict. A fUML activity built from constructs SysML v2 has no spelling for (object identity and classifier tests, ReadExtentAction, DestroyObjectAction, association links, buffer nodes, operation calls and replies, unlimited naturals, WriteLine, the exception tests) is not-expressible; one the reference re-fires an action in, once per token on a multiplicity-1 pin, is differs-by-design (alignment note row A15: a SysML v2 node is one performance that follows all its successions); one the emitter has no rule for yet is not-expressible by the emitter, its class still expressible and the construct named after not yet translated: tools/referee/fuml/emit.go Emit, spellParameters; tools/referee/fuml/run.go Execute over runtime.ExploreWith; tools/referee/fuml/referee.go Referee; tools/referee/fuml/baseline.go Reproduces; tools/cmd/fuml-referee -check tools/referee/fuml/emit_test.go, referee_test.go, tools/cmd/fuml-referee/main_test.go; docs/project/fuml-referee-baseline.json against docs/project/fuml-referee-expected.json (go run -C tools ./cmd/fuml-referee -check, run in CI over the downloaded suite) ⚠️ Approximate (23 of the 55 activities pass, none fails, 4 are differs-by-design, 28 not-expressible — 27 by the classifier and 1 by the emitter, TestSignalReceiver, whose ReadSelfAction in an activity performed on its own yields the performance, which fUML writes an attribute of as an object; an edge weight other than 1 and an object-flow cycle through control nodes are refusals no activity of the suite reaches — attributed to the emitter, not the executor; no pass is evidence of SysML v2 conformance)
Step budget enforcement context.go incrementStep; budget configured by OPENSYSML_MAX_STEPS (budget.go BudgetsFromEnv) robustness_test.go:testStepBudgetExceeded, budget_test.go:TestRaisedBudgetRunsLongerLoop ✅ Faithful (the reported limit is the effective one, and names the variable that raises it)

State Machine (SysML v2 States — Systems Library/States.sysml, over KerML StatePerformances)

No external referee: the pinned artifact evaluates expressions but executes neither actions nor state machines headlessly, so every row in this section is self-assessed against the specification text, the normative library and our own goldens.

Semantic Rule Implementation Test Case Status
A named succession or transition endpoint that resolves to a non-vertex or non-action node is reported during analysis; positional, implied, unresolved and flow ends remain unchanged resolve/transition.go ResolveEndpoint; resolve/edge.go and resolve/document.go; passes/action_endpoint.go ActionEndpointPass; lower/action_nodes.go ActionNodes and ActionEndpointAccepted, including lazy inherited action-node collection passes/succession_endpoint_test.go:TestResolvedStateEndpointNotVertexIsReported, :TestActionEndpointPassReportsResolvedNonNodes, :TestActionEndpointPassAcceptsLoweredNodes, :TestActionEndpointPassDiagnosticAgreesWithLowering, :TestActionEndpointPassInheritedNodes, :TestActionEndpointPassIgnoresFeatureChainsThroughNonNodes, :TestStateSuccessionEndpointSpellingsAcceptVertices; lower/action_inherited_test.go; runtime/testdata/conformance/action_inherited_endpoint.sysml ⚠️ Approximate (a feature chain rooted in a part usage is legal notation and stays silent during validation, but execution still reports the existing lowering error because invoking an action through that chain is not implemented)
Initial state identification lower/state_graph.go:ToStateGraph; state_executor.go:686 initialize state_simple.sysml ✅ Faithful
A succession out of a state body's own entry subaction names the state it starts in (entry; then off;), which is how a machine designates where it starts lower/entry_transition.go addEntryTransition, UnconditionalStart; lower/state_graph.go collectTransitions SuccessionEdge case + isEntrySubaction lower/state_notation_test.go:TestToStateGraph_EntrySuccessionNamesInitialState, lower/transition_source_test.go:TestToStateGraph_UnguardedEntryTransitionIsInitial, state_entry_succession_initial.sysml conformance, tests/parser/testdata/parse/behavior_exhibit_state_body.golden ✅ Faithful
A state body's two-ended first a then b; is the SuccessionAsUsage a -> b that succession first a then b; spells with its keyword (SysML.xtext SuccessionAsUsage under StateBodyItem), so it lowers to the same completion (trigger-less) transition, resolving nested, qualified, chained (c.c1), region-local and pseudostate endpoints as a succession does, and designates no initial state; a machine whose only edges are such successions and no entry marker has no initial state, reported as an exhibited machine with none is. A succession end written as a feature chain names the nested vertex the qualified spelling names — c.c1 and c::c1 are one endpoint, as source or target, in either spelling, and the chain's first segment reaches a vertex nested anywhere in the machine (c1.deep for c::c1::deep) as a qualified end's does — and one whose member, or whose operand, is not a vertex is reported as a qualified end naming none is parser/behavior.go (state-body first dispatch: a guarded first a if g then b; is a transition, a two-ended first a then b; a SuccessionAsUsage, and only a one-ended first a; an InitialNode); parser/parser.go bodyContext.carriesActions (a state body carries vertices, not a token flow); parser/succession.go atTwoEndedFirst, atChainedFirstSuccession; resolve/references.go connectorEnd (state-machine succession ends are endpoints, a chained end its operand then its member); resolve/transition.go ResolveEndpointRef, resolveEndpointChain, lookupEndpointChain, VertexInScope (over endpointParts); lower/endpoints.go EndpointRef, EndpointResolver.Endpoint (a name or a feature chain); lower/state_graph.go collectUsageTransitions → addCompletion, vertexFor (over endpointPrefix); passes/state_transition.go checkEndpoint, endpointName parse/state_body_first_succession.golden (state definition, state usage, nested state, exhibited state, qualified ends, inherited start, guarded first … if … then, an action body's first kept as an initial node), lower/state_graph_nested_test.go:TestKeywordlessSuccessionLowersLikeTheSuccessionKeyword, lower/state_notation_test.go:TestToStateGraph_InitialDesignationIsRecordedOnTheGraph, passes/state_transition_test.go:TestStateBodyFirstIsASuccession, passes/succession_endpoint_test.go:TestEndpointNamingNoMemberIsReported (an unresolved end of either spelling), :TestStateSuccessionEndpointSpellingsAcceptVertices, :TestStateSuccessionChainedEndpointsAcceptNestedVertices, :TestResolvedStateEndpointNotVertexIsReported (outer.mode), lower/state_graph_nested_test.go:TestSuccessionChainedEndpointsNameNestedVertices, resolve/references_test.go, export/behavior_test.go (a sysml:SuccessionAsUsage, no sysx:InitialNode), conformance state_first_succession_chain.sysml (entry; then a; with first a then b; first b then c; first c then done; completes with the log "a b c "), state_first_succession_without_entry.sysml (first b then c; alone: no initial state), state_first_succession_chained_endpoint.sysml (b.b1, b::b2, c1.deep, c.c1.deep as source and target of both spellings), robustness_test.go:object_exhibited_machine_whose_only_edge_is_a_first_succession ✅ Faithful (previously the two-ended form parsed as an InitialNode with a successor and lowered as an entry transition into b, so the machine started in b without an entry marker and, beside entry; then a;, never left a; the pinned pilot accepts the keyword-less spelling without diagnostics)
A one-ended first a; in a state body orders nothing: a state body has no token flow for an initial node to start passes/state_transition.go CodeFirstNamesNoTarget (constraint tier, an error in every mode), beside the notation tier's nonstandard-notation warning that the production exists in an action body alone; lower/transition_source.go IsStateSource (an InitialNode is not a state source) passes/state_transition_test.go:TestOneEndedFirstInAStateBodyIsReported, :TestTransitionToFirstMarkerIsIllegal, passes/nonstandard_notation_test.go:TestOneEndedFirstOutsideAnActionBodyIsAnExtension, robustness_test.go:state_transition_endpoint_naming_a_first_marker ✅ Faithful (reported, never a silent no-op: the lowering has no vertex for it and a transition naming it as an endpoint is refused)
Guarded entry transitions — SysML v2 §7.18.3 EntryTransitionMember (entry; if cold then heating; if not cold then idle;, entry assign x := …; if c then s;, entry action boot { } if c then s;, transition boot if c then s;): the transitions out of a body's entry action choose the state the body starts in. They are tried in declaration order each time the body is entered — at machine start and whenever a transition enters the composite state, orthogonal region or exhibited state whose body they belong to — after the entry action itself has run, and the first whose guard holds is entered; an unguarded then s; among them is taken when reached; when alternatives are written and none holds the machine has nowhere to start. A typed or specializing state that writes entry transitions of its own starts by them alone, the inherited ones being replaced as its own entry behavior replaces the inherited one; one writing none keeps the inherited start. An entry transition carries a guard at most: a trigger, an effect, or a target that is not a state is ill-formed lower/entry_transition.go EntryTransition, StateGraph.EntryTransitions (keyed by the body's owner, kept in declaration order), withOwnEntryTransitions (a body's own alternatives replace the inherited ones, from lower/state_graph.go collectGroupTransitions), StateGraph.StartOf, lowerEntryTransition (typed EntryTransitionShapeError for a trigger or effect, EntryTransitionTargetError for a non-state target); lower/state_graph.go machineState (the machine's own entry behavior runs before its alternatives are tried); runtime/state_executor.go startIn, entryGuardHolds, enterStartOf (from initialize, transitionToInto, enterRegionsInto, which enters a region of a parallel state through the substate standing for it, so its entry behavior has run and its guards read that substate's own attributes; the state it descends to is the one the active configuration records for its region, the one events are scheduled from, and the one checked for completion, so an alternative into done completes the machine as it starts), completeIfDone (judges a composite state complete once its every region started in done), runtime/state_region_transition.go moveBetweenRegions, enterOutside; runtime/errors.go ErrNoEntryTransitionHolds; passes/state_transition.go checkEntryTransition (CodeEntryTransitionShape, CodeEntryTransitionTarget); view/behavior.go stateMachineNode (the state rendering draws each body's entry transitions as edges out of its start, in declaration order and carrying the guard as transition edges do, and marks only StateGraph.UnconditionalStart initial), view/mermaid.go writeStateNode ([*] --> s : [guard] inside the body) lower/transition_source_test.go:TestToStateGraph_GuardedEntryTransitionsKeepDeclarationOrder, :TestToStateGraph_EntryTransitionShape (trigger, effect, trigger after an unguarded start, pseudostate target), passes/state_transition_test.go:TestGuardedEntryTransitionIsLegal, :TestEntryTransitionShapeIsReported, :TestTransitionOutOfEntryActionIsLegal, :TestTriggeredTransitionOutOfEntryActionIsNotAVertex, :TestEntryActionTransitionIntoPseudostateIsNotAVertex, view/render_test.go:TestStateRenderingDrawsGuardedEntryTransitions (+ goldens state-entry.text.golden, state-entry.mermaid.golden: machine, composite-state and orthogonal-region alternatives), conformance state_entry_transition_guard_first.sysml, state_entry_transition_guard_second.sysml, state_entry_transition_default.sysml (unguarded alternative), state_entry_action_transition_guarded.sysml (named entry action, transition boot if c then s;), state_entry_transition_nested_regions.sysml (+ trace golden: machine, composite-state and orthogonal-region alternatives, the machine's entry assign read by its guards), state_entry_transition_reentry.sysml (+ trace golden: a composite state's alternatives re-tried on each entry, state_usages_independent.sysml starting each typed usage by its inherited entry transition), state_entry_transition_region_composite.sysml (+ trace golden: a region's transition into a composite state whose alternative chooses a nested state, which then answers the next signal), state_entry_transition_done.sysml (an alternative into done completes the machine as it starts, after its exit behavior), state_entry_transition_nested_done.sysml (a transition into a parallel state whose every region starts in done completes the machine), goldens state_entry_transition_guarded.sysml (guarded and unguarded alternatives after entry;, an entry assignment and a named entry action, in a composite state, an orthogonal region and an exhibited state; accepted clean by the pinned pilot), state_target_transition_placements.sysml (entry; then s; at each depth), robustness_test.go:no_entry_transition_guard_holds, :entry_transition_target_is_not_a_state, :entry_transition_carries_a_trigger, :entry_transition_into_done_completes_at_initialize, :named_entry_action_transition_into_done_completes_at_initialize (transition begin then done; and succession first begin then done; out of a named entry action complete the same way, a declared state done being entered instead), :own_entry_transitions_replace_inherited_ones (a specializing machine, a typed usage, a guarded typed usage and a typed orthogonal region start where their own alternatives say; a usage redeclaring only the entry behavior keeps the inherited start), :region_entry_transitions_into_done_complete_at_initialize, :nested_regions_into_done_complete_at_initialize, :transition_into_nested_regions_in_done_completes, :region_start_descends_through_entry_transitions, :region_entry_guards_read_the_region_state_attributes, :leaving_regions_descends_through_entry_transitions ✅ Faithful (every accepted shape, the trigger and effect rejections and the attribute-target rejection were refereed against the pinned pilot both ways, see pilot-differential.md; a pseudostate target cannot be refereed, the pilot having no choice/junction grammar, and an action-usage target the pilot accepts is reported here by the endpoint rule every transition is held to)
A transition out of a state body's own entry action names the state it starts in (entry action initial { } transition initial then off;), the entry action standing in for a start pseudostate (SysML v2 §7.19.3) ast/state_entry.go EntryActions/StateEntryActions/IsEntryAction; resolve/transition.go startAction; passes/state_transition.go machine.startActions; lower/state_graph.go startsAt resolve/transition_test.go, passes/state_transition_test.go:TestTransitionOutOfEntryActionIsLegal, lower/state_notation_test.go, state_entry_action_transition_initial.sysml conformance ✅ Faithful (an ordinary action named as an endpoint is still reported)
Termination when a transition reaches done state_executor.go completeIfDone (see the completion rule below) state_simple.sysml ✅ Faithful
State entry actions state_executor.go:749 enterState state_do_behavior.sysml ✅ Faithful
State exit actions state_executor.go:810 exitState state_transition_effect.sysml ✅ Faithful
An entry/do/exit action given by reference (entry warmUp;) is a performed action usage subsetting the referenced action (StateActionUsage → PerformedActionUsage → PerformActionUsageDeclaration) parser/behavior.go parseStateSubaction; parser/defusage.go parsePerformedActionReference tests/parser/testdata/parse/state_subaction_reference.golden, parser/state_subaction_test.go, parser/negative_test.go:entry_reference_no_semicolon, resolve/state_subaction_test.go, runtime/state_behavior_test.go:TestStateSubactionByReferencePerformsAction ✅ Faithful
State do behavior runs while its state is active, one action per round state_executor.go startDoActivity, runDoRound state_do_behavior.sysml, state_do_activity_test.go ✅ Faithful
A do behavior is one performance that may wait: an accept after/accept at in its flow parks it on the shared clock and it is the state's action for the round its instant comes in, an accept Sig parks it until a matching message is in flight (rather than deadlocking, as a standalone action with nothing to post one does), and the machine — its transitions, its other regions' do behaviors, its timed exit — goes on around it. A do behavior performs once: when its body ends the state has completed (StatePerformances.kerml StatePerformance: succession [1] entry then [*] middle; succession [*] middle then [1] exit) runtime/state_statements.go doRun, startDoRun, doRun.resume/resumable/clockWaits (the behavior runs on a body coroutine, bodyRun with awaitsMessages, paused by action_executor.go/action_subflow.go through Context.pauseForClock/pauseForMessage where its flow, or the action a node performs, waits); state_executor.go doAction.run/due/finished, runDoRound (resumes a paused behavior whose wait has ended), clockWaits/NextWait (the paused behaviors' waits are the machine's, so an advance stops at them and Waiting on the clock lists them), HasPendingDoWork; a signal a paused behavior is parked at an accept for is one the machine takes: AcceptsMessage/reactsTo/takesMessage (so HasPendingSignal, and ObjectBehavior.hasPendingWork through it, count it as the object's work), Decide and siblingsAccepting ask the run through doBehaviorsTaking → state_statements.go doRun.acceptsMessage → action_executor.go ActionExecutor.acceptsMessage, so %send posts it rather than refusing it; the dispatch (broadcastEvent) and its preview (decide) select the takers by one rule: of the transitions selectTransitions picks, the one chooseTransitions draws for each candidate fires (drawn once, before the takers are settled, so the preview, the takers and the firing follow one choice), and a do behavior goes on with the message (resumeDoBehaviors → doRun.offer, the message read as the run's mail through Context.readingMail/acceptable/takeAcceptable, so it is consumed once) unless a transition chosen for it leaves the behavior's state (leftByChosen, leaves: the transition exits its source and, unless its target — the states firing it enters, statesEntered: the branch a choice or junction routes along under the guards as they stand, a fork's branches, the state a history restores — lies inside the source, the states enclosing the source up to the innermost one also enclosing the target, as transitionToInto exits them) — the transition ending the state is the message's only taker there, while a transition between the state's own substates, and one in a sibling region, share the one dispatch with the do behavior; Dispatch.Resumed/Decision.Resumes name the behaviors that went on, an event they took is neither deferred nor reported dropped (advance.go dueProgress.noteDispatch) conformance state_do_action_timed_accept_cancelled_on_exit.sysml + trace golden, state_do_action_declaration_order.sysml + trace golden, state_concurrent_do_action_bodies_timed.sysml + trace goldens, robustness_test.go:testStateDoBodyAcceptWaitsForTheMessage (the machine suspends in its state with no do work due; the message posted later makes the body due and it finishes), :testStateDoBodyAcceptIsDecidedForASend (the previews take the signal and leave the body parked; the dispatch consumes it once), :testStateDoBodyAcceptYieldsToATransition (Decide and the dispatch agree the transition out of the state is the only taker; the body is ended with the state, the message consumed once), :testStateDoBodyAcceptGoesOnAcrossASubstateTransition (a transition between two substates leaves the state active, so its do behavior goes on with the message the transition fires on, as decided; the message consumed once), :testStateDoBodyAcceptYieldsToASubstateTransitionLeavingIt (a transition from a substate to a state outside takes the message alone), :testStateDoBodyAcceptFollowsTheTransitionChosen (of two transitions a substate enables, the one chosen stays inside, so the do behavior goes on with the message, the alternative leaving notwithstanding), :testStateDoBodyAcceptFollowsTheChoiceBranchTaken (a transition into a choice whose guarded branch stays inside and whose default leaves: the branch the guard selects decides whether the do behavior goes on), :testStateDoBodyAcceptSharesTheDispatchWithARegion (a do behavior in one region and a transition in the other both take the one dispatch, as decided), classifier_behavior_test.go:TestStateDoBehaviorAwaitingAMessageIsWokenByASibling (the message a sibling object sends is work of the object whose do behavior is parked for it, and wakes it), repl/send_test.go:TestSendReachesADoBehaviorParkedAtItsAccept, :testStateDoBodyNestedAcceptCancelledOnExit; cmd/sysml -instantiate … -state … -advance 20 on a do action poll { action wait accept after 3 [SI::s]; then action count assign ticks := ticks + 1; } ends in finished with ticks = 1 ✅ Faithful
An entry or exit behavior, or a transition effect, is performed whole at the instant it is triggered (TransitionPerformances.kerml: transitionLinkSource then effect, effect then transitionLink.laterOccurrence; StatePerformances.kerml: entry then middle): a body of theirs whose flow waits on the clock is refused with a typed error naming the behavior and the wait, no node after the wait runs, and the clock does not move runtime/state_statements.go executeBehavior (Context.holdClock around the run), runtime/action_body_run.go holdClock/driveClock (ErrStateBehaviorWaits, raised where the flow would otherwise advance the clock itself) robustness_test.go:testStateEntryBodyWaitsForTheClock (at initialize), :testStateExitBodyWaitsForTheClock, :testTransitionEffectBodyWaitsForTheClock ✅ Faithful (self-assessed: the library orders a state's entry before its middle and a transition's effect before the target's entry, at one transitionLink instant; a blocking entry would leave the state neither entered nor not, which nothing in the library describes. The pinned validator accepts the shape, as it has no execution surface for state machines)
An entry, do or exit behavior written as an inline action body (entry action { … }, do action named { … }, exit action { … }) executes the statements it states, locals and loops among them, in any nesting of composite states; an empty body is a behavior that does nothing. A braced block without the keyword (entry { … }, do { … }, exit { … }, a transition's do { … }; SysML.xtext StateActionUsage/EffectBehaviorUsage: PerformedActionUsage ActionBody) is one anonymous action usage with that body — the same tree as entry action { … }, the keyword left empty so the spelling is kept — so its declarations are local to the block and shadow the state's, and the block is one behavior parser/behavior.go parseBracedActionUsage (from parseStateSubactionBlock, parseTransitionEffect); lower/state_behavior.go LowerBehaviors/lowerStateBehavior (the body is lowered to a Block, its locals in the block's own frame), lower/state_graph.go StateGraph.Behaviors; runtime/state_statements.go executeBehavior/stateStmtHost tests/parser/testdata/parse/state_anonymous_action_body.golden, state_braced_block_one_action.golden (the braced and the action spellings, the same structure), state_anonymous_action_body.sysml + trace golden (entry/do/exit ordering, nesting, empty bodies), state_braced_block_local_attribute.sysml + trace golden (a k declared in each of the four blocks shadows the machine's, which stays untouched), state_transition_braced_do_then_accept.sysml + trace golden (a transition's block of three statements, an accept-triggered follow-on with its own), robustness_test.go:testEmptyAnonymousActionBody, :testNonTerminatingAnonymousDoBody (ErrStepLimitExceeded), robustness_braced_block_test.go (empty blocks, a block of one terminate, an unbound name in a block, a block-local attribute reached from outside refused as unresolved) ✅ Faithful
An inline entry, do or exit body that states a token flow of its own — successions (first start; then action a; then done;, first a then b;), forks, joins, decisions with guards, and action nodes with a flow of their own — runs that flow as a standalone action's body does: then done completes the behavior, the attributes the body declares are the performance's own (defaults evaluated where written, a node's assignment read by the next, shadowing the machine's without writing it), a dangling or unstartable succession, or the body or a node of it declaring return, is a typed error before any node runs; a flow no first starts begins at its one node no succession leads to (do action poll { action wait accept after 3 [s]; then action count assign ticks := ticks + 1; }), two such nodes or a cycle leaving the start unstated and reported, in the flow a nested node states as in the body's own lower/state_behavior.go lowerBehaviorBody (a body stating a flow, statesOwnFlow, is lowered through ToActionGraph to a stated Block, lower/case_body.go StartFlow/CaseFlowStart giving it its declaration-order start; one stating none stays a statement block), lower/action_subflow.go lowerActionNode (a nested node's flow is started the same way); runtime/state_statements.go stateStmtHost.runFlow/runOwnFlow/setFeature (the block runs through the behavior's own ActionExecutor: checkResultParameters, declareRootFeatures, declareAcceptPayloads, initializeAttributes, runSubflow; noFlowStart names the unpreceded nodes or the cycle) tests/parser/testdata/parse/state_action_body_successions.golden, lower/state_behavior_test.go, state_do_action_successions_first_start.sysml + trace golden, state_do_action_successions_named_first.sysml, state_do_action_fork_join_decision.sysml, state_do_action_body_attributes.sysml, state_entry_exit_action_successions.sysml + trace golden, robustness_test.go:testStateBlockNodeOwnFlowRuns, :testStateDoBodyNodeReturnParameter, :testStateDoBodyReturnParameter, :testStateDoBodyDanglingSuccession, :testStateDoBodyFirstThenUndefined, :testStateDoBodyFlowWithoutStart, :testStateDoBodyNestedNodeDanglingSuccession, :testStateEntryBodyDanglingSuccession, :testStateDoBodyFlowThatNeverEnds, :testStateDoBodyFlowWithTwoStarts, :testStateDoBodyStartsAtItsUnprecededStep, :testActionFlowStartsAtItsUnprecededStep, :testActionFlowWithTwoStarts, :testActionFlowCycleWithoutStart, :testStateDoBodyNestedNodeStartsAtItsUnprecededStep, :testActionNestedNodeStartsAtItsUnprecededStep, :testActionNestedNodeWithTwoStarts, lower/state_behavior_test.go:TestStateBehaviorBodyStartsAtItsOneUnprecededStep, :TestStateBehaviorBodyWithAmbiguousStartKeepsNoInitial, lower/action_subflow_test.go:TestActionNodeSubflowStartsAtItsOneUnprecededStep, :TestActionNodeSubflowWithoutOneStartKeepsNoInitial, lower/action_succession_test.go:TestStartFlow, conformance state_do_action_declaration_order.sysml + trace golden ✅ Faithful
A state machine's own entry, do and exit behaviors (state def M { entry action { … } then s; … exit action { … } }) frame its run whether or not it has orthogonal regions of its own: the entry behavior runs when the machine starts, before its entry transitions are tried and the start state entered, the do behavior runs alongside its states, and the exit behavior runs once a completing transition has left its last state (SysML v2 §7.16 StateDefinition: a state definition is itself a StateAction with entryAction, doAction, exitAction) lower/state_graph.go StateGraph.Machine, machineState (the graph-only root state, built for every machine); runtime/state_executor.go enterMachine (from initialize), exitMachine (from completeIfDone) conformance state_machine_own_behaviors.sysml (a machine without regions; on the earlier reading its own entry and exit behaviors were skipped), state_entry_transition_nested_regions.sysml (an entry assign read by the machine's own guards), state_parallel_entry_behavior.sysml (a parallel machine) ✅ Faithful
An inline do body is interrupted where a transition out of its state is triggered (§7.18.3: the source state's do action, "if it is still being performed, is interrupted"), in every spelling of the body (do action { … }, do action named { … }, the braced do { … }): a do round runs one statement of the body — a statement of a for/while/loop iteration, of a nested block or of a branch taken is one of its own, one step of a token flow the body states (each of its tokens one node) is one — then yields, so the pending statements of a body under way are dropped with the behavior when the state is left, and orthogonal regions' inline bodies interleave statement by statement, the order within a round the do round's choice runtime/state_statements.go doRun (startDoRun, resume, resumable: a body run with bodyRun.yields pauses at the statement boundary after the statement it performed, bodyPause.yielded, and is due again in the next round); runtime/statements.go stmtEngine.run/loop/forLoop/blockFlow (Context.yieldBody before the next statement, iteration or node once one performed); runtime/action_subflow.go driveSubflow (a stated flow yields before the next node a token performs, control nodes and waits aside); state_executor.go runDoRound, stopDoAction → bodyRun.end (the frames of a body yielded between statements are abandoned as those of one paused on a wait are) state_do_body_interrupted_by_signal.sysml + trace golden (s1, the accept, the exit behavior, neither s2 nor s3); state_concurrent_inline_do_bodies.sysml + .expected.json + trace goldens (a for and an if in two regions' inline bodies, the four interleavings of state_concurrent_do); state_anonymous_do_atomic.sysml + .expected.json + trace goldens (one inline action of three statements per region: 124356 in entry order, the same eight values as state_concurrent_do, whose braced do { … } is the same anonymous action); state_do_action_successions_first_start.trace.golden (one flow step a round); state_terminate_entry_do_exit_behaviors.trace.golden (terminate reached in the round after the statement before it); robustness_resumable_inline_do_body_test.go (a for body left mid-loop drops its pending iterations with no frame, do work, clock wait or goroutine left behind; a body left at a clock wait after a loop leaves the clock empty; a non-terminating loop and a non-terminating stated flow each end with ErrStepLimitExceeded) ✅ Faithful
A statement of an inline body may perform an action (entry action { assign c := c + 1; perform Bump; }), the performed action being lowered as an effect rather than an unsupported usage lower/action_graph.go lowerStatement (an action usage naming what it performs → Effect{EffectPerform}); runtime/state_statements.go stateStmtHost.effect state_anonymous_body_perform.sysml conformance ✅ Faithful
A typed do, entry or exit usage whose body declares the pins of the action it performs and nothing else (do action poll : Poll { inout n = ticks; }; SysML v2 §7.16: a StateSubactionMembership owns an ActionUsage, whose body may bind its parameters as any action node's does) performs that action as the one node of the behavior's flow, its in and inout pins read from the bound features when the performance starts and an inout pin written back to its feature when the performance ends — not before, so a performance the state's exit abandons writes nothing back. A behavior that both performs an action and states executable steps of its own is still reported rather than one of the two being chosen silently lower/state_behavior.go lowerStateBehavior (a performing usage with declaresOnlyFeatures lowers to a one-node Block through lowerBlockFlow; one with steps to Unsupported), lower/action_graph.go lowerFeatures/inoutValueBinding (an inout pin valued by a feature name is a PinBinding to that feature, FromValue marking it as read from the pin's value); runtime/action_frame.go writeOutputs (an inout pin valued by a name the enclosing performance holds no feature of — an enumeration literal, inout mode = Mode::idle or an imported idle — was initialized by it and writes nothing back; one valued by a feature name that the performance holds writes back to it, and an out pin bound to a name nothing holds is still ErrBindingEnd); runtime/state_statements.go stateStmtHost.runFlow (the node performs through the behavior's ActionExecutor, its bindings checked as a standalone action's are: ErrUnboundParameter names an in pin nothing binds, ErrUnresolvedReference a pin bound to a feature the state does not declare) conformance state_do_action_typed_inout_writes_back.sysml + trace golden (ticks counted once, written back at the performance's end), state_do_action_typed_inout_cancelled_on_exit.sysml + trace golden (the exit at 10 s ends a performance paused until 33 s; ticks keeps its 5), state_do_action_typed_inout_valued_by_a_literal.sysml + .expected.json (inout mode = Mode::idle beside inout n = ticks: ticks written back, the literal written nowhere), robustness_test.go:testStateDoTypedActionInputUnbound, :testStateDoTypedActionPinBoundToMissingFeature, :testStateDoTypedActionInoutValuedByAnImportedLiteral (inout mode = idle through import Mode::*), :testBehaviorPerformingAnActionAndStatingABody (the mixed form) ✅ Faithful for the pin-binding form; ⚠️ Approximate for the mixed form (rejected at execution, not at parse: the pinned SysML.xtext reads entry action mixed : Bump { … } as a PerformActionUsage with a body (StateActionUsage → PerformedActionUsage ActionBody), and neither the pinned validator nor we report anything on either form, so what is unadjudicated is the meaning of executable steps beside a performed action, which the reference cannot execute. Whether an inout pin of an abandoned performance writes back is self-assessed from Performances.kerml: a binding of a parameter holds for the whole performance, and an abandoned one has no end to hand its value out at)
Concurrently active states interleave their do behaviors one statement per round — a braced do { … } and a do action { … } alike, each one inline body; which of the states with an action due acts first in a round is a choice point (KerML StatePerformances.kerml: the do behavior is a middle performance of its state, ordered after its entry and before its exit, and no succession joins a step of one region's to a step of another's) state_executor.go runDoRound (the behaviors with an action due — a next behavior, or a paused one whose wait has ended — each perform one action, in the order chooseDoAction draws from the scheduling policy: entry order under declared and reverse, a draw under seed:<n>, every order under explore), chooseDoAction/regionOrderChoice (choice do round at t=0.0: states lwork, rwork react (unordered; took lwork first); one due alone is no choice), doAction.due state_concurrent_do.sysml + .expected.json (outcomes: the four values two rounds of two orders reach, derived in the semantic oracle) + trace goldens under the default, declared and seed:1; state_concurrent_do_action_bodies_timed.sysml + .expected.json + trace goldens (two action bodies each parked at accept after 2 [s], due together at t=2.0); state_do_action_test.go:TestDoBehaviorsOfOrthogonalRegionsInterleave ✅ Faithful
Exiting a state abandons the rest of its do behavior, a behavior paused mid-flow included: its wait leaves the clock, a message sent afterwards wakes nothing, and no node after the wait runs (StatePerformances.kerml: middle performances end before the state's exit) state_executor.go stopDoAction (from exitState; ends the doRun paused on the clock or for a message), Release (a machine withdrawn from the clock ends them too, the action a paused body performs included, through action_body_run.go bodyRun.end; classifier_behavior.go ObjectBehavior.leaveClock releases so, so a behavior dropped with its object's failed start leaves no paused work on the clock); state_statements.go doRun.end state_do_action_test.go:TestDoBehaviorIsCancelledWhenItsStateIsExited, conformance state_do_action_timed_accept_cancelled_on_exit.sysml + trace golden (the exit at t=10.0 and nothing at t=12.0), state_do_action_signal_accept_cancelled_on_exit.sysml + trace golden (a do body parked at an accept for a signal nothing sends), state_do_action_typed_inout_cancelled_on_exit.sysml + trace golden, robustness_test.go:testStateDoBodyNestedAcceptCancelledOnExit (the accept one node deep; no wait left on the clock, a message after the exit revives nothing), :clock_advance (a do behavior paused in a timed action leaves the clock with its dropped machine: after a failed start nothing waits and an advance runs nothing of it) ✅ Faithful
A state completes only once its do behavior has finished state_executor.go scheduleCompletionTransitions state_do_activity_test.go:TestCompletionWaitsForTheDoBehavior ✅ Faithful
A machine completes when a transition reaches done, the end shot StateAction::done the standard library gives every state (stdlib/Systems Library/Actions.sysml, written in a state body by the OMG pilot corpus StateTest.sysml): the states it leaves run their exit actions, the executor reports StateCompleted, an orthogonal machine completes only once every concurrent region has reached it, so a region completing leaves its siblings running. done written in a composite state's body is that composite's own endShot (States.sysml: ref state done: StateAction :>> Action::done, StatePerformance::endShot;), so the composite completes when its do behavior has ended and every one of its regions is at done: its nil-trigger transitions are scheduled as a leaf's are — guards read then, one event per enabled transition at the current instant, ordered as any completion event — and the machine ends only when its own top-level regions are all at done; a completed composite with no enabled completion transition stays completed and active, its triggered transitions still firing, and the machine runs on. Completion is stated, not inferred — a state with no outgoing transition does not complete, because an ancestor or cross-region transition may still leave it — and a machine declaring a state of its own named done reaches that state instead lower/state_graph.go targetVertex, completion (a completion vertex per machine and per region, recorded in the graph's completion set), StateGraph.Completes; lower/state_graph.go completionOwner (a done among a declared state's members is that state's completion); runtime/state_executor.go completeIfDone → scheduleCompletedComposites, completedComposite, stateComplete, machineComplete (over TopRegions), regionComplete, settleDoActions and runtime/state_region_transition.go consume Completes, not syntax lower/state_completion_test.go, runtime/state_completion_test.go, runtime/testdata/conformance/state_completion_done, state_completion_absent, state_completion_all_regions, state_completion_nested_regions, state_completion_through_pseudostate, state_outer_completion_to_next, state_composite_completion_then_machine_done, state_composite_completion_nested, state_composite_completion_inside_region, state_entry_transition_nested_done, state_completion_nested_regions_stay_active, state_entry_transition_nested_done_stay_active (+ their trace goldens), state_completion_test.go:TestCompositeCompletionQueuesItsTransitionsLikeALeaf, :TestCompositeCompletesOnceItsDoBehaviorAndItsBodyHaveBothEnded, :TestCompletedCompositeWithoutCompletionTransitionStaysActive, snapshot_test.go:TestSnapshotStepsReachAPendingCompositeCompletionAndAHeldDeferral, robustness_test.go:state_event_after_completion, state_completion_rests_in_done, state_nested_region_completion_keeps_siblings_running, parser/removed_state_final_marker_test.go ✅ Faithful
A signal trigger written when <name> or a defer <name> (OpenSysML spellings) names an event, not a model element, and is left unresolved; one whose name no declaration visible in scope and no send payload type or feature in the workspace accounts for is reported as the lint undeclared-signal, with the resolver's nearest names as suggestions. Its resolution and run-time matching by name are unchanged passes/lint_undeclared_signal.go UndeclaredSignalPass; passes/lint_signal_gather.go passes/lints_test.go TestUndeclaredSignal*; workspace/model/lints_test.go; state_signal_discriminate.sysml ⚠️ Approximate (a lint over extension notation: a warning in every mode, switched off by its code)
Deferred events retained while a deferring state is active, delivered afterwards in arrival order — a message on the bus addressed to the machine that its active configuration defers, accepting it nowhere, is the machine's to take and hold, as one it accepts is ; a deferring state holds the occurrence back from every enabled transition except one sourced by that state or by a state nested in it (with several deferring states each must be so overridden, else the occurrence is deferred whole), the deferral being determined rather than a choice point; a held occurrence is released, with its arrival ID and the current clock time, to whatever configuration the deferring state's exit leaves parser/behavior.go parseDeferMember (defer <event>[, <event>]*;); lower/state_graph.go stateNodeFromUsage, collectDeferred; state_executor.go defersEvent, deferringStates, deferralOutranks (from selectTransitions, shared by dispatchEvent and Decide), encloses, recallDeferredEvents, acceptableMessage, defersMessage tests/parser/testdata/parse/state_defer.golden, parser/state_notation_test.go:TestDeferMemberParsing, lower/state_notation_test.go:TestToStateGraph_DeferNotation, state_deferred_event.sysml + state_undeferred_event.sysml conformance, state_deferral_outranks_sibling_region, state_deferral_outranks_enclosing_state, state_deferral_nested_override, state_deferral_nested_outranks_sibling_region (+ trace goldens), state_deferred_test.go (TestDeferralOutranksASiblingRegionsTransition, TestDeferralOutranksAnEnclosingStatesTransition, TestTransitionNestedInTheDeferringStateOverridesDeferral, TestDeferralInEachRegionMustBeOverriddenForTheEventToFire, TestEventBlockedByAGuardIsStillDeferred), snapshot_test.go:TestSnapshotStepsReachAPendingCompositeCompletionAndAHeldDeferral, signal_injection_test.go:TestPostedMessageTheActiveStateDefersIsHeld, repl/send_test.go:TestSendDefersWhatTheActiveStateDefers ✅ Faithful
Earliest transfer first (Occurrence::incomingTransferSort defaults to earlierFirstIncomingTransferSort): a recalled event is dispatched before the events that arrived while it was deferred, and a completion event before either executor_common.go eventHeap.Less, isCompletionEvent; state_executor.go recallDeferredEvents state_deferred_test.go:TestRecalledEventPrecedesLaterArrivals ✅ Faithful
A transition out of a composite state is enabled while any of its substates is active: matching walks outward from every active leaf through the lowered containment (StateGraph.ParentState), the innermost enabled transition wins for the same event (KerML StatePerformances::StatePerformance::acceptable, whose isDispatch invariant lets an enclosing state performance accept a transfer only when no substate performance in the dispatch scope accepted it), and a false guard does not consume it state_executor.go broadcastEvent, selectTransitions, losesToNestedTransition, nestedIn, activeLeaves, fireFrom, enabledTransitions, acceptsSignal/acceptsSignalFrom (an enclosing state's accept takes a signal off the bus) conformance/state_composite_outer_transition.sysml, :state_composite_inner_priority.sysml, state_composite_transition_test.go:TestCompositeStateHandlesEventItsSubstateDoesNot, :TestFalseGuardInsideACompositeStateFallsOutward, :TestTransitionOutOfAnIntermediateCompositeStateKeepsItsOwnerActive, :TestOuterCompositeStateStillHandlesItsEventAfterASubstateMoved, robustness_test.go:signal_no_level_of_a_composite_state_accepts ✅ Faithful
Two transitions out of one state enabled by one event: each is a StateTransitionPerformance (Kernel Semantic Library StatePerformances.kerml) whose transitionLink is HappensBefore[0..1], and nothing in the library orders one against another, so which fires is the executor's pick; the innermost-wins rule between a substate's transition and its enclosing state's is SysML v2/KerML order, not a pick state_executor.go enabledTransitions, transitionEnabled, probeTransition (every transition out of the state is matched against the event and its guard and join evaluated — a call trigger's arguments bound for the guard and unbound after — the first enabled fires under the default policy; the ones after it are read in a beginProbe preview that restores the budget, the trace, every effect and the object identities the preview took, and one that fails to evaluate is no alternative and no error, recorded as a guard-unevaluable note by unevaluableTransition), chooseTransition, transitionChoice (several enabled are a choice point naming the state, the trigger and the transitions by position and target; the pick among them is made only for a candidate that survived conflict resolution); the notes ride on the dispatchCandidate into fireFrom, and transitionDecided records them once the firing transition's guard has been read for the last time — a candidate another region's firing disabled meanwhile reports nothing — after selectCandidates has kept one candidate per transition and losesToNestedTransition has dropped a composite state's candidate to a nested one (a transition several regions select through their enclosing state is one choice, and ancestor priority is never among the alternatives); state_change_trigger.go observeChangeConditions, probeChangeGuard, risenChangeTransitions (every change transition risen by one poll is collected the same way: a state's guards are read live until the first enabled, the ones after it in a beginProbe preview, and one that fails to evaluate is no alternative and no error, noted as guard-unevaluable and left armed; the first declared fires) conformance/state_choice_transition_conflict.sysml + .expected.json (both outcomes listed as admissible) + trace golden; conformance/state_choice_ancestor_priority_not_reported.sysml + .expected.json + trace golden (no choice line); conformance/state_choice_shared_ancestor_regions.sysml + .expected.json + trace golden (one choice line for two regions); conformance/state_choice_ancestor_outranked_not_reported.sysml + .expected.json + trace golden (no choice line); conformance/state_choice_unevaluable_transition.sysml + .expected.json + trace golden; conformance/state_choice_change_transition_conflict.sysml + .expected.json (both outcomes) + trace golden; choice_test.go:TestTransitionChoiceNamesStateAndEvent, :TestAncestorPriorityIsNotAChoice, :TestSharedAncestorChoiceIsReportedOnce, :TestAncestorChoiceSuppressedByNestedTransitionIsNotReported, :TestLaterGuardErrorIsNotAChoiceNorAFailure, :TestFirstTransitionFailureStillFailsTheRun, :TestNotesOfATransitionBlockedBeforeFiringAreDropped, :TestNotesOfATransitionFailingInItsEffectAreKept, :TestChangeTransitionChoice, :TestLaterChangeGuardErrorIsNotAChoiceNorAFailure, :TestChangeTransitionChoiceUnderHierarchyAndRegions, :TestProbedGuardLeavesObjectIdentitiesUntouched; grpc/choice_test.go:TestExecuteState_ChoicePointDiagnostics; repl/choice_test.go:TestAdvanceReportsChoicePoints ✅ Faithful (declaration order is tool-defined and kept; the pick is reported, and the ordered case is not)
Executors due at one instant of the clock are unordered: two performances waiting on the same TimeInstantValue (Kernel Semantic Library Clocks.kerml) are HappensBefore-linked by nothing, so which of an action token and a state transition, of two state machines, or of two actions runs first when their instant comes is the executor's pick advance.go Context.runDue (the executors with work due at the current instant, in creation order, are a choice point of kind ChoiceDueOrder placed at t=<instant> and naming each executor — action <name>, state machine <name> of object #n — and the one run first; the pick is drawn by scheduler.go scheduler.pickDue under the run's policy — the last created under reverse, the default, so a run holding one executor is unchanged; the first created under declared; a draw under seed:<n>; under explore, the exploring run's next slot, scheduler.describe giving it the due-order kind and alternatives so the witness names the executor run first and every order of the due executors is a linearization replayed; one executor alone due is no choice and records nothing; within one executor, its work runs in the order it always has; the executors polling a change condition once the definite work at the instant has settled are drawn the same way, so which watcher sees what another's reaction changed is the pick); choice.go ChoiceDueOrder, ChoicePoint.String (choice at t=5.0: due action watcher, state machine blinking of object #1 (unordered; ran state machine blinking of object #1 first)) conformance/clock_action_state_due_together.sysml + .expected.json (outcomes: either may run first) + trace goldens under the default, declared and seed:1; conformance/clock_two_machines_share_clock.trace.golden (two machines due together); conformance/clock_action_accept_after.trace.golden, clock_action_accept_at.trace.golden, clock_do_action_while_token_waits.trace.golden (one executor due: no choice line); scheduler_test.go:TestDueOrderChoice (the pick under each policy, and none recorded for one executor), :TestChangeWatchOrderChoice (two machines polling one condition: the pick under each policy decides which sees the raised value and which what the first left); repl/runtime_commands_test.go:TestAdvanceMovesActionAndStateDebuggersTogether; explore_test.go:TestExploreDueOrder (three machines due together explore to their six orders, each witnessed by a due-order choice among 3 then among 2, and the driven policies each take one of them), conformance_test.go:TestExecutionConformance (clock_action_state_due_together explored: both orders reached, no other); repl/explore.go exploreRunFor (RunFor under explore: the behaviors named started on one clock in every run, advanced once, their outcomes joined by Context.JointOutcome under each behavior's name), repl/explore_test.go:TestRunForExploresEveryDueOrder, cmd/sysml/run_test.go:TestExploreAdvanceRunsBehaviorsOnOneClock ✅ Faithful (the default order — the executor started last first — is tool-defined and pinned by the trace golden; the pick is reported, and one executor alone is not; explore reaches every order)
Taking a transition out of a composite state exits the states being left innermost-first, then runs the effect, then enters the target; leaving a composite with orthogonal regions exits every active region first state_executor.go transitionToInto (exit to the least common ancestor), exitStates/exitedByAncestorRegion (a state whose region another state being left owns is exited by that state's recursive region teardown, so the chain walk does not exit it a second time), state_region_transition.go leaveRegion (which instead clears the region a state is active in, recording its history, before exiting that state, since a region's active state may be nested below it), moveBetweenRegions, exitRegionTo, leaveTopRegions conformance/state_composite_exit_order.sysml + trace golden (two levels of nesting), :state_composite_orthogonal_exit.sysml + trace golden, :state_composite_nested_regions_exit_once.sysml + trace golden (a region holding a composite with a region of its own: every level exits exactly once), robustness_test.go:exit_of_nested_regions_with_a_history_pseudostate ✅ Faithful
Every state transition is external — its source is exited, its effect runs, and its target is entered afresh — including when the target is the source itself or one of its substates: KerML orders transitionLinkSource then effect and effect then transitionLink.laterOccurrence (Kernel Semantic Library TransitionPerformances.kerml, TransitionPerformance), the source's exit is its last step (StatePerformances.kerml, StatePerformance: entry then middle, middle then exit) and a state transition orders that exit after its guard (StateTransitionPerformance: guard then transitionLinkSource.exit). So a simple state transitioning to itself runs exit, effect, entry, and a composite is exited and re-entered around the effect with its regions restarting at their initial states state_executor.go transitionToInto, encloses (the source is the target or contains it); state_region_transition.go moveBetweenRegions (the exit boundary is the source's parent where the source encloses the target, so a composite self-transitioning inside an orthogonal region is exited too, while its sibling regions stay as they were) conformance/state_composite_self_transition.sysml + trace golden (exit substate, exit composite, effect, re-enter composite and its region), :state_composite_to_substate.sysml + trace golden, :state_composite_self_transition_in_region.sysml + trace golden, :w6e_state_simple_self_transition.sysml + trace golden (exit, effect, entry on a simple state), parse/w6e_state_self_transition.golden, state_composite_transition_test.go:TestOneEventTakesACompositesTransitionOnce, w6e_state_self_transition_test.go:TestSimpleSelfTransitionExitsAndReEntersItsState, :TestSimpleSelfTransitionInARegionLeavesSiblingRegionsAlone, robustness_test.go:composite_self_transition_with_no_substate_to_re_enter, w6e_robustness_test.go:TestSimpleSelfTransitionThatNeverSettlesIsBounded, :TestSimpleSelfTransitionEffectReadingAnUnknownFeatureIsReported; conformance/state_transition_guard_exit_effect_entry_order.sysml + trace golden (the order made observable through values: the guard reads the pre-exit value, the effect the exit's, the entry the effect's — derived from the same library text in the semantic oracle) ✅ Faithful (self-assessed: the pinned reference has no execution surface for state machines, so this row is assessed against the library text cited above and our own goldens, not against another implementation. One residual is tool-defined: a composite state stating no starting substate — no initial and no region — is re-entered with no substate active, since nothing names the substate to enter)
An event reaches only the regions still active when it is dispatched state_executor.go broadcastEvent (each active leaf is re-checked before it is offered the event) state_deferred_test.go:TestExitedNestedRegionDoesNotReactToTheSameEvent ✅ Faithful
Run-to-completion bounds one dispatch: only the leaves active when the event was taken off the queue are offered it, each taking at most one transition, so a state the event entered does not react to it as well state_executor.go broadcastEvent, selectTransitions (the transitions are all selected against the configuration the event was dispatched for, then fired; leaves in sibling regions of one composite state select its transition once between them, and the dispatch ends once a transition reaching done completes the machine) state_composite_transition_test.go:TestOneEventTakesOneTransitionPerActiveLeaf, :TestOneEventTakesACompositesTransitionOnce ✅ Faithful
Concurrent regions react to one event each, and a transition one region takes disables only the states it left; the order the regions react in is one the library leaves open (each StateTransitionPerformance is ordered against its own source and target only, StatePerformances.kerml; UML 2.5.1 §14.2.3.9.4 leaves the firing order of the transitions selected for one event undefined), so it is a choice point drawn a unit at a time — a firing is not atomic across regions: TransitionPerformances.kerml orders a firing's own source exit, effect and target entry and nothing between two firings in sibling regions — reported under every policy as choice on <trigger>: next <a>(exit), <b>(exit) (unordered; took <a>(exit) first) (a choice-point diagnostic of kind region order; one per draw among the firings with a unit ready, whatever depth their active state sits at, a firing whose source another's unit left performing nothing): declared and reverse take the firings whole in region declaration order, seed:<n> draws each unit as it draws every other pick, and explore runs every interleaving of the firings' units; a change occurrence raising the conditions of transitions in several regions is dispatched through the same draw state_executor.go dispatchInOrder (the candidates surviving losesToNestedTransition are the queues of one unitFront labelled on <event>, each firing yielding at its unit boundaries — source exit, effects, target entry — and a candidate whose leaf another's unit left dropping out; broadcastEvent and state_change_trigger.go pollChangeEvents both dispatch through it), state_unit_front.go unitFront.drain (the next unit drawn through scheduler.choose whenever two or more queues are ready, recorded as a ChoiceRegionOrder, a unit performing no behavior riding with the performing unit beside it; activeLeaves sorts the leaves by regionPath, the declaration index of every region between the machine and the leaf, rather than by depth, so pick 0 is declaration order), losesToNestedTransition; choice.go ChoiceRegionOrder conformance/state_explore_region_order.sysml + .expected.json (both orders listed as admissible, derived in the semantic oracle § Transitions in sibling regions enabled by one event: each fires, in which order is open) + trace golden, explore_test.go:TestExploreSiblingRegionOrder (exploration reaches both orders in two runs; reverse and declared take declaration order and report the choice with a1 taken; seed:1 and seed:8 reach the two effect orders, each replaying its run; once for an accepted signal and once for a change occurrence), conformance/state_change_region_order.sysml + .expected.json (both orders admissible for the change-triggered shape) + trace goldens under the default, declared and seed:1, choice_test.go:TestChangeTransitionChoiceUnderHierarchyAndRegions (a change poll reports the region order ahead of the one state's transition choice), :TestNotesOfATransitionBlockedBeforeFiringAreDropped (the region order is the one note of a dispatch whose second region was blocked by the first's effect), repl/choice_test.go:TestAdvanceReportsRegionOrderChoice, grpc/choice_test.go:TestExecuteState_RegionOrderChoiceDiagnostics, conformance/state_explore_region_order.seed-1.trace.golden (its draws recorded at the entry and at each firing unit), conformance/state_composite_region_depth_order.sysml + .expected.json (both orders admissible) + trace goldens (the deeper leaf's region is declared second and reacts second under the default), :state_composite_region_deeper_first.sysml + .expected.json + trace goldens (the mirror: it is declared first and reacts first under the default), :state_call_trigger_regions.sysml + .expected.json, :state_parallel_broadcast.sysml + .expected.json (both orders admissible), :state_typed_region_order.sysml + trace golden, state_composite_transition_test.go:TestOneRegionsInnerTransitionLeavesAConcurrentRegionsOwnTransition, conformance/state_firing_units_interleaved.sysml + .expected.json (two regions' firings of an exit and an effect each: the six linearizations of two chains of two listed as admissible, derived in the semantic oracle § Transitions in sibling regions enabled by one event: each fires, in which order is open) + trace goldens, robustness_region_order_test.go (firing_unit_order_naming_a_unit_of_no_firing: a replayed witness naming a unit no firing has is refused and the move rolled back) ✅ Faithful (every case whose orthogonal regions react to one event lists both orders as admissible, so the harness explores each and checks both are reached and no third is; state_typed_region_order names its two regions' states alike, so both orders are one outcome)
Entering the regions of a composite state, a fork's branches and a history's restored regions is concurrent (SysML v2 §7.18.1: parallel substates are performed concurrently; PSSM §8.5.5), so which region's next entry runs is a choice point, reported under every policy as choice entering <state>: next <a>(entry), <b>(entry) (unordered; took <a>(entry) first) — fork <name> for a fork's branches, whose branch effects are units of the same front and whose shared owner is entered once by the branch drawn first (a choice-point diagnostic of kind entry order; one per draw among the regions with a unit ready, a unit performing no behavior riding with the performing unit beside it unless its entry generates a completion event, whose place in the pool the draw decides): declared and reverse take region declaration order, seed:<n> draws each unit, explore runs every interleaving of the regions' entry units, and a replayed witness naming a region the front does not hold is refused and its move rolled back state_region_entry.go enterRegionsInto, enterRegions (one queue per region on a unitFront labelled entering <state>), enterForkBranches (fork <name>); state_unit_front.go unitFront.drain, performUnits; choice.go ChoiceEntryOrder; replay.go parseOrderChoice (entering, fork) conformance/state_region_entry_order.sysml + .expected.json (the six interleavings of two regions' two entries each listed as admissible, derived in the semantic oracle § Regions of a parallel state entered on one occurrence: each is entered, in which order is open) + trace goldens, :state_region_entry_order_uneven (three: one region of one entry against one of two), :state_region_entry_nested_front (six: a region entering a composite of two regions of its own, the inner front drawn inside the outer's unit), :state_fork_branch_order (four: the branch effects and the entries), :state_history_restore_order (eight: a deep history restoring two regions), explore_test.go:TestExploreSiblingRegionOrderNamesTypedRegions (a.r1(entry): same-named states in two regions are told apart by region), repl/explore_test.go:TestRunStateMachineExploresEveryRegionEntryOrder (the explore table names each run's entering draws), grpc/choice_test.go:TestExecuteState_RegionOrderChoiceDiagnostics (the entering diagnostic under declared and seed:), replay_test.go (an entering witness line replayed, a refused one rolled back), robustness_region_order_test.go (entry_order_naming_a_region_the_front_does_not_hold, deep_wide_front_beyond_the_run_budget), :state_completion_pool_entry_order (two regions' silently entered completing states: the completions' effects log in the draw's order, both orders under check), :state_completion_pool_history_order and :state_completion_pool_deep_history_order (the same through a shallow and a deep history's restore), :state_completion_pool_fork_order (through a fork's branches), :state_region_completes_at_own_done (a nested composite's done completes its own region, not the enclosing one), robustness_completion_order_test.go (entry_order_naming_a_completing_state_the_front_does_not_hold, completions_dispatched_in_the_replayed_draws_order, witness_ordering_the_pool_after_the_draw_is_left_over, wide_completing_front_beyond_the_run_budget) ✅ Faithful (the firing of a completion a region's entry enables is dispatched as a step of its own after the front settles, not drawn against the sibling region's entries, as every v2 completion is — the alignment note finding 11 adjudicates that it should be: the PSSM traces that want it inside the front run a UML initial transition's effect, which v2 can spell only as a completion transition's; the completion events two regions' entries generate are queued as each entry unit is performed, so the pool dispatches them in the entry draw's order under every policy, PSSM §8.5.9)
Exiting the regions of a composite state is concurrent as well (PSSM §8.5.5), so which region's next exit runs is a choice point, reported under every policy as choice exiting <state>: next <a>(exit), <b>(exit) (unordered; took <a>(exit) first) (a choice-point diagnostic of kind exit order; each region exits innermost first, the composite's own exit following every region's): declared and reverse take region declaration order, seed:<n> draws each unit, explore runs every interleaving, and a replayed witness naming a state not being left is refused and its move rolled back state_executor.go exitState, state_region_transition.go (one queue per region on a unitFront labelled exiting <state>); choice.go ChoiceExitOrder; replay.go parseOrderChoice (exiting) conformance/state_region_exit_order.sysml + .expected.json (the three orders of two regions' exits and the composite's own listed as admissible, derived in the semantic oracle § Regions of a parallel state left on one occurrence: each is exited, in which order is open) + trace goldens, :state_composite_orthogonal_exit + trace golden (gains the exiting line, order unchanged), scheduler_test.go (an exiting draw among the kinds one run records), robustness_region_order_test.go (exit_order_naming_a_state_not_being_left) ✅ Faithful
A do behavior evolves on its own thread beside the machine (PSSM §8.5.5; StatePerformances.kerml), so a due do step and a dispatch due at the same instant are unordered: under check, replay and explore the machine runs one unit at a time — one token move of one due do behavior's flow (a statement of an inline body, a step of a do behavior given as an action, a token inside a nested perform, an iteration of a loop), or the dispatch — and the draw is a choice point, reported as choice at t=<instant>: next do <state>, dispatch <event> (unordered; took do <state> first) (a choice-point diagnostic of kind step order; dispatch change <condition> for a change trigger risen). A message in flight is delivered behind the events already queued, so the dispatch drawn is the one the queue then makes: the signal by name (dispatch accept <signal>) once nothing is ahead of it, else the event ahead — a queued event nothing accepts hides the signal until the round closes. Only a dispatch that would take its occurrence — fire a transition, or let a do behavior parked at an accept go on — is drawn; one that would defer or drop it waits until no do move is due; a do body parked at an accept offers no move until its occurrence is dispatched. Events tied at the head are previewed each on its own: the acting ones are drawn against the step (one by name, several as the bare dispatch whose dispatch order is then among them alone) and a dropped one is not, so it hides no acting one. The fixed policies (declared, reverse, seed:<n>) run the whole round first and dispatch after it, so no default trace moves; explore and check enumerate the dispatch at each place within the round; a replayed witness naming a state the round does not offer or a dispatch that is not due is refused and its move rolled back state_executor.go oneUnit, dueDoActions, stepDue, chooseStepOrder, chooseDoAction, dueDispatch, eventActs (previewed and rolled back), runStep/runDoRound for the fixed policies; action_body_run.go bodyRun.steps, bodyPause.tokenStep (a do run pauses after one token move); action_subflow.go drawOneMove, stepSubflowSweep (the whole sweep under the fixed policies); check_moves.go enabledMoves, doMoves, dispatchMoves (the due do behaviors' token moves and the acting dispatch offered together); choice.go ChoiceStepOrder; replay.go parseOrderChoice (at t=) conformance/state_do_step_or_dispatch.sysml + .expected.json (two outcomes: the do step logged or the dispatch leaving the state first, derived in the semantic oracle § A do step and a dispatch due at one instant: which goes first is open) + trace goldens, conformance/state_do_step_among_completions.sysml + .expected.json (a region's do step at each place among the other regions' completion effects, themselves in either entry order: six outcomes) + trace goldens, conformance/state_do_step_or_tied_dispatch.sysml + .expected.json (two tied time triggers, one guarded on the step's effect: the unguarded one alone is drawn against the step, three outcomes) + trace goldens, conformance/state_do_step_cuts_typed_do.sysml + .expected.json (a do behavior given as a two-step action cut at either step or taken after it: two outcomes) + trace goldens, conformance/state_do_step_cuts_nested_perform.sysml + .expected.json (an inline do body performing that action between two assignments, cut at any of its moves: four outcomes) + trace goldens, conformance/state_do_step_cuts_control_node_body.sysml + .expected.json + .check.expected.json (an inline do body forking through a fork with a body of its own, which is a move the dispatch falls before or after: five outcomes, the fixed policies' among them, checked exact) + trace goldens, conformance/state_do_action_loop_timed_exit.sysml + .expected.json + .check.expected.json (a forked do loop through timed waits against a timed exit: four outcomes, the fixed policies' left = right = 1 among them, checked exhaustive), robustness_do_step_token_grain_test.go (a do flow that never rests against a queued dispatch ends at the dispatch or the do-step budget, under explore and check; a replayed step order line naming a do body parked at an accept is refused), cmd/sysml/spacecraft_showcase_test.go (the showcase's vehicle and ground station checked together reach the fixed policies' battery = 39 beside the cut sweeps' 41), step_order_tied_test.go (the step order names the acting tied event alone; every witness replays), step_order_pending_signal_test.go (a signal in flight is drawn as the queue would dispatch it: undrawn behind a queued event nothing accepts, behind a queued event that acts by that event's name, alone by its own; every witness replays), state_join_completion_* (a completion or timer that cannot act is not drawn, so the join's segment keeps waiting), replay_test.go (a step order line refused with the run restored), robustness_region_do_step_test.go (step_order_naming_a_state_with_no_due_do_step, step_order_naming_a_dispatch_not_at_the_head, step_order_at_a_unit_offering_no_draw, endless_do_behavior_under_explore_hits_the_do_step_budget) ✅ Faithful
The do behavior a state's entry starts runs concurrently with the entries left in the move — the sibling regions' on the same front and, for a composite or the machine, its own substates' (StatePerformances.kerml succession entry then middle, do subsetting middle and ordered against nothing a sibling region or a substate performs; SysML v2 §7.18.1; PSSM §8.5.5 and Entering 002: the do activity "executes concurrently with any subsequent Behaviors associated with entering the State, such as the entry Behaviors of substates"), so a do behavior begins as its state's entry unit ends, before the state's regions or serial body are entered, and each due token move of the do behaviors the move began is drawn against the entry units still ahead under the entry site's own draw — on a front, as a queue of its own, the entering <state> (or fork <name>, or a firing's on <event>) choice with do <state> an alternative beside the entry labels, choice entering <state>: next do <a>, <b>(entry) (unordered; took do <a> first), for as long as another queue has a unit left; down a serial body no front orders, each entry unit on the way against the due steps under the owner's entering <state> (entering <machine> at the top level), choice entering work: next do work, w1(entry) (unordered; took do work first) — the remaining moves then drawn against the dispatch after the move settles as the row above; no new choice kind. A do behavior that ends while the body of its state is still ahead of the move completes nothing; the body's done completes the state. A do step is never silent. Only the one-move engines (check, replay, explore) offer the unit: the fixed policies (declared, reverse, seed:<n>) run every entry whole and the do round after the move as before, consume no random number for it, and no golden of theirs moved; a witness naming do <state> before the state's entry has performed, or where no entry unit is left, is refused and its move rolled back; a do body parked at an accept offers no move on the front state_unit_front.go offer → offerDoSteps, unitsPending, unitHead.doStep, drawOnPath (pathDraw, unitHead.site), moveDoStep; state_executor.go startDoAction (from enterStateInto/enterLazily right after activateState; the move records the behavior in began), resumeEntering (a held entry's chain), dueAmong, countDoStep, bodyAhead (completeIfDone); lower/state_footprint.go enters (the entering transition's footprint carries the do behaviors of the states it enters, so the checker's reduction sees the step the move may take) conformance/state_do_step_before_sibling_entry.sysml + .expected.json + .check.expected.json (two outcomes: the step before or after the sibling's one entry, derived in the semantic oracle § A do step and a sibling region's entry due inside one entry: which goes first is open) + trace goldens, :state_do_step_before_sibling_entries (six: the step first, between or after two siblings' entries in either order), :state_do_step_before_nested_entries (six: against a sibling composite's two nested entries), :state_do_step_nested_before_outer_entry (six: the step of a state nested in one region's parallel start state against the outer sibling's entry and its own sibling's), :state_do_step_before_fork_branch (three: a fork's branch into a do state against the other branch's effect and entry, the step never before its own entry), :state_do_step_typed_before_sibling_entry (two: an action def do behavior of two counted steps, the sibling's entry before the first or after both), :state_do_step_before_history_restore (twelve: a deep history restoring a do state beside a logged entry, the step against the restored entry and against the first occurrence's firing), :state_do_step_cut_by_sibling_completion (six: a two-move do body cut by the sibling's completion out of the parallel state before, between or after its moves), :state_do_step_cut_by_sibling_terminate (five, PSSM Terminate 002's shape: the first segment before the sibling's entry, after it or never, the second never), :state_do_step_before_own_substate_entries (six: a composite's own do step against its two regions' logged entries), :state_do_step_before_own_body_entry (three: down a serial body, before, between or after two nested entries), :state_do_step_machine_before_top_entries (six: the machine's own do step against its top regions' entries), :state_do_step_way_down_before_fork_branch (six: a composite entered on the way to a fork's branch, its do step against the other branch's effect and entry and its own substate's), each .check.expected.json exact; robustness_do_step_entry_front_test.go (TestRuntimeRobustnessDoStepEntryFront: a do flow that never rests against a sibling entry ends at the sibling's completion or the do-step budget; a witness naming the step before its entry is refused; a body parked at an accept offers no draw; the fixed policies enter everything before the round; a composite's own do flow against its own body ends at the entry or the budget, its step named before the composite is entered is refused, it replays before, between and after its serial body's entries, a do behavior ending ahead of its body completes nothing, and the fixed policies run the body whole before the round), replay_test.go (the do <state> alternative in the entering witness lines), lower/state_footprint_test.go:TestTransitionFootprintsCoverEnteredDoBehaviors ✅ Faithful
A time trigger on a composite state counts from entering that state and fires while a substate is active; a substate moving does not restart it, and leaving the state destroys it, so re-entering the state times the whole interval again state_executor.go scheduleTransitionEvents (the enclosing states of every active leaf are scheduled too), scheduleTimeTransitions, exitState (drops the state's timers and withdraws the time events they queued, through executor_common.go (*EventQueue).Withdraw), dispatchEvent (the expired timer is un-marked so a transition staying in its source re-arms it, and an event whose source was left is dropped) conformance/state_composite_outer_time_trigger.sysml, conformance/state_composite_region_time_trigger.sysml + trace goldens, :state_time_trigger_restarts_on_re_entry.sysml + trace golden (a state left before its timer expires fires only a full interval after it is re-entered, timed against a sibling region's clock), robustness_test.go:stale_composite_timer_in_a_region, state_composite_transition_test.go:TestTimedSelfTransitionFiresEveryPeriod (a timed self-loop fires once per period) ✅ Faithful (a composite inside an orthogonal region fires region-locally: dispatchEvent routes through fireFrom, so the sibling regions stay as they were)
A change condition on a composite state is watched while a substate is active; a poll resolves the transitions it enables exactly as the equivalent signal does — innermost wins, one transition per region, a shared transition once — and a condition whose guard blocks it consumes nothing state_change_trigger.go pollChangeEvents (selects through the shared selectCandidates and dispatches through dispatchInOrder, which resolves conflicts with losesToNestedTransition and draws the region order through chooseRegion, the same steps broadcastEvent takes), observeChangeConditions, risenChangeTransitions (the guard is evaluated before the transition is selected, so a blocked one leaves the condition watched by the states outward of it and by the other leaves), state_executor.go fireFrom state_composite_transition_test.go:TestChangeConditionOnACompositeStateFiresWhileASubstateIsActive, :TestGuardBlockedChangeConditionDoesNotSilenceTheOtherRegions, :TestChangeConditionTakesTheInnermostTransitionOnly, state_change_trigger_test.go:TestChangeTriggerConsumesTheRiseForALosingTransition ✅ Faithful (RunToCompletion polls, so a when transition on a composite state fires without an external caller; one rise is one occurrence, so a transition that lost the conflict waits for the next rise rather than firing on the next poll)
Deferral by an ancestor state and across orthogonal regions; a deferral outranks an enabled transition in an enclosing state or a sibling region, and only a transition nested in the deferring state overrides it state_executor.go defersEvent, deferringStates, deferralOutranks state_deferred_test.go:TestCompositeStateDefersForItsSubstates, TestDeferralSpansOrthogonalRegions, TestDeferralOutranksASiblingRegionsTransition, TestDeferralOutranksAnEnclosingStatesTransition, TestTransitionNestedInTheDeferringStateOverridesDeferral, TestDeferralInEachRegionMustBeOverriddenForTheEventToFire ✅ Faithful
Deferring a non-dispatchable trigger reports lower/state_graph.go collectDeferred robustness_test.go:defer_of_non_deferrable_trigger ✅ Faithful
A transition's source and target name vertices of the machine they are written in, resolved by the name-resolution tier so a misspelled endpoint reports with the other name diagnostics rather than when the machine is lowered resolve/transition.go (*Resolver).ResolveEndpoint; resolve/edge.go and resolve/document.go route succession and transition ends through it; passes/state_transition.go and passes/action_endpoint.go; consumed by lower/state_graph.go (*StateGraph).vertex via lower.EndpointResolver resolve/transition_test.go:TestResolveEndpointsThatNameVertices (sibling, nested, sibling region, history of a composite state, sourceless accept ... then), :TestResolveEndpointMisspelledIsReportedWithASuggestion, :TestResolveEndpointNotAVertexIsReported, :TestEndpointLookupForLoweringReportsNothing, passes/succession_endpoint_test.go:TestResolvedStateEndpointNotVertexIsReported, :TestStateSuccessionEndpointSpellingsAcceptVertices, state_transition_endpoint_qualified.sysml conformance, robustness_test.go:state_transition_endpoint_misspelled, :state_transition_endpoint_never_resolved ⚠️ Approximate (a qualified endpoint resolves like any other name; an unqualified one, and one whose owners name only some of the scopes between, fall back to the first vertex of the machine whose name path ends in it, in declaration order, which is what the notation's leniency allows and what two vertices of the same name in sibling regions are told apart by only when the endpoint qualifies them. An endpoint naming no vertex leaves its edge out of the graph rather than failing the lowering, so a machine whose model was never resolved — the REPL and the gRPC handlers build a fresh resolver — still runs; an endpoint that resolves but names no vertex of this machine is reported by the state transition check, see the dangling-transition rows)
Transition firing state_executor.go:535 fireTransition state_transition_effect.sysml ✅ Faithful
Transition guard evaluation state_executor.go:218 scheduleTransitionsForState state_choice_pseudostate.sysml ✅ Faithful
Transition effect actions, whether written as a statement (do assign x := 1) or as a performed action (do perform Bump) lower/state_behavior.go LowerBehaviors (the membership a performed action is contributed through is unwrapped and each behavior is lowered to statements); state_executor.go:535 fireTransition → state_statements.go executeBehavior state_transition_effect.sysml, state_transition_effect_perform.sysml conformance ✅ Faithful
A state behavior or transition effect written as an action usage with neither a body nor an action performed (entry action hello;, do action log { }) names an action of no content and executes as nothing, as a bodyless nested action in an action body does lower/state_behavior.go lowerStateBehavior (an empty body, as lowerActionExecution gives a step stating neither expression nor action) state_behavior_action_of_no_content.sysml conformance, tests/migrate/states_test.go:TestStateMachineCrossRegionTransitionsAndPseudostates (a migrated effect whose whole body is a console print) ✅ Faithful
AcceptEvent triggers (when signal) state_executor.go matchesEvent (signal identity by triggerMatches, then the receiver the message reaches by Context.messageReaches) state_signal_discriminate.sysml, accept_port_addressed_via_only.sysml, accept_part_addressed_receiver_only.sysml ✅ Faithful
Signals sent from state behaviors reaching the machine state_statements.go stateStmtHost.send, state_executor.go deliverPendingSignal state_send_self_signal.sysml + trace golden, signal_test.go:TestSendOfNamedTypeReachesStateMachine ✅ Faithful
A signal in flight on the context bus is dispatched by a single step as well as by a run to completion, so the REPL debugger and RunToCompletion agree; it is due now, so a step takes it ahead of a timer set for later state_executor.go ProcessNextEvent, acceptableMessage, AcceptsMessage, HasPendingSignal, HasPendingWork; repl/meta.go %advance repl/runtime_commands_test.go:TestAdvanceDeliversPendingPortSignal, state_transition_accept_via_port.sysml, runtime/signal_injection_test.go:TestProcessNextEventTakesAPendingSignalBeforeALaterTimer ✅ Faithful
A signal injected from outside the model (%send at the REPL) travels the same bus as send Signal(args) to <object> from a behavior, typed by the signal definition and addressed to the object, and an argument the signal has no feature for is refused runtime/signal.go Context.SignalMessage, NamedSignalMessage; state_executor.go Performer; classifier_behavior.go Context.ExhibitedMachineOf; repl/send.go %send (addressed to the object as a whole: every machine it exhibits and action it performs is asked, receiversOf); repl/meta.go %state <machine> <object> attaches to the exhibited machine of that kind runtime/signal_injection_test.go:TestSignalMessageDrivesTheExhibitedMachine, :TestExhibitedMachineOf, :TestAcceptTakingNamesThePerformedActionsAccept; repl/send_test.go (TestSendDrivesAnAcceptTransition through TestSendIsInHelpAndCompletion, TestStateOnAnObjectAttachesToItsRunningMachine, TestStateOnAnObjectStartsWhatItDoesNotRun, TestSendReachesAPerformedActionParkedAtItsAccept through TestSendIsDispatchedToTheDebuggedActionAtItsAccept) ✅ Faithful, self-assessed (the pinned reference has no prompt to inject a signal from, so nothing external adjudicates this)
A message in flight is taken by one machine of the object it reaches: a machine whose guards would drop it leaves it for a sibling machine of the same object that would fire on or defer it, in attachment order, so a run and a single step route it alike; deciding a message beforehand is a probe that leaves nothing behind — no budget spent, no behavior started, no object, variant selection or feature value materialized by a guard kept state_executor.go takesMessage, yieldsTo, siblingsAccepting, Decide; classifier_behavior.go abandonInstancesSince, forgetVariantsNaming, forgetValuesNaming; signal.go TakeMessage runtime/signal_injection_test.go:TestSignalGoesToTheSiblingMachineThatFiresOnIt, :TestDecideLeavesNoVariationSelectionAGuardMaterializes; repl/send_test.go:TestSendReachesTheMachineWhoseGuardLetsItThrough ✅ Faithful, self-assessed (the pinned reference runs one machine per test, so nothing external adjudicates the choice among siblings)
CallEvent triggers (accept op(param) notation, operation and argument matching, arguments bound for guard/effect) parser/behavior.go parseTriggerEvent/parseCallEvent; symbols/bodyscopes.go triggerParameterDefiner (parameters are members of the transition, reachable from its own guard/effect); state_executor.go matchesEvent EventCall case, bindTriggerArguments, InvokeOperation; perform.go callPayload (a queued call carries the declaration its arguments select among the owner's same-named operations, Call.Declared), callTriggerOperations (a trigger names the declarations with an input for each of its parameters, those with exactly its parameters when any has — all of several differing in their parameters' types alone, since the notation writes no types — so a call of a declared operation fires only the triggers naming its declaration, as a UML CallEvent names one operation) tests/parser/testdata/parse/state_call_trigger.golden, lower/trigger_test.go:TestTriggerClassification_CallTrigger, model/behavior_body_resolve_test.go call-trigger parameter cases, state_call_trigger{,_guard,_nested,_regions}.sysml conformance, signal_test.go:TestCallEventMatchesOperationName, :TestRejectedCallLeavesNoArgumentsBehind, robustness_test.go:call_of_unhandled_operation, :call_argument_of_wrong_type, robustness_call_results_test.go:TestRuntimeRobustnessCallResults/overload_fires_the_trigger_naming_its_declaration ✅ Faithful (a call trigger on an enclosing composite state sees the invocation while a substate is active)
A synchronous call of an operation a call trigger accepts returns to the caller once the run-to-completion step the call event triggers is done, with the values the behaviors that step fires — the transition's effect, an entry or an exit — returned or assigned to the operation's out/result parameters, by name, the last write winning — the parameters the operation declares as a member of the machine's owner (or of the machine itself when it stands alone) when it declares one, among several so named the one the call's arguments select as a call in the model would (ErrAmbiguousInvocation before the call is queued when they select none), the arguments checked against that declaration's inputs as an invocation's are (ErrUnboundParameter before the call is queued for an unbound or unknown one, ErrTypeMismatch for one of the wrong type, an omitted input carrying its default), the call event carrying that declaration so it fires only the triggers naming it — an inout the step writes nothing to going back as the caller passed it, since its parameter value is the argument until a behavior writes it (PSSM §8.5.9 CallEventExecution) — every output the step returned when the trigger names no declared operation; a call a state defers holds its caller through the machine's later steps until it is recalled and dispatched; a call the run leaves queued or deferred has not returned, and one no transition accepts is discarded and returns nothing (PSSM §8.5.9 CallEventOccurrence; the caller is released only after the step) runtime/perform.go StateExecutor.Call (queues the call event with InvokeOperation, runs the machine at the current instant until that event has been dispatched — callReleased stops the run loop after the unit dispatching it, so events the step queued and timers it armed stay for the machine's later runs, while a deferred call keeps the run going through the steps until it is recalled — and reports a held call as ErrCallNotReturned through eventDisposition), pendingCall (captured by snapshot.go capture and held_image_behavior.go imagedState, so an undone step or a materialized image restores the call as it stood), callPayload (the declaration through invoke_operation.go memberCalled, the selection InvokeOperation makes, its inputs through operationInputs, the check InvokeOperation makes, and each input's value or default checked against its parameter as an assignment is, callInputs), newPendingCall, callTaken and recordCallOutput (outputs kept only from a behavior the pending call's own event fires, not from nested behavior another occurrence runs, and only under the names the declared operation returns, so a helper's other inout/out stays the object's own; a declared inout argument fills in under a transition that fired on the call and no behavior wrote to, so a discarded call still returns nothing); action_frame.go performanceOwner.returnAround and assignEnclosingBy route a nested action's return or output assignment to the enclosing behavior's parameter of that name (action_executor.go, calc_statements.go, state_statements.go hosts) conformance/state_call_trigger_results.sysml (.expected.json, trace golden: compute doubled by the effect, negated by the entry), robustness_call_results_test.go:TestRuntimeRobustnessCallResults (held, recalled, untaken, empty, repeated and erroring calls; the caller released before the completion step its call queued and before a timer it armed, a signal queued ahead dispatched first, a declared operation returning its own parameters alone, an inout argument returned as passed, as written and not at all when nothing takes the call; an overloaded operation returning the outputs of the declaration the arguments select, firing the trigger naming that declaration, and refused as ambiguous when they select none; a call leaving a declared input unbound or naming none, or carrying a wrong-typed one, refused before it is queued; an omitted input carrying its default; a calc and a constraint returning their result alone; a rolled-back step restoring the call), call_capture_test.go:TestStateCaptureTakesThePendingCall, :TestHeldImageCarriesThePendingCall ✅ Faithful
Sourceless transitions (accept … then, if … then, then, transition if … then, transition then) — SysML v2 §7.18.3 TargetTransitionUsage: a transition usage written without a source part, whose source "is taken to be the closest lexically previous state usage" in the body that declares it, so it is a member of the body that declares the state it leaves, written after that state, at any depth (a state def body, an exhibited or performed state usage body, a composite state's body, an orthogonal region's body); the pilot's UsageUtil.getPreviousFeature derives it the same way, looking back over the other transitions chained off that state. A pseudostate declared before the shorthand is not that state usage: a choice, junction, join or fork is left by transition first <pseudostate> … then …; only ast/transition_source.go ImplicitTransitionSource (the previous-member rule over the complete ordered body, looking past the sourceless transitions chained off the same state and the succession then state s; lists after s); lower/transition_source.go ImplicitSource, IsEntryTransition, IsStateSource (a PseudostateNode, InitialNode or FinalNode is not a state source), the typed ErrNoTransitionSource and TransitionSourceError (TransitionSourceNotVertexFormat, TransitionSourceRegionFormat, TransitionSourcePseudostateFormat, TransitionSourceMarkerFormat); lower/state_graph.go lowerTransitionMember lowers the shorthand from the vertex the rule names, over the inherited and own members lower/state_inheritance.go materialises with their owner and scope; passes/state_transition.go (*transitionChecker).checkImplicitSource reports the same rule at the constraint tier (CodeNoTransitionSource, CodeTransitionSourceNotVertex) parser/behavior_test.go TestParseStateBody_SourcelessTransitionForms, goldens state_target_transition_guard.sysml and state_target_transition_placements.sysml (top-level, composite, orthogonal-region placements with trigger, guard, effect and dotted targets, each source=""; both accepted clean by the pinned pilot), lower/transition_source_test.go (TestToStateGraph_SourcelessTransitionLeavesThePrecedingState, :…InheritedSourcelessTransitionLeavesEachMaterialization, :…SourcelessTransitionWithNothingBefore, :…SourcelessTransitionAfterANonVertex — start marker, explicit transition, succession usage, triggered and guarded shorthand after a choice, triggered shorthand after a join, :…SourcelessTransitionAfterARegion), passes/state_transition_test.go:TestSourcelessAcceptTransitionIsLegal, :TestSourcelessTransitionChainAndSuccessionAreLegal, :TestSourcelessTransitionWithNothingBeforeIsReported, :TestSourcelessTransitionAfterANonVertexIsReported (a pseudostate before the shorthand among them, and the explicit transition first pick … form it names staying legal), :TestSourcelessTransitionAfterARegionIsReported, conformance accept_then_transition.sysml, state_target_transition_top_level_timed.sysml (+ trace golden), state_target_transition_nested_timed.sysml (+ trace golden: one firing, one entry action, no self-loop), state_target_transition_guard.sysml, state_target_transition_after_do_action.sysml, robustness_test.go:sourceless_transition_with_nothing_before, :sourceless_transition_after_a_non_state (a do action, an attribute, a choice pseudostate) ✅ Faithful (the earlier reading — the shorthand written inside the state it leaves, with that containing state as its source, and refused at the machine's top level — was wrong: the pinned pilot rejects the nested placement with parse errors (no viable alternative at input 'accept'), and accepts the flat placement this implementation now lowers, so accept_then_transition.sysml was rewritten into the flat form. A shorthand written first in its body, or after a member that is not a state of this machine — a do action, an attribute, an in parameter, a written succession, documentation, a pseudostate, a region of a parallel state — is reported by the constraint tier with the member named, and the lowering keeps the same typed errors as a backstop; the pilot rejects each of those placements it can parse too, by its grammar or by A transition with an accepter must have a state as its source, and has no grammar for choice/junction to referee the pseudostate placement against)
ChangeEvent triggers (when expr) state_executor.go matchesEvent, RunToCompletion (polls after each micro-step and again at quiescence); state_change_trigger.go pollChangeEvents, SuspendReason state_executor_test.go:TestStateChangeEvent, state_change_trigger_test.go:TestChangeTriggerRunsWithoutAnExternalPoll, :TestChangeTriggerFiresOnRiseFromDoBehavior, :TestChangeTriggerDoesNotRefireUnchangedCondition, :TestChangeTriggerFalseConditionIsReported, conformance/state_change_trigger_autonomous.sysml, :state_change_trigger_rising_edge.sysml, :state_change_trigger_event_order.sysml + trace golden ⚠️ Approximate (driven by the run itself and fired on the condition rising; KerML has no clock, so re-testing once per micro-step is a tool-defined cadence — see the known limitation)
TimeEvent triggers (accept after <duration> relative, accept at <time> absolute) wait on one clock the runtime context owns (Kernel Semantic Library Clocks.kerml: Clock::currentTime is one TimeInstantValue every TimeEvent of a performance reads against), shared by every state machine and action the context runs, nested performances included parser/behavior.go parseAcceptTransition; clock.go Clock (Now, Waits, NextDue: the current instant in SI::s and every wait — state timers and action accepts alike — in due order), Context.Clock, Context.dueInstant, timeMagnitude (a duration carrying a unit is converted to the clock's SI::s), judgeTimeTriggerType (an argument the declarations make no DurationValue after after or no TimeInstantValue after at is refused as ErrTimeTriggerType before it is evaluated, through semantics/valuetype.go TimeEventConforms, the judgement the validation pass makes; only an argument the declarations leave open reaches evaluation and unit conversion); state_executor.go scheduleTransitionEvents, scheduleTimeTransitions (a timer's due instant is computed against the shared clock and withdrawn when its state is left), CurrentTime (a view of the clock), matchesEvent, awaitClock, clockWaits; action_executor.go awaitClock, clockWaits, visibleWaits (the waits of the actions a paused body performs, for NextWait, TimeWaits and Step); action_subflow.go, action_body_run.go Context.pauseForClock (a nested performance runs on the enclosing clock, not a copy, and a wait of its pauses the body's run rather than moving the clock) state_timed_triggers.sysml golden, state_timed_transitions.sysml conformance, conformance/clock_two_machines_share_clock.sysml + .expected.json + trace golden (a part's machine and its nested part's machine advance together), conformance/clock_action_signal_delayed_state.sysml + .expected.json + trace golden (an action sends after a wait and the machine accepts it), conformance/clock_do_action_while_token_waits.sysml + .expected.json + trace golden, state_executor_test.go:TestStateExecutor_AbsoluteTimeEvent, state_time_trigger_test.go:TestTimeTriggerUnitIsConverted, :TestTimeTriggerSubSecondUnit, :TestTimeTriggerAbsoluteInstantWithUnit, :TestTimeTriggerRejectsNonTimeDimension, :TestTimeMagnitudeRejectsNonTimeDimension, :TestTimeTriggerRefusesTheTypeValidationRefuses, :TestTimeTriggerAdmitsATypedFeature, conformance/state_time_quantity_seconds.sysml, :state_time_quantity_instant.sysml, :state_time_quantity_unit_ordering.sysml + trace golden, robustness_test.go:non_numeric_time_trigger, :time_trigger_of_a_non_time_dimension, :time_trigger_of_the_type_validation_refuses, :clock_advance ✅ Faithful (known limitation: the instant or duration must evaluate to a constant when the machine is initialised; accept at vehicle.maintenanceTime in the training 25. Transitions/Change and Time Triggers.sysml healthStates, whose vehicle is an unbound in parameter, validates and lowers but is refused at initialize with time duration must be constant, got instance)
Signal discrimination state_executor.go matchesEvent (qualified signal identity plus subtype conformance via Context.messageMatches) state_signal_discriminate.sysml ✅ Faithful
Unmatched signal dropped state_executor.go matchesEvent state_signal_unmatched.sysml ✅ Faithful (an injected event no transition matches is dropped; a message on the bus no active transition accepts is left in flight for another consumer, signal_test.go:TestStateMachineLeavesForeignSignalPending)
Hierarchical substates state_executor.go:131 getParentChain, :147 getLCA state_orthogonal_regions.sysml ✅ Faithful
Orthogonal regions state_executor.go broadcastEvent, state_region_transition.go fireTransitionInRegion; region order from lower.StateGraph.TopRegions and CompositeStates state_orthogonal_regions.sysml, region_pseudostate_test.go:TestRegionPseudostateExitOrderIsDeterministic ✅ Faithful
Choice pseudostates: the guards are read on arrival, after the incoming segment's effect has run, against the data as it then stands; several enabled branches are the existing transition choice point (ChoiceTaken at the choice, enumerated by explore), an unguarded branch is the else branch, and no enabled branch is the typed ErrChoiceWithoutBranch naming the choice state_route.go resolveRoute (resolves a route up to its first choice), travel (exits the states every branch leaves, runs the segments' effects, then resolves the choice), resolveChoice, pickBranch, enabledBranches (guards in declaration order); errors.go ErrChoiceWithoutBranch state_choice_pseudostate.sysml, state_region_choice.sysml, state_choice_after_incoming_effect, state_choice_dynamic_conflict (+ trace goldens), explore_test.go:TestExploreDynamicChoiceBranches, robustness_test.go:state_choice_without_an_enabled_branch, :state_do_body_accept_yields_to_an_open_choice, :state_do_body_accept_runs_before_the_choice_reads ✅ Faithful
A succession (succession first s then a;) names its endpoints the way a transition does, so it reaches a nested, region-local or qualified vertex, and a pseudostate as well as a state resolve/transition.go ResolveEndpoint; resolve/edge.go and resolve/document.go route succession ends; passes/state_transition.go; lower/state_graph.go collectTransitions (UsageSuccession and SuccessionEdge cases) → (*StateGraph).endpointVertex/endpointState, over the same lower.EndpointResolver a transition's endpoints resolve through passes/succession_endpoint_test.go:TestResolvedStateEndpointNotVertexIsReported, :TestStateSuccessionEndpointSpellingsAcceptVertices, lower/state_graph_nested_test.go:TestSuccessionReachesAPseudostate, :TestSuccessionQualifiedTargetNamesTheVertexItQualifies, :TestToStateGraph_EntrySuccessionNamesInitialState ✅ Faithful (previously matched the endpoint's last name segment against a flat state list, which reached no pseudostate and could bind a same-named vertex of another state)
Two regions may declare same-named pseudostates, and each is a vertex of its own lower/state_graph.go StateGraph.Pseudostates (declaration-ordered slice, not keyed by name), addPseudostate lower/state_graph_nested_test.go:TestSameNamedPseudostatesInSiblingRegionsAreBothCollected ✅ Faithful
Junction pseudostates: the route through a junction is resolved statically, before the incoming transition fires and before any effect runs; every outgoing guard is read against the data as it then stands, several enabled branches are the transition choice point at the junction (ChoiceTaken drawn and recorded only as the transition fires, after the region order among several candidates and the transition's own guard read again, enumerated by explore, replayed by a seed; a candidate another region's reaction disarms draws nothing; no branch guard is read again — the route beyond each enabled branch, through any further junction, is settled with the transition — so a branch enabled at selection is taken along it though another region's effect since changed what its guards read; the draw is made as the move begins, so a replay refused at a choice beyond the junction undoes the draw, its note and its ChoiceTaken with the move), the unguarded branches are the default when no guard holds, and no enabled branch leaves the compound transition unenabled; a history's default transition through such a junction draws and records the same way state_route.go resolveRoute, followOut, enabledBranches (later guards read in a preview that is undone), junctionDraw, travel, travelResolving, settleDraws, pickBranch; state_executor.go fireTransition, defaultHistoryRoute state_junction_pseudostate.sysml, state_completion_through_pseudostate.sysml, state_junction_several_enabled_branches.sysml, state_junction_drawn_as_its_transition_fires.sysml, state_junction_guards_read_once.sysml, state_junction_beyond_a_draw_read_once.sysml, state_history_default_through_junction.sysml (+ trace goldens, .check.expected.json), explore_test.go:TestExploreStaticJunctionBranches, :TestExploreJunctionDrawnAsTransitionFires, :TestExploreHistoryDefaultThroughJunction, replay_test.go:TestReplayRefusedChoiceUndoesTheJunctionDrawBeforeIt, :TestReplayRefusedChoiceUndoesTheHistoryDefaultsJunctionDraw, robustness_test.go:region_pseudostate_without_satisfied_guard ✅ Faithful (static, unlike a choice; which of several enabled branches is taken is open, as at a choice)
Fork pseudostates: the branches enter one composite state's orthogonal regions, one target per region, at least two, none guarded or triggered (UML 2.5.1 §14.2.3.5 Pseudostates: the transitions outgoing from a fork "must not have a guard or a trigger"), bypassing the initial state of each region they name — a target may lie below a region's own substates, the region being the one it lies in transitively, and the branch enters every state on the way down; a region the fork leaves out starts at its own initial state, and a region without one may be entered by a fork's branch alone — a machine with another way into the composite state that would start such a region by default (a transition to the state itself, to another of its regions or its history, the machine's entry naming it, or another fork — nested in one of its regions, or above it — passing through) is refused when lowered. Entering through a fork exits the source configuration down to the ancestor the source and the composite share — every region of the composite, in declaration order, when the source lies inside them, the composite itself staying active — then the first branch in region order runs its effect and enters the states still on the way down, and every region enters in declaration order, each branch's effect before its target — a region the fork leaves out never brings the composite in ahead of a branch, however early it is declared — the effects reading and writing the attributes of the state declaring the fork, as a transition leaving one of its substates does; branches ending at done complete the composite, or the machine, as an ordinary entry does. A fork's static footprint covers the regions it leaves to start by default, so the checker's reduction keeps their entry behaviors' reads, writes and completion dependent on what other units do lower/fork_plan.go ForkPlan, planFork, forkOwner, enclosingRegion, ForkStarted, checkForkOnlyRegion, defaultEntryInto, defaultStart, forkStartsByDefault; lower/state_footprint.go stateFootprintBuilder.pseudostate (fork case), entersRegion; lower/state_graph.go ownTransitionEffects; state_executor.go fireForkTransition, leaveForFork, exitRegionsOf, completeIfDone; state_region_entry.go forkEntry, enterForkBranches, enterRegion, enterLazily lower/fork_plan_test.go (TestToStateGraph_ForkEntersRegionsWithoutInitial, :TestToStateGraph_ForkBranchEffectsOwnedByDeclaringState, :TestToStateGraph_ForkKeepsRegionInitialApart, :TestToStateGraph_ForkBranchTargetsNestedState, :TestToStateGraph_ForkBranchesNestedInOneRegionFail, :TestToStateGraph_RegionWithoutInitialOrForkFails, :TestToStateGraph_ForkOnlyRegionEnteredByDefaultFails, :TestToStateGraph_ForkOnlyRegionEnteredByAnEnclosingEntryFails, :TestToStateGraph_ForkOnlyRegionOmittedByAnotherForkFails, :TestToStateGraph_ForkOnlyRegionEnteredByANestedForkFails, :TestToStateGraph_ForkOnlyRegionKeepsExplicitEntries, :TestToStateGraph_ForkShapeRejected), lower/state_footprint_test.go:TestTransitionFootprintsCoverForkOmittedRegions, state_fork_join_pseudostate.sysml + trace golden, state_fork_enters_regions_without_initial.sysml, state_fork_in_composite_enters_parallel_substate.sysml, state_fork_through_inactive_ancestors.sysml, state_fork_within_active_ancestor.sysml, state_fork_within_active_region.sysml, state_fork_completes_owner.sysml, state_fork_completes_nested_owner.sysml, state_fork_enters_nested_region_states.sysml, state_fork_enters_nested_parallel_state.sysml, state_fork_omitted_region_declared_first.sysml, state_fork_from_within_owner_regions.sysml, state_fork_from_within_nested_owner_regions.sysml (+ trace goldens), state_fork_branch_effect_owns_state_attribute.sysml, fork_join_test.go:TestForkBypassesTargetedRegionInitials, robustness_test.go:fork_branches_share_region, :fork_leaves_a_region_without_a_way_in, :fork_only_region_entered_by_default, :fork_branch_with_a_trigger, :nested_fork_starts_an_outer_region_by_default ✅ Faithful
Join pseudostates state_executor.go:782 fireJoinTransition, :827 joinSources (declaration order) pseudostate_test.go:TestJoinWaitsForEveryBranch, fork_join_test.go:TestForkJoinVisitOrderIsDeterministic ✅ Faithful
A compound transition through a pseudostate declared inside a composite state exits and runs its effects segment by segment (UML 2.5.1 §14.2.3.8.4, the segments' behaviors "executed in sequence", each after the exits of the states its segment leaves; PSSM §8.5 on exit points): a transition first Inner accept Sig do { A } then Owner::x; into a junction x of Owner, continued by first Owner::x do { B } then Out;, exits Inner, runs A, exits Owner, runs B, then enters Out — not every exit first. The segments' effects are grouped by segment (routeEffect.segment); a segment leaving from a pseudostate of a state exits that state's descendants the move leaves and the states up to that segment's boundary before its effects, and a segment from the machine's body exits what the move has still to leave. Into a join of Owner from its orthogonal regions, each region's transition exits its source and runs its effect (the regions in an open order), then Owner exits, then the join's outgoing transition runs. A route that ends at a terminate action, or at a history pseudostate, keeps its effects in that order. This is the runtime form the SysML v1 migrator writes a composite state's exit point in (docs/reference/sysml-v1-migration.md, Pseudostate exitPoint on a composite State); a transition into a junction of a composite state from outside runs the state's entry behavior before the junction's outgoing transition and the target's entries, which the migrator's entry point form relies on state_route.go travelResolving, leaveAlong, leftBySegment, segmentBoundary, vertexState; state_executor.go terminateAlong, terminateAt, moveToHistory (effects passed through) state_junction_exit_effect_before_owner_exit.sysml + .trace.golden, state_join_exit_from_regions_before_owner_exit.sysml + .trace.golden (admissible orders), state_junction_entry_skips_default_initial.sysml + .trace.golden (an entry through a junction runs the state's entry behavior, skips the region's entry; then, and history restores the substate); robustness_junction_exit_route_test.go:TestRuntimeRobustnessJunctionExitRoute (a junction left through with no outgoing transition, one whose outgoing transition ends at a shallow history, one whose every guard is false: typed errors naming the junction); tests/migrate/states_test.go:TestCompositeStateConnectionPointsKeepTheUMLOrder (the migrated station_points.xmi run end to end) ✅ Faithful
History pseudostates (shallow and deep). A region is recorded per region, in the state it was left in, so a region left by a transition that started inside its own composite state's region is restored to that composite state and its inner configuration rather than to the region's initial state; a region left with no active state at all has nothing to restore; a region or body left at done completed and leaves no history. A shallow or deep history with nothing recorded takes its own outgoing transition when it has one and otherwise performs the owning state's default entry, through its entry transition, as a plain transition into the composite would; an owner with no entry transition either is the typed ErrHistoryWithoutEntry naming the history and its owner parser/behavior.go parseStateMember (history <name>;, shallow history <name>;, deep history <name>;); state_executor.go fireHistoryTransition, :historyEntry, :historyRecorded (an empty history, read by state_route.go resolveRoute too when settling the default transition), :hasDefaultEntry, :deepestRecorded, exitState (records the configuration left), :recordChildHistory, :recordRegionHistory, :forgetRegionHistory, errors.go ErrHistoryWithoutEntry, state_region_transition.go leaveRegion, exitRegionTo, lower/state_graph.go PseudostateOwner tests/parser/testdata/parse/state_history.golden, parser/state_notation_test.go:TestHistoryPseudostateParsing, lower/state_notation_test.go:TestToStateGraph_HistoryNotation, state_shallow_history.sysml, state_deep_history.sysml, state_history_revisit.sysml + trace golden, state_deep_history_region_composite.sysml, history_test.go:TestShallowHistoryRestoresLastSubstate, :TestDeepHistoryRestoresInnermostSubstate, :TestHistoryRestoresOrthogonalRegions, :TestDeepHistoryRestoresBelowRegion, :TestDeepHistoryRestoresARegionLeftFromInsideItsCompositeState, :TestHistoryTakesDefaultTransitionWhenUnvisited, :TestHistoryOverACompletedConfigurationIsADefaultEntry, state_history_empty_default_entry, state_deep_history_empty_default_entry, state_history_after_completion_default_entry, state_history_after_completion_default_transition (+ trace goldens), robustness_test.go:history_outside_composite_state, :history_without_record_default_or_entry ✅ Faithful
Composite state with regions entered by a plain transition state_executor.go transitionToInto (keeps the region configuration entering it just built) history_test.go:TestHistoryRestoresOrthogonalRegions ✅ Faithful
Leaving a composite state exits only its own regions state_executor.go exitState (scoped to CompositeStates[state]) history_test.go:TestExitingNestedRegionsKeepsSiblingRegions ✅ Faithful
A transition between two regions of one composite state exits its source only: KerML StatePerformances::StateTransitionPerformance orders private succession [*] guard then [1] transitionLinkSource.exit, so the composite state is neither exited nor re-entered, the source's region is left without an active state, and the regions holding neither endpoint keep theirs. A target nested inside the target region's own composite state moves that inner region, exiting the state it was running. A region whose target is nested inside a composite state it is not running records that composite state as its active one. A source active in a region nested deeper than its target's region leaves its region set up to the level the two share, exiting the composite state holding its own region. The level is found by walking outward through the region declaring the region owner's nearest region-declared ancestor, so a region owned by a plain substate is not missed. A target outside the composite state still exits it and its regions — each state on the way out exactly once, the region a state is active in being cleared before that state is exited, since a region's active state may be nested below it — and a nested non-orthogonal transition still exits up to the endpoints' least common ancestor state_region_transition.go fireTransitionInRegion, concurrentRegionsFor, enclosingRegion, siblingRegionContaining, moveBetweenRegions, exitRegionTo, leaveRegion, isBelowOrEqual; state_executor.go getLCA (nested and outward transitions only) state_transition_cross_region.sysml + trace golden, state_transition_cross_region_third_region.sysml, state_transition_cross_region_nested_target.sysml, state_transition_cross_region_inactive_wrapper.sysml, state_transition_cross_region_deep_source.sysml, state_transition_cross_region_substate_owner.sysml, state_transition_leave_composite_substate_region.sysml, state_transition_sibling_region.sysml, state_region_cross_pseudostate.sysml + trace golden, cross_region_transition_test.go:TestCrossRegionTransitionExitsSourceOnly, :TestCrossRegionTransitionIntoNestedTargetExitsTheAbandonedState, :TestCrossRegionTransitionIntoInactiveCompositeRecordsTheEnteredState, :TestCrossRegionTransitionFromDeeperRegionExitsUpToTheSharedLevel, :TestCrossRegionTransitionFromARegionOwnedByASubstate, :TestNestedTransitionExitsUpToTheLCA, :TestTransitionOutOfCompositeStateExitsEveryRegion, :TestTransitionOutOfCompositeStateExitsNestedStatesOnce, region_pseudostate_test.go:TestRegionPseudostateIntoSiblingRegionExitsSourceOnly, robustness_test.go:state_cross_region_transitions_ping_pong ✅ Faithful
A transition between two substates of a state that is itself a region of a parallel state (state def M parallel { state left { entry action …; state prep; state work; transition first prep when Go then work; } … }) exits and enters only below the shared ancestor: left stays active, so its entry behavior does not run again and its do behavior is not restarted (KerML StatePerformances.kerml, StatePerformance: entry then middle, one entry per activation), while a transition out of left still exits it and one into it enters it afresh state_region_transition.go regionKeep, regionKeeps (the least common ancestor of the endpoints is kept when it lies inside the region or is the region's own owner, whichever the graph records for the region), moveInRegion state_parallel_owner_entry_once_intra_region.sysml (owner one level up: entered 1, worked 1, exited 0), state_nested_parallel_owner_entry_once_intra_region.sysml (owner two levels up inside a nested parallel state, then a transition out of it: entered 1, exited 1) ✅ Faithful (previously the owner was taken to lie outside its own region, so the move exited to the region's boundary and the owner's entry and do behaviors ran a second time)
Nested substates of a composite state declared textually lower/state_graph.go stateNodeFromUsage (carries substates and nested pseudostates into the graph) lower/state_graph_nested_test.go:TestToStateGraph_NestedPseudostateOwner ✅ Faithful
A state usage typed by a state definition is that definition's content: its substates, its initial (entry; then …) transition, its entry/do/exit behaviors, its transitions, its deferred events and its attributes, composing through any depth of typed usages (Systems Library/States.sysml: abstract state def StateAction :> Action, StatePerformance with substates: StateAction[0..*] and stateTransitions: StateTransitionAction[0..*], so a usage of a state definition is an occurrence of that StateAction and carries its features; KerML §7.4 feature typing). Names written in the definition's body resolve in that body's scope, not in the usage's lower/state_inheritance.go inheritedContent, addMembers/addMember, cloneStateNode, behaviorsIn; lower/state_graph.go stateNodeFromUsage, lowerStateBehaviors; resolve/state_type.go lower/state_inherited_test.go:TestToStateGraphTypedStateUsageInheritsSubstates, :TestToStateGraphInheritanceTwoLevelsDeep, conformance state_usage_inherits_definition.sysml, state_usage_inheritance_two_levels.sysml, state_transition_into_inherited_substate.sysml, parse/state_typed_substate.golden, repl/runtime_commands_test.go:TestStateDebuggerStepsThroughInheritedContent ✅ Faithful
Two usages of one state definition are two occurrences: separate vertices, separate active configurations and separate attribute values, including for an exhibited machine whose attributes are features of its state performance occurrence (States.sysml: StateAction :> Action, StatePerformance; Parts.sysml: exhibitedStates: StateAction :> stateActions, performedActions) lower/state_inheritance.go stateInstance (push/pop/putVertex/findVertex/putStateDecl/findCompletion), lower/state_graph.go StateAttributes; runtime/state_executor.go stateAttrs, initializeStateAttributes, attrFramesFor, stateAttributeValues, StateData; runtime/state_statements.go assignStateAttribute lower/state_inherited_test.go:TestToStateGraphTwoTypedUsagesAreIndependent, conformance state_usages_independent.sysml, exhibited_state_typed_usage.sysml ✅ Faithful
A typed usage's own body adds to what it inherits and redeclares what it names again: a substate or region of an inherited name is the one the usage writes, and an entry, do or exit behavior the usage states replaces the inherited one, a state having one of each (States.sysml: entry action entryAction :>> 'entry', do action doAction : Action :>> 'do', exit action exitAction :>> 'exit'); an attribute the usage restates is the same feature with the value the redeclaration gives lower/state_inheritance.go redeclare, pickBehaviors, keptSubstates, keptRegions, keptAttributes, replacedSubstates lower/state_inherited_test.go:TestToStateGraphUsageBodyAddsAndRedeclares, TestToStateGraphUsageRedeclaresInheritedSubstate, conformance state_usage_body_redefines.sysml, state_usage_redeclares_substate.sysml ✅ Faithful (self-assessed: the library gives a state one entry, do and exit action, so a stated behavior redefines rather than appends; a same-named substate is one substate, not two)
A typed state usage in a parallel body is an orthogonal region entered at the initial state its definition declares, and a state definition written in a parallel body declares a type rather than a region lower/state_graph.go parallelRegions, parallelRegionState, regionBody, parallelOwnedMember lower/state_inherited_test.go:TestToStateGraphTypedParallelRegion, conformance state_typed_parallel_regions.sysml, state_parallel_body_region_definition.sysml, state_typed_region_order.sysml + trace golden, robustness_test.go:parallel_state_body_unsupported_member ✅ Faithful
A definition reaching itself through the content it types has no finite materialization, and content a usage inherits that state lowering cannot represent is reported: both are typed errors, never a hang or a silently dropped member lower/state_inheritance.go ErrRecursiveStateTyping (inheritedContent, materializing), ErrUnsupportedStateContent (unsupportedInherited, loweredElsewhere) lower/state_inherited_test.go:TestToStateGraphRecursiveTypingIsAnError, :TestToStateGraphUnsupportedInheritedMemberIsAnError, robustness_test.go:state_usage_typed_by_itself, :state_usage_mutually_recursive_typing, :state_usage_inherits_unsupported_member ✅ Faithful
The library's States::StateAction contributes no content to a machine typed by or specializing it, however it is named (: StateAction through an import, : States::StateAction, state def Phase :> StateAction): its ref state self : StateAction would otherwise recurse. The runtime reports the declaration as withheld, which lowering takes as settled — only a name the resolver cannot resolve, or a machine lowered without one, is looked up through the scope tree. A usage so typed that states its own body runs it; one stating none has no initial state and fails at initialize() like any such machine (no initial state found in state machine StateAction) lower/state_inheritance.go StateTypeResolver, StateTypeWithholder, stateType; runtime/state_executor.go stateTypes.WithholdsStateType, stateActionFQN; runtime/errors.go ErrNoInitialState lower/state_type_resolver_test.go, robustness_test.go:state_def_specializing_the_library_state_action, :exhibited_state_typed_by_the_library_state_action, :exhibited_state_typed_by_the_library_state_action_with_a_body, :exhibited_state_typed_by_a_state_action_specialization, classifier_behavior_test.go:TestObjectExhibitsAMachineTypedByTheLibraryStateAction, conformance exhibited_state_typed_by_library_state_action.sysml, exhibited_state_typed_by_library_state_action_bodiless.sysml ⚠️ Approximate (withholding is faithful: the implicit specialization every state has never contributed content either. What a body-less usage typed by StateAction does is unadjudicated — in SysML a state with no substates is a simple state that is simply active, whereas the runtime refuses to materialize an object exhibiting a machine with nothing to run)
Choice/junction/entry/exit reached from inside an orthogonal region state_region_transition.go fireTransitionInRegion, moveBetweenRegions, leaveRegion, pseudostateTarget state_region_choice.sysml, state_region_exit_pseudostate.sysml, state_region_cross_pseudostate.sysml + their trace goldens, region_pseudostate_test.go, robustness_test.go:region_pseudostate_without_satisfied_guard, :region_pseudostate_cycle ✅ Faithful (a branch staying in the source region moves only that region; one into a sibling region of the same composite state exits the source only; one leaving the composite state exits the region set in declaration order, recording history on the way out)
Pseudostate chains (a pseudostate routing into another): each junction is resolved statically at the point the route reaches it, each choice lazily on arrival, so a junction after a choice reads the data the choice's segment left state_route.go resolveRoute, followOut (cycle detected), travel, resolveChoice region_pseudostate_test.go:TestRegionLocalJunctionChainIsFollowed, state_pseudostate_chain_junction_choice, state_pseudostate_chain_choice_junction, state_pseudostate_chain_choice_choice (+ trace goldens), robustness_test.go:region_pseudostate_cycle ✅ Faithful
Nested action invocation in entry/do/exit/effect state_executor.go:1075 executeAction, invoke_action.go invokeAction state_behavior_test.go:TestStateDoExitAndTransitionEffectPerformAction ✅ Faithful
Run-to-completion semantics (Occurrences::Occurrence::isRunToCompletion, Occurrences::Occurrence::runToCompletionScope, including effective redefinitions on states and machines) lower/run_to_completion.go:resolveRunToCompletion lowers the effective value and ancestor scope into StateGraph; state_run_to_completion.go:holdEntry and entryStep split entry cascades at scoped boundaries, filter free dispatches, and expose the choice to check, explore and replay state_executor_test.go:TestStateRunToCompletion, robustness_test.go:run_to_completion_redefined_false, :run_to_completion_scope_narrowed, :run_to_completion_redefined_by_specialized_def, :run_to_completion_redefined_in_orthogonal_region, :run_to_completion_redefined_undecidably, :run_to_completion_redefined_through_alias, :run_to_completion_redefined_through_redefining_feature, :run_to_completion_defaults_restated, :run_to_completion_default_restored_by_specialization, :run_to_completion_default_masked_by_specialization, view/render_test.go:TestStateRenderingRefusesWhatTheRuntimeRefuses, repl/runtime_commands_test.go:TestStateDebuggerRefusesRunToCompletionRedefinition, conformance state_run_to_completion_defaults_restated.sysml, state_run_to_completion_default_restored.sysml, state_run_to_completion_redefined_false.sysml, state_run_to_completion_scope_narrowed.sysml, state_run_to_completion_inherited_redefinition.sysml, state_run_to_completion_region_redefinition.sysml, state_run_to_completion_unverified.sysml, state_run_to_completion_alias_redefinition.sysml, state_run_to_completion_false_self_signal.sysml, state_run_to_completion_default_self_signal.sysml, state_run_to_completion_scope_sibling_region.sysml, state_run_to_completion_scope_sibling_region_default.sysml, state_run_to_completion_false_machine.sysml, robustness_test.go:TestRuntimeRobustness, robustness_run_to_completion_scope_test.go:TestRuntimeRobustnessRunToCompletionScope ✅ Faithful (the default keeps one occurrence per run-to-completion step and the whole machine as scope; state and machine redefinitions execute with the declared scope, while missing and non-ancestor scopes remain typed lowering refusals)
Event queue management state_executor.go:1127 EventQueue state_executor_test.go ✅ Faithful
Deterministic dispatch order executor_common.go eventHeap.Less (time, then arrival), state_executor.go orderedActiveRegions (region declaration order) state_call_trigger_regions.sysml ✅ Faithful
A transition names exactly one source and one target vertex, both of the machine it is written in (TransitionUsage::source: ActionUsage[1..1], ::target: ActionUsage[1..1] in the SysML v2 metamodel bundled as stdlib/Systems Library/SysML.sysml; KerML TransitionPerformances::TransitionPerformance takes one transitionLinkSource: Performance[1] and one transitionLink: HappensBefore[0..1]) passes/state_transition.go StateTransitionPass.Run → (*transitionChecker).checkEndpoint, over the vertices lower/vertices.go VertexDecls collects with the lowering's own collectVertices; lower/state_graph.go (*StateGraph).vertex keeps the typed construction error as the backstop passes/state_transition_test.go:TestTransitionTargetInSiblingRegionIsLegal, :TestTransitionTargetInSiblingRegionKeywordIsLegal, :TestTransitionTargetInUnrelatedMachineIsIllegal, :TestSuccessionTargetInUnrelatedMachineIsIllegal, :TestTransitionToHistoryIsLegal, :TestTransitionTargetResolvingToNonVertexIsIllegal, :TestSourcelessAcceptTransitionIsLegal, :TestTransitionToFirstMarkerIsIllegal, :TestTransitionToFinalStateIsLegal, :TestStateUsageMachineIsChecked, conformance/state_transition_sibling_region.sysml, robustness_test.go:state_transition_endpoint_in_another_machine, :state_transition_endpoint_naming_a_first_marker ✅ Faithful (a vertex of a sibling orthogonal region, a history pseudostate of a composite state and the sourceless accept … then form are legal; a vertex of another machine, a first/then marker named as a target and an endpoint resolving to a non-vertex are reported — the last of them by endpoint resolution, which owns it. A marker named as a source is left to lowering: see the row below)
A routing pseudostate has a transition out of it, so a transition reaching it does not terminate nowhere (SysML v2 has no pseudostate notation or semantics; choice/junction/fork/join are the documented OpenSysML extension of docs/reference/grammar/README.md, whose reference semantics is UML 2.5.1 §15.7.18) passes/state_transition.go (*transitionChecker).checkMachine, routingPseudostate passes/state_transition_test.go:TestJunctionChainTerminatingNowhereIsIllegal, :TestJunctionWithOutgoingTransitionIsLegal, :TestJunctionLeftBySuccessionIsLegal, robustness_test.go:state_junction_without_an_outgoing_transition ✅ Faithful (choice, junction, fork and join only; a history is excluded, since what it resumes need not be written as a transition out of it. The chain reaching such a pseudostate is acyclic, so state_region_transition.go cycle detection does not find it)
The start a state inherits (States::StateAction::start) named as a succession's source (first start then off;, succession first start then off;) designates where the machine starts, the way entry; then off; does; named as a triggered transition's source it is no vertex of the machine lower/state_graph.go startShot, isStartEndpoint, startsAt (the inherited state usage no vertex of the machine was collected for); (*StateGraph).vertex reports the transition source as a construction error model/transition_first_test.go:TestTransitionFirstStart (clean at check time), runtime/w6e_robustness_test.go:TestFirstMarkerNamedAsATransitionSourceIsRefused (the construction error names start as no vertex), parse/state_body_first_succession.golden (first start then off; in a state definition) ⚠️ Approximate (whether first start then off; beside transition t1 first start … then off; declares a second transition out of one start — illegal under UML 2.5.1 §15.7.18 — or names the same one twice is a reading of SysML v2 §7.19.3 not settled here; the succession alone lowers and runs, and the machine is refused only when start is also a triggered transition's source. The pinned reference validator reports nothing on that model, as we report nothing, so the disagreement is about execution, which it cannot adjudicate)
Completion transitions, out of a leaf and out of a completed composite state alike state_executor.go scheduleCompletionTransitions (from scheduleFromLeaf, settleDoActions and completeIfDone → scheduleCompletedComposites) state_simple.sysml, state_outer_completion_to_next, state_completion_test.go:TestCompositeCompletionQueuesItsTransitionsLikeALeaf ✅ Faithful
A transition written in the standard first/accept/then form, with the trigger on a line of its own, and with a name of its own (SysML.xtext TransitionUsage) parser/behavior.go parseTransitionMember/parseTransitionTail; lower/state_graph.go Transition.Name; runtime/state_executor.go transitionDescription (the name is what a diagnostic about the transition reports) parse/behavior_exhibit_state_body.golden, parse/state_transition_variants.golden, conformance/state_transition_accept_via_port.sysml, negative_test.go:transition_trigger_no_target, :transition_two_triggers, :transition_two_targets, :transition_do_without_action ✅ Faithful
accept … via <port> on a transition (SysML.xtext AcceptParameterPart) parser/behavior.go parseTransitionTail; lower/state_graph.go Transition.Via; runtime/state_executor.go matchesEvent/acceptsSignal/deliverPendingSignal conformance/state_transition_accept_via_port.sysml ✅ Faithful (a transition naming a port fires only for an occurrence routed or addressed to that port; one naming none takes a message addressed to the performer, not one addressed to a port of it)
A transition's trigger takes a transfer by the receiver it reaches: accept … via <port> receives at that port, and an accept naming no port receives as the performer, this (SysML v2 §7.16.7 accept action usage; KerML Transfers::Transfer, received by its target occurrence). A port alpha.inPort is a composite sub-occurrence of alpha, not alpha, so a transfer sent to it — send new Ping() to alpha.inPort, or routed to it over a connector — is not received by a via-less accept of alpha's machine, while one sent to alpha itself is taken by the via-less accept and not by accept … via inPort runtime/state_executor.go matchesEvent (every Message payload, via or not, is held to Context.messageReaches, the same test acceptsSignalFrom and the action executor's acceptMatch apply; a call or change event carries no Message and is matched by its trigger alone); runtime/signal.go Message.reaches (Port must match: a port-addressed or port-routed transfer never satisfies an empty via), messageReaches (port identity) conformance/accept_port_addressed_via_only.sysml + .expected.json + trace golden (a via-less accept declared before accept … via inPort on the same state is not enabled by a port-addressed transfer, so only the via transition fires and no choice line is recorded), conformance/accept_part_addressed_receiver_only.sysml + .expected.json + trace golden (a transfer addressed to the part is taken by the via-less accept, accept … via inPort declared before it is not enabled), conformance/send_identity_same_named_ports.sysml + trace golden (the via-less accept Ping → strayed negative control is not enabled, no choice line), signal_test.go:TestAcceptRoutingAgreesBetweenDispatchAndAcceptance (matchesEvent and acceptsSignalFrom enable the same transition for a port-addressed, a part-addressed and a port-routed message) ✅ Faithful
A transition's accept payload is bound for its guard and effect (accept w : Warning do assign level := w) lower/state_graph.go classifyTrigger (AcceptEvent.Payload); runtime/state_executor.go bindAcceptPayload conformance/state_transition_accept_payload.sysml ✅ Faithful (bound while the transition is taken and unbound if it does not fire, as a call trigger's arguments are)
The exit behavior of the state a transition leaves reads that transition's accepted data by the transition's name (exit action { in level : Integer = warn.w ?? alarm.a; }, in p : Boolean = 'T1.1.2'.p1): the exit is a step of the transition performance that accepted the occurrence (StatePerformances.kerml: accept then transitionLinkSource.exit), run before the transition's effect (TransitionPerformances.kerml: transitionLinkSource then effect), and accept w : Warning and accept op(p1) declare w and p1 features of the transition (StateTransitionAction::payload is bind payload = accepter.payload, States.sysml), so the read is of the taken transition's own feature — a signal's payload, a call's arguments — and of nothing while the transition is not the one being taken, ?? choosing among several leaving transitions; an outer transition's data reaches the exits of the substates it leaves, and a completion or data-less transition binds nothing, the parameter keeping its default (PSSM §8.5.5: a state behavior's parameters are bound from the triggering occurrence's data, so an occurrence carrying none binds none); the entered state's do behavior reads the transition that entered it for its whole run, the state performance holding the transfer that triggered it (StatePerformance::incomingTransitionTrigger); the transition's own guard reads its payload the same way (if raise.l > 5, if raise.d.level > 5), being a step of the transition performance after the trigger is accepted (TransitionPerformances.kerml: guard subsets enclosedPerformances, trigger then guard), and a guard on a segment out of a choice or junction reads the accepting segment's payload by that segment's name, the compound transition being one performance; a guard naming a transition not being taken reads null, so comparing it is the operator's type error rather than false lower/state_graph.go Transition.Accepted, AcceptedNames (the names the trigger binds); runtime/state_executor.go taking (the transition being fired, held from the exit through the entry, restored around a rejected firing and a join's segments), bindTriggerArguments, bindAcceptPayload, evalTransitionStep/stepFiring (a guard or probability evaluated within a firing: the candidate transition's own before it is taken, the compound firing state_route.go resolveRoute holds for a segment out of a pseudostate), passesGuard (a non-Boolean guard is ErrTypeMismatch); runtime/state_statements.go currentFiring, stateStmtHost.dataFrame (the taken transition and its payload in the frame every behavior of the firing reads), runtime/state_executor.go startDoAction (doAction.firing, the entering firing a do behavior reads through its run); runtime/transition_payload.go transitionPayload (a chain whose base is a transition of the firing: its accepted value, the null value for a transition not being taken, NoValueError for an accepted name the taken transition bound nothing to); runtime/frame.go frame.snapshot, runtime/invoke_predicate.go flattenFrames (the firing kept by a body snapshotted for a later read and by the frames a state-declared predicate closes over) conformance/state_exit_signal_payload.sysml, state_exit_call_arguments.sysml, state_exit_shared_by_two_transitions.sysml, state_exit_nested_reads_outer_transition.sysml, state_exit_completion_binds_nothing.sysml, state_exit_payload_deferred_read.sysml, state_exit_payload_nested_predicate.sysml, state_route_effect_reads_accepting_segment.sysml, state_do_reads_entering_transition.sysml (.expected.json each, the last with its .check.expected.json), state_choice_guard_reads_accepting_segment.sysml, state_junction_guard_reads_call_argument.sysml, state_guard_reads_own_payload_member.sysml, state_guard_names_transition_not_taken.sysml, state_guard_reads_deferred_payload.sysml (.expected.json each), robustness_exit_parameters_test.go:TestRuntimeRobustnessExitParameters (a payload of the wrong type, a transition not taken read without a fallback, a signal carrying no payload, a call missing an argument, a fallback read), robustness_guard_payload_test.go:TestRuntimeRobustnessGuardPayload (a guard reading a payload of the wrong type, a member the transition binds nothing under, a transition binding no payload, a guard left non-Boolean by the read), PSSM Event 017 B, Event 019 B, Event 019 C (docs/project/pssm-referee.md) ✅ Faithful
An accept's payload name is bound to what the message carries: the single value where the send carried one, else an occurrence of the signal the send named, its features set from the send's named and positional arguments (accept t : Telemetry via dish after send new Telemetry(frames = 3.0) reads t.frames; a payload-less send new Ping() binds a bare Ping occurrence). A message carrying neither a value nor a signal is ErrNoValue (KerML Transfers.kerml — a Transfer carries items, so a typed message is an occurrence of its signal) runtime/signal.go acceptedValue/positionalArg; runtime/state_executor.go bindAcceptPayload; runtime/action_executor.go (accept binding) conformance/accept_binds_signal_occurrence.sysml + .expected.json, robustness_test.go:accept_payload_without_a_value ✅ Faithful
A transition triggered by a subsetted event (accept :> shutDown) lower/state_graph.go classifyTrigger (AcceptEvent.Subsets); runtime/state_executor.go triggerMatches/acceptsSignal parse/behavior_accept_subsets.golden, conformance/action_accept_subsets_event.sysml (the same matching rule in an action body) ✅ Faithful
A transition's do effect written as a statement is terminated by the transition's own ; (SysML.xtext TransitionUsage ends with ActionBody, while EffectBehaviorUsage carries no ;), in the standard first … then spelling parser/behavior.go expectStatementEnd/atTransitionEffectStatement/atEffectEnd; parser/defusage.go parseUsage/parseReferenceMemberUsage parse/state_transition_effect_statement.golden, conformance/state_transition_effect_assign.sysml, conformance/state_transition_effect_assign_first_then.sysml (with their .trace.golden), negative_test.go:transition_effect_perform_two_semicolons, :transition_effect_assign_two_semicolons, :transition_effect_no_semicolon, :body_assignment_no_semicolon ✅ Faithful (a second ; is an error, as ActionBody takes one terminator; a statement outside a transition effect still needs its own ;)
Bodied exhibit state (exhibit state spacecraftModes { … }, SysML.xtext ExhibitStateUsage) parser/behavior.go (the exhibited state's body is parsed as a state body); ast/dump.go (the exhibit state keyword is recorded) parse/behavior_exhibit_state_body.golden, parse/classifier_behaviors.golden, negative_test.go:exhibit_state_unclosed_body ✅ Faithful (the body parses, resolves and lowers as a state machine, and an object of the type runs it — see the Classifier Behaviors map)
A state's entry, do or exit behavior written as a reference through a feature chain (exit controller.coolDown;) performs the chain's last action on the object the chain reaches from the machine's own performer, so the behavior's this is that object and its writes land there, not on the machine lower/state_behavior.go (a FeatureChainExpr reference is kept as the behavior's target); runtime/state_statements.go (the chain is evaluated against the exhibiting object; the value must be one live object) ; runtime/invoke_action.go performerOf/beginCallee conformance state_subaction_on_part (the part's cooled is set, the machine's own cooled stays false) ✅ Faithful, self-assessed (no external referee executes state behaviors)

Classifier Behaviors (KerML §8.4.4.3 Behaviors / performances; SysML v2 §7.16 exhibit/perform)

No external referee: the pinned artifact evaluates expressions but executes neither actions nor state machines headlessly, so every row in this section is self-assessed against the specification text, the normative library and our own goldens.

Semantic Rule Implementation Test Case Status
A behavior a type exhibits or performs is bound to every object of the type: materializing the object gives it an execution of its own, and two objects of one type run independently lower/classifier_behavior.go ClassifierBehaviorOf/BehaviorMembers; runtime/classifier_behavior.go startClassifierBehaviors/attachClassifierBehavior; runtime/instance.go instantiateOwnedBy conformance/object_exhibits_state_machine.sysml, conformance/two_objects_exhibit_independently.sysml (with their .trace.golden), runtime/classifier_behavior_test.go, repl/classifier_behavior_test.go:TestStateDebugsTheMachineAnObjectExhibits, migrate/reception_test.go (a migrated v1 reception, performed from creation, runs its method on each of several signals with nothing starting it) ✅ Faithful
A body of the behavior reads and writes the performing object's own feature values, and a message addressed to one object reaches that object's machine and not a sibling's runtime/classifier_behavior.go assignPerformerFeature; runtime/state_statements.go, runtime/action_statements.go (assignment through the performer); runtime/signal.go addressOwner (the owner chain an address is resolved against) conformance/object_machine_writes_own_features.sysml, conformance/object_addressed_send_one_sibling.sysml ✅ Faithful (a body written in the performing declaration; a name only the performer declares is refused — see the row below)
The performing object is not a namespace a behavior body's names resolve in: a body reaches the object's features through a name in scope where it is written (a body inline in the declaration that holds the feature), through this, or through a parameter, and a name that resolves nowhere is refused by name resolution and by execution alike resolve/this_context.go ThisContext/IsOccurrenceThis; resolve/document.go resolveFeatureChain (a chain from this reads the owning object); runtime/classifier_behavior.go assignPerformerFeature/namesPerformerFeature; runtime/eval.go selfFeatureInScope/thisValue model/behavior_body_names_test.go; repl/behavior_body_names_test.go; conformance performed_action_writes_performer_feature, performed_action_writes_this_feature, exhibited_state_writes_this_feature (+ trace), standalone_action_exchanges_values_through_parameters; runtime/robustness_test.go:standalone_action_naming_a_performer_feature, :standalone_action_writing_a_performer_feature, :standalone_action_naming_this_of_an_unowned_performance ✅ Faithful, self-assessed from Occurrences::Occurrence::this ("the context Occurrence within which this Occurrence takes place"), Objects::Object::ownedPerformances ("feature redefines this default that") and Parts::Part ("ref part this : Part :>> Action::this, ownedPerformances::this = that as Part"): the owning object is the this of the performances it owns, which makes the reach explicit rather than a namespace of the body. this in a standalone behavior is the performance itself, so this.<feature> there is ErrThisNotAnObject; a bare performer-only name is ErrUnresolvedReference on read and ErrPerformerFeatureNotInScope on write
An exhibited state is a StatePerformance, so attributes the machine declares are initialized from and written through the occurrence held by the exhibit usage; nested states share that root occurrence, while a directly executed machine with no exhibit occurrence retains executor-local state data lower/state_graph.go (lowerAttributes); lower/classifier_behavior.go StatesBehaviorBody; runtime/classifier_behavior.go performanceOccurrence/attachClassifierBehavior; runtime/state_executor.go newStateExecutorForOccurrence/initializeAttributes/assignAttribute; runtime/statements.go and runtime/state_statements.go assignData parse/exhibited_state_attribute_redefinition.golden; conformance exhibited_state_own_attribute, exhibited_state_nested_attribute, exhibited_state_guard_reads_attribute (+ trace), exhibited_state_two_objects, exhibited_state_occurrence_default, state_attribute_without_performer; runtime/classifier_behavior_test.go:TestExhibitedMachineWritesItsOwnOccurrence; runtime/robustness_test.go:object_exhibited_machine_attribute_write_violates_multiplicity; grpc/instance_graph_test.go:TestInstantiate_ReturnsExhibitedStateValues; client/python/tests/test_runtime_integration.py:test_exhibited_state_values_read_through_the_occurrence ✅ Faithful, self-assessed from States::StateAction :> StatePerformance and Parts::exhibitedStates :> performedActions. Occurrence slot values, including a redefinition on the exhibit usage, initialize execution before entry behavior runs; %current keeps the mirrored state data. A machine-declared name takes this path; an undeclared name writes the like-named performer feature only where it resolves there.
An action a part performs is an Action :> Performance, so attributes and out/inout parameters the action declares are initialized from and written through the occurrence held by the perform usage; nested nodes of the action write that same root occurrence, while a directly executed action with no performer retains executor-local feature values lower/action_graph.go (lowerAttributes); runtime/classifier_behavior.go performanceOccurrence/attachClassifierBehavior; runtime/action_executor.go newActionExecutorForOccurrence/initializeAttributes/setFeature; runtime/action_statements.go assignOuter/assignData parse/performed_action_attribute_redefinition.golden; lower/action_body_test.go:TestActionAttributeLowering_KeepsValuelessAttributes; conformance performed_action_own_attribute, performed_action_out_parameter, performed_action_nested_node, performed_action_later_node_reads (+ trace), performed_action_two_objects, performed_action_occurrence_default, performed_action_writes_performer_feature, action_attribute_without_performer; runtime/classifier_behavior_test.go:TestPerformedActionWritesItsOwnOccurrence, :TestDirectlyExecutedActionKeepsItsFeaturesLocal; runtime/robustness_test.go:object_performed_action_attribute_write_violates_multiplicity, :object_performed_action_occurrence_holds_a_non_object; grpc/instance_graph_test.go:TestInstantiate_ReturnsPerformedActionValues; client/python/tests/test_runtime_integration.py:test_performed_action_values_read_through_the_occurrence ✅ Faithful, self-assessed from Actions::Action :> Performance and Parts::performedActions :> actions, enactedPerformances. The occurrence is authoritative: a write goes to it and is read back, so the executor data a completed run reports (%continue "Results", and the context a gRPC execution response carries) mirrors what the occurrence holds. Occurrence slot values, including a redefinition on the perform usage, initialize execution before the flow runs; an in/inout member the perform usage binds is an argument written first, while an out member with a value declares the answer's default and is not one — nor is any directed member a replacement body, so perform action report : Report { out total = 7; } answers 7 and still runs Report's flow (lower/classifier_behavior.go isBehaviorArgument/statesBehaviorBodyMember, lower/classifier_behavior_test.go:TestClassifierBehaviorOutMemberIsNotAnArgument, runtime/classifier_behavior_test.go:TestPerformedActionDeclaringAnOutputDefaultStarts, :TestPerformedActionOutputDefaultKeepsTheReferencedFlow). An action stating no flow takes its inputs the same way before completing at once. An action-declared name takes this path even where the performer has a like-named feature; an undeclared name writes the performer's feature only where it resolves there. A subperformance invoked by a nested action usage has a runtime frame of its own but no occurrence of its own: its pins are read as node.pin and reported under that path, and its out values also flow back into same-named features of the performing action, and so into its occurrence.
Startup and quiescence are tool-defined (KerML gives no real time, only Clocks.kerml's currentTime that a run advances): feature values and constant defaults come first, then the behavior is initialized and run until no event is due at the current instant, no do action is runnable and no message is in flight, bounded by the event and do-step budgets; advancing the context's clock (Context.Advance) then runs everything due up to the new instant — state events, action tokens whose instant has come, change-condition polls and do rounds — instant by instant in due order, an advance of zero dispatching what is already due and one with nothing waiting moving the clock alone runtime/classifier_behavior.go startClassifierBehaviors/drainObjectBehaviors; runtime/state_executor.go RunToQuiescence; runtime/advance.go Context.Advance, advanceToNextDue, runDue (every due executor is run to quiescence at the instant, then the executors watching a change condition poll it, each round's order drawn by the scheduler — the driver takes its turn once control returns to it — until nothing more is due there; a run that gets nowhere settles its executor until another gets somewhere or the clock moves, so each round is bounded by the budget of what it runs), AdvanceReport (what moved: events, do steps, action steps, dropped signals and the notes recorded) robustness_test.go:object_exhibited_machine_never_settles, :object_exhibited_machine_without_an_initial_state, :clock_advance (zero moves and fires nothing; nothing waiting is not an error; a wait due after the advance stays queued and its executor reports the wait, not a deadlock; a negative, infinite or not-a-number advance, and one leading past the last instant a float64 holds, is ErrNegativeDuration and leaves the clock where it was, as is an accept after that would; a wait in a flow a body runs or in an action a node performs pauses the token's work until the clock is advanced to the wait, so a bounded advance never runs past its deadline, while a run of the action's own advances to it; a token held at a join beside a wait is not work due; a message for one flow does not wake another's wait; a machine that never settles is stopped by the event budget, which names itself; an action's step budget spans every instant of one advance, so a timed loop is stopped by ErrActionStepLimitExceeded rather than starting afresh at each wake; the event budget counts state events, not the rounds an advance takes, so a budget of one still lets a lone action wake; an accept when beside a timer fires at the instant another executor makes its condition true, whether the action or machine is driven by an advance or drives the clock itself; the behaviors of an object whose start failed leave the clock, the siblings started before the failure included), conformance/object_exhibits_state_machine.trace.golden, budget_test.go:TestStateBudgetsAreConfigurable, repl/runtime_commands_test.go:TestAdvanceMovesActionAndStateDebuggersTogether, cmd/sysml/run_test.go:TestAdvanceRunsActionsAndStatesOnOneClock, :TestAdvanceWithoutBehavior ⚠️ Approximate (tool-defined: an exhausted budget is reported, naming which budget it was — ErrBehaviorBudget still wraps it for the quiescence path, over the bound's own ErrStateEventLimitExceeded or ErrDoStepLimitExceeded — and a machine or action waiting on the clock is quiescent, so advancing the clock drives it. Every bound a model can reach is listed under Runtime bounds)
A performed action parked at an accept is quiescent rather than deadlocked, and a message a sibling object sends later wakes it — as does one injected from outside the model: %send at the REPL reaches the accept the object's performed action (top-level, or nested in it) is parked at, and names it runtime/action_executor.go RunToQuiescence/HasPendingSignal, AcceptsMessage/AcceptTaking (the accept a parked token, or one held in a nested action's paused run, takes a message at, as TakingAccept); runtime/classifier_behavior.go hasPendingWork; repl/send.go receiversOf (every behavior the object runs is a receiver), debuggedTarget (a bare %send defaults to the object an %action <name> <object> session performs on behalf of) runtime/classifier_behavior_test.go:TestPerformedActionAwaitingAMessageIsWokenByASibling, :TestPerformedActionWithoutAFlowStillMaterializes; runtime/signal_injection_test.go:TestAcceptTakingNamesThePerformedActionsAccept, :TestTakingAcceptString; repl/send_test.go:TestSendReachesAPerformedActionParkedAtItsAccept, :TestSendReachesAnAcceptNestedInAPerformedAction, :TestSendToAnObjectRunningAMachineAndAnAction, :TestSendDefaultsToTheActionSessionsObject, :TestSendIsDispatchedToTheDebuggedActionAtItsAccept ⚠️ Approximate (tool-defined: a standalone %action run still reports ErrAcceptDeadlock, where nothing can post the awaited message; %action <name> <object> starts a fresh performance beside the object's running one rather than attaching to it)
A performed action whose execution fails for an unbound input — a required in parameter of a nested step bound by nothing — ends the performance, not the performer: materializing the object records the failure on the behavior (ObjectBehavior.Err), which counts as ended from then on — it takes no step, receives no message, and the REPL behavior listing shows it as failed — while every other failure of a performed action still fails the object's creation, and an explicit perform obj.beh.start or -action execution of the same action is refused for the same input runtime/classifier_behavior.go ObjectBehavior.Err, attachClassifierBehavior, startBehaviorsOf (typeBound), drainObjectBehaviors, hasPendingWork; runtime/lifetimes.go completed; runtime/check_invocation.go Invocation.executors; repl/send.go receiversOf, repl/meta.go behaviorStatus robustness_performed_action_inputs_test.go:TestRuntimeRobustnessPerformedActionInputs; conformance performed_action_unbound_input_nested_{bare,one,one_one,one_many,zero_many,zero_one} (+ _executed), performed_action_unbound_input_toplevel_{...} ⚠️ Approximate (tool-defined boundary: SysML v2 §7.17.6 makes a perform usage a referential behavior of its owner, and nothing in §7.6 or KerML ties an object's creation to its performances succeeding, so an unbound in of a nested step is recorded on the performance; every other failure of a performed action still fails creation, which the runtime's existing error contracts pin. The invocation rule follows §7.6.3: a parameter writing no multiplicity takes the effective multiplicity of what it redefines or subsets, else the implicit [1..1] where it qualifies, else [0..*] — so a bare in is optional and the recorded-failure boundary is the inputs whose effective multiplicity requires a value ([1..]), which explicit executions still refuse with ErrUnboundParameter. The recorded failure ends the life the performance's occurrence began, is journaled (a snapshot restore or a rolled-back creation undoes it), is carried by a held image, and is recorded the same way when the clock's advance — Advance or another executor driving the shared clock — reaches the unbound input rather than a start or a message wake)
A behavior a type declares without exhibiting or performing it (action count : Count; in a part def) is bound to no object at creation: new T(), a materialization and occurrenceOf run nothing of it, and an explicit start, perform obj.beh.start; — fUML's StartObjectBehaviorAction, the start a behavior's Action::start snapshot names — gives the object its own execution of it, this in the body the object, its writes landing on the object's features, an accept it parks at woken by a message sent afterwards (the object's behaviors are drained once the enclosing top-level performance ends, as well as at its start), the object alive with its values once the behavior completes; a start of a behavior the object already runs starts nothing more, and one a member of a general the object's type specializes binds runs on the specialized object. A start on no one object (null, a collection, a destroyed object) is ErrPerformerNotObject, of a member that is no behavior of the object ErrNoSuchBehavior, and one that fails is undone whole — no execution attached, no write kept, the object as it was; an older parked behavior a message of the started one wakes runs only once the start is kept, so its move is never inside the start's undo lower/action_graph.go EffectStart, performEffect, startedBehavior (a perform naming the start of a behavior held by an object); lower/action_subflow.go StartUsage; lower/classifier_behavior.go StartableBehaviorOf (an exhibited or performed behavior, or an action or state usage the type merely declares); runtime/start_behavior.go startEffect, startTarget, startBehaviorOn (journaled: rolled back whole on failure), startableBehaviorOf (the member, or one redefining it, on each of the object's types); runtime/classifier_behavior.go runsBound, runAttachedBehaviors, drainObjectBehaviors, holdDrivenWork; runtime/context.go settledObjects; runtime/action_statements.go, runtime/state_statements.go (the start effect in a body); runtime/trace.go RecordBehaviorStart lower/start_effect_test.go; runtime/robustness_classifier_behavior_test.go:TestRuntimeRobustnessClassifierBehaviorStart (construction_starts_no_declared_behavior, start_runs_the_behavior_as_the_object, started_behavior_is_woken_by_a_later_message, a_second_start_runs_nothing_more, an_inherited_behavior_starts_on_the_specialized_object, start_on_no_object_is_refused, start_of_no_behavior_is_refused, a_failing_start_is_undone_whole, a_behavior_woken_by_a_start_runs_once_the_start_stands, start_is_traced_as_the_objects_own_execution); runtime/classifier_behavior_test.go:TestStartedActionAwaitingAMessageIsWokenByASibling; the fUML referee's ActiveClassBehaviorSender and TestSpecializedSignalSend, which create, start and then signal an object as the reference does ✅ Faithful (fUML §8.8.1 ObjectActivation, §8.10.2 StartObjectBehaviorActionActivation: the behavior of an object nobody starts never runs, and the object outlives it — see the alignment note's object-lifecycle rows; the pinned reference implementation executes both suite activities to the same end, and a behavior the type exhibits or performs keeps the v2 reading, bound to every object at materialization, in the first row of this map)
An object's parameter space is its own: an action's out parameter answers the caller even where the performing object declares a feature of that name runtime/action_statements.go assignOuter; runtime/action_executor.go declaresParameter runtime/classifier_behavior_test.go:TestOperationOutputNamedLikeAFeatureAnswersTheCaller ✅ Faithful
A failed materialization leaves no behavior of the object attached or queued, and an edited model drops an object whose behavior body changed rather than resuming it on the values the old body wrote runtime/classifier_behavior.go startClassifierBehaviors/forgetBehaviorsFrom; runtime/adopt.go writeBoundBehaviors runtime/classifier_behavior_test.go:TestFailedMaterializationLeavesNoBehaviorBehind, repl/classifier_behavior_test.go:TestRewritingTheExhibitedMachineDropsTheObject, :TestObjectMachineSurvivesAnUnrelatedDeclaration ⚠️ Approximate (tool-defined: the spec describes one fixed model, so what a live execution does when the model is edited is a REPL policy)
A second materialization of one name is a second object, with its own identity and its own behaviors; occurrenceOf remains the reuse path for a named occurrence runtime/instance.go instantiateOwnedBy; repl/query.go instantiateNamed (which object the name now denotes) robustness_test.go:second_instantiation_of_one_type, repl/classifier_behavior_test.go:TestSecondInstantiateIsAnotherObject ⚠️ Approximate (tool-defined; the spec leaves object creation semantics open)
Invoking an operation of an object's type runs it with that object as performer, whichever behavior the member is — an action, a calc or a constraint — binding named arguments as the call machinery binds them, or a positional list to the effective input parameters in signature order (KerML §8.2.5.8.3: an ArgumentList is positional or named, never a mix; §8.4.4.9.5: a positional list binds parameters in declaration order) runtime/invoke_operation.go InvokeOperation (named) and InvokeOperationWith (OperationArguments, positional or named; operationOf settles which behavior the member is and, among same-named members, selects one through semantics.Model.SelectAmongArguments in its PerformsOperation mode — the overload selection an invocation expression uses, without the expression's preference for a calc, so an action and a calc of one name are told apart by the arguments' types; operationInputs binds a positional list to semantics.Model.SignatureParametersOf — signatureOf's in/inout parameters in signature order, out and result excluded — refusing a surplus with ErrOperationArity and a mixed list with ErrMixedArguments; an action runs through ExecuteActionPerformedBy, a calc through the existing calc invocation with the object as its featuring object, a constraint through the existing condition evaluation against the object); runtime/eval.go evalInvocation preserves that performer for nested calc invocation expressions; repl/meta.go %invoke (parseInvokeArguments: bare expressions or <p>=<expr> pairs, a mixed list and a parameter named twice refused before the object is reached) runtime/classifier_behavior_test.go:TestInvokeOperationPerformedByTheObject, :TestInvokeOperationWithPositionalArguments (a defaulted trailing parameter omitted, an inout read and written, an out taking no position, two same-named calcs told apart by arity, a same-named action and calc by argument type, fewer, surplus and mixed lists), robustness_positional_invoke_test.go:TestRuntimeRobustnessPositionalInvoke, repl/classifier_behavior_test.go:TestInvokeBindsPositionalArguments, :TestCalcInvocationExpressionSeesPerformingObject (an action, direct and nested calcs reading a mutated feature, an anonymous result, and a constraint), :TestInvokeOperationFailureModes, robustness_test.go:operation_invoked_with_unbound_parameters, :operation_constraint_body_cannot_be_evaluated, repl/classifier_behavior_test.go:TestInvokeRunsAnOperationOnTheObject, :TestInvokeReportsItsFailureModes ✅ Faithful, self-assessed (the pinned reference cannot invoke an operation on an object, so nothing external adjudicates this). A calc's value comes back under its result parameter's name, or result where the result is anonymous, and a constraint's verdict comes back under result — a false verdict is an answer, not ErrViolated, since the invocation asked for the value. A body that cannot be evaluated is still an error. A state member is refused with ErrUnsupportedClassifierBehavior; its exhibited state machine runs during materialization instead. A member that is no behavior at all is ErrNotABehavior. Arguments bind by name or by position on the Go API and the REPL alike; the gRPC surface exposes no operation invocation. A positional list is bound to the same effective signature an invocation expression is bound to, so the two surfaces cannot drift
An object typed by a behavior — a performance occurrence, the action def a v1 activity becomes when a part performs it — runs no classifier behaviors of its own: its perform/exhibit members are steps of the performance that runs it, not behaviors bound to the occurrence, so a body's action call : Sub; runs once as a step and this in it is the performer; a reference that is no action is ErrNotABehavior runtime/classifier_behavior.go classifierBehaviorsOf (a behavior type binds none); runtime/signal.go isBehaviorType; runtime/invoke_action.go actionCandidates (ErrNotABehavior) conformance performed_action_def_perform_members_are_steps + .expected.json; migrate/opaque_migration_test.go:TestSwimlaneBodiesAndGuardsRunAgainstTheRepresentedPart (a migrated workflow whose action body performs a sub-activity through its performer) ✅ Faithful, self-assessed (KerML §8.4.4.3: performances are bound to the objects of a Class that is not itself a Behavior; a Behavior's steps are its own subperformances)
A perform action x ::> part.action; usage (an action usage referencing a feature chain) performs the chain's last action with the object the chain reaches as its performer, so this in the performed body is that object — tel.point moves the telescope, not the station; an action usage referencing or typed by a qualified name still runs on the caller's own performer. The chain is evaluated in the caller's context when the node fires: a chain holding no object is ErrPerformerNotObject, one holding several objects is refused, one whose last segment is not an action is ErrPerformerNotObject too, and one reaching an object that was destroyed is ErrOccurrenceDestroyed naming when it was destroyed parser/defusage.go (a ::> reference to a feature chain on an action usage); lower/action_graph.go performsAction (RelReferences to a FeatureChainExpr); runtime/invoke_action.go actionInvocation (target or chain), performerOf (the chain evaluated over the caller's self, resolved to an instance through Context.Instance), beginCallee; runtime/action_executor.go stepNestedAction parse/perform_action_on_part.golden; conformance perform_action_on_part + trace golden; robustness_perform_on_part_test.go:performer_holds_no_object, :performer_holds_several_objects, :chain_ends_in_no_action (ErrPerformerNotObject), :performer_was_destroyed (ErrOccurrenceDestroyed); migrate/behavior_test.go:TestActivityMigratesToAnExecutableActionDef (a migrated v1 CallOperationAction on a target pin runs through this form) ✅ Faithful, self-assessed (SysML v2 §7.16: an action usage that references another performs it as a subperformance of the referenced feature's featuring occurrence; the pinned reference cannot execute one)
A typed usage that also references a feature chain, perform action spin : Motor::Spin ::> drive.motor.spin { in rpm = 30.0; }, performs the chain's last action on the object the chain reaches, as the untyped form does, and its body binds the callee's in parameters by name: the reference names both the action and its performer, so it settles what is performed where the typing only restates the action's definition; an unqualified name the body reads that no argument binds is read from the caller's scope when the callee is performed. A chain holding no object or ending in no action is refused with ErrPerformerNotObject parser/defusage.go (a typing followed by a ::> reference to a feature chain); runtime/invoke_action.go nestedInvocation (the reference subsetting wins over the typing), referencedInvocation; runtime/action_frame.go (invocation inputs bound from the body's in p = … arguments, then from the enclosing scope for an unqualified callee) parse/perform_typed_subsetting.golden; conformance perform_typed_action_on_nested_part, accept_payload_bound_into_typed_action + trace goldens; robustness_perform_typed_on_part_test.go:reference_names_the_performer, :performer_holds_no_object, :chain_ends_in_no_action; migrate/interaction_test.go, migrate/call_port_test.go, migrate/reception_test.go (a migrated v1 call message, a CallOperationAction routed over a port and a reception's method run through this form) ✅ Faithful, self-assessed (SysML v2 §7.16: an action usage typed by a definition and referencing a feature performs the referenced feature; the pinned reference cannot execute one)
A perform action usage whose declaration names no element — no references/::> clause, no typing, not the perform a; reference form — performs itself: EventOccurrenceUsage::eventOccurrence is "the referenceFeature of the ownedReferenceSubsetting for the EventOccurrenceUsage, if there is one, and, otherwise, the EventOccurrenceUsage itself" (SysML v2 §8.3.16), and PerformActionUsage::performedAction redefines it (§8.3.17: "Unless it is the PerformActionUsage itself, the ActionUsage to be performed is related to the PerformActionUsage by a ReferenceSubsetting"). perform action boost { in amount = level; } and perform action idle; are therefore action usages with an empty or parameter-only body, their in members binding the performance's parameters, not references to a body held elsewhere. ExhibitStateUsage::exhibitedState redefines performedAction with the same rule (§8.3.17: "Unless it is the StateUsage itself, the StateUsage to be exhibited is related to the ExhibitStateUsage by a ReferenceSubsetting Relationship" — an ExhibitStateUsage is "also a PerformActionUsage, with its exhibitedState as the performedAction"), so exhibit state modes { in cmd = port.cmd; … } and exhibit state idle; exhibit themselves; a self-exhibited machine whose body declares no initial state fails at initialization with ErrNoInitialState, like any machine stating an empty body lower/classifier_behavior.go ClassifierBehavior.NamesBehavior/namesBehavior (the declaration names a body only through the reference form, a reference subsetting or a typing); runtime/classifier_behavior.go classifierBehaviorChain (an exhibit or perform declaration naming nothing returns the declaration itself as the body rather than ErrUnresolvedClassifierBehavior) lower/classifier_behavior_test.go:TestClassifierBehaviorNamesBehavior; runtime/classifier_behavior_test.go:TestPerformedActionNamingNothingPerformsItself, :TestExhibitedStateNamingNothingExhibitsItself, :TestExhibitedStateNamingNothingWithNoInitialStateIsReported; conformance performed_action_self_target, performed_action_self_target_empty, exhibited_state_self_target (+ trace golden), exhibited_state_self_target_empty; robustness_perform_action_self_test.go, robustness_exhibit_state_self_test.go (a perform/exhibit binding that names an element resolving to no body still reports ErrUnresolvedClassifierBehavior); parse/perform_action_self_target.golden, parse/exhibit_state_self_target.golden ✅ Faithful for perform action and exhibit usages

Expression Evaluation

Semantic Rule Implementation Test Case Status
Binary operators (+, -, , /, %, *, <, >, ==, ===). An ordering operator (<, >, <=, >=) is declared by the Kernel Function Library abstractly in DataFunctions (KerML 1.1 §9.3.4) and ScalarFunctions (§9.3.5) and concretely by the numeric libraries (NaturalFunctions, IntegerFunctions, RationalFunctions, RealFunctions; §9.3.6–§9.3.9) and StringFunctions (§9.3.12) alone, so the runtime orders numbers, the unbounded value, Strings, quantities and the literals of an enumeration specializing a numeric type (enum def Level :> Integer { low = 1; high = 3; }: Level::low < Level::high is true), and every other operand — a literal of a plain enumeration, a Boolean, a part or metadata instance, an attribute-def instance, a function value, a sequence, a set, null, a measurement reference — is OperandTypeError (wrapping ErrTypeMismatch) naming the operator, both operands and the library function that would have to declare it (Color::red < Color::blue is operator '<' is not defined for the enumeration literal Color::red and the enumeration literal Color::blue; DataFunctions::'<' is abstract and no library function declares '<' for the enumeration Color, which is no ScalarValue), the same through comparisonValues for the operator, its '<'(x, y) library form, ->minimize/->maximize and the compiled calc tier eval.go evalOperator → evalArithmetic/evalComparison/evalEquality/evalIdentity; Context.comparisonValues, Context.orderingGap, constComparison, errors.go OperandTypeError calc_simple_add.sysml, calc_modulo_operator.sysml, calc_identity_operators.sysml, calc_ordering_without_library_function.sysml, messages_test.go:TestOperandTypeErrorMessage, robustness_test.go:ordering_operand_with_no_library_ordering, measurement_ref_test.go (m < s) ✅ Faithful
Boolean operators (and, or, xor, implies), short-circuiting where they can eval.go evalLogical constraint_literal.sysml, calc_boolean_operators.sysml ✅ Faithful
Unary operators (-, +, not) eval.go evalUnary calc_unary_operators.sysml ✅ Faithful
Conditional (if c ? a else b) and null coalescing (??), both lazy eval.go evalConditional/evalNullCoalesce calc_conditional_branch.sysml, calc_null_coalesce.sysml ✅ Faithful
Literal values (Integer, Real, Boolean, String) eval.go:109 evalLiteral* calc_simple_add.sysml ✅ Faithful
* in an expression position is the unbounded value (KerML §8.4.4.6 LiteralInfinity, typed ScalarValues::Positive): it exceeds every finite Integer, Real and Natural, equals itself, orders consistently under <, <=, >, >=, and prints as * in a value, a trace, a solver pin and a document-query cell. It is no number, so every arithmetic operation over it — +, -, *, /, %, **, unary + and unary - — is ErrTypeMismatch naming the operation, never an infinity, a NaN or a finite answer runtime/eval.go evalLiteralInfinity, constArithmetic, constCompare; semantics/eval.go Value.IsUnbounded, UnboundedOrder, OrderSatisfies, FormatConst; semantics/valuetype.go exprConformance; solve/pin.go; repl/docquery.go runtime/infinity_test.go:TestInfinityValue, :TestInfinityComparison, :TestInfinityArithmeticRefused; conformance value_unbounded_comparison, value_unbounded_arithmetic_refused; parse/unbounded_and_metadata_access.golden; robustness_test.go:arithmetic_over_the_unbounded_value, :unbounded_value_compared_with_a_string ✅ Faithful
ref.metadata is a MetadataAccessExpression (KerML 1.0 §7.4.9.2, §8.3.4.8.15, §8.4.4.9.7): it yields the metadata annotating the element ref names as a sequence of metadata instances in textual order — each inline @-annotation and metadata … about usage placed by the source position of the annotating element, across files in the index's document order — each instance carrying the feature values the annotation body binds and, where the body binds none, the metadata type's own declared defaults; and then, last, the element's reflective metaobject (§8.3.4.8.15: the result includes a metaobject for the referenced element's own metaclass, after its annotations — the pilot evaluator answers the same count, so seatBelt.metadata of a part annotated once is two values and of an element nothing annotates is one). Metadata read off a value (1.metadata) is ErrTypeMismatch, and a name that resolves to nothing is ErrUnresolvedReference; an element no loaded reflective metaclass classifies — a model indexed without the KerML library — is ErrNoMetaclass naming it, the whole read refused with no annotation object left behind rather than answered with the annotations alone (every declaration a library-backed model holds is classified: a connector end as ReferenceUsage or, in an interface, PortUsage; a cross feature as ReferenceUsage; binding, transition and satisfy usages as their own metaclasses, per SysML.xtext; a dependency as KerML::Dependency and a rep … language textual representation as KerML::TextualRepresentation, KerML declarations in either language) parser/expr.go parsePostfixes (metadataAccessRef); semantics/annotations.go Model.ElementMetadataOf (sorts the annotation side table an element filter classifies by — order-blind — by document, then span), documentRanks, metadataBindings; runtime/metadata.go evalMetadataAccess, metadataSubject, metadataInstance; runtime/metaobject.go reflectiveMetaobject; semantics/annotations.go aboutAnnotations reaches an about annotation from a re-indexed twin of the annotated symbol, so the prompt's own scope tree reads it as the index does runtime/metadata_test.go:TestMetadataAccessBoundValues, :TestMetadataAccessAboutForm, :TestMetadataAccessEmpty (the metaobject alone), :TestMetadataAccessSameObjects (one metaobject across reads), :TestMetadataAccessNotAnElement, :TestMetadataAccessUnresolved, :TestMetadataAccessUnknownFeature; conformance metadata_access_annotations, metadata_access_textual_order; semantics/annotations_about_test.go:TestElementMetadataOrderedByDocumentThenPosition; parse/unbounded_and_metadata_access.golden; robustness_test.go:metadata_of_a_value, :metadata_of_an_unresolved_name, :metadata_without_the_reflective_library; semantics/metaclass_of_test.go; repl/metadata_about_eval_test.go:TestEvalMetadataReadsAboutAnnotations ✅ Faithful
Feature reference resolution eval.go:141 evalFeatureReference constraint_literal.sysml ✅ Faithful
A clock's currentTime is the run's shared clock (Kernel Semantic Library Clocks.kerml: Clock::currentTime, Occurrences::Occurrence::localClock, Clocks::universalClock): localClock.currentTime, this.localClock.currentTime and part.localClock.currentTime evaluate to the instant Context.Clock() stands at, a Real in seconds where the clock is Clocks::Clock and a Time::TimeInstantValue where it is Time::Clock — the instant on the clock's own scale, whose magnitude is the seconds since the run began, the same instant accept at waits for (Time::universalClock binds no TimeScale, so no named scale such as Time::UTC is asserted, and a comparison with a point on one is refused); the read is not a stored feature value, so it is never stale; a part holding no object reads nothing; a redefinition of currentTime (attribute now :>> currentTime;) is the same feature under another name, read and refused alike, bare in the clock's body or through a part; an assignment to currentTime is the typed ErrClockNotAssignable runtime/clock_read.go clockMember/isClock/isClockTime (the redefinition chain, not the name)/ClockValue; runtime/eval.go chainMemberValue, selfFeatureValue (the clock before a stored feature); runtime/assign_chain.go, classifier_behavior.go assignPerformerFeature (the write refused) conformance clock_read_local_clock_elapsed, clock_read_time_clock_instant, clock_read_redefined_current_time + .expected.json; robustness_clock_read_test.go:current_time_is_not_assigned, :redefined_current_time_is_not_assigned, :redefined_current_time_is_not_assigned_through_a_part, :redefined_current_time_reads_the_run_clock, :time_instant_is_no_point_on_a_named_scale (ErrUnevaluableLibraryFunction) (ErrClockNotAssignable), :clock_of_a_part_holding_no_object (ErrMultiplicityViolation), :current_time_reads_the_run_clock; migrate/opaque_migration_test.go:TestSwimlaneBodiesAndGuardsRunAgainstTheRepresentedPart (-observe this.Time_Acq_Total over a migrated workflow) ✅ Faithful, self-assessed (the pinned evaluator has no clock to advance; KerML §9.2.7 Clocks: currentTime is derived from the clock's localClock and no TimeInstantValue is assigned to it by a performance)
A model-level reference to a feature the model gives no value evaluates to an undetermined value, not an error and not <unset> (KerML 1.1 §7.4.9 Expressions: a FeatureReferenceExpression evaluates to the values of the feature it references, of which a bare attribute u; states none; §7.3.4.1: a feature with no multiplicity holds exactly one value). The pinned pilot leaves such an expression unevaluated — AttributeUsage u, OperatorExpression + — a non-answer rather than an error, and ValUndetermined is that non-answer as a first-class result: it carries the reason (u has no value in the model), the multiplicity the values conform to ([1] for u, [2..4] for xs : Real[2..4]) and the values it certainly holds, renders as <undetermined> on every surface (-eval, %eval, %eval in), crosses gRPC as Value.undetermined {reason, count} and decodes in every client. Only evaluation nothing features (modelLevel: no object, element or behavior frame) answers this way; an instantiated object holding no required value is still <unset> and ErrNoValue when used, a calc usage read as a value still names its outputs, a definition still cannot be evaluated runtime/undetermined.go Undetermined, NewUndeterminedValue, undeterminedFeatureValue; runtime/eval.go withoutValue, modelLevel, undeterminedFeature, Context.EvalDeclaredValue; runtime/value.go ValUndetermined, UndeterminedText; grpc/convert.go ValueToProtoIn (Value_Undetermined), ProtoToValue (ErrUndeterminedNotAccepted) runtime/undetermined_test.go:TestUnboundFeatureReadsUndeterminedAtModelLevel, :TestDeclaredValueOfUnboundFeatureIsUndetermined, :TestUnresolvedNamesStayErrorsBesideUndetermined, :TestInstanceLevelMissingValueStaysErrNoValue, :TestUndeterminedIsAValueKind, robustness_test.go:object_feature_without_a_value, repl/evalin_test.go, repl/runtime_commands_test.go:TestEvalArrayShapedByItsFeatures, grpc/undetermined_value_test.go, conformance scenario evaluate/a_feature_the_model_leaves_unvalued_is_undetermined, pilot-exec-diff undetermined_operands:unbound-ref (pilot-unevaluated) ✅ Faithful — externally refereed: the pilot returns the unevaluated expression for every such read (unbound-ref, add-unbound, nested-unbound, gt-unbound, if-unbound, index-by-unbound); adjudicated in pilot-execution-referee.md
An operation over an undetermined operand is undetermined unless another operand fixes the result: arithmetic, comparison, equality and identity, unary -/not, a conditional whose test is open (neither branch is evaluated; the result holds as many values as the branches declare, the fixed count both share — if b ? 1 else 2 holds [1], so size(if b ? 1 else 2) is 1 — or the range covering both, [1..2] for if b ? (1, 2) else 3, [0..*] where a branch's count is not declared; the ControlFunctions::'if' form reads its deferred branches the same way), ?? over an operand that may be empty (holding either that operand, then at least one value, or the fallback, the count covering both with the fallback's count read from the declarations — rack.loose ?? 3 holds [1..2], size(u ?? 3) is 1, notEmpty(rack.loose ?? 3) true; the ControlFunctions::'??' form reads its deferred fallback the same way), casts, ranges, indexing by an open index, feature chains (u.foo is still unresolved: members are checked against the feature's type before the chain is left open; a chain through an open collection reads the member of every value it certainly holds and keeps those as the values the result certainly holds, of the count the chain's declarations multiply plus theirs — rack.gear.tag over fixed :> gear and tagged :> gear { :>> tag = "x"; } with tag : String default = "d" is <undetermined> of [2..*] certainly holding "d" and "x", so includes(rack.gear.tag, "x") and rack.gear.tag->exists{in x; x == "x"} are true, excludes(rack.gear.tag, "d") and rack.gear.tag->forAll{in x; x == "d"} false, and includes(rack.gear.tag, "zz") stays open), sequence and set construction, and every library or model function applied to an open argument (twice(u)), of the count its result declares. An open operand is judged by the type its feature declares, as a determined value of that type would be: an operator or library parameter that admits no value of that type is ErrTypeMismatch before anything is left open — s - 1, -s, s > 1, s < r, not s, s and true, if s ? 1 else 2, (10, 20, 30)#(s), RealFunctions::'-'(s, 1), RealFunctions::sqrt(s), twice(s) for s : String, b - 1 for b : Boolean, StringFunctions::Length(r) for r : Real, twice(rack.gear) for a calc over Real — while an operation the type admits stays <undetermined> (s + "a", s < "a", r - 1, not b, if b ? 1 else 2, RealFunctions::sqrt(r), StringFunctions::Length(s)), an untyped u admits every operation, and a constant operand folds whatever the open one's type (false and s, b and false are false); the same relation admits an open value written to a typed feature, so the argument to a model calc's typed parameter is judged the same way. An open operand that certainly holds several values (xs : Real[2..4], ss : String[2..*], bs : Boolean[2]) is no scalar, and is judged as a determined sequence is: an operator taking one value is ErrTypeMismatch (xs + 1, xs > 1, -xs, (10, 20)#(xs), ss + "a", bs and true, not bs, if bs ? 1 else 2), a one-valued library or calc parameter ErrMultiplicityViolation (RealFunctions::'+'(xs, 1.0), RealFunctions::abs(xs), StringFunctions::Length(ss), StringFunctions::Substring(ss, 1, 1), BooleanFunctions::'|'(true, bs), twice(xs)), the parameter's declared multiplicity checked before a function decides its open arguments itself; one that may hold a single value (os : Real[0..4]) is still taken and stays <undetermined>, as are equality, identity, ?? and every operation taking a collection (size(xs), head(xs), includes(xs, 1.0), xs#(1), xs->collect{…}). The named ControlFunctions::'if' checks its open test as the if ? else operator does: 'if'(r, 1, 2) is ErrTypeMismatch, 'if'(bs, 1, 2) ErrMultiplicityViolation, 'if'(b, 1, 2) <undetermined>. A determined operand that alone makes the operation fail whatever the open one holds still fails it, in the operator and the library function forms alike: u / 0, u % 0, r / 0.0, RealFunctions::'/'(r, 0.0), IntegerFunctions::'%'(u, 0) and NaturalFunctions::'/'(u, 0) are ErrDivisionByZero, u + * the type mismatch the unbounded * is, while r / 2.0 stays <undetermined>. A scalar numeric library function checks every determined argument against its parameter's domain before it reads an open one: OpenSysMLMathFunctions::log(u, -1.0), log(u, 1.0) and log(-1.0, u) are ErrArithmeticDomain, IntegerFunctions::max(1.5, u) and NaturalFunctions::min(-1, u) the type mismatch, RationalFunctions::gcd(1.5, u) ErrArithmeticDomain and RationalFunctions::rat(u, 0) ErrDivisionByZero, while log(u, 10.0), IntegerFunctions::max(1, u) and abs(u) stay <undetermined> of [1]; an open argument declared to hold more than one value (RealFunctions::max(xs, 1.0)) is the multiplicity violation a sequence is. A library function checks its determined positions before it reads an open operand: a position no value of the operand admits fails it — Substring(s, 0, 2), includingAt(xs, 1.0, 0), subsequence(xs, 0, 1), excludingAt(xs, 0), and includingAt(xs, 1.0, 6), subsequence(xs, 2, 5), excludingAt(xs, 5) past the most xs : Real[2..4] admits, are ErrIndexOutOfRange — one every value admits leaves the result <undetermined> (Substring(s, 1, 2), includingAt(xs, 1.0, 1), subsequence(xs, 1, 2), excludingAt(xs, 1)), and one that selects nothing whatever the operand holds answers empty (Substring(s, 3, 2) is "", subsequence(xs, 3, 2) the empty sequence). The count is propagated where the model fixes it: (1, u) holds exactly two values so size((1, u)) is 2, and a chain multiplies the counts along it. includes/excludes decide from the elements both sequences certainly hold (includes((1, u + 1), 1) is true, excludes((1, u + 1), 1) false, includes((1), (2, u)) false since the determined (1) lacks the 2, excludes((1), (1, u)) false) and stay open when the sought element is itself unknown (includes((1, 2), u + 1), includes((1, 2), (1, u))). Indexing by an open index is undetermined unless the sequence is certainly empty, where it is the index error ()#(1) is (()#(u)). A sequence fixes each position up to its first element of open count, so indexing by a determined position answers where the model fixes it: (10, u, 30)#(1) is 10 and #(3) 30, #(2) <undetermined> of [1], #(4) ErrIndexOutOfRange, (10, xs, 30)#(1) 10 and #(2) <undetermined> for xs : Real[2..4]; head, last, tail, subsequence, excludingAt and includingAt read the fixed positions the same way (last(tail((10, u, 30))) is 30, includingAt((10, u, 30), 20, 2)#(4) 30). An insertion index is checked against the bound plus one in the saturating range arithmetic, so includingAt(vast, 1.0, 9223372036854775807) over vast : Real[0..9223372036854775807] is <undetermined> rather than rejected. select, reject, selectOne and collect apply their body to the elements the collection certainly holds and keep what results, the unknown rest staying open with a conservative count: (1, u)->select{in x; x == 1} certainly holds 1 and counts [1..2], ->reject{in x; x == 1} [0..1], ->selectOne{in x; x == 1} [1]; select and reject apply their test once more to a representative of the elements the collection may hold beyond the certain ones and decide for them as a whole — a test that does not depend on the element keeps or drops them all, so notEmpty(rack.gear->select{in x; true}) is true, rack.gear->reject{in x; true} and xs->reject{in x; true} the empty sequence, xs->select{in x; true} <undetermined> of [2..4], (1, u)->select{in x; x > 0} [1..2] (the test open on the representative), and a test that fails on any element fails the filter (xs->select{in x; 1 / 0 > 0} is ErrDivisionByZero); collect applies its body to the same representative and multiplies that count by theirs, so ->collect{in x; x + 1} certainly holds 2 and counts exactly [2] (size is 2), ->collect{in x; (x, x)} [4], rack.loose->collect{in x; x.mass} [0..2], and only a body of open count (->collect{in x; xs} for xs : Real[2..4], [4..8]) or an open collection leaves the result open, and includes((1, u)->select{in x; x == 1}, 1) is true runtime/undetermined.go undeterminedResult, undeterminedOne, undeterminedElements, undeterminedFiltered, undeterminedCollected, mayHoldUnknown, unknownElementOf, unknownCountOf, guaranteesOne, nonEmptyCount, undeterminedInvocation, openArgumentsOf, undeterminedAware, chainThroughUndetermined, Undetermined.unknownCount, Undetermined.Positions, spanOf, positionAt, positionsBetween, fixedPrefixOf, fixedPositionsOf; runtime/eval.go evalOperator, evalConditional, declaredCount, evalNullCoalesce, coalesceNull, Context.arithmeticValues, definiteArithmeticError, chainOverElements; runtime/library_operators.go checkOperands, naturalDivision; runtime/builtins_named.go builtinControlIf, deferredCount; semantics/collection.go Model.ValuesHeldBy, valuesHeldByEither, Range.Covering; runtime/collections.go undeterminedIndex, builtinSequenceHead, builtinSequenceTail, builtinSequenceLast, sequenceOfPositions, applyTest, filter, builtinControlSelectOne, builtinControlCollect, includesUndetermined, excludesUndetermined, fixedIndex, indexWithin, insertAt, builtinSequenceSubsequence, builtinSequenceExcludingAt; runtime/library_functions.go stringSubstring, registerLibraryFunction, numericScalars, scalarDomain, positiveReal, logarithmBase, integerDomain, naturalDomain; runtime/library_conversions.go wholeDomain, denominatorDomain; runtime/library_operators.go openScalar; runtime/open_domain.go openOperandType, describeOpenOperand, certainlySeveral, openValueMayBe, openOperandAdmits, admittedBy, openBinaryOperands, openUnaryOperand, openBoolOperand, openNumericIndex, arithmeticDomain, comparisonDomain; runtime/write_conformance.go valueConforms; runtime/undetermined.go openInvocation, fixedArg; runtime/cast.go classifyUndetermined; runtime/range.go runtime/undetermined_test.go:TestOperatorsPropagateUndetermined, :TestNullCoalescingKeepsOperandCounts, :TestDefiniteArithmeticErrorsSurviveOpenOperands, :TestDefiniteLibraryErrorsSurviveOpenOperands, :TestScalarFunctionsCheckDeterminedArguments, :TestFiltersDecideOverUnknownElements, :TestSequenceCountsAddUpFixedMultiplicities, :TestFixedPositionsOfOpenSequencesAnswer, :TestMembershipDecidesFromKnownElements, :TestUndeterminedIndexIntoEmptySequenceIsOutOfRange, :TestCollectionTransformsKeepKnownElements, :TestCollectOverOpenCollectionKeepsFiniteCounts, :TestOpenConditionalKeepsBranchCounts, :TestInvocationsPropagateUndeterminedArguments, :TestTypedOpenOperandsKeepOperatorDomains, :TestSeveralOpenValuesAreNoScalar, :TestNamedConditionalChecksOpenTest, :TestChainThroughOpenCollectionKeepsKnownValues, semantics/collection_test.go:TestValuesHeldByConditional, repl/evalin_test.go:TestEvalInDeclarationScopeCompoundsOverValuelessFeaturesAreUndetermined, :TestEvalInDeclarationScopeChainIsUnresolvedForALinkOfAnotherName, pilot-exec-diff undetermined_operands:add-unbound, :nested-unbound, :gt-unbound, :eq-unbound, :neg-unbound, :if-unbound, :index-by-unbound, :includes-unknown-element, :excludes-unknown-element, :includes-unknown-probe, :size-mixed, :includes-known-absent, :excludes-known-shared, :index-empty-by-unbound, :select-keeps-known, :select-known-count, :collect-keeps-known, :collect-fixed-count, :collect-loose-count, :size-if-unbound, :size-if-pairs, :size-if-differing, :size-coalesce-unbound, :notempty-coalesce-loose, :div-unbound-by-zero, :mod-unbound-by-zero, :div-unbound-by-two, :substring-unbound-zero, :substring-unbound-valid, :substring-unbound-inverted, :includingat-unbound-zero, :includingat-unbound-past, :includingat-unbound-valid, :subsequence-unbound-zero, :subsequence-unbound-past, :subsequence-unbound-valid, :excludingat-unbound-zero, :excludingat-unbound-past, :excludingat-unbound-valid, :index-fixed-first, :index-fixed-last, :index-fixed-open, :index-fixed-past, :head-fixed, :last-fixed, :last-tail-fixed, :subsequence-fixed-index, :excludingat-fixed-index, :includingat-fixed-index, :index-open-count-first, :max-integer-real-unbound, :max-integer-unbound, :rat-unbound-zero-denum, :gcd-fraction-unbound, :select-constant-gear-nonempty, :reject-constant-gear-empty, :select-constant-unbound-count, :reject-constant-unbound-empty, :select-failing-unbound, :typed-string-minus, :typed-string-negate, :typed-string-ordered-number, :typed-string-not, :typed-string-and-true, :typed-string-condition, :typed-string-index, :typed-string-two-open, :typed-boolean-minus, :typed-real-length, :typed-real-function-string, :typed-string-concat, :typed-string-ordered-string, :typed-real-minus, :typed-boolean-not, :typed-boolean-condition, :typed-real-index, :typed-string-and-false, :typed-boolean-and-false, :chain-known-tag-default, :chain-known-tag-redefined, :chain-open-tag, :chain-includes-default, :chain-includes-redefined, :chain-includes-unknown, :chain-excludes-known, :chain-exists-known, :chain-forall-refuted, :chain-open-mass, :chain-size-open, :chain-loose-tag, :chain-no-member, :named-if-real-test, :named-if-several-test, :named-if-boolean-test, :several-plus, :several-greater, :several-negate, :several-index, :several-boolean-and, :several-library-plus, :several-library-length, :several-library-substring, :several-equal, :several-size, :several-head, :optional-plus, :optional-index, vast_bounds:includingat-vast-max, :includingat-vast-one ✅ Faithful — externally refereed: the pilot agrees on includes-unknown-element, excludes-unknown-element, size-unbound, select-constant-gear-nonempty, reject-constant-gear-empty, the nine positional cases it evaluates (index-fixed-first, index-fixed-last, index-open-count-first, head-fixed, last-fixed, last-tail-fixed, subsequence-fixed-index, excludingat-fixed-index, includingat-fixed-index), size-mixed, includes-known-absent, excludes-known-shared, select-keeps-known, collect-keeps-known, collect-fixed-count, size-if-unbound, size-coalesce-unbound, notempty-coalesce-loose, typed-string-and-false, typed-boolean-and-false, chain-known-tag-default, chain-known-tag-redefined, chain-includes-default, chain-excludes-known and chain-forall-refuted and leaves the operator forms unevaluated, the typed-operand forms among them (s - 1, s + "a", …, where it evaluates not s and s and true to true and false by comparing the unevaluated usage element to a literal, two ours-error that are no verdict against the type mismatch), u / 0 and u % 0 among them, as it does the zero-position library forms and the four scalar-function forms whose determined argument is checked (max-integer-real-unbound, rat-unbound-zero-denum, …), while its IndexOutOfBoundsException for the past-the-end ones and for subsequence(xs, 1, 2) indexes the one unevaluated usage element; its 1 for size-if-pairs counts the unevaluated if (a disagree that is no verdict), and its "d" for chain-open-tag reads the default through the one unevaluated PartUsage gear, never seeing the subsetters, so its false for chain-includes-redefined and chain-exists-known (two more disagree) and for chain-includes-unknown are no verdicts either; its false/true for eq-unbound, not-unbound, includes-unknown-probe and excludes-unknown-probe compare the unevaluated usage element to a literal and are not read as answers (adjudicated in pilot-execution-referee.md)
The conditional and, or and implies (ControlFunctions: in firstValue : Boolean[1]; in expr secondValue[0..1]) answer whenever one operand fixes the result. The first operand short-circuits as the library states — false and x, true or x, false implies x never read x. An undetermined first operand does not abort: the second is read, and where it fixes the result on its own — (u > 3) and false is false, (u == 1) or true and (u == 1) implies true are true — that is the answer, since every value of the unknown yields it. x and true, x or false, x implies false, not x and the eager xor, &, | over an unknown stay <undetermined>. The library declares nothing for a first operand that is not a Boolean value, so the folding is a reading; the alternative — laziness makes a determined first operand a precondition, leaving all three <undetermined> — is recorded in the referee document and not taken runtime/eval.go evalLogical, logicalWithOpenLeft, combineBooleanValues; runtime/builtins_named.go builtinControlLogical runtime/undetermined_test.go:TestBooleanOperatorsFoldOnEitherConstantOperand, grpc/undetermined_value_test.go:TestEvaluate_ConstantOperandFoldsBesideUndetermined, conformance scenario evaluate/a_constant_operand_decides_a_boolean_over_an_undetermined_one, pilot-exec-diff undetermined_operands:and-first-false, :or-first-true, :implies-first-false, :and-second-false, :or-second-true, :implies-second-true, :and-second-true, :or-second-false, :implies-second-false, :not-unbound ✅ Faithful — externally refereed: all six folding cases agree with the pinned pilot; the three the constant does not fix are ours-undetermined against a pilot line that compares the unevaluated expression to a literal (adjudicated in pilot-execution-referee.md, Boolean folding on the second operand)
A model-level read of a usage whose multiplicity the model leaves open yields no definite count (KerML 1.1 §7.3.4.1, §7.4.12 Multiplicities: a multiplicity bounds the number of values; it does not choose one). size(rack.gear) over gear[1..*], size(rack.loose), isEmpty(rack.loose) and notEmpty(rack.loose) over loose[0..2], size((rack.gear, rack.loose)), rack.gear#(7) and a chain through such a usage (rack.gear.mass) are <undetermined>, carrying the bounds ([1..*], [0..2], [1..*] for the pair). What the bounds fix still answers: notEmpty(rack.gear) is true and isEmpty(rack.gear) false since the lower bound is 1. A fixed multiplicity keeps its definite answer — size(rack.slots) over slots[3] is 3, rack.slots#(2) an instance, size(rack.slots.mass) 3, size(rack.lone) 1, size(Mode::ON) 1 — and so does the fixed count 0: a feature declared [0] (part vacant[0], attribute none : Integer[0], a [0] quantity) reads as the empty sequence, typed where its dimension is known, so size(rack.vacant) is 0 and isEmpty(rack.vacant) true. Materializing the minimum multiplicity is the instantiated-object contract and is unchanged: on %instantiated objects size(rack.gear) is 1 and isEmpty(rack.loose) true. The model-level read never materializes an open collection: a lower bound too large to build into objects (many[10001..*]) reads <undetermined> of its bounds where an object-level read reports a multiplicity violation, and the values features subsetting the collection contribute (fixed :> gear) are what it certainly holds — includes(rack.gear, rack.fixed) is true, rack.gear->exists{in x; x == rack.fixed} true, includes(rack.gear, rack.lone) <undetermined> — a subsetter whose own count is open being read the same way rather than built up to its lower bound, contributing the fewest values it holds and no made-up object: part sub[10001..*] :> base leaves base[0..*] <undetermined> of [10001..*] holding nothing certain (notEmpty(store.base) true), three[3..*] :> cap[0..3] fixes size(store.cap) at 3, and pool[0..*] under inner[2] and outer[5..*] certainly holds the two of inner and [5..*] values — as are the elements a quantifier decides from: (1, u)->exists{in x; x == 1} is true on the witness, (1, u)->forAll{in x; x > 2} false on the counterexample, anyTrue((true, b)) true, and (1, u)->exists{in x; x == 2} <undetermined>. A quantifier also applies its test to a representative of the elements the collection may hold beyond the certain ones, so a test that does not depend on the element decides: over a collection certainly holding one (gear[1..*], many[10001..*]) ->exists{in x; true} is true and ->forAll{in x; false} false, over any collection ->exists{in x; false} is false and ->forAll{in x; true} true, while rack.loose->exists{in x; true} and rack.gear->forAll{in x; x.mass > 1.0} stay <undetermined>. A bound the model does not evaluate (a : Real[n], an : Real[1..n] over a valueless n : Natural) fixes no count either: the model-level read is <undetermined> of the bounds the declaration does fix ([?..?], [1..?]; notEmpty(an) is true, and a sequence or chain over it keeps a lower bound of what its known operands fix) and is never materialized, where an object-level read stays the unknown multiplicity error runtime/undetermined.go openFeatureRead, memberFeatureValue, openCollectionValue, holdsOnlyUnset, knownElementsOf; runtime/eval.go undeterminedFeature, Context.emptyOfSymbol; runtime/instance.go Instance.openFeatureValue, materializeIntrinsic (an openPopulation stops before the lower bound); runtime/subsetting.go openSubsettingContributions, eachSubsetterOf, fewestOf; runtime/collections.go quantify (over unknownElementOf, deciding by guaranteesOne), truthOf, openCountReason, certainlyEmpty, certainlyNonEmpty; runtime/collections.go emptiness, undeterminedIndex, builtinSequenceSize; runtime/eval.go chainOverElements; runtime/condition.go Context.readThrough; semantics/collection.go Range.Plus, Range.Times, Range.Covering, atLeast; semantics/multiplicity.go Range.AdmitsMore, Range.MayAdmitMore runtime/undetermined_test.go:TestFixedCardinalitiesStayDefinite, :TestExactlyZeroFeatureReadsEmptyAtModelLevel, :TestOpenCardinalitiesAreUndetermined, :TestOpenCollectionIsNotMaterializedAtModelLevel, :TestOpenCollectionKnowsItsSubsetters, :TestOpenSubsettersAreNotMaterializedAtModelLevel, :TestQuantifiersDecideFromKnownElements, :TestQuantifiersDecideFromConstantTests, :TestInstantiatedObjectKeepsMaterializedMinimums, :TestUnknownMultiplicityBoundsReadUndeterminedAtModelLevel, semantics/multiplicity_test.go:TestRangesWithUnknownBounds, optional_feature_test.go:TestValuelessDeclarationIsUndeterminedHoweverSpelled, :TestInheritedDeclarationIsUndeterminedOfInheritedMultiplicity, :TestOptionalOccurrenceDeclarationIsUndeterminedUntilInstantiated, grpc/undetermined_value_test.go:TestEvaluate_OpenCardinalityIsSentUndeterminedWithBounds, pilot-exec-diff undetermined_operands:size-slots, :size-lone, :size-enum-literal, :size-gear, :notempty-gear, :isempty-gear, :isempty-loose, :notempty-loose, :size-loose, :size-gear-loose, :index-slots, :index-gear, :size-many, :notempty-many, :includes-subsetter, :includes-other, :subsetter-open-base, :subsetter-open-not-empty, :subsetter-capped-size, :subsetter-mixed-includes, :exists-known-witness, :forall-known-counterexample, :exists-unknown, :anytrue-known-witness, :vacant-ref, :size-vacant, :isempty-vacant, :exists-constant-gear, :forall-constant-gear, :exists-constant-loose, unknown_bounds:unknown-bound-ref, :size-unknown-bound, :notempty-unknown-upper, :isempty-unknown-bound ✅ Faithful — externally refereed as far as the pilot reaches: it agrees on size-lone, size-enum-literal, notempty-gear, isempty-gear, notempty-many, the three quantifier forms a known element decides and the two a constant test decides over gear[1..*] (its true for exists-constant-loose quantifies over the unevaluated PartUsage loose, which may hold nothing); it materializes nothing, so its size(rack.slots) = 1 and size((rack.gear, rack.loose)) = 2 count unevaluated operand expressions and its includes(rack.gear, rack.fixed) = false compares two unevaluated usage elements, as its size(rack.vacant) = 1 and isEmpty(rack.vacant) = false count the unevaluated PartUsage vacant (the four disagree), evidence that no definite answer exists, not a source for the values; it rejects a model whose bound names a valueless feature outright (Must have a Natural value), so the unknown_bounds cases are pilot-error and self-assessed; the three readings not taken — error, materialized minimum, empty for [0..n] — are recorded in pilot-execution-referee.md, Open cardinality at model level
Qualified name resolution (A::B::C) eval.go:53 Eval + resolve/qualified.go calc_qualified_names.sysml ✅ Faithful
A qualified name evaluates as the checker resolves it, imports included (KerML §8.2.3.5 import, §8.2.3.3 visibility): a segment that names a member a public import re-exports — of every member (Bq::x with public import A::*), of one (public import A::x), recursively (import A::**), through a façade of a façade, by short name or through an alias — reaches the same element the checker reaches, which is how the library's ISQ and SI re-export the ISQ part packages (ISQ::speed, SI::speed); a private import stays reachable only from inside the importing namespace, and a name the checker rejects fails at evaluation with the checker's own message and classification (unresolved reference: Priv::x; ambiguous reference: Twice::t (2 candidates) when several members answer to a segment) runtime/eval.go evalNameGeneral resolves the whole name through resolve.Resolver.ReadQualified (the checker's walkQualified, so the two tiers cannot disagree), whose Reading carries the element, the resolved segments — routing a calc usage's outputs to evalCalcUsageMembers and keeping the variant/literal reports in unresolvedQualifiedName — and the candidate count of a name rejected as ambiguous; a reading is memoized by scope and node, so one expression evaluated in several scopes answers in each with what that scope sees calc_qualified_name_through_import.sysml, calc_qualified_name_private_import.sysml, calc_qualified_name_ambiguous.sysml, qualified_import_test.go:TestQualifiedNameThroughImportEvaluates, :TestQualifiedNameThroughImportRejectedAsChecked, :TestQualifiedNameThroughImportKeepsTypedErrors, :TestLibraryQuantityThroughFacadeResolves, :TestQualifiedNameEvaluatedInSeveralScopes, resolve/reading_test.go:TestReadQualifiedAnswersPerScope, :TestReadQualifiedReportsFailure ✅ Faithful (a library quantity reached through a façade resolves to its declaration, which like SI::m carries no scalar value, so the evaluator reports that declaration rather than a missing member)
Type coercion (Integer→Real) eval.go:344 toReal calc_type_coercion.sysml ✅ Faithful
Exponentiation (**, ^) — Integer operands with a non-negative exponent give an Integer (IntegerFunctions::'**'), any other numeric pair a Real (RealFunctions::'**') semantics/eval.go Pow, shared by the folder's evalArithmetic and runtime/eval.go evalArithmetic calc_library_functions.sysml, exponentiation_test.go ✅ Faithful
An enumeration literal is a value of its enumeration (SysML v2 §7.6.4, §8.3.7 EnumerationUsage): a literal declaring no value evaluates to itself, and identity is the declaration it names — Color::red == Color::red is true, Color::red == Color::green false, and a literal of another enumeration false, since the two are unrelated values (the mismatch is a type-tier diagnostic, not a runtime one). A set keys a literal on that declaration, so the same literal twice is one element runtime/value.go ValEnumLiteral/Value.LiteralText, eval.go EvalContext.enumLiteralValue/valueEqual, value_equality.go valueKeyFunc, semantics/enumeration.go EnumerationOwning/LiteralsOf enum_literal_default_slot.sysml, eval_test.go:TestEval_EnumerationLiteralIsAValue, TestEval_EnumerationLiteralEquality, TestEval_EnumerationLiteralInASet, robustness_test.go:enumeration_name_that_is_not_a_literal ✅ Faithful
A literal of an enumeration specializing a scalar type (enum def GradePoints :> Real { A = 4.0; }) is a value of that type, so it evaluates to the value it declares and computes as one runtime/eval.go EvalContext.enumLiteralValue enum_literal_scalar_valued.sysml, eval_test.go:TestEval_EnumerationLiteralWithAValue ✅ Faithful
A literal is an occurrence of its enumeration, so the features it declares are readable (Level::high.n) and every read of that literal answers about one object runtime/eval.go chainMemberValue (ValEnumLiteral arm), Context.enumLiteralObject enum_literal_own_attributes.sysml, eval_test.go:TestEval_EnumerationLiteralOwnAttributes, TestEval_EnumerationLiteralReadTwiceIsOneObject, robustness_test.go:chain_through_a_literal_without_that_attribute ✅ Faithful
An enum-typed feature's default materializes as a feature value, and a literal renders as the enumeration writing it qualifies it (c = Color::red) in %features, in a trace and in a diagnostic repl/meta.go formatFeatureValue, runtime/trace.go FormatTraceValue, runtime/describe.go describeOperand enum_literal_default_slot.sysml, repl/meta_test.go:TestFeatureValuesEnumerationLiteral, describe_test.go:TestDescribeOperandEnumerationLiteral ✅ Faithful
A literal crossing the API boundary keeps its identity: it travels as Value.enum_literal carrying the declaration FQN, the enumeration's FQN and the qualified rendering, and an incoming literal is resolved against the model it names rather than reconstructed, so a literal no declaration of that model matches is an error and never a null grpc/convert.go enumLiteralToProto/enumLiteralFromProto, api/proto/sysml.proto EnumLiteral; Python opensysml/enumeration.py EnumLiteral, values.py, connection.py _python_to_value grpc/convert_enum_test.go:TestEnumLiteralToProto, TestEnumLiteralRoundTrip, TestEnumLiteralRoundTripInSequence, TestEnumLiteralUnresolvedIsAnError, TestInstantiate_EnumTypedFeatureValueCarriesLiteral; client/python/tests/test_enumeration.py, test_wire_compat.py:test_enum_literal_is_an_added_value_arm ✅ Faithful (advertised as the enum_values capability; a literal of an enumeration specializing a scalar crosses as that scalar, as it evaluates to one)
A Complex crosses the API boundary as one value: Value.complex carries the real and imaginary parts as two doubles, so 1.0 + 2.0i cannot be read as a sequence of two Reals, and an incoming complex decodes to one ValComplex. Every client this repository ships decodes it to one native number — Go opensysml.Complex, Python complex, Node { kind: "complex" }, Java Value.ComplexValue, Rust Value::Complex — and renders it in rectangular form grpc/convert.go ComplexToProto/ProtoToComplex, capability_response.go (complex_values arm), api/proto/sysml.proto Complex; client/opensysml/value.go Complex; client/python/opensysml/values.py, connection.py _python_to_value; client/node/src/core/values.ts; client/java/.../Value.java ComplexValue, internal/Protos.java; client/rust/opensysml/src/domain.rs Complex grpc/convert_complex_test.go, client/opensysml/complex_test.go, client/python/tests/test_complex.py, client/node/test/values.test.ts, client/java/.../ProtosTest.java, client/rust/opensysml/tests/client.rs; conformance/scenarios/04-evaluate.json, 05-instantiate.json (complex.sysml) over gRPC, Connect protobuf and Connect JSON ✅ Faithful (advertised as the complex_values capability; a service withholding it reports an unsupported null naming the number, and a client built before the arm existed reads it as an unknown Value arm)
An Array, a Vector and a VectorQuantity cross the API boundary whole, in both directions: Value.array carries dimensions and the elements flattened in row-major order (each element a Value, so an array of quantities or of arrays nests), Value.vector carries the numeric components as Values so an Integer and a Real component stay distinct, and Value.vector_quantity carries one Quantity per component — magnitude, unit as written and reduced unit term, so a composed unit (m/s) and per-component units survive. Every client this repository ships maps them to a native shape that checks its own invariants — Go opensysml.Array/Vector/VectorQuantity, Python Array/Vector/VectorQuantity dataclasses, Node { kind: "array" | "vector" | "vectorQuantity" }, Java Value.ArrayValue/VectorValue/VectorQuantityValue, Rust Value::Array/Vector/VectorQuantity — and one sent as an action input or calc argument decodes to the same runtime value; a malformed one (dimensions the elements do not fill, a non-positive extent, a non-numeric vector component, an empty vector quantity, a unit without its reduction) is a typed error on whichever side sees it first, never a value with a different shape grpc/convert.go arrayToProto/protoToArray, vectorToProto/protoToVector, vectorQuantityToProto/protoToVectorQuantity, ErrArrayDimensionNotPositive, ErrArrayShapeMismatch, ErrVectorComponentNotNumeric, ErrVectorQuantityEmpty; capability_response.go (structured_values arm); api/proto/sysml.proto Array, Vector, VectorQuantity; client/opensysml/value.go, convert.go, client.go (structured_values preflight); client/python/opensysml/values.py, connection.py; client/node/src/core/values.ts; client/java/.../Value.java, internal/Protos.java; client/rust/opensysml/src/domain.rs grpc/convert_structured_test.go, client/opensysml/structured_test.go, structured_internal_test.go, client/python/tests/test_structured.py, client/node/test/values.test.ts, client.test.ts, client/java/.../ProtosTest.java, ApiIntegrationTest.java, client/rust/opensysml/src/domain.rs tests, tests/client.rs; conformance/scenarios/04-evaluate.json, 10-evaluate-calc.json (structured.sysml) over gRPC, Connect protobuf and Connect JSON ✅ Faithful (advertised as the structured_values capability; a service withholding it reports an unsupported null naming the value and refuses one sent to it with UNIMPLEMENTED, the clients refusing before the round trip; a client built before the arms existed reads each as an unknown Value arm)
A set and a tensor quantity cross the API boundary whole, in both directions: Value.set carries the members as Values (so a set of quantities, of objects or of sets nests), listed in canonical order and readable in any order, and Value.tensor_quantity carries dimensions and one Quantity per row-major component — magnitude, unit as written and reduced unit term — at any rank, a rank-one tensor staying a tensor_quantity rather than becoming a vector_quantity. Every client this repository ships maps them to a native shape that checks its own invariants — Go opensysml.Set/TensorQuantity, Python SetValue/TensorQuantity, Node { kind: "set" \| "tensorQuantity" }, Java Value.SetValue/TensorQuantityValue, Rust Value::Set/TensorQuantity — each set order-insensitively equal and refusing a repeated member, each tensor indexed row-major with one index per dimension; a malformed one (a repeated set member, a non-positive dimension, components that do not fill the dimensions, a component without its quantity) is a typed error on whichever side sees it first grpc/convert.go setToProto/protoToSet, tensorQuantityToProto/protoToTensorQuantity, CheckTensorShape, ValueCarriesSet, ValueCarriesTensor, ErrSetElementRepeated, ErrTensorDimensionNotPositive, ErrTensorShapeMismatch, ErrTensorComponentMissing; capability_response.go (set_values, tensor_values arms); api/proto/sysml.proto ValueSet, TensorQuantity; client/opensysml/value.go, convert.go, client.go (the two preflights); client/python/opensysml/values.py, connection.py; client/node/src/core/values.ts; client/java/.../Value.java, internal/Protos.java; client/rust/opensysml/src/domain.rs grpc/convert_set_tensor_test.go, client/opensysml/set_tensor_test.go, structured_internal_test.go, client/python/tests/test_set_tensor.py, client/node/test/values.test.ts, client.test.ts, client/java/.../ProtosTest.java, PublicTypesTest.java, ApiIntegrationTest.java, client/rust/opensysml/src/domain.rs tests, tests/client.rs; conformance/scenarios/01-server-info.json, 04-evaluate.json, 10-evaluate-calc.json (set_tensor.sysml) over gRPC, Connect protobuf and Connect JSON ✅ Faithful (advertised as the set_values and tensor_values capabilities, each on its own; a service withholding one reports an unsupported null naming the value and refuses one sent to it — nested anywhere in the argument — with UNIMPLEMENTED, the clients refusing before the round trip; a client built before the arms existed reads each as an unknown Value arm)
A bare measurement reference crosses the API boundary whole, in both directions: Value.measurement_ref carries the unit as written, its reduced unit term (required wherever the unit names one, as Quantity requires it) and the canonical id of the declaration a named unit is (SI::metre), which a composed unit (m / s, a DerivedUnit) omits. Every client this repository ships maps it to a typed reference — Go opensysml.MeasurementRef, Python MeasurementRef (over Unit), Node { kind: "measurementRef" }, Java Value.MeasurementRefValue, Rust Value::MeasurementRef — and one sent as a calc argument decodes to the same ValMeasurementRef, so ConvertQuantity(q, ref) converts through it; a malformed one (no unit and no id, a named unit without its reduction, an id naming no unit declaration, a reduction disagreeing with the declaration's own) is a typed error on whichever side sees it first grpc/convert.go MeasurementRefToProto/ProtoToMeasurementRef/declaredMeasurementRef, ValueCarriesMeasurementRef, ErrMeasurementRefEmpty, ErrMeasurementRefNeedsIndex; capability_response.go (measurement_refs arm); api/proto/sysml.proto MeasurementRef; client/opensysml/value.go, convert.go, client.go (measurement_refs preflight); client/python/opensysml/values.py, connection.py; client/node/src/core/values.ts; client/java/.../Value.java, internal/Protos.java; client/rust/opensysml/src/domain.rs grpc/convert_measurement_ref_test.go, client/opensysml/measurement_ref_test.go, client/python/tests/test_measurement_ref.py, client/node/test/values.test.ts, client.test.ts, client/java/.../ProtosTest.java, ApiIntegrationTest.java, client/rust/opensysml/src/domain.rs tests, tests/client.rs; conformance/scenarios/01-server-info.json, 04-evaluate.json, 10-evaluate-calc.json (measurement_ref.sysml) over gRPC, Connect protobuf and Connect JSON ✅ Faithful (advertised as the measurement_refs capability, separate from structured_values so a client built against those arms keeps reading a bare reference as unsupported: measurement reference m; a service withholding it reports that unsupported null and refuses one sent to it with UNIMPLEMENTED, the clients refusing before the round trip)
A quantity crosses the API boundary in both directions: Value.quantity carries the magnitude with the kind it was written in (Integer or Real), the unit as written and the reduced unit term, so a quantity read from the service can be sent back as an input and evaluates against the unit it names — commensurability is decided over the reduction, so a unit named without one is refused client-side rather than compared by bare magnitude opensysml/values.py Quantity.to_pb, Unit.to_pb/Unit.reduced, connection.py _python_to_value; service side grpc/convert.go ProtoToQuantity, ProtoToValueIn client/python/tests/test_quantity.py: test_a_quantity_encodes_as_the_message_the_service_decodes, test_an_unreduced_unit_is_refused_before_it_is_sent, and against a live service TestQuantityAgainstTheService::test_a_quantity_sent_as_a_calc_argument_round_trips, ::test_a_quantity_input_binds_into_an_action, ::test_a_sent_quantity_is_commensurable_with_the_models_own_units ✅ Faithful
A function value crosses the API boundary as Value.function: calc_id, the qualified name of the calc it is a value of, and self_id, the id (within the answering response) of the object it was read off, 0 for none — identity being the pair. One sent as an argument (EvaluateCalc, ExecuteAction, RunAnalysis) is rebound to that calc of the named model and invoked through the calc-typed parameter it binds; an empty calc_id, one naming no calc, or any non-zero self_id is refused in band (ErrFunctionUnbound), never a null — objects live only within the call that created them, so a self_id is never matched to whichever object a later call numbered the same — and a function value nested in a sequence or array is found wherever it sits. A value closing over a behavior body's bindings cannot be named by calc and object, so it crosses as unsupported: function <calc> (ErrFunctionNeedsRuntime on the way in). Every client this repository ships maps the arm to a typed value — Go opensysml.Function, Python opensysml.Function, Node { kind: "function" }, Java Value.FunctionValue, Rust Value::Function — and refuses to send one to a service lacking the capability grpc/convert.go functionToProto/functionFromProto/ProtoToRuntimeValue, ValueCarriesFunction, ErrFunctionUnbound, ErrFunctionNeedsRuntime; capability_response.go (function_values arm); service.go CapabilityFunctionValues; api/proto/sysml.proto Function; client/opensysml/value.go, convert.go, client.go (function_values preflight); client/python/opensysml/values.py, connection.py; client/node/src/core/values.ts; client/java/.../Value.java, internal/Protos.java; client/rust/opensysml/src/domain.rs grpc/convert_function_test.go:TestFunctionRoundTrip, :TestObjectBoundFunctionsDoNotCrossCalls, :TestMalformedFunctionsAreRejected, :TestFunctionCapability, :TestValueCarriesFunction; client/opensysml/function_test.go; client/python/tests/test_function.py; client/node/test/values.test.ts, client.test.ts; client/java/.../ProtosTest.java, ApiIntegrationTest.java; client/rust/opensysml/tests/client.rs; conformance/scenarios/01-server-info.json, 04-evaluate.json, 10-evaluate-calc.json (function.sysml) over gRPC, Connect protobuf and Connect JSON ✅ Faithful (advertised as the function_values capability; a service withholding it reports the unsupported null and refuses a function argument with UNIMPLEMENTED, the clients refusing before the round trip)
A metaobject crosses the API boundary as Value.metaobject: element_id, the qualified name of the element it reflects on — its identity — and metaclass_id, the qualified name of the metaclass that classifies that element (its own, never the cast's target: seatBelt meta KerML::Feature crosses as Meta::seatBelt : SysML::Systems::PartUsage). Its reflective features are not serialized; the receiving side reads them from its model. One sent as an argument (EvaluateCalc, ExecuteAction, RunAnalysis) is rebound to the named model's element, metaclass_id resolved from the model when omitted and refused in band when it names another metaclass (ErrMetaclassMismatch); an empty element_id or one naming no element is refused in band (ErrMetaobjectUnbound), one two declarations share is refused as ambiguous (ErrMetaobjectAmbiguous) rather than bound to the first, never a null, and a metaobject nested in a sequence, set or array is found wherever it sits. An anonymous element, which has no qualified name to send, crosses as unsupported: metaobject of an element with no qualified name; a service withholding the capability answers unsupported: metaobject <element> : <metaclass> instead of the arm. Every client this repository ships maps the arm to a typed value equal by element — Go opensysml.Metaobject, Python opensysml.Metaobject, Node { kind: "metaobject" }, Java Value.MetaobjectValue, Rust Value::Metaobject — rejects an empty element_id on the way in, and refuses to send one to a service lacking the capability grpc/convert.go metaobjectToProto/metaobjectFromProto/ProtoToRuntimeValue, ErrMetaobjectUnbound, ErrMetaobjectAmbiguous, ErrMetaclassMismatch; capability_response.go (metaobject_values arm); service.go CapabilityMetaobjectValues; api/proto/sysml.proto Metaobject; client/opensysml/value.go, convert.go, client.go (metaobject_values preflight); client/python/opensysml/values.py, connection.py, capabilities.py; client/node/src/core/values.ts, capabilities.ts; client/java/.../Value.java, internal/Protos.java, Capabilities.java; client/rust/opensysml/src/domain.rs grpc/convert_metaobject_test.go:TestMetaobjectRoundTrip, :TestMalformedMetaobjectsAreRejected, :TestUnnamedMetaobjectCrossesAsUnsupportedNull, :TestAmbiguousMetaobjectIsRejected, :TestMetaobjectCrossesAsCalcArgument, :TestMetaobjectCapability; client/opensysml/metaobject_test.go; client/python/tests/test_metaobject.py; client/node/test/values.test.ts, client.test.ts; client/java/.../ProtosTest.java, PublicTypesTest.java, ApiIntegrationTest.java; client/rust/opensysml/src/domain.rs (tests); conformance/scenarios/01-server-info.json, 04-evaluate.json, 10-evaluate-calc.json (metaobject.sysml) over gRPC, Connect protobuf and Connect JSON ✅ Faithful (advertised as the metaobject_values capability; a service withholding it reports the unsupported null and refuses a metaobject argument with UNIMPLEMENTED, the clients refusing before the round trip)
An unqualified name resolves as a written reference does — the enclosing scope chain, inherited members, imports, then the global index — and the declaration it finds is evaluated in its own declaring scope, so the imports in force where a value was written answer the names that value uses runtime/eval.go evalFeatureReference (scope arm) via resolve/unqualified.go Resolver.LookupName, EvalContext.evalIn action_body_package_member.sysml, action_body_declarer_scope.sysml, body_scope_test.go:TestBodyScopeImportSpellings, robustness_test.go:action_body_unresolved_feature ✅ Faithful

Scope of an expression in a behavior body

An expression written inside an action or state machine body resolves its names in the scope it was declared in, and the values live above that scope: a frame binding (the action's feature space, a block-local declaration, a call trigger's argument) shadows a same-named declaration the scope reaches, and the innermost frame wins. The scope travels with the IR — internal/ir/lower records it on the graph, on each lowered statement and block, on each state and on each transition when it lowers them — so the executors read a scope rather than re-deriving one from symbol.Decl (AGENTS.md §4).

Semantic Rule Implementation Test Case Status
An attribute default in a behavior body is evaluated in that body's scope, so a unit an import brought in resolves (attribute h : LengthValue = 500.0 [m];) lower/action_graph.go lowerAttributes + ActionGraph.Scope; runtime/action_executor.go initializeAttributes; lower/state_graph.go + runtime/state_executor.go initializeAttributes action_body_quantity_descent.sysml, state_body_quantity_scope.sysml, tests/parser/testdata/parse/action_body_quantity_statements.golden ✅ Faithful
A parameter or attribute default an action inherits from a generalization and does not redefine is seeded when the performance starts, as an owned default is — evaluated in the scope it was declared in, after the owned ones, so a parameter the action redefines is read as redefined (KerML 1.0 §7.4.7: an inherited feature is a feature of the specializing type, with its value); a default that fails to evaluate is reported when the action starts, naming the parameter runtime/action_executor.go performanceFeatures (the graph's attributes, then semantics.Model.MembersOf less what the library base types contribute) → initializeAttributes/declaresAttribute conformance action_inherited_default_root (a body reassigning x leaves the seeded z = x * 2 alone), action_node_inherited_default (typed, invoked and performed nodes; a redefined x read by an inherited z); robustness_test.go:node_inherited_default_that_cannot_be_evaluated ✅ Faithful
A statement in a nested action node resolves in that node's scope; a statement in a loop body or an if branch in the block's own scope lower/action_graph.go lowerStatement, lowerBlock, childScope (lower/scope.go); runtime/action_statements.go evalIn action_body_quantity_descent.sysml, action_body_shadows_enclosing_scope.sysml ✅ Faithful
A decision guard and an inline expression resolve in the action's own scope, its feature values shadowing it runtime/action_executor.go stepDecisionNode, stepActionExecutionNode action_body_shadows_enclosing_scope.sysml, action_control_flow.sysml ✅ Faithful
A transition's guard, effect, time-event duration and change-event condition resolve in the scope the transition was written in; a call trigger's parameters and an accept trigger's payload are members of the transition, reachable by simple name from its guard, effect and body and inaccessible by simple name elsewhere; the exit of the state the transition leaves reads them qualified by the transition's name (row above) lower/state_graph.go Transition.Scope/BodyScope (via symbols.TriggerScope); symbols/bodyscopes.go triggerParameterDefiner, payloadParameterDefiner; passes/w8c_feature_reference.go; resolve/document.go; runtime/state_executor.go passesGuard, scheduleTransitionsForState, pollChangeEvents, executeAction; runtime/state_region_transition.go runEffect state_body_quantity_scope.sysml, state_call_trigger_guard.sysml, state_call_trigger_regions.sysml, state_transition_accept_payload.sysml, model/behavior_body_resolve_test.go accept-payload case ✅ Faithful
A state's entry, do and exit behaviors resolve in that state's scope, nested states and orthogonal regions included lower/state_graph.go StateGraph.StateScopes, collectStates, collectRegionStates; runtime/state_executor.go stateScope state_body_quantity_scope.sysml, state_concurrent_do.sysml, state_region_cross_pseudostate.sysml ✅ Faithful
A body member of an inherited or performed behavior is evaluated in the declarer's scope, not in the scope performing it runtime/invoke_action.go invokeAction, runtime/context.go chainMembers + EvalContext.evalIn action_body_declarer_scope.sysml ✅ Faithful
A frame binding shadows the enclosing scope, and an inner block shadows an outer one runtime/action_statements.go evalIn (frames pushed over the scope), runtime/eval.go evalFeatureReference (frames consulted first) action_body_shadows_enclosing_scope.sysml, robustness_test.go:loop_body_declaration_does_not_leak ✅ Faithful
A name or unit the declaring scope does not reach is reported, not evaluated as a bare magnitude runtime/eval.go (ErrUnresolvedReference), semantics/units.go (ErrNotAUnit) robustness_test.go:action_body_unresolved_unit, :action_body_unresolved_feature, :state_body_unresolved_unit ✅ Faithful
A %constraint/%requirement verdict is evaluated in the element's declaring scope, with or without an instance repl/meta.go declaringScope repl/runtime_commands_test.go:TestConstraintResolvesUnitsOfItsOwnPackage ✅ Faithful
An expression typed at the prompt is evaluated in the namespace the session is working in — the namespace a member typed there would be written in — so that namespace's members and the units its imports bring in resolve unqualified (1.0 [m/s], mass * 2) repl/meta.go promptScope, doEval; repl/lookup.go lookupSymbol (a name the session declares nowhere is resolved there) repl/runtime_commands_test.go:TestEvalResolvesImportedUnitsUnqualified, TestPromptScopeIsTheLastNamespaceDeclared ⚠️ Approximate (self-assessed: the pinned artifact has no prompt surface, so no reference verdict exists. The notation says nothing about a prompt, so "the namespace the session works in" is the last one it declared: declaring a second package moves it, and the first package's members and imports are then reached by qualified name only — a context named explicitly, %eval in <qualified-name> : <expression>, pins it instead)
Arguments of a %calc command are a list of expressions parsed by the expression parser, so an argument containing spaces — a quantity, a parenthesized expression, a nested call — is one argument; successive arguments are separated by a comma or by whitespace, and the invocation form Fall(a, b) is accepted repl/meta.go doCalc, parseExprList, splitCalcArgs; parser/parser.go Parser.Offset repl/runtime_commands_test.go:TestCalcParsesExpressionArguments, TestCalcSeparatesSignedArguments ⚠️ Approximate (self-assessed: the pinned artifact has no prompt surface to referee an argument list. Whitespace separates two arguments only where the first is a complete expression and the second is one — 5 -3 is two arguments, 5 - 3 one — since whitespace is no terminator in the notation; named arguments, Fall(v0 = …), are reported as unsupported rather than bound: the notation writes them in an invocation's parentheses, a production the prompt's argument list is not)
A quantity's magnitude is rendered in a result table by the same convention as a bare Real, the stored value keeping its full precision repl/meta.go formatValue, formatConst; semantics/quantity.go Quantity.TextWithMagnitude repl/runtime_commands_test.go:TestFormatValueQuantityUsesRealFormatting ✅ Faithful
A qualified name given to a command is the name the notation writes (KerML §7.2.5 unrestricted name): a segment needing quotes — a space, a keyword as a name, punctuation — is quoted, anywhere in the chain, and is one argument rather than being split on its space; the quoting is notation, so the name is normalized to the one the index records before lookup, and a name reported back is spelled so it can be typed into the next command. A name written unquoted that the notation requires quoted (T::SA-506 for 'SA-506') is not parsed as that name — an expression reads it as T::SA - 506, an object reference stops at the - — so when the token that did parse (SA) is the unquoted start of a declared name in scope, the unresolved-reference diagnostic offers the quoted spelling and states which characters require quoting, on every surface that resolves a name: the resolver's unqualified and qualified paths, expression evaluation, the REPL's symbol lookup and object references, and the CLI flags built on them; the parse itself is unchanged repl/meta.go parseArgs, indexOutsideName; repl/qualname.go plainName, notationName; repl/lookup.go lookupSymbol, parseObjectRef, ObjectRefError.Hint; repl/discover.go notFoundError, unquotedNames; suggest/suggest.go Unquoted, QuotingRule, Hint, Notation; suggest/table.go Table.Unquoted; resolve/suggest.go Resolver.UnresolvedName, UnresolvedMember, unquotedFor, unquotedMembers; runtime/eval.go (the unresolved-name paths) repl/qualname_test.go:TestQuotedNamesAcceptedByNameTakingCommands, :TestQuotedNameNamesTheSameObjectThroughout, :TestParseArgsKeepsQuotedNamesWhole, :TestQuotedNameFailuresAreReportedNotPanics, :TestNotationNameRoundTrips; suggest/suggest_test.go:TestUnquoted, :TestQuotingRule, :TestHint, :TestNotation, suggest/table_test.go; resolve/unquoted_test.go; repl/unquoted_name_test.go:TestUnquotedNameIsPointedAtItsQuotedDeclaration, :TestQuotedSpellingWorksAndSubtractionStillSubtracts, :TestNoQuotingHintWithoutAQuotedDeclaration; cmd/sysml/run_test.go:TestInstantiateOfAnUnquotedNamePointsAtTheQuotedOne ✅ Faithful
The same spelling rule holds over gRPC: a symbol ID is resolved whether it is written quoted ('My Pkg'::Car) or in the unquoted spelling the index records (My Pkg::Car), on every RPC taking a symbol ID grpc/service.go lookupNamed, unquotedName grpc/spelling_test.go:TestInstantiateAcceptsBothSpellings, :TestSymbolIDSpellingHoldsOnEveryRPC, :TestSymbolIDSpellingDoesNotInventSymbols ✅ Faithful
An expression is evaluated in a context named explicitly — %eval in <qualified-name> : <expression> — either the named element's namespace or, where an object was materialized under that name, that object's feature values; :: inside the name is not the separator repl/meta.go doEvalLine, evalIn, splitPinnedContext, contextSeparator; repl/lookup.go objectNamed; runtime/eval.go Context.EvalWithScopeOn (one run, so the step budget bounds a pinned evaluation as it bounds an unpinned one) repl/evalin_test.go:TestEvalInNamespaceReadsItsMembers, :TestEvalInInstanceReadsItsFeatureValues, :TestEvalQualifiedNameIsNotAContextSeparator, :TestEvalInFailuresAreTypedNotPanics, :TestEvalWithoutContextIsUnchanged, :TestEvalInInstanceIsBoundedByTheStepBudget; runtime/robustness_test.go:eval_on_an_instance_spends_the_step_budget ✅ Faithful
A session that loses an object or a debugger session says so and why: a reload carries an object over where the reloaded declaration still resolves to the shape it was materialized against, and a reset, which can prove nothing, reports the loss so the next %instances/%features/%step explains it repl/session.go clear, resetLoss; repl/carryover.go carryOverObjects; repl/notices.go lossOnReset, lossAtSubmission, lossOnBudgets; runtime/context.go Adopt repl/reset_test.go:TestClearReportsWhatItTook, :TestCommandsAfterClearExplainTheLoss, :TestClearEndsDebuggerWithAReason, :TestReloadKeepsObjectsItStillResolves, :TestLoadThatChangesDeclarationsReportsTheLoss ✅ Faithful
A submission whose text the parser cannot close (an unterminated body, quoted name or block comment, typed or loaded) is reported and kept out of the text the session analyses, so the next submission is parsed against what was there before it and drops nothing repl/enclosure.go closesItsOwnText, maskedText; repl/session.go acceptFrom, joined, openDiagnostics, diagnostics repl/openinput_test.go:TestLoadedOpenFileDoesNotPoisonTheNextSubmission, :TestOpenTypedSubmissionKeepsTheBuffer, :TestOpenSubmissionKeepsReportingTheRestOfTheBuffer, :TestOpenSubmissionKeepsWarningSeverity, :TestOpenSubmissionEchoesItsOwnLine, :TestRetypingANamespaceDoesNotMergeAMaskedSubmission, :TestMaskedSubmissionIsNotReportedAsBlockingTheChecks, :TestOpenSubmissionSurfacesStayTyped, repl/session_test.go:TestUnparseableRedeclarationDropsNothing ✅ Faithful (the buffer keeps the text for %save, so nothing typed is lost; a submission that does not close is not merged with the one after it, and its own findings keep the severity and code the parser gave them while the rest of the buffer is still analysed and reported)
Load-time diagnostics are reported the way interactive ones are, against the file and its own line numbering, and the non-interactive path's status reflects them repl/run.go LoadFile, LoadFileSummary, LoadFilesSummary, Diagnostics, HasErrors; repl/render.go renderSyntax repl/openinput_test.go:TestLoadReportsSyntaxDiagnostics, :TestLoadFileSummaryReportsSyntaxDiagnostics, :TestLoadFilesSummaryMatchesLoadingEachFile, :TestLoadReportsUnresolvedReference, :TestReloadingAFixedFileClearsItsSyntaxError ✅ Faithful (a syntax error in a loaded file is an error for HasErrors, which is what sysml -validate/%features in a script exits on; the exit status itself is cmd/sysml's)
An expression whose subject is reached through a declaration is evaluated on the object in effect for it, so a nested redefinition is honored by %eval, %constraint and %requirement alike repl/lookup.go subjectFor, carrierInstances, nestedObjects, featureChainSymbol repl/subject_test.go:TestEvalThroughDeclarationHonorsNestedRedefinition, :TestEvalThroughDeclarationWithoutObjectUsesDeclaredValue, :TestEvalThroughDeclarationWithTwoNestedCarriersIsAmbiguous, :TestCheckHonorsNestedRedefinitionThroughDeclaration ✅ Faithful (one shared subject seam: the runtime graph is walked to the object carrying the declaration, two carriers are an AmbiguousSubjectError, and no object at all still reads the declared value)
Two loaded files that open one package are two declarations of that name, and the load says so repl/merge.go reopenedNamespaces; repl/notices.go dropReport.notice repl/reopen_test.go:TestLoadingTwoFilesThatOpenOnePackageSaysSo, :TestLoadingDistinctPackagesSaysNothingAboutReopening, :TestReloadingOneFileSaysNothingAboutReopening, :TestRetypingAPackageStillMergesInteractively ⚠️ Approximate (self-assessed; the pinned artifact loads no session. Maintainer decision, recorded here: a loaded namespace keeps its file's identity so re-loading that file replaces only its own contribution, which merging the two openings would make impossible without one file's edit deleting the other's members. Both openings' members are declared and reachable qualified; an unqualified reference across the two does not resolve, and the note says to qualify it. Re-typing a package at the prompt still folds into the one in the session)
%view <name> reports a view's exposed elements, its nested views and its conformance to the viewpoints it satisfies repl/view.go Session.View, doView, viewElementLine, conformanceLines, concernEvaluator (the runtime requirement engine, through runtime.Context.CheckSatisfactionOn); over semantics/expose.go Model.ExposedElements, Model.NestedViews and semantics/conformance.go Model.ViewConformance repl/view_test.go:TestViewListsWhatItExposes, :TestViewListsNestedViews, :TestViewExposingNothingIsNoError, :TestViewOfANonViewIsTyped, :TestViewOfAnUnknownNameReports, :TestViewIsInHelpAndCompletion, :TestViewReportsViewpointConformance, :TestViewReportsAViolatedConcernPerElement, :TestViewWithoutASatisfyReportsNoConformance, :TestViewReportsASatisfyThatIsNoViewpoint, :TestViewConformanceOutputIsDeterministic, :TestViewCreatesNoObjectOfItsOwn, :TestViewLeavesNoAmbiguityForALaterCheck, :TestViewEvaluatesTheSessionObject, :TestViewSharesTheObjectOfAQuotedName ✅ Faithful (a view exposing nothing says so; a non-view is semantics.ErrNotAView; a nested view is asked for its own exposed set; the report changes no model and is deterministic in ordering. A concern is evaluated against the object the session holds for an exposed element, and otherwise against one materialized in a runtime of the report's own, so a report registers no object of its own)
%print [name] writes the session's model back as notation at the prompt — the whole buffer, or one element and its body — through the writer a .sysml save writes with, so comments and text survive and the print can be submitted again to rebuild the same model repl/print.go doPrint, printSession, printElement, declarationSpan; convert/convert.go SysMLElement, ConvertTolerant → format.Source; repl/lookup.go lookupSymbol (the quoted/qualified spellings) repl/print_test.go:TestPrintWholeSession, :TestPrintElement, :TestPrintQuotedAndQualifiedNames, :TestPrintKeepsComments, :TestPrintRoundTripsThroughSubmit, :TestPrintEmptySession, :TestPrintUnresolvableName, :TestPrintSymbolWithoutNotation, :TestPrintSaysNothingAboutRDF, :TestPrintLeavesInstancesAndBufferUntouched, :TestPrintLeavesActionDebuggerRunning, :TestPrintLeavesStateDebuggerRunning, :TestPrintCompletion, :TestPrintOfUnparsableSessionWarns ✅ Faithful (a read: nothing is materialized, the buffer is unchanged and a debugging session keeps running. An empty session, a name nothing declares and a symbol this session holds no source of each answer in one line; notation only, so no RDF notice follows a print)
A name the session cannot find is offered the qualified names it is known under, nearest scope first and bounded repl/qualsuggest.go qualifiedSuggestions, nestedInNonNamespace, writableName; repl/discover.go notFoundError, suggestSymbol repl/qualsuggest_test.go:TestQualifiedSuggestionsPreferTheSession, :TestQualifiedSuggestionsPreferPackageMembers, :TestQualifiedSuggestionsAreCapped, :TestUnresolvedMessageOffersRankedNames, :TestQualifiedSuggestionsSkipUnwritableNames ✅ Faithful (what the session declares outranks the library, a package's member outranks a name nested inside another element — a library function's parameter is no suggestion for a type — and at most suggest.Limit are offered)
A satisfaction verdict names the assertion in the notation's own spelling, quoting each inner name that needs quotes repl/satisfy.go satisfyText; repl/qualname.go notationName repl/satisfy_test.go:TestSatisfyQuotesInnerNames ⚠️ Approximate (self-assessed; the pinned artifact reports no verdict. The verdict line and the Verdict.Subject a caller reads are quoted; the condition text a runtime.ViolationError carries is rendered in runtime/condition.go, which this slice does not own, so a quoted name inside the condition of a failed assertion is still printed unquoted)

KerML Function Library (KerML §9.3 Function Library)

The library declares these functions abstractly — a signature and no body — so the runtime supplies the implementation. Dispatch is by the declaration a call resolves to, and only a declaration the library documents make is answered by an implementation, so a model's own calc sqrt — under RealFunctions or anywhere else, with a body or without one — is the model's: evaluated from its body, or ErrNoResultExpression. A call resolves as the checker resolves it: an unqualified sqrt(2.0) denotes RealFunctions::sqrt only where the model imports it, and a qualified name reaches the library whatever the model imports.

Arguments follow the vendored signatures: a Real parameter accepts an Integer (ScalarValues declares Integer :> Rational :> Real), an Integer parameter rejects a Real rather than truncating it, and a Natural parameter rejects a negative value. A result that is not a finite value of the declared type — the square root of a negative, an inverse sine outside [-1.0, 1.0], a floor beyond the Integer range — is reported at evaluation rather than returned as a NaN, an infinity or a wrapped integer.

Beyond the scalars, a Complex is one scalar value of its own kind (runtime/value.go ValComplex, a complex128), which only ComplexFunctions::rect, polar, i and the Complex operations construct: a numeric pair (re, im) is a two-element sequence, never read as a Complex. A Real is a Complex with a zero imaginary part (ScalarValues declares Real :> Complex), so a number binds to a Complex parameter, and a Complex on the real axis equals the Real it is and classifies as that Real does in the scalar lattice (complexPrimType: rect(2.0, 0.0) is an Integer, rect(2.5, 0.0) a Real, rect(0.0, 1.0) a Complex), while it stays one value however it is typed. A collection of Complex values is a sequence of them, so ComplexFunctions::sum/product fold it, keeping Real elements Real as the library's reduce '+' does (aggregateComplex; fixture calc_library_complex_sum_real_axis).

Structured values — an Array, a vector and a vector quantity — are three value kinds of their own (runtime/array.go; runtime/value.go ValArray, ValVector, ValVectorQuantity), each one value however many elements it holds, so that a sequence of them keeps its grouping and none is mistaken for the flat sequence of its elements:

  • ValArray carries Dimensions []int64 and the Elements []Value in row-major order — the last index varies fastest, as Collections.kerml documents Array::elements — and is what a Collections::Array (and every specialization: Matrix, Tensor, the *Array families) evaluates to. Its derived features read out of the value: rank is the number of dimensions, flattenedSize their product, dimensions and elements the sequences carried; a rank-0 array holds one element. It prints as Array(2, 3)[1, 2, 3, 4, 5, 6], dimensions first, so it is never read as the six-element sequence. A model declares one by shaping the usage's own features — attribute a : Array { :>> dimensions = (2, 3); :>> elements = (1, 2, 3, 4, 5, 6); } — and the object that usage names is the Array value wherever it is read (arrayOfObject, objectValue, EvalDeclaredValue): as an operand, through a feature chain (a.rank), and at the REPL (%eval a). The value keeps the object it was read from (Array.Object), so its direct type stays the specialization (LabeledGrid, not Array, for write conformance) and a member the Array does not declare (grid.label on an attribute def LabeledGrid :> Array) is read from that object (chainMemberValue) whether the value is named directly or came back from a calc, a parameter or another attribute; equality and hashing compare dimensions and elements only. A usage stating dimensions or elements — even as () — is judged an Array: one whose elements does not fill its dimensions (dimensions = (); elements = ();, whose flattenedSize is 1) is ErrMultiplicityViolation naming both counts, and dimensions whose product does not fit an Integer, or an index offset that would not, are ErrArithmeticOverflow rather than a wrapped size. The shape is the pinned pilot evaluator's: asked about that usage, eval-sysml answers grid itself as the unevaluated AttributeUsage (it prints no Array value), grid.rank as 2 and CollectionFunctions::'array#'(grid, (2, 1)) as 4 — row-major, one-based — which is what this runtime answers.
  • ValVector carries Elements []semantics.Value, the numeric components, and is what VectorFunctions::VectorOf, CartesianVectorOf, CartesianThreeVectorOf and every vector operation return. dimension is its number of elements (the one dimension the NumericalVectorValue :> Array specialization declares), elements the sequence of them. An object of a NumericalVectorValue specialization (attribute t : Tagged { :>> dimension = 2; :>> elements = (1, 2); }) reads as the vector its elements shape and keeps the object (Vector.Object, as Array.Object does), so the specialization's own members answer on it — directly, through a calc parameter, and on a calc's result (t.tag, tagOf(t), pass(t).tag) — while dimension, dimensions, elements, rank and flattenedSize answer as the member of the object's type declares them, by the Collections::Array feature it redefines rather than by name (arrayFeatureNamed, structuredMember); a specialization whose dimension the library fixes (ThreeVectorValue::dimension = 3) takes it without restating it (fixedDimensions), and an object whose elements do not fill that shape, or whose dimensions state a rank above one, is ErrMultiplicityViolation rather than a vector (vectorOfObject; conformance calc_library_vector_specialization_members, calc_library_vector_specialization_fixed_dimension, TestVectorObjectMemberReadsFollowTheDeclaration, TestAbandonedVectorObjectIsForgottenByItsHolders). The object behind an array or vector a run wrote is carried into a re-analysis with its holder (adopt.go carriedObject, walkValue, rewrite; TestAdoptCarriesTheObjectsBehindWrittenArraysAndVectors), so those members still answer after %load re-reads the model. A vector built by value (attribute w : Tagged = VectorOf((1, 2));) has no object and so no members beyond the vector's own: w.tag is ErrTypeMismatch. It prints between angle brackets, ⟨1, 2, 3⟩, distinct from a sequence's [1, 2, 3]; the pilot prints no vector at all (its VectorOf has no evaluable body), so the notation is a decision recorded here. It conforms to VectorValue, NumericalVectorValue, CartesianVectorValue, and to CartesianThreeVectorValue when its dimension is 3 (structuredValueType, write_conformance.go), so it binds to a feature typed by any of these and CartesianThreeVectorOf with two components is ErrMultiplicityViolation. A structured value written to a feature typed by any other specialization of its kind's base type — Collections::Array for an Array (the type of the object it was read from, for one that keeps its own members), NumericalVectorValue for a vector, VectorQuantityValue for a vector quantity (structuredBaseType) — is admitted only when the shape that type fixes holds: a dimension, dimensions, rank or flattenedSize it redefines to a constant, the multiplicity of its dimensions, and the type its elements are declared of (structuredConforms, shapeRefusal, elementsConform). So a two-component vector does not bind to CartesianThreeVectorValue or ThreeVectorValue, nor a 2×3 Array to a type fixing dimensions = (2, 2), and the refusal names the declaration it fails (it declares dimension = 3); while VectorOf((1, 2, 3)) binds to a model's own attribute def IntegerVector :> NumericalVectorValue { :>> elements : Integer; } — a sibling of the Cartesian types, not a specialization — and VectorOf((1, 2.5, 3)) does not (calc_library_vector_custom_specialization* conformance, testStructuredValueOutsideTheDeclaredShape). A sequence of numbers written where a vector parameter is declared (inner((1, 2), (3, 4))) is still read as a vector — the pilot's VectorOf((1,2,3)) argument form — while a sequence given several vectors' worth of numbers is not silently split.
  • ValVectorQuantity carries Num []semantics.Value and one Unit per component, and is what a scalar quantity times a vector is (2 [m] * VectorOf((1.0, 2.0)), VectorCalculations::scalarQuantityVectorMult, vectorScalarQuantityMult, vectorScalarQuantityDiv, and the *// operators). Its num is the vector of magnitudes; the unit composes through the scalar quantity's own canonicalization (quantity.go scaleQuantities, composedQuantity: [m] * [m] is [SI::'m²'], [m] / [s] is [SI::'m/s']) rather than a second rule. It prints as the vector followed by the unit, ⟨2.0, 4.0⟩ [m], and conforms to Quantities::VectorQuantityValue. Its inner, norm and angle are the Number the library declares them — the magnitude computed over the num components, the unit dropped by declaration (return : Number[1], where QuantityCalculations::'*'/'/'/sqrt keep a ScalarQuantityValue; the inconsistency is drafted in omg-issues.md) — so a Number feature takes them and the checker and runtime agree (conformance calc_library_vector_quantity_norm). CartesianVectorOf takes Real[*], but a quantity's num is Number[1..*] (Quantities::TensorQuantityValue) and the unit lives on an axis, so a vector of no components takes no unit: CartesianVectorOf(()) [m], and scaling the empty vector by a scalar quantity, are ErrMultiplicityViolation (vectorQuantityValue, scaleVectorQuantity; conformance calc_library_vector_quantity_empty) rather than a unitless empty value. Its mRef answers the one measurement reference every axis shares ((2 [m] * VectorOf((1.0, 2.0))).mRef is m); a vector written over a coordinate frame ((1.0, 2.0, 3.0) [spatialCF], VectorCalculations::'[') carries the frame and answers it as mRef (p.mRef == spatialCF; ValCoordinateFrame, below), so axes in different units are answered by the frame that declares them; a vector whose axes differ and whose frame is not held — the components of a transform result are always in a frame, so only a value the wire or a client assembled — is ErrUnevaluableLibraryFunction naming VectorQuantityValue::mRef, never a unit dressed up as a frame.
  • ValMeasurementRef (measurement_ref.go) is a measurement reference as a value: the unit a quantity is measured in, on its own. It carries the same semantics.Unit a quantity does — the spelling written, the UnitProduct naming the declarations it is composed of, and the UnitTerm reduction to a scale over base units — so SI::m, SI::'m/s' and MeasurementReferences::one each evaluate to one (Context.MeasurementUnitValue; the REPL evaluates a unit's name the same way), m * s, m / s and m ** 2 compose one (composeMeasurementRefs, the UnitProduct composing and the term reducing exactly as [m] * [s] does for a quantity), and it prints as the unit a quantity prints after its magnitude: m, 'm/s', m*s, m**2. Two references are equal, and hash alike, when they reduce to the same term (SI::'m/s' == m / s, km != m, km / m == m / mm); a reference naming a unit of dimension one is only itself (rad != sr, one != m / m), as a quantity in it stays a quantity where a cancelled ratio is a number. It is typed by the unit definition declaring it (m is a LengthUnit, hence a MeasurementUnit, ScalarMeasurementReference, TensorMeasurementReference); a composed unit is a DerivedUnit — the library's "unit that depends on powers of other measurement units" — so it binds to DerivedUnit (kpl : DerivedUnit = km / L in the OMG vehicle example) and to a unit definition of its dimension (m * m to AreaUnit, km / h to SpeedUnit; Model.MeasurementRefConforms, judged the same by the checker — judgedAsMeasurementRef, MeasurementRefExprConformance — and the runtime's write conformance). A unit raised to a Real the checker cannot fold (m ** e, e : Real) is a DerivedUnit whose dimension only the runtime knows, so the checker binds it to any unit definition and the runtime judges the written value (m ** 2.0 to LengthUnit is refused at the write; Model.MeasurementRefExprType, TestMeasurementUnitPowerOfAnUnknownExponent); an untyped feature valued by a unit expression (attribute area = m * m;) is typed by it (Model.ExprResultType, MeasurementRefFeatureType), so ToString(area) selects MeasurementRefCalculations::ToString and ConvertQuantity(q, area) is accepted, by the checker and the runtime alike (TestInferredMeasurementUnitFeature, TestMeasurementRefValues). A reference of another dimension (m / s to LengthUnit) or a number written to a unit-typed feature, a reference written to a quantity-typed one (m to LengthValue, m * m to AreaValue — the checker judges a composed unit as the DerivedUnit it is before it compares dimensions, which alone would pass it), and a unit written to a feature typed by a measurement scale (s, or h * s / min, to Time::TimeScale or IntervalScale: the dimensional fallback is for unit-definition targets only, Model.IsMeasurementUnit, a scale of the same dimension being another kind of reference) are refused by both. A measurement scale itself — Time::UTC, SI::'°C_abs', a ScalarMeasurementReference that is not a MeasurementUnit (Model.IsMeasurementScale) — is a ValCoordinateFrame of one axis (below): MeasurementReferences::IntervalScale :> MeasurementScale, CoordinateFrame, so the value carries the scale's unit, its transformation placing it on another reference and its quantityValueMapping, and Time::UTC, 3 [Time::UTC], UTC.unit and ConvertQuantity(300.0 [K], SI::'°C_abs') evaluate; the checker binds one to its declared type (t : Time::TimeScale = Time::UTC) and refuses it elsewhere (SI::'°C_abs' to ThermodynamicTemperatureUnit), as the runtime does. A quantity written to a unit-typed feature (hp : PowerUnit = 745.7 [W] in the OMG individuals example) is not judged, as the pilot implementation accepts it; the feature then holds the quantity, and 200 [hp] is ErrNotAQuantity, hp naming no unit. A quantity's num and mRef read (q.mRef == SI::m), and the quantity functions the library declares over a reference compute: QuantityCalculations::'['(3.0, m) is 3.0 [m], ConvertQuantity(3 [km], m) is 3000.0 [m] (Quantity.ConvertTo, through the two reductions; ConvertQuantity(3 [m], s) is ErrIncommensurableUnits), MeasurementRefCalculations::'*', '/', '**', '^' and ToString ("m/s"); a vector quantity's mRef reads when its axes share one unit (VectorOf((1.0, 2.0)) [m] answers m) or when it was written over a frame (((1.0, 2.0, 3.0) [spatialCF]).mRef answers spatialCF). Arithmetic the library does not declare — m * 3, m + m, -m, m < s, m ** m — is ErrTypeMismatch, not a quantity of magnitude 1. What the value itself fixes (isBound, mRefs, isOrthogonal, dimensions, rank, order) it answers directly; a reference naming a declaration (UnitProduct identity: SI::km, a model's own furlong) answers the declaration's other members from the object that declaration materializes as, the one %features SI::km shows (measurementRefFeature, declarationMember over occurrenceOf), with the library's redefinitions and defaults followed: km.unitConversion.conversionFactor is 1000.0 (ConversionByPrefix::conversionFactor = prefix.conversionFactor), km.unitConversion.referenceUnit is m — a reference, so ConvertQuantity(3 [km], km.unitConversion.referenceUnit) is 3000.0 [m] — km.unitConversion.isExact is true (UnitConversion::isExact default true), m.quantityDimension.quantityPowerFactors#(1).exponent is 1, m.unitPowerFactors#(1).unit is m (SimpleUnit's unit = self, the object read as the reference it declares), K.definitionalQuantityValues#(1).num is [273.16] (DefinitionalQuantityValue::num is Number[1..*]), and a base unit's unitConversion is the empty [0..1]. A unit composed at runtime (m / s, m ** 2; a DerivedUnit) names no declaration, so unitConversion, quantityDimension, unitPowerFactors and definitionalQuantityValues of one are ErrUnevaluableLibraryFunction naming MeasurementReferences::DerivedUnit and the reduction (TestMeasurementRefDeclarationMembers, TestMeasurementUnitDeclarationMembers — the checker types the same chains by the library's records; conformance instance_measurement_reference_members, instance_measurement_reference_failures; repl/measurement_ref_test.go). Which library attribute definitions are held as values and which materialize as objects is decided by specialization, in one place (semantics/shape.go Model.ValueHeld, read by FrameFeature through HeldByValue): a scalar, an enumeration, a Quantities::TensorQuantityValue or a MeasurementReferences::TensorMeasurementReference — so IntervalScale, TimeScale and CoordinateFrame, which descend from VectorMeasurementReference, stay values — is held as a value and its own members (num, mRef, mRefs, isBound stated by the value) stay out of an object's shape; every other one (UnitConversion, ConversionByPrefix, UnitPrefix, QuantityDimension, QuantityPowerFactor, UnitPowerFactor, DefinitionalQuantityValue, QuantityValueMapping, SystemOfQuantities) is a record materialized as an object with its library members, so a model's own attribute myConv : ConversionByPrefix { :>> prefix = kilo; :>> referenceUnit = m; } lists and answers conversionFactor = 1000.0, and a model's own TensorMeasurementReference usage that does not restate isBound answers the inherited default false (TestValueHeldIsDecidedBySpecialization, TestHeldByValueKeepsTheValuesOwnMembersOutOfTheShape, repl/measurement_ref_test.go:TestFeaturesOfModelOwnedLibraryRecords). A base quantity the library states without a magnitude (QuantityPowerFactor::quantity = isq.L) reads as the valueless value it is (<unset>), as any valueless feature of a value type does. A reference embedded in a written value is rebound across re-analysis like the objects behind an array are (adopt.go unitsOf, planUnit, rewriteUnit: a model's own furlong follows the re-read declaration, in the product and in the reduction alike, so a base unit the model declares without a conversion — which reduces to itself — is carried over and still equals a fresh chain (TestAdoptRebindsAModelsOwnBaseUnit); a unit the new model no longer declares, or reduces differently — furlong re-declared as 220 m — refuses adoption naming the unit and both reductions, since a magnitude written under the old factor would read as another quantity under the new; TestAdoptRefusesAUnitWhoseReductionChanged; the unit an empty quantity collection remembers for the zero its sum yields is judged the same way, TestAdoptRebindsTheUnitOfAnEmptyQuantitySequence); the solver refuses to pin one (ErrNotPinnable), a document query binds a single unit but not an operator over units, and over gRPC and Connect it crosses whole as the measurement_ref arm of the wire Value — the unit as written, its UnitTerm reduction, and the canonical id of the declaration a named unit is (SI::metre for m and for the alias SI::m), which a composed unit (m / s, a DerivedUnit naming no one declaration) omits — and comes back as the same ValMeasurementRef, a named one resolved against the model's own declaration and refused when the id names nothing, names no unit, or the reduction sent disagrees with the declaration's own (grpc/convert.go MeasurementRefToProto, ProtoToMeasurementRef, declaredMeasurementRef; convert_measurement_ref_test.go TestMeasurementRefRoundTrip, TestMalformedMeasurementRefsAreRejected, TestMeasurementRefCrossesEveryValueSurface). The arm is advertised as the measurement_refs capability, on its own rather than under structured_values: a client built against the three structured arms keeps reading a bare reference as the unsupported null it read before (unsupported: measurement reference m), and a service withholding the capability refuses one sent to it with UNIMPLEMENTED (capability_response.go; TestMeasurementRefCapability, TestValueCarriesMeasurementRef).
  • ValTensorQuantity (tensor.go, tensor_functions.go) is a tensor quantity: the rank-n generalization of a vector quantity, and the value Quantities::TensorQuantityValue :> Array describes — dimensions in row-major order as an Array's, one magnitude per component (num), one measurement reference per component (the mRefs of its TensorMeasurementReference), and isBound. TensorCalculations::'['(elements, mRef) builds one over a TensorMeasurementReference the model declares with :>> dimensions and :>> mRefs (a 2×2 stress tensor over (Pa, Pa, Pa, Pa), a 3×3 inertia tensor over kg*m**2): the elements must number mRef.flattenedSize (ErrMultiplicityViolation naming the count, the dimensions and the size otherwise), each pairs with the reference at its row-major position, and one reference does not broadcast to four components — mRefs is declared ScalarMeasurementReference[1..*] nonunique :>> elements, so as Array::elements it must fill the dimensions, and a one-element mRefs over (2, 2) is the Array shape error. It prints as its shape and row-major components with the unit they share (Tensor(2, 2)[1.0, 2.0, 3.0, 4.0] [Pa]) or with each component's own (Tensor(2, 2)[1.0 [Pa], 2.0 [kPa], …]), and conforms to TensorQuantityValue and Collections::Array and to no scalar or vector type. Its dimensions, rank/order, flattenedSize, elements, num, isBound and mRef read (mRef answers the model's reference usage when the value was built over one, and otherwise the array of the components' units); # and array# index it exactly as an Array — one index per dimension, the last varying fastest, rank and range errors typed (componentArray, reusing Array.at). contravariantOrder and covariantOrder are never fabricated: the library gives them no default, '[' sets neither, and only their sum is constrained (orderSum), so reading one is ErrUnevaluableLibraryFunction quoting that constraint; isBound of a tensor a bodiless calculation produced is the same, since the declaration says nothing about its result's boundness. '+'/'-' (and the operators) are componentwise over two tensors of one shape — the right component converted into the left's unit, ErrIncommensurableUnits when it cannot be, ErrMultiplicityViolation naming both shapes when the dimensions differ — scalarTensorMult/TensorScalarMult scale every component by a Number, scalarQuantityTensorMult/TensorScalarQuantityMult by a scalar quantity whose unit composes with each component's by the scalar rule (2 [m] * Tensor(2, 2)[…] [Pa] is in Pa*m); isZeroTensorQuantity holds when every magnitude is zero, and isUnitTensorQuantity decides a square order-two tensor against the identity and is ErrUnevaluableLibraryFunction naming the shape it needs for any other, the library defining no identity there. Every one of these accepts a scalar or vector quantity where it declares a TensorQuantityValue (ScalarQuantityValue :> VectorQuantityValue :> TensorQuantityValue) and answers a value of the operands' rank: a scalar for order 0, a vector quantity for order 1, a tensor above (tensorResult). A result whose component units all cancel follows the scalar rule above — a ratio of like quantities is a number of no unit (3 [Pa] * 2 [Pa**-1] is 6), and a vector quantity so reduced is a plain vector (vectorResult) — so TensorScalarQuantityMult(Tensor(2, 2)[…] [Pa], 2 [Pa**-1]) is the plain Array(2, 2)[…], which, like the number and the vector, the TensorCalculations functions do not take back as a TensorQuantityValue; what a quantity of dimension one carries is one decision for every rank and is not made here. What remains unevaluable is what the text does not determine: tensorVectorMult, vectorTensorMult, tensorTensorMult (no contraction convention), VectorCalculations::outer (declared to return a VectorQuantityValue, which an order-two product is not), and TensorCalculations::transform (needs a coordinate frame); each names itself and the reason. It is carried over by Adopt with every component's unit rebound (TestAdoptRebindsATensorsComponentUnits), refused by the solver as a non-scalar and refused as a document-query binding. Rank is unbounded: a TensorMeasurementReference with :>> dimensions = (2, 2, 2) and eight mRefs builds a rank-three tensor, # takes exactly three indexes (cube#(2, 1, 2) is the sixth row-major component), and the shape checks are the Array's at every rank — too few or too many indexes ErrMultiplicityViolation naming the count and the rank, an index below 1 or above its dimension ErrIndexOutOfRange naming the index and the range, a non-Integer index ErrTypeMismatch, a component count off flattenedSize ErrMultiplicityViolation, and '+'/'-' between two shapes ErrMultiplicityViolation naming both. Arithmetic and printing keep the shape (Tensor(2, 2, 2)[…] [Pa]), and the trace renders the same text (FormatTraceValue). It crosses gRPC whole as the tensor_quantity arm — dimensions and one Quantity per row-major component, advertised as the tensor_values capability; a rank-one tensor stays a tensor_quantity, not a vector_quantity (Values in the gRPC rows below). It is not compiled natively: sysml -compile refuses a calc that declares or builds one with codegen.UnsupportedError (type Quantities::TensorQuantityValue is not Integer, Real or Boolean), and it needs no RDF literal form, as no value kind has one — the mapping writes the model, never an evaluation, so a tensor-valued feature of any rank is its TensorCalculations::'[' invocation and an indexing its # tree, standard expression shapes that round trip exactly with the source text stripped and whose load-bearing structural predicates (operator, function, referent, and the operands by ParameterMembership or legacy argument) carry the round trip (the mapping). Conformance instance_tensor_quantity, instance_tensor_quantity_failures, instance_tensor_rank_three, instance_tensor_rank_three_failures; robustness tensor_quantity_failure_modes (every rank-three failure); TestTensorQuantity*, TestTensorQuantityRankThree, TestTensorCalculationResultTypes, TestTensorCalculationsBindToTheirDeclaredTypes; repl/compile_test.go:TestCompileRefusesWhatItCannotCompile (TensorParam, TensorBuilt); export/set_tensor_rdf_test.go:TestSetAndTensorValuesRoundTripAsExpressions, TestSetAndTensorGraphsAreStandardShaped, TestSetAndTensorStructuralPredicatesCarryTheRoundTrip, TestSetOrderAndTensorShapeSurviveTheHop.
  • ValCoordinateFrame (coordinate_frame.go, frame.go, frame_read.go) is a coordinate frame or a measurement scale as a value. The OMG corpus this project pins populates frames — Annex A's SimpleVehicleModel (attribute spatialCF : CartesianSpatial3dCoordinateFrame[1] { :>> mRefs = (m, m, m); }, velocityCF = spatialCF/s), the geometry example's datum with (x, y, z)[datum] literals and a TranslationRotationSequence, the validation suite's MissionElapsedTimeScale — and the library itself places SI::'°C_abs' : IntervalScale on K. A model-owned usage typed CoordinateFrame (or a subtype) with its :>> mRefs evaluates to the frame it declares (referenceValueOfObject, readFrame), the geometry example's datum :>> coordinateFrame { :>> mRefs = (mm, mm, mm); } included: a renamed redefinition whose body describes the value governs over the default it inherits as a same-named one does (subsetting.go sharedRedefinitionName, declarationValues; TestRenamedRedefinitionBodyGovernsAnInheritedValue), while that example's component frames, whose mRefs default to (that.that as SpatialItem).coordinateFrame.mRefs, stay a typed error: the as cast is evaluated now (cast.go evalCast), but that.that is not — an object features no that, so the chain reports itself before the cast sees a value. The value carries the declaration, its type, the dimensions the type fixes ('3dCoordinateFrame' states 3; a scalar reference (), one axis), one ValMeasurementRef per axis read from mRefs (the flattened size of dimensions gates the count: a frame stating no mRefs is ErrNoValue naming TensorMeasurementReference::mRefs[1..*], a count off the flattened size ErrMultiplicityViolation), and the transformation the usage states (below). A scale is the one-axis case: IntervalScale :> MeasurementScale, CoordinateFrame, so SI::'°C_abs', Time::UTC and a model's TimeScale carry their unit as the one axis, plus the quantityValueMapping and the placement on another reference when the declaration states them (readScale). A frame prints as its name followed by its axis units — spatialCF [m, m, m], velocityCF [m/s, m/s, m/s], a composed one as the expression that made it, datum / s [mm/s, mm/s, mm/s] — a scale as its name, SI::'°C_abs'; describe says coordinate frame or measurement scale; %features shows mRefs, dimensions, transformation, and for a scale unit, transformation.origin (structuredFeature). The object a frame usage materializes carries the MeasurementReferences and Collections members that describe it (semantics/shape.go DescribesReference); the library's transformation { :>> target = that; } is not one of them, since the runtime answers the target of a frame's own transformation as that frame (frame_read.go readTarget), and a %features listing of the object still reports flattenedSize as an error, dimensions->reduce '*' naming a reducer the runtime does not evaluate (the Found, not fixed row below), while frame.flattenedSize reads from the value. It conforms to its declared type and that type's generals (CartesianSpatial3dCoordinateFrame, '3dCoordinateFrame', CoordinateFrame, VectorMeasurementReference, TensorMeasurementReference; a scale to IntervalScale, MeasurementScale, ScalarMeasurementReference), judged alike by the checker and the runtime (semantics/coordinate_frame.go; passes/typecheck_dimension.go judgedAsMeasurementRef; TestCoordinateFrameExpr, TestComposedFrameConforms, TestBoundComposedCoordinateFrame). Two frames are equal, and hash alike, when they are the same object — the frame a usage declares is one object, however many times it is read — or, for a composed frame, when their dimensions, axes, scale and transformation agree (spatialCF / s == velocityCF; CoordinateFrame.equal, key). Frame arithmetic: MeasurementRefCalculations::'CoordinateFrame*'/'CoordinateFrame/', and the *// operators over a frame and a unit, compose every axis with the unit (spatialCF / s is a frame of axes m/s, m/s, m/s, dimensions unchanged), typed by the calc's CoordinateFrame — so velocityCF : CartesianVelocity3dCoordinateFrame[1] = spatialCF / s binds, as the pilot's checker accepts it; a scale composes with nothing ('°C_abs' * s is ErrUnevaluableLibraryFunction: its points are on the scale, not in a unit to compose). A vector literal over a frame, VectorCalculations::'['((1.0, 2.0, 3.0), spatialCF), is a ValVectorQuantity with the frame's unit per axis and the frame as mRef (p.mRef == spatialCF, p.num the three numbers); an element count off mRef.flattenedSize is ErrMultiplicityViolation; the frame may be named by a feature chain ((1.0, 2.0, 3.0) [vehicle.body]). Vector arithmetic keeps the frame: p + p, p - q, -p, 2 * p and p / 2 are over p's frame, and p / 1 [s] is over the frame CoordinateFrame/ composes ((p / 1 [s]).mRef == velocityCF), while '+', '-', inner and angle between vectors over two frames, or over a frame and over none, are ErrTypeMismatch naming both — the library gives such a pair no common coordinates, and transform is the way from one frame to the other. A vector over a frame equals no vector over another frame or over none, so restating a feature's vector over another frame is a change its dependents follow. A quantity on a scale, 21.5 [SI::'°C_abs'] or 0 [Time::UTC], is a scalar quantity whose magnitude is in the scale's unit and whose mRef is the scale. ConvertQuantity converts through the scale's placement: a scale S with transformation : CoordinateFramePlacement { source = R; origin = o } maps x [S] to x · f + o on R and q on R to (q − o) / f on S, f the factor of S.unit relative to R.unit and o converted to R.unit, so ConvertQuantity(300.0 [K], SI::'°C_abs') is 26.85 ['°C_abs'] (26.850000000000023 in binary64, as 300.0 - 273.15 is) and ConvertQuantity(26.85 [SI::'°C_abs'], K) is 300.0 [K]; two scales convert through a common source (scale_conversion.go). Where the scale also states a quantityValueMapping (°C_abs: 0.01 ['°C'] ↔ 273.16 [K]) the mapping must agree with the placement, and a disagreement is a typed error naming both offsets rather than a choice between them. A scale that states neither (Time::UTC, every OrdinalScale, CyclicRatioScale, LogarithmicScale) is a value, but converting to or from it is ErrUnevaluableLibraryFunction naming the scale and the missing transformation or mapping; a placement whose origin is not a quantity on the source (the validation suite's definitionalEpochInUTC, an Iso8601DateTime), or whose basisDirections is not the identity 1 [R], is a typed error naming that; a chain of placements that returns to a scale it passed (a scale placed on itself, or two on each other) is ErrCyclicFeatureValue naming the scale, not a loop. transform(transformation, sourceVector) (frame_transform.go) re-expresses a vector quantity over a frame in the transformation's target: sourceVector.mRef must be the transformation's source (ErrTypeMismatch naming both otherwise, a uniform-unit vector with no frame included), and the result is a vector quantity over the target with the target's axis units. The library fixes a placement's origin as the location of the origin of the target frame as a vector in the source frame and basisDirections as the target's basis in the source, so a placement maps target coordinates into source ones, v_source = origin + B v_target, and transform applies the inverse, B⁻¹ (v − origin), with the directions normalized (they are directions; a zero or a linearly dependent direction is a typed error). A TranslationRotationSequence applies its elements in order, a Translation shifting the origin by its translationVector converted to the source axis units (ErrIncommensurableUnits otherwise) and a Rotation turning about axisDirection by angle — an angular measure converted through its unit, 90 ['°'] and 1.5707963267948966 [rad] agreeing to the library's seven-digit degree — intrinsically (about the axis as the earlier elements moved it, the default) or extrinsically (isIntrinsic = false, about the axis fixed in the source); an AffineTransformationMatrix3d applies its rotationMatrix and translationVector in the 4×4 layout its documentation gives; a NullTransformation is the identity. A transformation of any other type is ErrUnevaluableLibraryFunction naming the declaration and the four shapes; a frame whose axes are in different units reoriented by a rotation or a basis is ErrIncommensurableUnits, the axes having nothing to mix in. Two transformations are equal when they are the same object or when source, target and steps agree (lbcf.transformation == trs in the geometry example). The solver refuses to pin a frame (ErrNotPinnable), a document query traverses one to its declaration, and across re-analysis a written frame follows the re-read declaration, its units and objects as a unit does (adopt_frame.go; TestAdoptRebindsTheFramesAWrittenValueNames). Over gRPC a frame or a transformation crosses as an unsupported null naming the value (unsupported: coordinate frame spatialCF [m, m, m]; TestCoordinateFrameCrossesAsUnsupported): the wire Value has no arm for one. Pilot referee: the pinned pilot evaluates none of CoordinateFrame/, '[' over a frame, ConvertQuantity or transform, so the probes in tools/referee/exec/testdata/cases/coordinate_frames.cases are pilot-unevaluated and the vendored library text is the authority; every reading made — transform direction, normalized directions, intrinsic rotations about the moved axes — is drafted in omg-issues.md.
  • ValCoordinateTransformation is the transformation a frame or a scale states, held as a value of its own so shifted.transformation reads, compares (turned.transformation == trs) and is what transform takes; it prints as its name with source and target, describes as a coordinate transformation, and is refused by the solver and the wire as a frame is.

Every structured value is compared and hashed by content (value_equality.go structuredKey), described (describe.go), traced (trace.go), rendered by the REPL (%eval, %features), pinned by the solver as a non-scalar refusal (solve/pin.go), and traversed by document queries (repl/docquery.go); TestEveryValueKindIsDispatched builds one value of every kind up to valueKindCount and fails on a kind any of these does not name. Over gRPC and Connect each crosses whole: the wire Value has an array arm (dimensions plus the elements in row-major order, each a Value), a vector arm (the numeric components as Values, Integer and Real kept apart) and a vector_quantity arm (one Quantity per component, unit and reduction included), and a value sent back as an action input or calc argument decodes to the same runtime value (grpc/convert.go arrayToProto/protoToArray, vectorToProto/protoToVector, vectorQuantityToProto/protoToVectorQuantity; convert_structured_test.go TestArrayRoundTrip, TestVectorRoundTrip, TestVectorQuantityRoundTrip, TestMalformedStructuredValuesAreRejected). A malformed one is refused with a typed error rather than read as something else: a non-positive dimension (ErrArrayDimensionNotPositive), elements not filling the dimensions (ErrArrayShapeMismatch), a vector component that is not a number (ErrVectorComponentNotNumeric), a vector quantity with no components (ErrVectorQuantityEmpty), or a component unit named without its reduction (the Quantity rules). The service advertises structured_values for the three arms and measurement_refs for the bare-reference arm above; the rows on the API boundary under Values record what each client maps them to.

Not represented, and reported by name rather than approximated: the tensor products — TensorCalculations::tensorVectorMult, vectorTensorMult and tensorTensorMult declare only their parameter and result types, and neither the package nor the Kernel states which indices contract or how the operands' contravariantOrder and covariantOrder combine, so no product is determined; VectorCalculations::outer, whose declared VectorQuantityValue return no outer product inhabits (drafted in omg-issues.md, unfiled); and TensorCalculations::transform, for which neither package states how a CoordinateTransformation acts on a tensor's indices — the runtime transforms a vector quantity only. A coordinate frame and a measurement scale are represented (ValCoordinateFrame above); what they cannot honestly compute — a scale with no placement and no mapping, a transformation of a shape the library gives no meaning, a placement origin that is not a quantity — names the declaration and what it lacks, and the unit text of a quantity is never promoted to a frame.

A library feature — a named constant a library declares and gives no evaluable value — takes its value from a seam of its own: libraryFeatures maps the feature's qualified name to a Go thunk, consulted only for a symbol a library document declared, so ordinary name resolution decides and a model's own pi keeps its own value. The value is recomputed per read rather than cached, which keeps a vector-valued feature from sharing one sequence between readers and keeps the value independent of whether the library index cache was warm (a warm cache restores library symbols without their AST).

Semantic Rule Implementation Test Case Status
RealFunctions: sqrt, abs, floor, round, max, min; floor and round return an Integer and report a whole Real at or beyond 2⁶³ as ErrArithmeticOverflow rather than a wrapped int64 runtime/library_functions.go integerResult TestLibraryFunctionValues, TestLibraryFunctionErrors; migrate/opaque_migration_test.go:TestTranslatedRoundingsStopAtTheIntegerRange (a migrated Math.floor/ceil/round at the boundary) ✅ Faithful
RationalFunctions/NumericalFunctions: abs, max, min (kind-preserving), isZero, isUnit runtime/library_functions.go TestLibraryFunctionValues ✅ Faithful
IntegerFunctions: abs, max, min; NaturalFunctions: max, min runtime/library_functions.go TestLibraryFunctionValues ✅ Faithful
TrigFunctions: sin, cos, tan, cot, arcsin, arccos, arctan runtime/library_functions.go TestLibraryFunctionValues ✅ Faithful
Domain, arity and argument-type failures reported at evaluation runtime/library_functions.go bindAndApply TestLibraryFunctionErrors ✅ Faithful
A model's declaration is never answered by a library implementation: with a body it is evaluated from that body, without one it is ErrNoResultExpression — for a scalar function (RealFunctions::ToReal, RealFunctions::'+', RealFunctions::sqrt) exactly as for a collection built-in runtime/library_functions.go libraryFunctionFor (Context.libraryDeclared, the same test Context.builtinFor applies) TestLibraryFunctionDoesNotHijackADeclaredBody, TestLibraryFunctionNeverAnswersAModelDeclaration; robustness bodiless_model_calc_named_as_a_builtin ✅ Faithful
Named argument binds to the parameter the signature declares (sin(theta = 0.0)); a name no parameter carries is reported (ErrUnknownParameter) rather than absorbed by an omitted [0..1] parameter runtime/library_functions.go bindAndApply, checkNamedArguments TestLibraryFunctionNamedArguments, TestVectorAndComplexNamedArguments, TestVectorAndComplexUnknownNamedArgument ✅ Faithful
TrigFunctions::deg/rad, whose library bodies (theta * 180 / pi, theta * pi / 180) had no evaluable pi runtime/library_functions.go (degreesFromRadians/radiansFromDegrees, over libraryPi) TestTrigDegreesAndRadians; conformance calc_library_trig_degrees (deg(rad(180.0))), calc_library_trig_degrees.trace.golden ✅ Faithful
VectorFunctions: VectorOf/CartesianVectorOf/CartesianThreeVectorOf, isZeroVector, '+', '-' (binary and the one-argument negation the signature's [0..1] second parameter allows), inner, norm, angle, scalarVectorMult/'*', vectorScalarMult, vectorScalarDiv, each with the cartesian* specialization the library declares of it, and sum/sum0 over a collection of vectors — the whole vendored package. Each answers a vector value (ValVector, Structured values above), printed ⟨1.0, 2.0⟩; a sequence of numbers given where the signature declares a vector is read as one, a sequence of vectors keeps its grouping runtime/library_functions.go registerVectorFunctions; runtime/vector_functions.go readVector, vectorCollection, vectorSum, vectorSum0, Context.vectorArithmetic TestVectorFunctionValues, TestVectorFunctionScalarValues, TestVectorAndComplexNamedArguments, TestVectorAndComplexFunctionErrors; conformance calc_library_vector_functions, calc_library_vector_norm (+ golden traces), calc_library_vector_sum, calc_library_vector_sum_flat_sequence, calc_library_vector_dimension_mismatch, pilot-exec-diff w6d:vector-is-zero (agrees), :vector-elements, :vector-add, :vector-norm-cartesian, :vector-norm-abstract, :vector-inner; conformance calc_vector_elements_chain, robustness elements_chain_of_a_non_numeric_collection, library_function_errors (vector rows) ⚠️ Approximate: elements of a vector is the sequence of its components (structuredFeature), so w6d:vector-elements and :vector-add agree with the pinned artifact, while elements of any other collection stays an element-wise member lookup. norm, inner, angle, sum and the vector notation are self-assessed: the pinned artifact does not evaluate its own library bodies for them (w6d:vector-norm-*, :vector-inner answer an InvocationExpression, VectorOf too), so it prints no vector to match. A mismatched dimension, a non-numeric element, an element or inner product outside the range of its kind, Real or Integer (elementArith, intArith, checkedNumeric), a zero vector where an angle or a direction is asked for, a division by zero, a two-component CartesianThreeVectorOf, and a flat sequence of numbers or a mixed collection where sum/sum0 want vectors are typed errors
ComplexFunctions: rect, polar, re, im, isZero, isUnit, abs, arg, '+', '-' (both arities), '*', '/', '**'/'^', '==', sum, product runtime/library_functions.go registerComplexFunctions, aggregateComplex (also reached by NumericalFunctions::sum/product over a collection holding a Complex, runtime/collections.go aggregate) TestComplexFunctionValues, TestComplexFunctionScalarValues, TestLibraryFunctionOptionalOperand, TestLibraryFunctionEmptyOptionalOperand (an empty collection written for a [0..1] operand is the same no value as null, argumentOmitted); conformance calc_library_complex_functions (i * i is -1.0), pilot-exec-diff w6d:complex-abs-qualified, :complex-mul-qualified, :complex-re, :complex-abs, :complex-is-zero, :complex-mul-re, :complex-is-zero-qualified ⚠️ Approximate: the unqualified call selects the declaration its argument types fit — abs(rect(3.0, 4.0)) is ComplexFunctions::abs and answers 5.0, isZero(rect(0.0, 0.0)) answers true (w6d:complex-abs, :complex-is-zero; the selection rule is the Invocation overload row under Static Expression Type Checking, and the runtime dispatches the declaration the checker selected); the operators are dispatched by operand kind, so '*' over a Complex computes (:complex-mul-re answers -1.0). The values themselves are self-assessed: the pinned artifact evaluates none of these (its re/im have no evaluable body, which is also why its ComplexFunctions::isZero(rect(0.0, 0.0)) answers false — w6d:complex-is-zero-qualified is unrefereeable, not a disagreement: the same run answers false for isZero(rect(3.0, 4.0)) too, so the value folds against unevaluated operands rather than deciding zero). A Complex is one value (ValComplex), so a Real binds to a Complex parameter, a numeric pair is refused as one (TestComplexFunctionsRejectNumericPairs), division by zero is reported, and the operators +, -, *, /, ** and == over a Complex operand compute as ComplexFunctions' (runtime/complex.go complexOperands, complexArithmetic, complexEqual)
A library feature with a library-supplied value: TrigFunctions::pi (which the library fixes by an invariant, not a value), ComplexFunctions::i (rect(0.0, 1.0)), VectorFunctions::cartesian3DZeroVector (⟨0.0, 0.0, 0.0⟩), VectorFunctions::cartesianZeroVector — the library's one feature of three vectors, the 1-, 2- and 3-dimensional zero vectors, answered as a sequence of three vector values [⟨0.0⟩, ⟨0.0, 0.0⟩, ⟨0.0, 0.0, 0.0⟩] that keeps its grouping runtime/library_functions.go libraryFeatures, registerLibraryFeature, Context.libraryFeatureValue, libraryFeatureByName TestLibraryFeatureValue, TestLibraryFeatureValueFromACachedSymbol, TestLibraryFeatureValueLeavesAModelsOwnFeatureAlone, TestLibraryFeatureZeroVectors, TestLibraryFeatureByName, TestLibraryFeatureImaginaryUnit; conformance calc_library_feature_zero_vector, calc_library_feature_zero_vectors ✅ Faithful
A library feature is read as a name, qualified (TrigFunctions::pi) or imported (i under import ComplexFunctions::*), and computes like any other value (2 * TrigFunctions::pi); a registered feature with no computable value reports itself (ErrUnevaluableLibraryFunction) rather than answering nothing runtime/eval.go evalName (both the resolved-scope and the qualified-member paths, consulting Context.libraryFeatureValue) TestLibraryFeatureNameReadFromACachedSymbol; conformance calc_library_feature_pi, calc_library_feature_imaginary_unit, calc_library_feature_zero_vector, calc_library_feature_zero_vectors ✅ Faithful
The seam is consulted after the ordinary lookups — frame binding, calc output, evaluated feature, instance feature value, enumeration literal — and answers only for a library-declared symbol, so a model feature shadowing a library name keeps its own value and name resolution decides which is read runtime/eval.go evalName (seam after Lookup/lookupOutput/selfFeatureValue, before the declaration's body and the cannot-evaluate failure) conformance calc_library_feature_shadowed_by_model; TestLibraryFeatureValueLeavesAModelsOwnFeatureAlone ✅ Faithful
A library declaration is answered by its built-in even where the vendored declaration carries a body, since a warm library index cache restores library symbols without their AST and the result must not depend on the cache runtime/library_functions.go libraryFunctionFor, Context.libraryDeclared TestLibraryFunctionAnswersALibraryDeclarationWithABody, TestLibraryFunctionDoesNotHijackADeclaredBody (a model's own declaration keeps its body) ✅ Faithful
StringFunctions: '+', Length, Substring, '<', '>', '<=', '>=', '==', ToString — the whole vendored package. Length counts characters (one per Unicode code point, so Length("héllo") is 5 over 6 bytes); Substring takes 1-based inclusive character positions and reports a position outside 1..Length(x) naming the character it indexed (ErrIndexOutOfRange, one identity for a sequence index and a string position, worded for both), an upper below lower selecting no character, as SequenceFunctions::subsequence answers for such a range; '==' declares String[0..1] operands, so two omitted operands are equal and an omitted one is not equal to a string runtime/library_functions.go registerStringFunctions, stringLength, stringSubstring, stringOrdering, stringEquals, stringToString, concatStrings, compareStrings TestStringFunctionValues, TestStringFunctionErrors, TestStringFunctionNamedArguments, TestVendoredFunctionsAreAllDispatchable (gating StringFunctions); conformance string_functions, string_empty, string_substring_out_of_range (+ golden traces) ✅ Faithful
The operators StringFunctions declares evaluate over two String operands: '+' concatenates, and '<', '>', '<=', '>=' order strings by character (UTF-8 orders bytes as it orders code points, so a byte comparison is code-point order). An operand of another type is reported, naming the operator and both operand types, and neither operand is coerced runtime/eval.go evalArithmetic (ast.OpAdd over two ValString), evalComparison (OperandTypeError) TestStringOperators, TestStringOperatorErrors, robustness_test.go:testStringOperandOfTheWrongKind; conformance string_operators, string_comparison, string_compared_with_a_number (+ golden traces) ✅ Faithful
StringFunctions::'==' specializes DataFunctions::'==', which is equality over any two values, so the == operator answers false for a String and an Integer rather than reporting; the explicit call StringFunctions::'=='(s, 3) reports the non-String argument, as every String-declared signature does. Two strings are equal by their characters, which is also what a collection membership test asks runtime/eval.go evalEquality → runtime/value_equality.go valueEqual, valueKey (ValString); runtime/library_functions.go stringEquals TestStringOperators (s == three is false, ("a", "b")->includes("b")), TestStringFunctionErrors ✅ Faithful
Domain library QuantityCalculations (28 declarations): every one computes over the quantity value — unit-aware sqrt, abs, floor, round, max/min, sum/product, the operators, comparisons, predicates and conversions (see Requirement, the quantity rows), and '[' and ConvertQuantity over a measurement reference value ('['(3.0, SI::m) is 3.0 [m]; Structured values above) runtime/quantity_functions.go registerQuantityCalculations; runtime/measurement_ref_functions.go quantityOf, convertQuantity TestQuantityCalculations, TestQuantityCalculationsReport, TestQuantityCalculationsAreAllDispatchable, TestMeasurementRef*; conformance instance_quantity_calculations, instance_quantity_calculation_failures, instance_quantity_trigonometry, instance_measurement_references, instance_measurement_reference_failures, calc_quantity_sum_imported, calc_quantity_sum_unimported ✅ Faithful
Domain library VectorCalculations (16): the ten over a numeric vector compute as their VectorFunctions counterparts (inner, norm, angle over vector quantities answer the declared Number, the magnitude over the num components with the unit dropped by declaration — drafted in omg-issues.md), and scalarQuantityVectorMult, vectorScalarQuantityMult, vectorScalarQuantityDiv answer a vector quantity (ValVectorQuantity, ⟨2.0, 4.0⟩ [m]) whose unit is composed by the scalar quantities' own rule; the five MeasurementRefCalculations over a scalar unit ('*', '/', '**', '^', ToString) compute over the measurement reference value; TensorCalculations (13): '[' over a model-declared TensorMeasurementReference, '+', '-', the four scalar multiplications, isZeroTensorQuantity and isUnitTensorQuantity (square order two) compute over a tensor quantity (ValTensorQuantity, Tensor(2, 2)[1.0, 2.0, 3.0, 4.0] [Pa]) and over the scalar and vector quantities the subtype chain admits; 'CoordinateFrame*'/'CoordinateFrame/', VectorCalculations::'[' and transform compute over the coordinate frame value (ValCoordinateFrame); outer, and TensorCalculations' three products and transform, report themselves by name, each with the reason (Structured values above) runtime/quantity_functions.go registerVectorCalculations, registerMeasurementRefCalculations, registerTensorCalculations; runtime/measurement_ref_functions.go; runtime/vector_functions.go scaleVectorQuantity over quantity.go scaleQuantities; runtime/tensor.go, runtime/tensor_functions.go TestVectorCalculations, TestQuantityCalculationsReport, TestQuantityCalculationsAreAllDispatchable, TestMeasurementRef*, TestTensorQuantity*, semantics/tensor_type_test.go, passes/typecheck_tensor_test.go; conformance calc_library_vector_quantity, calc_library_vector_quantity_norm, instance_measurement_references, instance_tensor_quantity, instance_tensor_quantity_failures, calc_library_unevaluable_function (TensorCalculations::tensorTensorMult); robustness library_function_errors (vector-quantity rows), measurement_reference_failure_modes, tensor_quantity_failure_modes ⚠️ Approximate (below)
A declaration this runtime has no representation for the values of reports itself by name (ErrUnevaluableLibraryFunction), never a wrong result runtime/library_functions.go registerUnevaluable TestUnevaluableLibraryFunctionsNameThemselves, TestVendoredFunctionsAreAllDispatchable (every vendored declaration of these packages either computes or names itself); conformance calc_library_unevaluable_function ✅ Faithful
Every declaration of every vendored Kernel Function Library package is dispatchable by name — the 17 packages BaseFunctions, BooleanFunctions, CollectionFunctions, ComplexFunctions, ControlFunctions, DataFunctions, IntegerFunctions, NaturalFunctions, NumericalFunctions, OccurrenceFunctions, RationalFunctions, RealFunctions, ScalarFunctions, SequenceFunctions, StringFunctions, TrigFunctions, VectorFunctions, and OpenSysMLMathFunctions. Each calc or function declaration, operator-named ones included, is registered as a value function (with the declared parameter names in declared order), as a builtin, or as unevaluable by its own name with a reason; no package and no name is skipped runtime/library_functions.go libraryFunctions, runtime/builtins.go builtins, runtime/library_unevaluable.go TestVendoredFunctionsAreAllDispatchable (reads the vendored .kerml, so a declaration the library adds fails the gate until it is registered) ✅ Faithful
Conversions: BaseFunctions::ToString (x: Anything[0..1]: the notation a value is written with — a Boolean, number or String literal, * for the unbounded value, an enumeration literal by name, a quantity with its unit as 1.5 [m]; an omitted or null x reads "null"; two or more values are ErrMultiplicityViolation; an instance, a variant or a body has no notation and is ErrTypeMismatch; a Complex stays unevaluable, see below), BooleanFunctions::ToString/ToBoolean, IntegerFunctions::ToString/ToInteger/ToNatural, NaturalFunctions::ToString/ToNatural, RationalFunctions::ToString/ToInteger/ToRational, RealFunctions::ToString/ToInteger/ToRational/ToReal. A String is parsed as the type's literal notation — decimal digits for an Integer, true/false for a Boolean, a decimal with an optional fraction and exponent for a Real or Rational — and a String that is no such notation is ErrInvalidNotation, never 0 or a panic ("NaN", "Inf", a hex float and a padded " 1.5 " are refused too — the notation is the whole String, nothing is trimmed). A negative given to ToNatural is semantics.ErrArithmeticDomain; a magnitude outside the Integer range, or a Real notation float64 cannot hold — one that overflows to an infinity ("1e400") or a nonzero one that underflows to zero ("1e-400"), as a literal or a String — is semantics.ErrArithmeticOverflow, never 0.0. ToInteger of a Real or Rational truncates toward zero: the vendored declarations carry no body and the library declares floor and round beside them, so a rounding conversion would duplicate one of those. RealFunctions::ToRational of a number answers the Real its x: Real parameter binds it as, an Integer widened like every RealFunctions form (ToRational(2) === 2.0), a Rational being a float64 here; RationalFunctions::ToInteger of an Integer keeps it semantics/eval.go ParseReal (decimal notation only — isRealNotation, any other text semantics.ErrRealNotation — shared by the Real literal in runtime/eval.go evalLiteralReal, runtime/compile.go, codegen/compile.go and the model-level folder); runtime/library_conversions.go registerConversionFunctions, parseReal, parseInteger semantics/eval_test.go:TestParseReal, :TestParseRealRejectsNonDecimalNotation; TestConversionFunctionValues, TestConversionFunctionErrors, TestRealToStringRoundTrips; conformance calc_library_conversions, calc_library_to_real_invalid_string, calc_library_to_natural_negative; robustness named_library_call_that_has_no_value, real_literal_that_underflows ✅ Faithful
ToString of a Real is the shortest decimal that reads back as the same float64 (FormatReal: 2.5 → "2.5", 1.0 → "1.0", 0.1 + 0.2 → "0.30000000000000004", 1e21 → "1e+21"), the rendering the REPL already prints a Real with, so ToReal(ToString(x)) == x for every finite Real. The pinned pilot evaluator was asked for ToString(1.0), ToString(0.1 + 0.2), ToString(1e21) and ToString(2.5) and answered each with the unevaluated InvocationExpression ToString — its ToString has no evaluable body — so there is no pilot rendering to match, and the round-tripping one was chosen runtime/library_conversions.go numberToString → runtime/value.go FormatReal TestRealToStringRoundTrips, TestConversionFunctionValues; conformance calc_library_conversions (roundTrip) ⚠️ Approximate (self-assessed: the pilot does not evaluate it, so the rendering is a decision, recorded here)
RationalFunctions::floor/round delegate to the Real implementations, a Rational being a float64 here (see docs/project/exact-rational-evaluation.md); RationalFunctions::gcd is the Integer gcd over whole-valued operands, computed exactly (math/big) over their magnitudes so any whole Rational is an operand — gcd(-2^63, 2) is 2, gcd(1.0e20, 6.0e18) is 2000000000000000000 — and only a divisor that is itself past the Integer range (gcd(2^63, 0), gcd(1.0e19, 1.0e19)) is semantics.ErrArithmeticOverflow; gcd(0, 0) is 0, and a non-whole operand semantics.ErrArithmeticDomain. RealFunctions::re/im/arg answer for a Real as the Complex it is with a zero imaginary part — re(x) is x, im(x) is 0.0, arg(x) is 0.0 or pi — rather than being marked unevaluable runtime/library_conversions.go registerConversionFunctions (rationalGCD, realPartOfReal, imagPartOfReal, argumentOfReal) TestConversionFunctionValues, TestConversionFunctionErrors; conformance calc_library_conversions ✅ Faithful
RationalFunctions::rat(numer, denum) is the binary64 quotient, the same value RationalFunctions::'/'(numer, denum) and the / operator compute (semantics.IntQuotient: the exact Integer ratio rounded once), so rat(1, 3) is 0.3333333333333333, rat(6, 4) is 1.5 and rat(1, 0) is ErrDivisionByZero as 1 / 0 is, never an infinity; a non-Integer operand is ErrTypeMismatch. numer(rat)/denom(rat) read the exact numerator and positive denominator, in lowest terms, of the ratio the binary64 holds (math/big.Rat.SetFloat64) — an Integer is itself over 1 — so numer(0.75) is 3, denom(0.75) is 4, numer(-0.75) is -3, numer(2)/denom(2) are 2/1, denom(0.0) is 1, and rat(numer(x), denom(x)) == x holds for every finite x whose terms are Integers (TestRationalTermsRoundTrip: negatives, zero, whole values, 0.1, 1.0 / 3.0, pi, 2^63 - 1). A term outside the Integer range (denom(0.0001) is 2^66, numer(1.0e19)) is semantics.ErrArithmeticOverflow, an infinity or NaN semantics.ErrArithmeticDomain, each naming the function runtime/library_conversions.go integersToRational, rationalNumerator, rationalDenominator, exactRationalTerm TestConversionFunctionValues, TestConversionFunctionErrors, TestRationalTermsRoundTrip, TestVendoredFunctionsAreAllDispatchable; conformance calc_library_rational_terms, calc_library_rational_zero_denominator, calc_library_rational_denominator_overflow; robustness named_library_call_that_has_no_value; pilot-exec-diff rational_terms.cases (rat-third, numer-tenth, … all pilot-unevaluated; quotient-by-operator agrees) ⚠️ Approximate, by design: a Rational is a binary64 here, so numer/denom answer the terms of the double nearest the written value, not of the rational the model wrote — numer(rat(1, 3)) is 6004799503160661 over 2^54, numer(0.1) is 3602879701896397 over 2^55 (36028797018963968) — the same class of artifact as 0.1 + 0.2 != 0.3, accepted as parity with the pinned pilot, which stores every LiteralRational as a Java double. The pure-spec answer (numer(rat(1, 3)) is 1) needs an exact Rational value kind, which exact-rational-evaluation.md declines; that record holds the verbatim probes showing the pilot evaluates none of the three (InvocationExpression rat/numer/denom for every case, rat(1, 0) included), so they are self-assessed
NumericalFunctions::sum0/product1 (the library declares no Integer/Rational/Real specialization of either) fold a collection as sum/product do, keeping the elements' kind, and answer the identity argument for an empty collection; an identity that is not isZero/isUnit as the library's own precondition asserts is ErrTypeMismatch, and a non-numeric element or an overflow is reported as for sum runtime/builtins_named.go builtinNumericalSum0, builtinNumericalProduct1, aggregateWithIdentity TestAggregationsWithIdentity, TestAggregationsWithIdentityErrors; conformance calc_library_aggregation_identity, calc_library_sum0_wrong_identity ✅ Faithful
Generic DataFunctions::max/min and ScalarFunctions::max/min order two values the way the < operator does — numbers keeping their kind as the numeric max/min do, strings by character, quantities by magnitude — answering the operand chosen; a pair the library declares no ordering for (two Booleans, two sequences, a string and a number) is ErrTypeMismatch runtime/library_operators.go registerGenericExtrema, genericExtremum TestOperatorFunctionValues, TestOperatorFunctionErrors; conformance calc_library_operator_call_forms (largest, smallest) ✅ Faithful
Every operator the library declares is callable as a function, and evaluates by the operator's own evaluator code, never a second arithmetic: '+', '-' (with y omitted, the unary sign), '*', '/', '%', '**', '^', '<', '<=', '>', '>=' in DataFunctions, ScalarFunctions, NumericalFunctions, RealFunctions, RationalFunctions, IntegerFunctions ('**'/'^' with a Natural exponent) and NaturalFunctions, each package's parameter types imposed on the arguments — a RealFunctions parameter is Real, so an Integer argument is bound as the Real it equals and the function answers a Real (RealFunctions::'+'(1, 2) is 3.0, '-'(5) is -5.0, '*'(2^40, 2^40) is 2^80 where the Integer product would overflow), as RealFunctions::max/abs already answer Reals, while RationalFunctions keep an Integer's kind as RationalFunctions::abs/max/min do (a Rational is a float64 here, see exact-rational-evaluation.md); NaturalFunctions::'/' is the exception to the shared arithmetic, computing the Natural its declaration returns: '/'(6, 3) is the Integer 2, a quotient no Natural equals ('/'(7, 2)) is semantics.ErrArithmeticDomain rather than a truncated or Rational answer, and '/'(6, 0) stays ErrDivisionByZero (the / operator on Natural operands still answers the Rational the pilot answers, see omg-issues.md); '==' in every package that declares it, each typed package (Boolean, Integer, Natural, Rational, Real) imposing its operand type on both given operands (IntegerFunctions::'=='(2, 2.0) is ErrTypeMismatch, where the == operator and BaseFunctions::'==' answer true) while an omitted operand stays admitted as null, and DataFunctions::'=='/'===' admitting DataValues only (a part or other occurrence is ErrTypeMismatch naming DataValue; BaseFunctions::'=='/'===' compare anything), '!=', '===' and '!==' in BaseFunctions/DataFunctions — every equality and identity form holds its [0..1] operands to one value: an empty collection is the same no value as null (BaseFunctions::'=='((), null) is true, '!=='((), "") true, as () == null is by the operator, isEmptyValue), a sole element stands for itself, and two or more values are ErrMultiplicityViolation naming the parameter (singleOperands); every other operand is declared [1] and held to exactly one value before its kind is judged — a one-element sequence or set stands for its element (IntegerFunctions::'+'(xs->select {in i; i > 2}, 1) adds that element), while an empty or several-valued operand is ErrMultiplicityViolation naming the parameter, never a type mismatch (soleValue, checkOperands, the generic max/min and NaturalFunctions::'/' included); 'not', 'xor', '|', '&' in BooleanFunctions, DataFunctions and ScalarFunctions (both operands given, so nothing short-circuits); '..' in DataFunctions, ScalarFunctions and IntegerFunctions; BaseFunctions::'#' (declared Positive[1..*] indexes: one selects from the sequence as SequenceFunctions::'#' does, several address an Array and select its element in row-major order as CollectionFunctions::'array#' does — over a flat sequence they are ErrTypeMismatch — and none is ErrMultiplicityViolation, builtinBaseIndex; conformance calc_library_base_index_many, calc_library_base_index_many_sequence, robustness base_index_with_several_indexes), CollectionFunctions::'#' and ','; CollectionFunctions::'=='. A failure names the function as the model writes it (function IntegerFunctions::'/': division by zero) runtime/library_operators.go registerOperatorFunctions, naturalDivision, equalityForm over runtime/eval.go arithmeticValues, comparisonValues, Context.equalityValues, unaryValue, combineBooleans, valueIdentical; runtime/builtins_named.go registerNamedOperatorBuiltins, runtime/range.go rangeBuiltin; runtime/library_functions.go writtenName TestOperatorFunctionValues, TestOperatorFunctionErrors, TestOperatorFunctionOperandMultiplicity, TestDataOperatorFunctionsRequireDataValues, TestSequenceOperatorCallForms, TestIndexCallFormErrors, TestEval_EqualityNull; conformance calc_library_operator_call_forms, calc_library_operator_singleton_operands, calc_library_operator_many_operands, calc_library_empty_equality, calc_library_base_index_many, calc_library_base_index_many_sequence; robustness named_library_call_that_has_no_value, data_equality_over_a_part, base_index_with_several_indexes ✅ Faithful
An input parameter whose multiplicity admits no value (in y: Integer[0..1]) may go without an argument, and is then null: the static arity check counts it as optional and the runtime binds null to it, so IntegerFunctions::'-'(5) and ControlFunctions::'if'(false, 1) are accepted and evaluated, and a model's own calc def Scale { in x: Integer; in factor: Integer[0..1]; ... } is called as Scale(5). A built-in receives one value per parameter it declares whatever the call wrote, positionally or by name, so a trailing omission is null too: SequenceFunctions::size() is 0, NumericalFunctions::sum() 0, ControlFunctions::'if'(false) and '??'() null, and subsequence(seq, 2) runs to the end as the library's endIndex default declares; a required parameter left out, or an argument past the last parameter, is ErrCalcArity. A parameter declaring no multiplicity holds one value and stays required (KerML 1.0 §7.4.7.2) semantics/multiplicity.go Range.AllowsNone, Model.IsOptionalParameter; passes/typecheck_expr.go checkArguments; runtime/invoke_calc.go calcParameter.optional, EvalContext.bindCalcParameter; runtime/builtins_signature.go bindBuiltinArgs, fillUnbound passes/typecheck_expr_test.go:TestExprInvocationOptionalParameterMayBeOmitted; conformance calc_parameter_optional_omitted, calc_library_operator_call_forms, calc_library_control_functions, calc_library_builtin_omitted_arguments; builtins_named_test.go:TestControlFunctionCallForms, :TestSequenceOperatorCallForms; robustness builtin_named_argument_that_binds_nothing ✅ Faithful
ControlFunctions::'if', 'and', 'or', 'implies' and '??' as functions keep the library's short-circuit semantics: the expr parameters are deferred, so only the branch 'if' selects, the second operand 'and'/'or'/'implies' needs, and the second value '??' falls back to are evaluated ('if'(true, 1, 1 / 0) is 1); a body written for one ({1 + 1}), or reached through an expr parameter of the calling calc (Pick(test, { base + 1 }, { 0 - base })), is applied rather than answered as a body, and one declaring a parameter is ErrBodyArity. 'if' with elseValue omitted answers null, as the [0..1] parameter allows; '??' falls back over an empty first operand — null, (), an empty set or a collection filtered down to nothing — in the function form and the ?? operator alike (coalesceNull, isEmptyValue) runtime/builtins_named.go builtinDeferredParams, builtinControlIf, builtinControlLogical, builtinControlNullCoalesce, EvalContext.evalDeferred TestControlFunctionCallForms, TestControlFunctionCallFormErrors; conformance calc_library_control_functions, calc_library_control_body_by_reference; robustness named_library_call_that_has_no_value, body_by_reference_that_cannot_be_applied ✅ Faithful
A call is dispatched by the declaration it resolves to, the model's declarations first: a built-in answers only a symbol the library declares, so a model's own NumericalFunctions::sum0 or size — with a body or without one — is the model's calc, evaluated from its body or reported as ErrNoResultExpression, never computed by the library's implementation of that name; a built-in binds named arguments by its declared parameter names in any order (sum0(zero = 0, collection = xs)), and a name it does not declare is ErrUnknownParameter, a name bound twice or mixed with positional arguments ErrCalcArity. A direct invocation through the runtime API (InvokeCalc, InvokeCalcNamed) takes the same path, so it binds and computes as the written call does; a body or expression value it hands to an expr parameter is applied only when the control function selects it, any other value is the operand's value runtime/eval.go invocationTarget, evalInvocation; runtime/builtins.go Context.builtinFor (Context.libraryDeclared); runtime/builtins_signature.go bindBuiltin, bindBuiltinArgs, bindBuiltinValues; runtime/invoke_calc.go invokeCalcWithSelf, invokeBuiltinValues conformance calc_library_builtin_shadowed_by_model, calc_library_builtin_named_arguments; robustness bodiless_model_calc_named_as_a_builtin, builtin_named_argument_that_binds_nothing; TestInvokeCalcDispatchesBuiltins, TestInvokeCalcNamedDispatchesBuiltins, TestInvokeCalcDefersBodyArguments ✅ Faithful
Occurrences have a lifetime in the runtime's own execution order (KerML §8.4.4 OccurrenceFunctions, Occurrences::Occurrence/Life, Performances::Performance): every object the runtime materializes, and every action or state performance it runs, is an occurrence with a begin and an end expressed as activation marks — the monotone counter the executors already order performances by, never wall-clock time — held in a side table keyed by object identity (Context.lives), so no Kernel Semantic Library frame member (portionOfLife, startShot, endShot) is added to an object and %features keeps its shape. An object begins when its whole does (a part materialized under a bench began when the bench did), a performed action's or exhibited state's occurrence begins when its performance starts and ends when it completes, and the lifetimes of the objects an evaluation materialized and abandoned are dropped with them. The information is read through Context.OccurrenceLife(id) (began, ended, destroyed), %features (a destroyed object prints (destroyed at N, alive since M) in place of features it no longer holds), %instances (, destroyed), and the execution trace (create: T #n, destroy: T #n) runtime/lifetimes.go life, Context.beginLife, beginPerformanceLife, endPerformanceLife, forgetLives, OccurrenceLife; runtime/instance.go materialize; runtime/action_executor.go initialize/retireToken; runtime/state_executor.go initialize/completeIfDone; runtime/trace.go RecordOccurrenceCreated/RecordOccurrenceDestroyed; repl/meta.go featureValueWalk.rows, Session.destroyedNote lifetimes_test.go:TestOccurrenceLifeBeginsWithWhole, :TestAbandonedObjectsLoseTheirLives; conformance occurrence_is_during_completed_performance, occurrence_is_during_in_exhibited_state, occurrence_lifecycle_in_performed_action (+ golden trace); repl/occurrence_test.go:TestOccurrenceFunctionsInSession; library_shape_test.go:TestShapeKeepsSystemsLibraryFeaturesAndLeavesOutTheKernelFrame passes unchanged ✅ Faithful
OccurrenceFunctions::'===' (x: Occurrence[0..1], y: Occurrence[0..1]) is same-occurrence identity: true iff both operands denote one object, so two separately materialized, structurally equal parts are !== while their attributes are ==; the operators ===/!== answer the same over objects (evalIdentity, which compares instances by identity). Two omitted operands are identical (as BaseFunctions::'===' answers), one omitted and one object are not; a data value is ErrNotAnOccurrence naming the parameter, two or more values ErrMultiplicityViolation. The pinned pilot evaluator (0.61.0) answers w1 === w1 true, w1 !== w2 true and BaseFunctions::'==='(w1, w1) true but OccurrenceFunctions::'==='(w1, w1) false — it folds the declared body x.portionOfLife == y.portionOfLife over features no value has — so the operator agreement, not the pilot's function answer, is what is matched (omg-issues.md) runtime/builtins_occurrence.go builtinOccurrenceSame, occurrenceOf; runtime/eval.go evalIdentity, valueIdentical TestOccurrenceFunctionArity; conformance occurrence_same_distinguishes_equal_parts; robustness OccurrenceFunctions::'==='(xs, xs), '==='(factor, factor) ✅ Faithful
OccurrenceFunctions::isDuring(occ: Occurrence[1]) — the vendored declaration takes the one occurrence and answers notEmpty(during), whether the call happens during it — is true while occ is alive at the evaluation: an object from its materialization until destroy, a performed action or exhibited state from its start until it completes, so isDuring(go) assigned inside go's body is true and read after go completed false (a performed action stating no flow performs no step, so it takes its inputs — a binding of an out parameter or of a name it does not declare failing the performer, as for a flowed action — and is complete at once, isDuring of it false); an owned part read from a state's entry action is true, and false after that action destroyed it. A data value is ErrNotAnOccurrence, an empty or several-valued argument ErrMultiplicityViolation, an object the context holds no lifetime for ErrOccurrenceLifetime — never a silent false. The pilot answers isDuring(1) and isDuring("x") true, folding during statically; not matched. Not computed: a nested action node named as a value (isDuring(inner) in Outer's body) is ErrNodePin (declares no result to read it as a value by), as every read of an action node without a result is — the performance is an occurrence, but the node is not a value the evaluator hands out; and forks run interleaved in one step order, so isDuring across concurrent branches answers for that order, which is one of the orders the specification admits runtime/builtins_occurrence.go builtinOccurrenceIsDuring; runtime/lifetimes.go Context.lifeOf, life.alive conformance occurrence_is_during_completed_performance, occurrence_is_during_in_exhibited_state, occurrence_lifecycle_in_performed_action; robustness OccurrenceFunctions::isDuring(xs), isDuring(factor) (a data value is also a type error the checker reports before execution); internal/frontend/repl/occurrence_test.go isDuring(a.n); TestOccurrenceLifeBeginsWithWhole, TestDestroyEndsPortionsAndRefusesReads, TestPerformedActionWithoutAFlowCompletes, TestPerformedActionWithoutAFlowTakesItsInputs, robustness no_flow_performed_action_checks_its_inputs ✅ Faithful (the two uncomputed cases above are typed errors)
OccurrenceFunctions::create(inout occ: Occurrence[1]) begins occ during the current performance: an occurrence the call is the first to reach — a part materialized by evaluating the argument — begins where the call reached it, ahead of the parts and performances reached with it (a state it exhibits or action it performs starts no earlier than its performer), and is answered, journaled so a rolled-back speculative evaluation forgets it, and traced as create: T #n; one that began before the call (a part the model read earlier, an object %instantiate made) is ErrOccurrenceLifetime naming when it began, since an occurrence begins once; a destroyed one ErrOccurrenceDestroyed. destroy(inout occ: Occurrence[0..1]) ends occ and, with it, every object it holds as a portion of itself (its owned parts, transitively, each once however many names of a redefined feature hold it), answering occ; an omitted occ answers nothing, as declared. Afterwards isDuring(occ) is false, every read or write of a feature of the destroyed object is ErrOccurrenceDestroyed (never a stale value) — through a binding too, whichever end names the destroyed object's feature: a live feature bound to it is not read from it, and a live value is not written into it (binding.go resolveBindingLocation, TestBindingRefusesADestroyedEnd, robustness binding_end_of_a_destroyed_object) — and the destroyed object performs nothing: an operation invoked on it, an action or state machine performed by it, is ErrOccurrenceDestroyed before it runs, even one touching no feature of it, so it sends no message (lifetimes.go checkPerformer, from InvokeOperation and the executor constructors; TestDestroyedObjectPerformsNothing, robustness operation_of_a_destroyed_object) — %features prints the destruction, and a second destroy is ErrOccurrenceDestroyed naming the first. A = value that read a feature of the destroyed object, isDuring of it, or all T is derived again when next read rather than answering what it derived before: destroy unmaterializes what derived from the ended objects' features (lifetimes.go forgetDerivedFrom), and the lifetimes are a dependency of their own (Context.lifetimes, readsLives from lifeOf and evalExtent, livesChanged from every change of a life — creation, a = value's own new T() included, destroy, terminate, a performance beginning or ending — sparing only what is deriving as it changes), journaled so a rollback restores what was derived, and carried by a held image — a = value that read the lives is derived again in the context it is materialized into once a life there changes, and the objects the image brings are a change of that context's lives: what its own objects derived from all T before is derived again over them (held_image.go imagedFeature.readsLives, HeldImage.Materialize; robustness_object_lifecycle_test.go derivations_that_read_lifetimes_derive_again_when_they_change, an_imaged_derivation_that_read_lifetimes_follows_the_lives_where_materialized). Destroying an occurrence ends the behaviors it performs with it: the state machine it exhibits and the actions it performs are terminated where they stand (terminated with occurrence: <name> in the trace), their own performance occurrences ended, and a behavior attached but not yet run takes no run; a destroy reached from inside one of those behaviors unwinds it as a terminate does and the enclosing performance goes on. The destroyed object is released from all T (the extent walk skips it), while a feature that still names it — a ref alias, an element of a collection — keeps the value, so size(cars) is unchanged and cars#(2).n is ErrOccurrenceDestroyed: the library declares destroy over the occurrence, not over what refers to it, and a stale reference is an error to read, not an empty one (isDuring of it is false). A portion that ended before its whole stays ended where it did. A snapshot (Image) of a graph whose held objects include a destroyed one carries it destroyed and materializes its behaviors as terminated. The pilot answers create(w1) and destroy(w1) with w1 and destroy(null) with nothing, no lifecycle being observable in it runtime/builtins_occurrence.go builtinOccurrenceCreate, builtinOccurrenceDestroy; runtime/lifetimes.go Context.createDuring, destroy, portionsOf (what its composite features hold and what it is home to, transitively), checkNotDestroyed; runtime/occurrence_terminate.go Context.endBehaviorsWith, noteEndingUndo (a journal under way — a constructor's, a probe's — puts an ended executor back where it stood on rollback), ActionExecutor.endsWith/endTerminated, StateExecutor.endsWith/endTerminated; runtime/classifier_behavior.go nextRunnableBehavior (an ended pending behavior takes no run), performanceOccurrence (a held performance occurrence resolves for a destroyed object too); runtime/extent.go Context.objectsOf; runtime/instance.go GetFeatureValue/SetFeatureValue TestCreateOnlyWhatTheCallReaches, TestCreateBeginsBeforeWhatItReachesWith, TestDestroyEndsPortionsAndRefusesReads, TestDestroyEndsAnAliasedPartOnce, TestDestroyEndsTheMachinePerformed, TestLifetimeChangesRollBackWithTheJournal; robustness_object_lifecycle_test.go:TestRuntimeRobustnessObjectLifecycle (destroy_through_a_reference_ends_the_held_object, destroy_ends_the_created_objects_machine, destroy_twice_is_refused, destroy_of_the_whole_ends_the_created_parts, explore_destroy_race_reaches_both_outcomes); conformance object_destroyed_at_runtime + trace golden; conformance occurrence_lifecycle_in_performed_action, occurrence_lifecycle_errors (destroyedTwice, readAfter, begunBefore); robustness OccurrenceFunctions::create(factor), destroy(factor) ✅ Faithful
OccurrenceFunctions::addNew(inout group: Occurrence[0..*] nonunique, inout occ: Occurrence[1]) and addNewAt(…, in index: Positive[1]) create occ as create does and insert it into group: addNew at the end, addNewAt before the 1-based index, index == size + 1 appending and any other index outside 1..size + 1 ErrIndexOutOfRange before anything begins (insertAt, the one insertion SequenceFunctions::includingAt uses); an element of group that is not an occurrence is ErrNotAnOccurrence. Approximate in its value: the declaration returns occ and mutates group through inout, but an expression call has no feature to write back to — an argument is a value, and writes belong to assign in a behavior body (a calculation body may not write a part's feature at all) — so the call answers the group after insertion, as including/includingAt do, for the body to write (assign spares := addNew(spares, spare)); group itself is unchanged by the call. The pilot answers addNew(group, w1) and addNewAt(group, w1, 5) with w1 and leaves group as it was, so neither its insertion nor its bounds are observable there runtime/builtins_occurrence.go builtinOccurrenceAddNew, builtinOccurrenceAddNewAt, occurrenceGroup; runtime/collections.go EvalContext.insertAt TestOccurrenceFunctionArity, TestCreateOnlyWhatTheCallReaches; conformance occurrence_lifecycle_in_performed_action (addNew then size), occurrence_lifecycle_errors (tooFar); robustness OccurrenceFunctions::addNew(xs), addNew(occ = xs), addNew(xs, factor), addNewAt(xs, xs), addNewAt(occ = xs, index = 1), addNewAt((), factor, 0) ⚠️ Approximate (self-assessed: the value is the group, not occ; inout group is not written back)
A body expression is a value closed over the environment it is written in — the frames and scope in force there — and is applied in that environment wherever it is later called, so a body passed on through an expr parameter (Keep(xs, { in x; x > threshold }), Keep doing xs->select pred) reads its writer's threshold, a body-local bound, or a reducer's weight, and a same-named parameter of the applying calc (Keep's own threshold or pred) never captures it; a name the writer's scope does not reach stays ErrUnresolvedReference. The bindings are copied into the closure, so a body a calc returns (return : expr = { in x; x > threshold }) still reads its threshold after the calc has returned and its frame storage has been reused; the calc evaluation whose outputs the body may name is detached from that storage too, so a body bound to the result (out threshold = n; out pred : expr = { in x; x > threshold }; bind result = pred;) or read from a usage nested in the calc's body works those outputs out from the invocation's own parameters, memoized in common with the invocation and with an output named in terms of itself still ErrCyclicOutput runtime/value.go exprValue, NewExprValue, Value.exprEnv; runtime/eval.go EvalContext.closure, runtime/frame.go frame.snapshot, runtime/calc_usage.go calcRun.detached; runtime/collections.go bodyOf, evalClosure, applyBody; runtime/builtins_signature.go evalArgument conformance calc_library_body_by_reference, calc_library_body_keeps_declaring_scope, calc_library_body_outlives_its_calc, calc_library_body_reads_calc_output, calc_library_control_body_by_reference; calc_run_detach_test.go:TestEscapedBodyReadsCalcOutputsAfterFrameReuse, :TestEscapedBodyReportsOutputCycle; robustness body_by_reference_that_cannot_be_applied ✅ Faithful

Found, not fixed — numeric library declarations that remain unevaluable. Each reports itself by name rather than answering:

Not implemented Why
MatrixFunctions The vendored Kernel Function Library declares no such package — only docs/ mention it — so there is nothing to dispatch. Not implemented rather than invented.
ComplexFunctions::ToString/ToComplex, BaseFunctions::ToString of a Complex No string notation for a Complex value is defined; inventing a rendering would make ToComplex(ToString(x)) a value nothing else in the library agrees on.
BaseFunctions::'[' Declared abstract, and the operator notation a[i, j] is the quantity notation num [unit] to the parser and the evaluator (evalIndexExpr): over an operand whose bracket names no unit the error (ErrNotAQuantity, notAQuantityError) says so, and when the operand is declared an Array, a vector or a feature of more than one value (declaredCollection, from the declaration alone — the operand is not evaluated to diagnose it) points at a#(i, j), which CollectionFunctions::'array#' and BaseFunctions::'#' evaluate. Reading a[i, j] as indexing by the operand's static type would be a checker rule as well as a runtime one, and is not made here.
BaseFunctions::as, meta, istype, hastype, '@', '@@'; ControlFunctions::'.' The operator notations all T, x istype T, x as T, x meta T, x @ M, x.f are evaluated, from their own expression nodes (all/as/meta/istype/hastype/@/@@ take a type, which the function form would have to receive as a value). The function forms report themselves rather than pretend.
DataFunctions::'~', ScalarFunctions::'~' Declared abstract and specialized by no concrete library function, so the complement denotes no operation on any value type — reported as the ~ operator is; adjudicated abstract-only in bitwise-complement.md.
QuantityCalculations::ConvertQuantity to or from a measurement scale that states neither a CoordinateFramePlacement nor a quantityValueMapping — Time::UTC, every OrdinalScale, CyclicRatioScale, LogarithmicScale in the vendored library The scale is a value (ValCoordinateFrame, Structured values above) and a quantity on it reads, but the library gives its points no relation to any other reference, so a conversion has nothing to compute by; the reason names the scale and the missing transformation or mapping. SI::'°C_abs', placed on K, converts.
VectorCalculations::transform over a CoordinateTransformation that is none of CoordinateFramePlacement, TranslationRotationSequence, AffineTransformationMatrix3d, NullTransformation Those four are the shapes the library documents; a user-defined subtype states no origin, basis, steps or matrix the runtime could apply, so the reason names the declaration and the four shapes.
TensorCalculations::tensorVectorMult, vectorTensorMult, tensorTensorMult; VectorCalculations::outer; TensorCalculations::transform The three products declare only their parameter and result types: neither TensorCalculations nor the Kernel states which indices contract or how the operands' contravariantOrder and covariantOrder combine, so no product is determined. outer declares return : VectorQuantityValue, which the order-two outer product of two vectors is not (drafted in omg-issues.md, unfiled). transform takes a CoordinateTransformation the runtime holds (ValCoordinateTransformation), but neither package states how it acts on a tensor's contravariant and covariant indices, and VectorCalculations::transform over a vector is the only law the library gives. Each names itself and the reason. Not invented; the structural and componentwise TensorCalculations compute (Structured values, ValTensorQuantity).

Sequence Indexing and Collection Operations (KerML §9.3 SequenceFunctions, CollectionFunctions, ControlFunctions)

A KerML sequence is not a value of its own kind: every value is a sequence — of one element where it is a scalar, of none where it is null — which is how the library's own isEmpty is seq == null and how 1->size() is 1. The runtime takes that view of a value in runtime/collections.go elementsOf, so the operations agree with the library's definitions for a scalar and for null as well as for a sequence or a set.

The index is 1-based, verified against the vendored declaration rather than assumed: SequenceFunctions::'#' declares in index: Positive[1] and SequenceFunctions::head is defined as seq#(1), last as seq#(size(seq)) and subsequence as (startIndex..endIndex)->collect {in i; seq#(i)}. An index of 0 is therefore not a position, and is reported rather than read as the first element.

The library declares each operation with a body — size recursively as if isEmpty(seq)? 0 else size(tail(seq)) + 1 — but that body is the specification of the operation, not the way to compute it, so a name denoting the library declaration dispatches to the implementation while a model's own declaration of that name is still evaluated from its own body.

The three notations a model can write an operation in — the collect/select notation (xs.{in x; …}, xs.?{in x; …}), the receiver form (xs->collect {…}, xs->size()) and the plain call (size(xs), SequenceFunctions::size(xs)) — all reach one implementation per operation, so they cannot drift apart.

Semantic Rule Implementation Test Case Status
seq#(i) is the i-th element counting from 1 (SequenceFunctions::'#', in index: Positive[1]), and an index of 0, a negative index or one past the end is a typed error rather than an empty or zero value runtime/collections.go evalSequenceIndex, elementAt, indexOf; runtime/quantity.go evalIndexExpr (non-bracket arm) runtime/collections_test.go TestSequenceIndexing, TestSequenceIndexingErrors; conformance calc_sequence_index, calc_sequence_index_out_of_range, calc_sequence_index_zero, calc_sequence_index_non_integer; robustness_test.go:sequence_index_names_no_position ✅ Faithful
The index and the quantity expression share one AST node and are told apart by the notation that produced them (ast.IndexExpr.Bracket), so 5 [m] is a quantity and xs#(1) is an element parser/expr.go (Hash and LBracket arms), runtime/quantity.go evalIndexExpr tests/parser/testdata/parse/quantity_expression.golden, parse/collection_operations.golden; runtime/collections_test.go TestSequenceIndexKeepsQuantityForm; conformance calc_sequence_index_and_quantity_form; parser/negative_test.go (index_no_paren, index_bracket_empty) ✅ Faithful
An index that is not one whole number (a Real, a Boolean, a string, a collection) is a typed error, statically where it is written as a literal and at evaluation otherwise passes/typecheck_expr.go inferIndex (the index conforms to Integer, a literal 0 and a literal past a written sequence's length; a whole number a model counts with is a position or not depending on its value, so an Integer-typed index is checked at evaluation rather than reported for not being declared Natural; a Real-typed or Rational-typed index is reported where it is written); runtime/collections.go indexOf (by value, through Value.WholeNumber: an index that reaches evaluation as the real 2.0 names the second element, one that is 1.5 names none and is not truncated) passes/typecheck_index_test.go TestIndexNonIntegerIndexReported, TestIndexZeroReported, TestIndexPastWrittenSequenceReported, TestIndexTypedIntegerNotReported, TestIndexByLoopVariableNotReported; runtime/collections_test.go TestSequenceIndexingErrors, runtime/value_conformance_test.go:TestSequenceIndexAcceptsAWholeValuedReal ✅ Faithful
collect answers the mapper's result for each element, in order, with the parameter the body itself declares bound to the element and the scope the body was written in still visible runtime/collections.go builtinControlCollect, applyBody; runtime/eval.go evalCollectExpr, evalCollectionNotation runtime/collections_test.go TestCollectionResults; conformance calc_collect_over_sequence, calc_collect_names_outer_variable, calc_nested_collection_operations ✅ Faithful
select/reject/selectOne/forAll/exists require the Boolean[1] result their expr parameter declares, and a selector answering anything else is a typed error rather than a dropped element runtime/collections.go filter, quantify, applyPredicate runtime/collections_test.go TestCollectionResults, TestCollectionOperationErrors; conformance calc_select_over_sequence, calc_select_predicate_not_boolean; robustness_test.go:select_predicate_is_not_a_condition ✅ Faithful
A body called with a number of arguments it declares no parameters for is a typed error (ErrBodyArity), never a call with a parameter left unbound runtime/collections.go bodyOf runtime/collections_test.go TestCollectionOperationErrors; conformance calc_collection_body_wrong_arity; robustness_test.go:collection_body_of_the_wrong_arity; parser/negative_test.go (body_param_no_name) ✅ Faithful
SequenceFunctions: #, size, isEmpty, notEmpty, includes, includesOnly, excludes, equals, same, union, intersection, including, includingAt, excluding, subsequence, excludingAt, head, tail, last — each computing what the vendored body specifies, except includingAt (below), equals by value and same by identity runtime/collections.go, registered in runtime/builtins.go runtime/collections_test.go TestCollectionResults, TestCollectionScalarResults; conformance calc_collection_aggregators ✅ Faithful, except the out-of-range endpoints of subsequence/excludingAt (see below)
includingAt inserts the values before the 1-based index, shifting the tail right, so the result is longer than the input by the values inserted; index == size + 1 appends, and any other index outside 1..size + 1 is a typed error (ErrIndexOutOfRange) runtime/collections.go builtinSequenceIncludingAt, registered in runtime/builtins.go runtime/collections_test.go TestCollectionResults, TestCollectionOperationErrors, robustness_test.go:testNumericLibraryCallThatHasNoValue; conformance calc_sequence_including_at, calc_sequence_including_at_appends, calc_sequence_including_at_out_of_range, pilot-exec-diff w6d:including-at, :including-at-appends, :including-at-out-of-range (the reference throws IndexOutOfBoundsException out of the vendored body for all three, the valid insertion included) ⚠️ Approximate: the vendored body drops the element at index instead of shifting it right, which would leave addAt removing and the library with no insertion. Insertion is implemented on the maintainer's ruling and the vendored body is recorded as an OMG source bug (omg-issues.md)
An endpoint of subsequence or excludingAt that is outside the sequence is a typed error (ErrIndexOutOfRange), while an empty range inside it is the empty sequence, which is how tail is subsequence(seq, 2) of a one-element sequence runtime/collections.go builtinSequenceSubsequence, builtinSequenceExcludingAt runtime/collections_test.go TestCollectionOperationErrors, TestCollectionResults, pilot-exec-diff w6d:excluding-at, :subsequence (both agree), :excluding-at-out-of-range, :subsequence-out-of-range (the reference fails too), :subsequence-empty-range ✅ Faithful (the in-range results match the pinned artifact and it fails on both out-of-range endpoints rather than answering the vendored truncation — by exception out of the library body, where this reports ErrIndexOutOfRange. The empty-range half is unrefereeable: the reference emits nothing, which it cannot distinguish from the empty sequence this answers)
CollectionFunctions: size, isEmpty, notEmpty, contains, containsAll, head, tail, last, # over a collection's elements, a set included runtime/collections.go, runtime/builtins.go runtime/collections_test.go TestCollectionScalarResults, TestCollectionOperationsOverSets ✅ Faithful
A Collections::Collection whose library redefinition of elements is unique and not ordered holds a set (ValSet), the library's own kind: Set ("unique and unordered", Collections.kerml:104-108), UniqueCollection ("unique and not necessarily ordered", :39-48) and Map (unique KeyValuePairs, :142-152). Bag inherits the root's nonunique (:22, :97-101) and stays a sequence, as does every OrderedCollection (:30-36) — Array, List, OrderedSet (ordered, :111-121), OrderedMap (:162-175) — and any feature a model declares ordered or nonunique itself. A set holds each member once ((3, 1, 2, 2, 3) is three members, (1, 2, 3) given already distinct is the same three, () the empty set), so size answers 3 and isEmpty reads the count; a value written or bound to such a feature is admitted as a set, and a set flowing into a feature that holds a sequence (ordered, nonunique, or a plain Integer[0..*]) becomes the sequence of its members in canonical order runtime/set_feature.go holdsSet, declaredOrderedOrNonunique, collectionOf, declaredCollection; shape.go EffectiveFeature.HoldsSet; instance.go admitted, materializeIntrinsic; subsetting.go (optional subsetters); value.go Set, NewSet, Set.Add, Set.Size conformance library_set_elements, library_set_elements_already_distinct, library_set_elements_empty, library_unique_collection_elements, library_map_elements, library_bag_elements, library_ordered_set_elements, library_set_operations; runtime/set_feature_test.go:TestCollectionElementsHoldTheLibraryKind, :TestSetFlowsIntoDeclaredCollections, :TestCollectionFunctionsOverCollectionObjects ✅ Faithful
Two sets are equal when their members are, in whatever order either was given (CollectionFunctions::'==' is col1.elements->equals(col2.elements), and a set's elements have no order to compare); contains and containsAll are membership; == reads a set against a sequence as its canonical sequence (an ordered context), so a set's elements equal a sequence only when the sequence lists the members in canonical order, while as a set's member a set is never the sequence of its members; a Set is never equal to a Bag or OrderedSet holding the same values, which are sequences runtime/eval.go equalValues, valueEqual (ValSet arm); runtime/collections.go builtinCollectionEquals, elementsOf; value.go Set.Equal, Set.Contains conformance library_set_operations (equalRegardlessOfOrder, elementsEqualRegardlessOfOrder, membership, allMembers, emptiness, notASequence); runtime/set_feature_test.go:TestSetAgainstSequenceComparesCanonically, :TestSetIsNotASequenceAsAMember, :TestCollectionFunctionsOverCollectionObjects; grpc/convert_set_tensor_test.go:TestMalformedSetsAreRejected ✅ Faithful
A set has no order of its own, so every operation that walks its members in order — collect, select, head, tail, #, == against a sequence, a trace rendering it, a write into a sequence-holding feature, the wire — sees them in one canonical order: by class (null, Booleans with false first, numbers ascending, complex numbers by real then imaginary part, strings lexicographically, quantities by dimension then magnitude, enumeration literals by declaration, objects by identity, then every other kind), each value placed by the value it equals whichever kind carries it (a complex number on the real axis among the numbers, an empty sequence or set with null), and within a class by that order, then by kind, then by contents — elements, components, unit reduction, the calc, object and run a function is a value of — so exactly the valueEqual values share a position. The order is total, so equal sets enumerate alike and a trace over a set is the same golden however the set was written runtime/set_order.go canonicalLess, canonicalClass; value.go Set.Elements (sorted once, cached); trace.go FormatTraceValue (ValSet, the same enumeration) conformance calc_set_consumed_by_ordered_operations and its trace golden; runtime/set_feature_test.go:TestCanonicalOrderIsTotal, :TestSameNamedLiteralsOrderByDeclaration, :TestFunctionsRenderedAlikeOrderByIdentity, :TestLikeRenderedValuesOrderByContents, :TestEqualSetsWithOtherRepresentativesEnumerateAlike, :TestSetRendersCanonically; runtime/collections_test.go:TestCollectionOperationsOverSets; runtime/statements_test.go:TestForElementsOrder ✅ Faithful — the order is this runtime's documented rule, since the library defines none; it is not a claim about the specification
What the library declares ordered stays a sequence: every SequenceFunctions result is Anything[0..*] ordered nonunique — union, intersection, including, includingAt, excluding included (SequenceFunctions.kerml:48-63) — so union(s.elements, t.elements) over two Sets is the ordered concatenation of their canonical members, repeats kept, not a set; (s.elements, s.elements) likewise lists each member twice. The Kernel Function Library declares no distinct function, so none is invented: the members of a sequence, each once, are what a Set's elements hold runtime/collections.go (SequenceFunctions builtins over elementsOf) conformance library_set_sequence_functions, library_set_operations (ordered, repeatable, plain) ✅ Faithful
A multi-valued feature not declared nonunique is unique: KerML 1.0 §7.3.4.2 ("The default is that the feature is unique"), §8.3.3.3.4 Feature::isUnique ("whether or not values for this Feature must have no duplicates", = true in the §8.3.3.3.1 overview), the concrete syntax MultiplicityPart (§8.2.4.3.1, KerML.xtext Nonunique, the one production that sets isUnique = false), and §8.4.3.4 Features Semantics item 6 ("If a Feature is unique, there are no values with the same markings") beside item 5 ("the multiplicity of a Feature includes the cardinality of its values, counting duplicates"). So OrderedSet::elements and OrderedMap::elements (ordered under UniqueCollection), a user's attribute xs : Integer[*] ordered and a plain attribute xs : Integer[*] alike hold no two equal values, and their order stays the sequence written. A repeat is refused, never deduplicated: dropping a value would bind the feature to something unequal to its expression and undercount its multiplicity. Where the repeat is constant-decidable in a literal sequence (= (1, 1), (1, 1.0), ("a", "b", "a"), (Color::red, …, Color::red)) the checker reports type.expr 1 (an Integer) is written at positions 1 and 2 of a unique feature, after and only where the type, dimension and count of the value have passed; every other write — an initializer reaching through a feature, a body-local declaration's initializer, an assign, a binding, a calc argument or result, a compiled calc — is the typed ErrUniquenessViolation with the same wording, after ErrMultiplicityViolation and the type check, and leaves the feature's prior value as it was. nonunique opts out and keeps every repeat; a feature held as a set (the row above) is exempt, uniqueness being the set's own definition; a value whose equality cannot be decided ((xs, xs) before evaluation) is silent at check time. Equality is the runtime's valueEqual — the equality ValSet uses — so 2 [kg] repeats 2000 [g], and a vector, tensor, instance or enumeration literal repeats itself. Pilot: the pinned validator names no value-level uniqueness constraint (its census has only validateSubsettingUniquenessConformance, declaration-level, validation-constraints.md) and its evaluator answers 1, 1, 2 for OrderedSet { :>> elements = (1, 1, 2); }, so it neither confirms nor refutes the refusal; the specification text is the authority semantics/uniqueness.go Model.IsUnique, DeclaredNonunique, UniquenessViolation; passes/typecheck_value.go checkValueUniqueness, constElement; runtime/uniqueness.go uniquenessRefusal, declaredUniquenessRefusal, multiValued; runtime/write_conformance.go checkBodyDeclaration (a body-local declaration's initial value); runtime/instance.go checkAdmits; runtime/write_conformance.go writeTarget.unique; runtime/shape.go EffectiveFeature.Unique; codegen/ir.go Checked.Unique, Param.Unique, emit_go_seq.go sysmlUnique, emit_c_seq.go sysml_unique_* passes/integration_test.go:TestPassesGoldenUniqueValues (testdata/passes/unique_values.sysml: xs : Integer[*] = (1, 1) reported, nonunique = (1, 1) accepted, Bag/Set silent), passes/typecheck_value_test.go; conformance library_ordered_set_elements_repeated (size of a valid OrderedSet is 3, a List keeps the repeat), library_ordered_map_elements_repeated (a Map drops the pair an OrderedMap refuses), value_unique_user_features (plain, ordered, nonunique, Real/Integer, quantity in two units, enumeration literal, string, dynamic), calc_unique_ordered_keeps_order + trace golden; runtime/robustness_test.go:testWriteOfARepeatedValueLeavesTheFeature (typed error, prior value kept, multiplicity and type first, nonunique, calc and action paths, a unique local initialized from a nonunique parameter); runtime/set_feature_test.go:TestSetBoundaryUnderUniqueness; semantics/uniqueness_test.go (IsUnique defaults, library collections, cycles, cache); repl/compile_test.go Seq::UniqI/UniqR/UniqB/UniqAs/UniqLoc/UniqLocInit/UniqLocFree (Go and C agree with the interpreter, 0.0/-0.0 one value) ✅ Faithful (self-assessed against the specification text; the pilot decides no value-level case)
Uniqueness through redefinition and subsetting: a fresh or subsetting feature stating neither nonunique nor ordered is unique (§7.3.4.4: "if the subsetted feature is non-unique, then the subsetting feature will still be unique by default"), and a redefinition stating neither takes the uniqueness of what it redefines, the conjunction over every redefined feature (§7.3.4.5: the redefining and redefined feature have "the same" values, so a restriction on one is a restriction on the other, as this runtime already inherits type, multiplicity and ordering through :>>), so :>> num = (0, 0, 1) over TensorQuantityValue::num (ordered nonunique, Quantities.sysml:29) and :>> mRefs = (mm, mm, mm) over TensorMeasurementReference::mRefs (nonunique, MeasurementReferences.sysml:59) stay legal, and Bag { :>> elements = (3, 1, 2, 2); } keeps its repeats. Library errata, read the other way: Collections::UniqueCollection::elements, Map::elements, OrderedSet::elements and OrderedMap::elements redefine the nonunique Collection::elements stating no keyword, which under redefinition inheritance would make every Set and Map nonunique; each carries the library's own note Redefinition of 'elements' is unique by default (Collections.kerml:47,120,151,174), so these four are read as unique and everything under them (Set, OrderedSet, Map, OrderedMap, and a model's :>> elements in one) inherits it. Recorded in omg-issues.md. Pilot evidence: the pinned validator models both '3dVectorQuantityValue'::num :>> num and CartesianSpatial3dCoordinateFrame::mRefs :>> mRefs as unique — redefining either nonunique is validateSubsettingUniquenessConformance (Subsetting/redefining feature cannot be nonunique if subsetted/redefined feature is unique) — while the library's own mRefs default (SI::m, SI::m, SI::m) and every (0, 0, 1) direction vector hold repeats, and its evaluator accepts them: the strict-default reading is unsatisfiable for the library the pilot ships, which is why redefinition inherits here semantics/uniqueness.go Model.IsUnique, isUnique, uniqueByLibraryNote, directRedefinedFeatures semantics/uniqueness_test.go:TestIsUniqueDeclarationAndDefault, :TestIsUniqueInheritsThroughRedefinition, :TestIsUniqueLibraryCollections, :TestIsUniqueTerminatesOnRedefinitionCycle; conformance library_ordered_set_elements_repeated, library_ordered_map_elements_repeated, library_unique_collection_elements, library_map_elements, library_bag_elements, instance_library_geometry_*, the ISQ vector and coordinate-frame conformance fixtures (unchanged) ⚠️ Approximate (self-assessed: redefinition inherits where §7.3.4.4's subsetting text would default to unique; four library declarations are read by their note against their text; the pilot decides only the declaration-level constraint)
A set crosses gRPC as the set arm (ValueSet.elements, each a Value, listed in canonical order; an incoming set may list them in any order, and one that repeats a member is ErrSetElementRepeated), advertised as set_values; a service withholding the capability answers an unsupported null naming the value and refuses one sent to it with UNIMPLEMENTED, nested anywhere in the argument. A set holding a member with no wire form — no arm carries it, or the service withholds its arm — is withheld whole as an unsupported null naming the set and the member's reason, never sent with nulls in the members' places, which two members rendering alike would make a repeated member. It is not compiled natively: sysml -compile refuses a calc declaring or reading one with codegen.UnsupportedError (type Collections::Set is not Integer, Real or Boolean). It has no RDF literal form, as no value kind has one: the mapping writes the model,
never an evaluation, so a Set-, UniqueCollection- or Map-valued feature is the
standard expression tree valuing its elements, round tripping exactly with the source
text stripped, and the model read back holds sets equal in whatever order the members were
written grpc/convert.go setToProto, protoToSet, ErrSetElementRepeated; grpc/capability_response.go (set_values); codegen/compile.go UnsupportedError; export/rdf_expr.go grpc/convert_set_tensor_test.go:TestSetRoundTrip, :TestMalformedSetsAreRejected, :TestSetHoldingAMemberWithNoWireFormIsWithheldWhole, :TestSetAndTensorCapabilities, :TestValueCarriesSetAndTensor; repl/compile_test.go:TestCompileRefusesWhatItCannotCompile (SetParam, SetElements, SetLocal); export/set_tensor_rdf_test.go:TestSetAndTensorValuesRoundTripAsExpressions,
TestSetAndTensorGraphsAreStandardShaped, TestSetAndTensorStructuralPredicatesCarryTheRoundTrip,
TestSetOrderAndTensorShapeSurviveTheHop; the Values gRPC rows below ✅ Faithful (the native and RDF refusals are typed and documented, not layouts)
CollectionFunctions::'array#'(arr, indexes) and BaseFunctions::'#' with several indexes select from a Collections::Array value (ValArray, Structured values under KerML Function Library) by one Positive index per dimension in the row-major order Collections.kerml documents — 'array#'(a, (2, 1)) over dimensions = (2, 3) is the fourth element, as the pinned pilot evaluator answers; a vector or vector quantity is indexed as the one-dimensional Array it specializes; a rank-0 array with no index is null, as the library body says. The count of indexes must be the array's rank (ErrMultiplicityViolation, naming arr.rank), each index within 1..dimensions#(i) (ErrIndexOutOfRange, naming the dimension and its range), and a usage whose elements do not fill its dimensions is ErrMultiplicityViolation naming flattenedSize; rank, flattenedSize, dimensions and elements of the value read out of it runtime/collections.go builtinArrayIndex, arrayIndex, builtinBaseIndex; runtime/array.go Array.at, structuredFeature, Context.arrayOfObject, Context.declaredArrayValue conformance calc_library_array_value, calc_library_array_features, calc_library_array_index, calc_library_array_index_rank_mismatch, calc_library_array_index_out_of_range, calc_library_array_empty_rank_zero, calc_library_array_specialization_members, calc_library_array_specialization_through_calc, calc_library_base_index_many, calc_library_base_index_many_sequence; robustness base_index_with_several_indexes, numeric_library_call_that_has_no_value ('array#' over a flat sequence); repl/runtime_commands_test.go:TestEvalArrayShapedByItsFeatures; TestEveryValueKindIsDispatched ✅ Faithful
An operation over an empty collection answers the empty collection and never calls its body, since there is no element to call it with runtime/collections.go elementsOf (an empty collection yields no elements) conformance calc_collection_ops_over_empty; runtime/collections_test.go TestCollectionResults ✅ Faithful
ControlFunctions: collect, select, selectOne, reject, reduce, forAll, exists, allTrue, anyTrue, minimize, maximize runtime/collections.go, runtime/builtins.go runtime/collections_test.go TestCollectionResults, TestCollectionScalarResults, TestCollectionOperationErrors ✅ Faithful
NumericalFunctions::sum/product and the specializations that fix the identity of an empty aggregation (sum0(collection, 0), product1(collection, 1)), keeping the elements' kind: Integers sum to an Integer, a Real anywhere makes the result a Real, and RealFunctions/RationalFunctions sum/product are Real from their identity on (sum0(collection, 0.0)), so an empty one is 0.0; an overflowing sum or product is reported rather than wrapped runtime/collections.go aggregate, foldNumeric, builtinRealSum runtime/collections_test.go TestCollectionScalarResults; conformance calc_empty_collection_aggregation ✅ Faithful
A collection of quantities aggregates to a quantity, in the unit of its first element and converting the rest, as the binary operator does; mixing a bare number with a measured value reports incommensurable units runtime/collections.go aggregate/aggregateQuantities, quantity.go addQuantities/scaleQuantities conformance cubesat_mass_rollup; runtime/collections_test.go:TestAggregateQuantities, pilot-exec-diff w6d:sum-quantities, :sum-quantities-mixed-units, :quantity-add, :add-bare-to-measured, :sum-empty (agrees, 0) ⚠️ Approximate (self-assessed: asked, but not answered — the pinned artifact evaluates no quantity arithmetic at all, answering an InvocationExpression/OperatorExpression for every case above, so the unit of the first element is still a choice with no reference verdict. An empty collection has no element to take a unit from, so it aggregates to the zero of its declared kind in that kind's coherent SI unit, or to the number 0/1 where the declaration fixes no dimension — the empty-aggregate row beside the QuantityCalculations rows above)
The unqualified, qualified and receiver (->) forms of an operation are one implementation, so (1,2,3)->size(), size((1,2,3)) and SequenceFunctions::size((1,2,3)) cannot disagree; a name the model itself declares still resolves to that declaration, and a bare size denotes the library's only where SequenceFunctions is imported runtime/builtins.go Context.builtinFor; runtime/eval.go evalInvocation (receiver prepended as the first argument) runtime/collections_test.go TestCollectionScalarResults; conformance calc_collection_receiver_form ✅ Faithful
A sequence is flat: an element of a sequence expression that is itself a collection contributes its elements, so (xs, ys) is xs->union(ys) — which is how the library defines union, as the sequence expression (seq1, seq2) — and a mapper answering several values contributes them all runtime/eval.go evalSequenceExpr; runtime/collections.go builtinControlCollect; passes/typecheck_expr.go writtenLength (a written length is knowable only where every element is a literal, so a literal index past a sequence of names is left to evaluation) runtime/collections_test.go TestSequenceExpressionsAreFlat; conformance calc_sequence_expression_is_flat ✅ Faithful
A receiver binds by position, so a call written with both a receiver and named arguments (x->f(a = 1)) states no parameter for the receiver and is a typed error (ErrReceiverWithNamedArgs) rather than a call the receiver is dropped from runtime/eval.go evalInvocation; passes/typecheck_expr.go checkArguments runtime/collections_test.go TestCollectionOperationErrors, TestReceiverWithNamedArgumentsIsReported; passes/typecheck_expr_test.go TestExprInvocationReceiverWithNamedArguments ✅ Faithful
A collection operation is an expression wherever an expression is allowed, including inside a calc body's while and for loops runtime/collections.go, runtime/action_statements.go conformance calc_collection_ops_in_for_loop, calc_collection_ops_in_while_loop ✅ Faithful
Every operation is bounded by the evaluation step budget, since each call of its body spends steps runtime/eval.go Context.step robustness_test.go:collection_operation_step_budget ✅ Faithful
A materialized element is bounded on its own, since it is memory the collection keeps rather than work a step does: every path that adds one to a sequence — a range, a sequence literal, ->collect, union/intersection/including/excluding/subsequence/tail/select — charges the element budget (OPENSYSML_MAX_ELEMENTS, default 1000000, ~104MB of Values) and reports ErrElementLimitExceeded, not the step limit. The count is what an evaluation holds, not what a run produced: a statement, and an evaluation outside a body alike (beginStep), releases the elements it materialized, so a loop or a long run building a small collection each step is bounded by its peak rather than its total, while a collection kept across statements had to be materialized in one of them and so was charged in full; a model-level read that stops short of materializing an open collection holds nothing and gives back what collecting its subsetters' values charged runtime/context.go chargeElements/elementScope/beginStep, runtime/instance.go materializeIntrinsic, runtime/statements.go statement, runtime/collections.go newSequence, runtime/range.go rangeSequence, runtime/eval.go evalSequenceExpr; budget from budget.go element_budget_test.go:TestElementBudgetBoundsEveryMaterialization, :TestElementBudgetIsNotTheStepBudget, :TestElementBudgetCountsElementsHeldNotProduced, :TestElementBudgetIsReleasedByEveryStep, :TestElementBudgetIsReleasedByOpenReads, :TestElementBudgetIsPerRun, robustness_test.go:collection_spends_the_element_budget, grpc/budget_test.go:TestNewServiceResolvesBudgets ✅ Faithful
An activation ends with the body execution it belongs to, so what the calc usages read in a body computed is discarded when that execution ends rather than held for the whole run runtime/statements.go finish/enterActivation, runtime/action_statements.go executeBody, runtime/invoke_calc.go runCalcBody action_activation_test.go:TestActionBodyActivationEndsWithTheBody, calc_usage_body_local_test.go ✅ Faithful
A calc usage declared among a state machine's members binds its inputs from the values the machine has reached, as one in a calc's or an action's body does, so a guard reading it is answered over the running attribute rather than over what it was declared with runtime/calc_usage.go enclosedByBehaviorBody, runtime/invoke_calc.go isStateSymbol conformance state_guard_reads_calc_usage ✅ Faithful
An evaluation outside a body — a decision guard, an inline node expression, a transition guard, change condition or duration, an attribute default, a feature value default, an action argument, a constraint or requirement check — is a scope of its own, so what a calc usage answers it, and the elements a collection it evaluates materializes, live no longer than that step: the next guard reads the usage again over the values the step before it assigned. A read through a part's feature chain belongs to the evaluation making it and shares its activation runtime/eval.go beginStep, runtime/state_executor.go evalStep, runtime/action_executor.go stepDecisionNode/stepActionExecutionNode/initializeAttributes, runtime/condition.go evaluateConditions, runtime/calc_usage.go calcUsageMemberValue conformance action_guard_reads_calc_usage; calc_usage_step_test.go:TestDecisionGuardReadsCalcUsagePerStep, :TestDecisionGuardsShareOneCalcUsageEvaluation, :TestPartChainReadBelongsToTheReadingActivation ✅ Faithful
A failing expression of literals alone is answered at the prompt with the failure itself, so sysml -e "(1,2,3)#(0)" reports the index rather than "no declarations loaded" repl/meta.go tryEvalLiteral, isLiteralAnswerError repl/runtime_commands_test.go TestEvalReportsTheAnswerOfALiteralExpressionThatFails ✅ Faithful
A name the session declares is answered by that declaration, so the prompt's literal pass declines an expression using one rather than letting a library operation of the same unqualified name stand in for it repl/meta.go tryEvalLiteral, declaresANameIn repl/runtime_commands_test.go TestEvalPrefersASessionDeclarationOverALibraryOperation ✅ Faithful

⚠️ An untyped body parameter takes its type from the element type of whatever the operand turns out to hold, which the expression checker does not track: an expression over the parameter (xs.?{in e; e + 1}) therefore has no static type, and its selector is checked at evaluation rather than where it is written; a body over a typed parameter (xs.{in e : Real; e + 1}) types the collect by its result (the collection-operation row under Static Expression Type Checking). Statically the checker reports what it can know — a selector whose result type is known and is not Boolean, an index that is no whole number, a literal index of 0 or past a sequence written out (passes/typecheck_expr.go inferIndex, inferSelect) — and the runtime checks the rest, so no wrong answer results from what is left unchecked.

Found, not implemented — declared collection operations this runtime does not evaluate. Each is a typed unresolved reference or unsupported error, never a wrong answer:

Not implemented Why
An Array written as a sequence (attribute m : Matrix = (1, 2, 3, 4)) A sequence of numbers is not an Array — it has no dimensions — and binding one to an Array-typed usage is the type mismatch it always was; the library's own shape, dimensions and elements redefined on the usage, is the one way to write an Array value, and there is no literal notation for one in the language to read.
A reducer named rather than written (->reduce min, as the library's own minimize is defined) A calc held as a value is a runtime value (Value.FunctionValue) and a body applying one (->minimize {in x; eval(x)}) evaluates, so the library's minimize/maximize bodies run as written; but a Kernel function named bare (min, max, +) is still not read as a value, reduce takes the body expression form (->reduce {in a; in b; …}), and minimize/maximize are implemented directly rather than through reduce min. A named reducer is reported as a type error, not read as a body.
SequenceFunctions::add/addAt/remove/removeAt, CollectionFunctions mutators These are behaviors, not functions: they declare an inout sequence, so they need mutable accumulation the language layer does not have. Deliberately out of scope.
at, first, reverse Not declared by the Kernel Function Library at all (head, #(1) and last are the declared spellings). Not implemented rather than invented.

OpenSysML Math Extension Library (non-normative)

The OMG Kernel Function Library declares no exponential, no logarithm, no two-argument arctangent, no ceiling and no truncating Integer quotient: RealFunctions has sqrt/floor/round/abs/max/min/'**'/'^', TrigFunctions has sin/cos/tan/cot/arcsin/arccos/arctan, IntegerFunctions::'/' answers a Rational, and that is all. The vendored OMG files stay byte-identical, so the missing signatures are declared in a clearly non-normative OpenSysML extension instead: internal/workspace/libs/stdlib/OpenSysML Libraries/OpenSysMLMathFunctions.kerml. It is bundled by the same embed.FS as the vendored tree and enters the same gates — the current bundled-library gate reports 103/103 clean, including this, the DocumentQueries, IdentityMetadata, DiagramLayout, OOSEM, MOSA, StateSpaceIntegration, Stochastic and RandomFunctions extensions. It is OpenSysML code under Apache 2.0, not OMG code under EPL-2.0; internal/workspace/libs/stdlib/NOTICE carves the subdirectory out of the OMG notice.

Reachability. A model writes import OpenSysMLMathFunctions::*; (or calls OpenSysMLMathFunctions::exp(x) qualified); both resolve like any other library package, with no diagnostic. A bare exp(x) with no import is reported unresolved reference: exp and does not evaluate: the call fails with the same unresolved-reference error, naming OpenSysMLMathFunctions::exp as the declaration an import would make visible, rather than being answered by a declaration the model never made visible. A bare sqrt(x) is governed by the same rule — the OMG function libraries are not implicitly imported either.

Semantic Rule Implementation Test Case Status
exp(x) — e raised to the power x runtime/library_functions.go (math.Exp) TestLibraryFunctionValues ✅ Faithful
ln(x) — natural logarithm, defined for x > 0.0 runtime/library_functions.go naturalLog TestLibraryFunctionValues, TestLibraryFunctionErrors ✅ Faithful
log(x, base) — logarithm to an explicit base, so base 10 and base e are never confused; base 10 and base 2 use math.Log10/math.Log2, which are exact where the ratio of logarithms is not runtime/library_functions.go logToBase TestLibraryFunctionValues, TestLibraryFunctionErrors ✅ Faithful
atan2(y, x) — full-quadrant angle, parameters ordered as in IEEE 754 and math.Atan2 runtime/library_functions.go atan2Real TestLibraryFunctionValues, TestLibraryFunctionAtan2NamedArguments ✅ Faithful
ceiling(x) — the least Integer not less than x, the counterpart of RealFunctions::floor: ceiling(2.1) is 3, ceiling(-2.9) is -2, ceiling(-9223372036854775808.0) is the least Integer (which -floor(-x) cannot reach, its negation being 2⁶³) and a whole Real at or beyond 2⁶³ or below −2⁶³ is ErrArithmeticOverflow, never a wrapped int64; a non-number is ErrTypeMismatch. This is what the v1 migration writes for a script's Math.ceil runtime/library_functions.go ceilingToInteger TestLibraryFunctionValues, TestLibraryFunctionErrors, TestRuntimeRobustnessCeiling (ceiling_beyond_the_integer_range, ceiling_of_the_least_integer, ceiling_of_a_boolean); conformance calc_integer_ceiling; migrate/opaque_migration_test.go:TestTranslatedRoundingsStopAtTheIntegerRange ✅ Faithful
quotient(x, y) — the Integer quotient of two Integers truncated toward zero, so quotient(x, y) * y + x % y == x for every y other than 0: quotient(7, 2) is 3, quotient(-7, 2) is -3, quotient(7, -2) is -3, exact over the whole Integer range where / answers the rounded binary64 (quotient(27021597764222979, 3) is 9007199254740993, which / cannot hold); a Real operand is ErrTypeMismatch. This is the quotient a Java or C / computes over two whole numbers, and what the v1 migration writes for a Java body's / runtime/library_functions.go integerQuotient TestLibraryFunctionValues, TestLibraryFunctionErrors; conformance calc_integer_quotient ✅ Faithful
quotient(x, 0) is ErrDivisionByZero; quotient(-9223372036854775808, -1), the one pair whose quotient (2⁶³) no Integer holds, is ErrArithmeticOverflow naming the pair, never a wrapped -9223372036854775808 runtime/library_functions.go integerQuotient TestLibraryFunctionErrors, TestRuntimeRobustnessIntegerQuotient (quotient_by_zero, quotient_of_the_least_integer_by_minus_one, quotient_of_a_real) ✅ Faithful
ln(0.0), ln(-1.0), log(x, 1.0), log(-1.0, 10.0), atan2(0.0, 0.0) report a domain error; exp beyond the Real range reports an overflow runtime/library_functions.go TestLibraryFunctionErrors, TestRuntimeRobustness/extension_library_function_outside_its_domain ✅ Faithful
The shipped declarations and the registered implementations cannot drift (names, parameter names, parameter order) runtime/library_functions.go registry TestOpenSysMLMathFunctionsMatchTheShippedDeclarations ✅ Faithful
Evaluable from a calc def body under import OpenSysMLMathFunctions::*; runtime/invoke_calc.go calc_opensysml_math_functions.sysml + golden trace ✅ Faithful
An unqualified call the model imports no declaration of fails with the unresolved-reference error the checker reports, naming the qualified declaration an import would make visible, so the diagnostic and the behavior agree instead of contradicting each other runtime/eval.go invocationTarget, unresolvedInvocation runtime/library_functions_test.go:TestLibraryFunctionUnqualifiedNames, TestRuntimeRobustness/calc_calls_an_unimported_extension_function, lsp/library_invocation_test.go:TestPublishDiagnosticsKeepsExtensionFunctionImportGated ✅ Faithful
The function listing covers every function the build implements, each with the package an import must name for its unqualified name to resolve, so a working function is advertised neither as unsupported nor as callable bare runtime/builtin_names.go Builtin.Package, Builtins, read by repl/discover.go doBuiltins and repl/complete.go runtime/library_functions_test.go:TestBuiltinsListEveryFunctionWithItsPackage, repl/discover_test.go:TestBuiltinsListsAnExtensionFunctionWithItsImport ✅ Faithful

Stochastic Execution (OpenSysML Libraries/Stochastic.sysml, OpenSysML Libraries/RandomFunctions.kerml; an OpenSysML extension, non-normative)

SysML v2 has no notation for a probability on a succession, a random-valued function, or a run repeated to measure a distribution; KerML gives a decision node with several holding guards no rule for which succession is taken, and the runtime treats that as a scheduling choice point (see Control-Node Successions). The constructs below are OpenSysML extensions declared in two bundled non-normative libraries, Stochastic (metadata def Probability { attribute p : Real; }) and RandomFunctions (uniform, uniformInteger, triangular, normal), under the same embed.FS and gates as OpenSysMLMathFunctions; the vendored OMG files are untouched, and a model using them stays standard SysML v2 that another tool reads as ordinary metadata and calls to functions it does not know. Modeled randomness is not scheduling nondeterminism: the token, write, region and due orders the spec leaves open stay unweighted choice points under every policy, and only the weights and draws a model states are random. None of this is spec compliance; it is the approximation Cameo Simulation Toolkit's «Probability» edges and random DurationConstraints need to come across sysml -convert sysml.

Semantic Rule Implementation Test Case Status
@Probability { p = w; } on a succession out of a decision node weights it; the lowered ActionEdge carries the expression losslessly and its constant value where it has one (a literal or arithmetic over literals, folded by semantics.EvalConst). Every succession out of one decision is weighted or none is; a weight is in [0, 1]; constant weights out of one decision sum to 1 within 1e-6; a weight that is no constant — a feature the action or its performer holds (p = pFast;), or arithmetic over one (p = 1.0 - pFast;) — is kept as the expression and type-checked against Probability::p where it is written, so a String or Boolean feature is refused before anything runs; a Probability missing p, naming p twice, stating another feature, or binding p to something that is not a number, and a Probability on anything but a succession out of a decision or a state transition (an action body, a plain succession) is refused. Each is the typed ErrProbability at lowering, before anything runs. The flow among a case's steps (analysis, verification) is lowered with the same reader, so a weighted decision among subcases is weighted as an action's is; the exported action graph (modelform.GraphsOf) and the rendered behavior view carry each weight beside its edge passes/typecheck_metadata_body.go checkPrefixMetadata, markMetadataBindings, checkMetadataBinding (a body value bound to the restated feature as any value is: type, dimension, count, uniqueness); lower/probability.go Probability, ProbabilityTolerance, WeightInRange, ErrProbability, ProbabilityError, probabilityReader, checkProbabilities, checkConstantWeights; lower/action_graph.go ActionEdge.Probability, ToActionGraphWith, lowerSuccession; lower/calc_body.go CalcBodyWith, lower/case_body.go caseSteps; analysis/modelform/graphs_action.go EdgeForm.Probability; libs/stdlib/OpenSysML Libraries/Stochastic.sysml passes/typecheck_metadata_body_test.go:TestPrefixMetadataBodyValueMustConformToTheRestatedFeature, :TestMetadataUsageBodyValueMustConformToTheRestatedFeature, :TestMetadataBodyValueIsBoundByTheRestatedFeatureWhole; runtime/robustness_feature_weights_test.go:TestRuntimeRobustnessFeatureWeights/weight_of_no_numeric_type_is_refused_before_the_run; lower/probability_test.go:TestProbability_ReadOnEverySuccessionForm, :TestProbability_ArithmeticOverLiteralsIsAConstantWeight, :TestProbability_NonConstantWeightIsKeptAsExpression, :TestProbability_Refusals; runtime/robustness_test.go:TestRuntimeRobustness/weighted_decision_whose_weights_do_not_sum_to_one, /weighted_decision_with_a_weight_outside_zero_to_one, /decision_mixing_weighted_and_unweighted_successions; stochastic_weighted_case_steps.sysml, runtime/stochastic_test.go:TestWeightedCaseStepsKeepTheirWeights; analysis/modelform/graphs_test.go:TestGraphsActionCarriesTheEdgeProbabilities ⚠️ Extension (no SysML v2 semantics to be faithful to)
A weighted decision draws among the successions whose guards hold (an unguarded weighted succession holds outright; there is no else branch), each in proportion to its weight over the holding weights' total — a guarded-off branch's mass is redistributed, not lost — under seed:<n> or a model seed; under declared and reverse with no model seed it takes the most probable holding branch, the first declared on a tie, so an unseeded run stays deterministic. The weights read when the decision is reached — the whole distribution, a guard excluding a branch or not — are checked as lowering checks constants: one that is not finite or outside [0, 1], a set not summing to 1 within the tolerance, or a holding total of zero is the typed ErrBranchWeights naming the branch and its value, never a silent renormalization; a weight read from the performer (an action def nested in a part def reading the part's attribute) is the value the performing object holds, its redefinition included, so 1.0 and 0.0 decide the branch whatever the seed draws. Weighted decisions draw from the modeled stream under every draw policy: a fixed -draws policy resolves RandomFunctions calls and leaves the weighted pick to the seed, and to the most probable branch when no seed is set. explore enumerates the weighted branches and check searches them as a set, exactly as they treat an unweighted decision; the weights are kept on the witness runtime/action_choice.go ActionExecutor.chooseBranch, branchWeights; runtime/scheduler.go scheduler.chooseWeighted; runtime/modeled.go weightedPick, mostProbable, checkWeights, ErrBranchWeights; runtime/action_choice.go branchWeights; runtime/choice.go ChoicePoint.Weights, Weighted; runtime/explore.go exploreSlot.asChoice stochastic_weighted_decision_seeded.sysml + .trace.golden + .check.expected.json, stochastic_most_probable_unseeded.sysml + .trace.golden; stochastic_weights_read_from_performer.sysml; runtime/robustness_feature_weights_test.go:TestRuntimeRobustnessFeatureWeights/weights_read_must_sum_to_one, /weight_read_outside_the_unit_interval_is_refused, /weight_read_from_the_performer_is_refused_out_of_range; runtime/draw_policy_test.go:TestWeightedDecisionsDrawTheSameUnderEveryPolicy; runtime/robustness_draw_policy_test.go:TestRuntimeRobustnessDrawPolicy/fixed_policy_leaves_unseeded_decisions_most_probable; runtime/stochastic_test.go:TestWeightedDecisionDrawsUnderASeed, :TestWeightedDecisionDrawsAmongTheHoldingBranches, :TestWeightedDecisionTakesTheMostProbableBranchUnseeded, :TestExploreEnumeratesWeightedBranches, :TestCheckSearchesWeightedBranchesAsASet ⚠️ Extension
RandomFunctions::uniform(lo, hi) (Real on [lo, hi)), uniformInteger(lo, hi) (Integer, both ends inclusive), triangular(lo, mode, hi) (lo <= mode <= hi, lo < hi) and normal(mean, sd) (sd >= 0; sd = 0 is the mean; a draw so far out that it would overflow to infinity is drawn again from the same stream, so every draw is a finite Real its witness admits) are scalar functions over numbers, registered as the math extension's are and held to the shipped declarations by the same gate; a bound that is not finite, uniform(hi, lo), a triangular whose mode is outside its range or whose range is empty, or a negative deviation is the typed ErrRandomDomain and draws nothing. A scalar result takes a unit as any number does, so accept after uniform(1, 80) [s] is a random duration; quantity-valued bounds (uniform(1 [s], 80 [s])) are not accepted runtime/random_functions.go registerRandomFunctions, drawUniform, drawUniformInteger, drawTriangular, drawNormal; runtime/modeled.go ErrRandomDomain; libs/stdlib/OpenSysML Libraries/RandomFunctions.kerml runtime/stochastic_test.go:TestRandomFunctionsDrawWithinTheirSupport, :TestNormalDrawsStayFiniteNearTheLargestReal, :TestUniformIntegerCoversItsRangeInclusively, :TestRandomFunctionsRefuseAnEmptyDomain, :TestRandomFunctionsRefuseANonFiniteBound; runtime/library_functions_test.go:TestVendoredFunctionsAreAllDispatchable/RandomFunctions; robustness_test.go:TestRuntimeRobustness/random_bounds_reversed ⚠️ Extension
Every draw and weighted pick comes from one modeled stream, separate from the token-shuffle stream: the explicit model seed (-seed <n>, %seed <n>, a conformance case's modelSeed) when one is set, else the seed:<n> schedule's seed through a second stream derived from it, so -schedule declared -seed 7 and -schedule seed:3 -seed 7 draw the same values and seed:<n> alone also draws. A run with no seed of either kind that reaches a random function, or a random duration, is the typed ErrUnseededDraw, naming the call and the flags that seed it — never a value drawn from the wall clock; an unseeded weighted decision takes the most probable branch as above. The modeled stream is saved and restored with every checker snapshot and every probe, so a branch probed and not taken consumes no draw runtime/modeled.go modeledStream, modeledSource, Context.SetModelSeed, ClearModelSeed, ModelSeed, modeledUnder, Context.draw, ErrUnseededDraw, UnseededDrawError, modeledSource.mark; runtime/context.go beginProbe; runtime/snapshot.go runtime/stochastic_test.go:TestModelSeedIsIndependentOfTheScheduleSeed, :TestRandomFunctionRefusesToDrawUnseeded, :TestProbeRestoresTheModeledStream; robustness_test.go:TestRuntimeRobustness/random_draw_without_a_seed, /random_duration_without_a_seed; stochastic_random_duration.sysml, stochastic_random_timer_order.sysml (a random due instant is drawn once, when the wait is established, and the due order of two random timers is the ordinary due-order choice point) ⚠️ Extension
A witness records each draw as draw <function>(<args>) = <value> beside its choices; -schedule replay:<file> and %replay consume every recorded draw in order in place of the stream, so a replayed run reproduces its random values and its weighted branch exactly. A witness whose draws the run cannot consume — one fewer than the run takes, one left over, one for a different call, or a value the call could not have drawn (outside its bounds — a uniform(lo, hi) with lo < hi admits [lo, hi), as its generator does, while triangular admits both ends — of the wrong kind, off the mean of a zero-deviation normal) — is refused as any unfollowable move is, ErrWitnessDraw under the replay refusal; a malformed draw line is ErrInvalidDraw when the witness is read. Rolling a replay back restores the draw position with the choice position runtime/modeled.go DrawTaken, ParseDraw, ErrInvalidDraw, ErrWitnessDraw, WitnessDrawError, modeledDraws, distribution; runtime/random_functions.go (each function's admits); runtime/replay.go Witness.Draws, replayRun.takeDraw; runtime/schedule_replay.go; runtime/check.go checker.witness runtime/stochastic_test.go:TestReplayReproducesTheDrawsAndTheWeightedBranch, :TestReplayRefusesDrawsItCannotConsume, :TestReplayRefusesDrawsOutsideTheCallsDistribution, :TestReplayFollowsTheRecordedWeightedBranch ⚠️ Extension
The trace and the choice listing report a weighted decision with its weights and its draw: choice step 2: decision select branches 1->fast p=0.7, 2->slow p=0.3 hold (weighted; drew 0.7748…, took 2->slow), and (weighted; took 2->fast) with no draw for the most-probable pick, so a reader can tell a modeled pick from a scheduling one runtime/choice.go ChoicePoint.Weights, Drew, Describe, weightedAlternatives, selection stochastic_weighted_decision_seeded.trace.golden, stochastic_most_probable_unseeded.trace.golden ⚠️ Extension
Monte Carlo: %runs <n> [<seed>] <action> [<observable>...] and sysml -action <a> -runs <n> -seed <s> [-observe <f>] run the action n times, run i under the model seed RunSeed(seed, i) (a fixed mix of the seed and the one-based run number, so the runs are distinct and every run of the same request reproducible), in a fresh context each, on the sweep machinery (SweepPlan.Runs, a synthetic run column), and report the table of the observables — every feature the action holds, <part>.<attribute> for each attribute of the one object each of its own part or item usages of one occurrence denotes (a part of several objects is not spelled, having no one value per attribute — so a migrated run configuration's observables are read off its target), and clock when none is named — then each numeric observable's n, min, mean, max, nearest-rank p50 and p90 and an eight-bin histogram (whole-number bins for integral values); the statistics of an Integer observable are computed on the Integers themselves — sorted, ranked and binned as int64, summed exactly for the mean — so a value beyond 2^53 is reported as the run took it, not as the nearest Real; a non-numeric observable is tabulated and not summarised. Under -draws min, max or average the runs derive no seed (SeedlessMonteCarloPlan; SweepPlan.Seedless, so Drawn() is false and no seed goes on the wire) and %runs leaves the seed out — %seed supplies one where it is set — while a seedless %runs under random is refused; -runs needs -seed unless -draws is fixed, and one -action, and refuses -sweep, -samples, -advance, a state and the checker flags; an observable no run produced, or named twice, is refused; %runs is refused under a replay schedule; a Monte Carlo analysis case (analysis def … :> Simulation::MonteCarlo, the OpenSysML library's analysis of repeated runs and the form a SysML v1 migration gives a simulation tool's Monte Carlo analysis pattern): %runs <n> [<seed>] <case> <subject> and sysml -analysis "<case> <subject>" -runs <n> -seed <s> perform the case's steps on a fresh object of its subject per run, seeded the same way, read the value it binds as observed after each, table the observations, then conclude the case once over the sample with runs (the completed count), mean, deviation (the sample standard deviation; empty under two runs) and outOfSpec (the runs in which a required check of the case did not hold) bound and its own outputs evaluated over them; a case specializing no Simulation::MonteCarlo, a subject named by #id alone, or -observe is refused, a single run leaves the statistics unbound (a return of one is a multiplicity error naming -runs) and the statistics are of the completed runs only; a quantity observed is sampled by magnitude in the first run's unit (commensurable units converted, another dimension or a mix of numbers and quantities refused) and mean/deviation are quantities in it; every run failing keeps each run's row and error in the table and leaves the case unconcluded rather than refusing a sample of nothing; a check decided run by run counts toward outOfSpec only and a check of a statistic, undecided until the sample is bound, is what the conclusion judges, so the last run's checks weigh no more than the others'; the count of runs is validated against the sweep budget before anything is sized by it; the sample deviation of Integers is summed exactly, so Integers beyond 2^53 one apart keep their spread, and that of Reals is scaled before squaring, so a finite sample never overflows to an infinite deviation; a failed run fails the case whatever comes of the sample, a sample or conclusion that cannot be made leaving only a table of completed runs unresolved runtime/montecarlo.go MonteCarloPlan, SeedlessMonteCarloPlan, RunSeed, RunNumber, Distribute, Distribution, HistogramBins, ErrSweepRuns; runtime/sweep.go SweepPlan.Runs, SweepPlan.Seedless, Drawn, MagnitudeValue; runtime/action_executor.go ActionExecutor.Results, collectPartsHeld; runtime/check_invocation.go Invocation.performerPrefixes; repl/runs.go Session.RunRuns, splitRunsTail; cmd/sysml/check.go (-runs), main.go; analysis/enginewire/wire.go Sweep.Runs; runtime/montecarlo_case.go MonteCarloCaseFQN, Context.IsMonteCarloCase, Context.RequireMonteCarloCase, Context.ObserveMonteCarlo, MonteCarloStatistics, Conclude; repl/montecarlo.go Session.RunMonteCarlo, monteCarloVerdict; libs/stdlib/OpenSysML Libraries/Simulation.sysml MonteCarlo action_part_attributes_in_outcome.sysml; analysis/external_question_test.go:TestWireSweepKeepsAZeroSeed/runs_without_a_seed; repl/draws_test.go:TestDrawsFixesTheRunsWithoutASeed; runtime/montecarlo_test.go:TestRunSeedIsDistinctPerRunAndReproducible, :TestMonteCarloPlanNumbersItsRuns, :TestDistributeSummarisesTheRuns, :TestDistributeBinsIntegersWhole, :TestDistributeKeepsLargeIntegersExact, :TestDistributeRealsAtTheEdgesOfTheRange; repl/runs_test.go:TestRunsReportsEachRunAndTheDistribution, :TestRunsDefaultsToEveryFeatureAndTheClock, :TestRunsKeepsLargeIntegersExact, :TestRunsDrawEachRunFromItsOwnSeed, :TestRunsAreTheSameUnderAnySchedule, :TestRunsRefusesWhatItCannotRun, :TestRunsRefusedUnderAReplay, :TestSeedFixesTheDrawsOfARun; cmd/sysml/runs_test.go:TestRunsThroughCLI, :TestRunsDefaultObservablesThroughCLI, :TestRunsJSONThroughCLI, :TestRunsMisuseThroughCLI, :TestSeedAloneSeedsOneRunThroughCLI, :TestRunsRefusesAnUnheldObservableThroughCLI; repl/montecarlo_test.go:TestRunsConcludesAMonteCarloCaseOverItsSample, :TestRunsOfOneLeaveTheDeviationEmpty, :TestRunsConcludeAQuantityObservedCaseInItsUnit, :TestRunsAllFailingKeepTheirRows, :TestRunsObservingNoNumberKeepTheirRows, :TestRunsFailedRunsOutweighAnUnconcludedSample, :TestRunsCountPerRunChecksWithoutJudgingTheLastRunTwice, :TestRunsRefusesACountBeyondTheBudget, :TestRunsRefusesAnOrdinaryAnalysisCase; runtime/montecarlo_test.go:TestMonteCarloSampleTakesQuantitiesInTheFirstRunsUnit; migrate/montecarlo_case_test.go ⚠️ Extension
Draw policy (-draws random|min|max|average, %draws, runtime.DrawPolicy): how every RandomFunctions call of a run resolves. random (the default) draws from the modeled stream as above; min, max and average take each call's least, greatest or mean value with no draw and no seed, min and max reading a bounded call's interval closed at both ends — uniform(lo, hi): lo, hi (the bound its random draws on [lo, hi) approach and never reach; a v1 duration interval is closed and its tool's max mode reads hi), (lo + hi) / 2; uniformInteger(lo, hi): lo, hi, the midpoint rounded toward hi for an odd span (uniformInteger(1, 6) averages to 4); triangular(lo, mode, hi): lo, hi, (lo + mode + hi) / 3; normal(mean, sd): average is the mean, and min and max are the typed ErrDrawUnbounded naming the call, since a normal with sd > 0 has no least or greatest value, while normal(mean, 0) draws nothing but the mean and so is the mean under every policy, random included, where it needs no seed and leaves the modeled stream untouched — so a random duration under a fixed policy is a fixed duration and the run's clock is deterministic under every seed and with none. The domain of a call is checked under every policy (uniform(hi, lo) stays ErrRandomDomain). The policy is a property of the context (Context.SetDrawPolicy) that each run captures as it starts, so a run paused across a change keeps the policy it began under and its witness names that one (DrawPolicyTaken), set by -draws, %draws, a conformance case's draws, and an analysis Question.Draws, which reaches the run, explore, check, sweep and standing engines and the gRPC handlers as the model seed does and is written on the wire as "draws":"<policy>" only when fixed. A witness records a fixed policy as draws by <policy> before its draws and replay: runs under it: a witness whose draws the recorded policy could not have made (a min witness recording a value above the bound, a policy over a call with no such point) is ErrWitnessDraw, a witness naming a fixed policy and recording no draw leaves them to the policy (each call resolves to its point, as an external engine's schedule replays, and the reported witness records what it took) while a partial record is refused, and an unknown policy is refused where it is spelled runtime/draw_policy.go DrawPolicy, DrawPolicyNames, ParseDrawPolicy, Fixed, ErrDrawUnbounded, DrawUnboundedError, Context.SetDrawPolicy, DrawPolicy, DrawPolicyTaken, distribution.fixedPoint, admitsUnder, realPoints; runtime/random_functions.go (each function's fixed); runtime/modeled.go Context.draw; runtime/scheduler.go scheduler.draw; runtime/replay.go drawPolicyPrefix, Witness.Draws, replayRun.drawsByPolicy; runtime/schedule_replay.go; analysis/question.go Question.Draws, apply, DrawsOf; analysis/external_question.go wireQuestion; analysis/enginewire/wire.go Question.Draws; analysis/external_standing.go replay.reported; grpc/engines.go; repl/schedule.go (%draws), Session.Draws, SetDraws; cmd/sysml/check.go (-draws); libs/stdlib/OpenSysML Libraries/Simulation.sysml DrawPolicy stochastic_draws_min.sysml, stochastic_draws_max.sysml + .trace.golden, stochastic_draws_average.sysml, stochastic_draws_max_normal_unbounded.sysml; runtime/draw_policy_test.go:TestDrawPolicySpellsAndParses, :TestFixedDrawPoliciesResolveEveryCallWithoutASeed, :TestAverageDrawsOfDiscreteAndUnboundedCalls, :TestRandomDrawPolicyIsTheSeededStream, :TestWitnessCarriesTheDrawPolicy, :TestReplayAdmitsDrawsUnderTheWitnessPolicy, :TestFixedDrawPolicyMakesTheClockDeterministic, :TestFixedPolicyWitnessWithoutDrawsReplaysByThePolicy; runtime/robustness_draw_policy_test.go:TestRuntimeRobustnessDrawPolicy/normal_has_no_min_or_max, /degenerate_normal_is_its_mean_under_every_policy, /degenerate_normal_draws_nothing_at_random, /policy_set_mid_run_waits_for_the_next_run, /unbounded_timer_stops_the_run_where_it_parks, /unknown_policy_is_refused, /witness_draw_the_policy_cannot_make, /witness_policy_over_a_call_it_cannot_resolve, /witness_left_to_a_policy_over_a_call_it_cannot_resolve, /fixed_policy_still_checks_the_domain; analysis/external_question_test.go:TestWireQuestionCarriesTheDrawPolicy; analysis/external_standing_test.go:TestExternalScheduleReplaysUnderTheQuestionsDrawPolicy; repl/draws_test.go:TestDrawsFixesTheRunsWithoutASeed, :TestSetDrawsAppliesToTheDebugger; cmd/sysml/compare_test.go:TestMigrationResultsThroughCLI ⚠️ Extension
Clock step (-clock-step <seconds>, %clock-step, Clock.step): the step a run's clock ticks by, as a simulation tool's fixed-step internal clock does. Every wait — accept after, accept at, a state's timer, a case's timed step — comes due at the first multiple of the step not before the instant it would end on a continuous clock (onTick: the ceiling in steps, a due instant within floating-point rounding of a tick read as that tick; a step so fine that the instant counts more ticks than a float64 holds leaves the instant as it is, every instant being on a tick the number cannot tell apart, and a tick past the last instant a float64 holds is ErrNegativeDuration before the wait is queued), so under a step of 1 a wait of 2.3 [s] set at t=0 comes due at t=3.0 and one of 2.0 [s] at t=2.0; a past absolute instant comes due at the current instant, on a tick or not, since the clock has nothing left to wait for. 0, the default, is the continuous clock, on which every wait comes due exactly when it ends and every existing fixture reads as before. The step is read when the wait is set (dueInstant), so a wait queued before the step changed keeps its instant; it is a property of the context (Context.SetClockStep) that a run captures as it starts and its witness names (ClockStepTaken), set by -clock-step, %clock-step, a migrated configuration's clockStep under -compare-results, and an analysis Question.ClockStep, which reaches the run, explore, check, sweep and standing engines and the gRPC handlers as the draw policy does and is written on the wire as "clockStep":<seconds> only when positive. A witness records a stepped clock as clock steps by <seconds> after the draw policy and before its draws, and replay: runs on the recorded clock whatever the context's step is; a continuous run's witness carries no line, and a line naming 0, a negative or non-numeric step, one placed after the draws or the moves, or a second line is the typed ClockStepParseError (unwrapping to ErrClockStep). A step that is NaN, infinite or negative is ErrClockStep wherever it is spelled, before anything runs. The rule is an approximation of the tool's clock: its documentation states that the internal clock advances the simulation time by stepSize (1.0 by default) once startTime is set, from which a wait's end being noticed at the tick after it follows, as the totals a tool stores of a stepped configuration's runs, on that grid, bear out; how the tool orders two waits due at one tick, or reads a wait ending exactly on one, is not documented, so ties are read as the continuous clock reads them runtime/clock.go Clock.step, Step, ErrClockStep, CheckClockStep, ParseClockStep, ClockStepParseError, Context.SetClockStep, ClockStep, ClockStepTaken, onTick, dueInstant; runtime/replay.go Witness.ClockStep, clockStepPrefix, readHeader; runtime/check.go checker.witness; runtime/schedule_replay.go replaySchedule, spellWitness; analysis/question.go Question.ClockStep, fresh, ClockStepOf; analysis/ask.go Request.ClockStep; analysis/external_question.go wireQuestion; analysis/enginewire/wire.go Question.ClockStep; analysis/external_standing.go replay.step; grpc/engines.go; repl/schedule.go (%clock-step), Session.ClockStep, SetClockStep; repl/compare.go CompareOptions.ClockStep; cmd/sysml/check.go (-clock-step); migrate/simconfig.go clockStep; simresults/simresults.go ConfigurationResults.ClockStep clock_step_waits_come_due_on_ticks.sysml + .trace.golden; runtime/clock_step_test.go:TestContinuousClockReadsWaitsExactly, :TestSteppedClockReadsWaitsOnTicks, :TestClockStepAbsorbsRounding, :TestCheckClockStep, :TestWitnessCarriesTheClockStep; runtime/robustness_clock_step_test.go:TestRuntimeRobustnessClockStep/step_that_is_no_number_is_refused, /negative_wait_is_refused_under_a_step, /step_set_mid_run_applies_to_the_waits_set_after_it, /past_instant_fires_at_once_under_a_step, /past_instant_fires_at_once_off_the_grid, /step_too_fine_to_tell_apart_leaves_the_wait_finite, /tick_past_the_last_instant_is_refused; analysis/external_question_test.go:TestWireQuestionCarriesTheClockStep; analysis/external_standing_test.go:TestExternalScheduleReplaysOnTheQuestionsClockStep; repl/clock_step_test.go:TestClockStepTicksTheRunsClock, :TestClockStepAppliesToTheDebugger; cmd/sysml/runs_test.go:TestClockStepThroughCLI; cmd/sysml/compare_test.go:TestMigrationResultsThroughCLI; migrate/simconfig_test.go:TestSimulationConfigRecordsTheClockStepOfTheToolsInternalClock ⚠️ Extension (approximates the tool's fixed-step clock; ties read as the continuous clock's)
Run configurations (Simulation::Configuration, an OpenSysML metadata def beside Stochastic; a SysML v1 migration's form of a simulation tool's run configuration): @Configuration { runs = n; draws = DrawPolicy::max; timeVariable = "t"; startTime = 0.0; stepSize = 1.0; timeUnit = "s"; parallelForks = true; } on an action def records how the behavior was meant to be run — the run count to pass as -runs, the policy as -draws, the tool's clock as documentation — and applies none of it: a run reads the runs and policy it is given. sysml -convert sysml writes one per «SimulationConfig» of a MagicDraw model, recognised by its profile's provenance (the vendor's host at /schemas/SimulationProfile.xmi, no other path — a profile so named elsewhere is a comment), as an action def holding the executionTarget individual as part target and performing the target's classifier behavior on it (perform action run ::> target.<behavior>), with the tool's result snapshots indexed per configuration in a JSON sidecar (-migration-results); -compare-results runs each configuration under its recorded runs, draws and clockStep (or -runs/-draws/-clock-step; the step is stepSize in timeUnit, 1.0 unless stated, of a configuration stating startTime, the tool's internal clock — a unit of no fixed length or a step of zero or less leaves it out with a note, and an unstated unit reads the step in seconds, as the model's bare durations are read, noting the tool's millisecond default) and reports the tool's and OpenSysML's min, mean, p50, p90 and max of each observable with the relative difference — and, for an observable a migrated Monte Carlo analysis def summarises (the sidecar's analysisCase and statistics), one row per declared return and per output of Simulation::MonteCarlo the tool stored without a return: the tool's pooled Mean and Deviation against the runs' by the same aggregation with their difference, N side by side, OutOfSpec recorded but not compared, being the tool's own criterion (repl/compare.go declaredStatistics, storedDeviation, statisticsTable; repl/compare_test.go:TestComparisonTableComparesTheDeclaredStatistics) — and a configuration with no snapshots, an observable no run holds, a non-numeric one (in every completed run or some, counted), one some completed runs do not produce at all (counted against the completed runs) or one the runs produce in more than one unit is reported, never left out or pooled across units; an -action naming several configurations by simple name is refused, listing them, and a name is read as the notation is, so a :: inside a quoted segment ('Sub::Group') is part of that segment, not a qualification; resultLocation packages that repeat or nest index each snapshot once, and a feature two slots of one snapshot hold numbers for is left out of that snapshot with a note, not resolved by slot order. A summarising snapshot another configuration's repeats — same name, same statistics, no observable both hold a different number of — is noted under each as a likely copy naming the other configuration and its result location (Results.Repeats, TestCompareNotesSnapshotsStoredTwice), and compared all the same; summaries of one observable whose means lie more than three standard errors apart cannot be of runs of one and the same model, so they are noted by name and the pooled mean is said to blend them (ConfigurationResults.Disagreeing, TestDisagreeingSummaries, TestComparisonTablePoolsSummarisedResults). A target the migration did not write, one that is no part, a state machine or a behavior-less classifier, a setting of no v2 meaning and a durationSimulationMode that is no policy are report entries with the reason, not a partial configuration; the migrated numbers are reported as run and not tuned libs/stdlib/OpenSysML Libraries/Simulation.sysml Configuration, DrawPolicy; migrate/simconfig.go simulationConfig, isSimulationProfile, configurationSettings, configurationTarget, inheritedClassifierBehavior; simresults/simresults.go Results, ConfigurationResults, Snapshot, Read; migrate/results.go resultSnapshots, snapshotSlot; repl/compare.go Session.CompareResults, CompareOptions, ObservablePair, comparisonTable; cmd/sysml/convert.go writeMigrationResults; cmd/sysml/check.go (-compare-results, -observe <stored>=<feature>) migrate/simconfig_test.go:TestSimulationConfigBecomesARunnableActionDef, :TestSimulationConfigReportsWhatItCannotRun, :TestSimulationConfigOfAnotherProfileIsNotARunConfiguration, :TestSimulationConfigRecordsTheClockStepOfTheToolsInternalClock; migrate/results_test.go:TestResultSnapshotsAreIndexedPerConfiguration, :TestResultSnapshotsReportWhatIsNotRead, :TestResultsSidecarRoundTrip, :TestOverlappingResultLocationsIndexEachSnapshotOnce, :TestRepeatedSnapshotSlotsHoldNoResult, :TestComparisonRunsEachConfigurationBesideItsStoredResults; repl/compare_test.go:TestComparisonTableRefusesMixedUnits, :TestComparisonTableRefusesNonnumericRuns, :TestComparisonTableRefusesRunsMissingTheObservable, :TestCompareRefusesAnAmbiguousName, :TestSameNameSplitsOutsideQuotes; migrate/behavior_test.go:TestPropertyBackedProbabilitiesAreReferences; cmd/sysml/compare_test.go:TestMigrationResultsThroughCLI; action_perform_chain_joins_object_performance.sysml ⚠️ Extension (records the tool's settings; applies the runs, draws and clock step the harness passes a run)
@Probability { p = w; } on a state transition weights it, read with the same probabilityReader and rules as a decision's: the lowered Transition carries the expression losslessly. A group is the transitions competing for one dispatch — every transition out of a choice or junction pseudostate, every completion transition out of a state, and the transitions out of a state on one trigger — the resolved signal or operation definition together with a structurally identical expression and the same via receiver, via p apart from via this.p (accept go apart from accept other, accept A::Go apart from accept B::Go) — while a weight on a transition out of a fork, join, initial, entry, exit or history pseudostate is refused since no branch pick happens there — checked at lowering with every member weighted or none, each weight in [0, 1], and constant weights summing to 1 within 1e-6; a group of one weighted transition whose constant is not 1 is refused by the same sum rule. Weights apply only among the transitions otherwise equally eligible — after trigger matching, guards, join synchronization, innermost-wins and losesToNestedTransition — so a substate's transition is never weighed against an enclosing state's; the pick is drawn once, at dispatch, within one run-to-completion step, and a single enabled transition draws nothing. Transitions sharing a time-trigger spelling arm one timer — the expiry is the one occurrence the group competes for, drawn among the holding members by weight — and a weight expression may read the trigger's bound arguments (accept route(priority) with p = priority). explore enumerates the alternatives, seed:<n> draws among them, replay: follows the recorded pick, and expression weights are re-checked at dispatch (sum, range, a holding total of zero, a weighted branch enabled beside an unweighted one) as the typed ErrBranchWeights lower/probability.go TriggerName, TriggerKey, TransitionGroups, checkTransitionProbabilities; lower/state_graph.go Transition.Probability, lowerTransitionMember, addCompletion; runtime/state_executor.go transitionWeights, drawTransition, chooseTransition, chooseCompletion, scheduleTimeTransitions, sameTimerGroup; runtime/state_route.go pickBranch; runtime/scheduler.go chooseWeighted; runtime/signal.go triggerName runtime/robustness_transition_probability_test.go:TestRuntimeRobustnessTransitionProbability (the lowering refusals among its subtests), TestExploreEnumeratesWeightedTransitions, TestExploreEnumeratesNestedWeightedTransitions, TestExploreReportsTransitionProbabilities, TestCheckWeightsViolationMass, TestExploreWeighsTransitionsByTriggerArguments, TestExploreWeighsTimedTransitionsAsOneOccurrence, TestCheckWeighsTimedTransitionsAsOneOccurrence; stochastic_weighted_transition.sysml, stochastic_weighted_transition_nested.sysml, stochastic_weighted_completion.sysml, stochastic_weighted_choice_pseudostate.sysml, stochastic_weighted_call_trigger.sysml, stochastic_weighted_time_transition.sysml + trace goldens ⚠️ Extension (no SysML v2 semantics to be faithful to)
explore reports each outcome's probability and check each violation's probability mass, in the same currency: a weighted choice point contributes the drawn alternative's share Weights[taken] / sum(Weights) of the weights drawn over, an unweighted one the uniform 1/n a seed:<n> takes each alternative with — so the figure is the model's own probability when every point along the way is weighted, and otherwise assumes the scheduling choices the spec leaves open are taken uniformly at random. A linearization's probability is the product of its slots' shares; an outcome's is their sum over the runs folded into it; a violation's mass is the sum of the path masses reaching a state where it holds — counted once per path, revisited states credited — a deadlock or failure carrying its path's mass. Both are exact only when the search is exact: an incomplete exploration reports them as lower bounds (≥ 0.3 in the table, probabilitiesLowerBound on the wire and in the JSON report, ; probabilities are lower bounds on the status line), and a check marks massLowerBound when a bound was hit, a state was reached again, or the persistent-set reduction left a move out runtime/explore.go ExploredOutcome.Probability, Exploration.Probability, ProbabilitiesBounded, exploreSlot.share, exploreRun.probability; runtime/explore_queue.go fold; runtime/check.go Violation.Mass, CheckReport.MassBounded, credit, shareOf, visitedState.violated; runtime/check_schedule.go checkRun.faced; cmd/sysml/report.go probability, probabilitiesLowerBound, mass, massLowerBound; api/proto/sysml.proto Outcome.probability, ExplorationStatus.probabilities_lower_bound; repl/explore.go, repl/checker.go runtime/stochastic_test.go:TestExploreWeighsLinearizations, :TestExploreWeighsUnweightedChoicesUniformly; runtime/robustness_transition_probability_test.go:TestExploreReportsTransitionProbabilities, TestCheckWeightsViolationMass; stochastic_weighted_transition.expected.json (probability per outcome) ⚠️ Extension (no SysML v2 semantics to be faithful to)

Static Expression Type Checking (KerML §7.4 Expressions, §8.3 Feature Values)

Checked before execution, at the type validation tier. Every rule is one-sided: a diagnostic is reported only when both the expected and the actual type are known, so unmodelled types never produce a false positive.

Semantic Rule Implementation Test Case Status
Scalar type lattice over ScalarValues semantics/exprtype.go PrimTypeOf/PrimConforms typecheck_expr_test.go ✅ Faithful
Feature value conforms to declared type passes/typecheck_expr.go checkUsageValue TestExprBindStringToIntegerAttribute ✅ Faithful
Arithmetic operand types (+ - * / % **) passes/typecheck_expr.go checkAddition/checkArithmetic TestExprAddIntegerAndStringRejected ✅ Faithful (+ over two Strings is concatenation, per StringFunctions::'+'; a String with a number is rejected at this tier, and semantics.PrimConforms widens numerics only, so neither is coerced to the other)
Boolean operand types (and or xor implies & \|, not) passes/typecheck_expr.go checkBinaryBoolean/checkUnaryBoolean TestExprAndOnIntegerRejected ✅ Faithful
Comparison operand types (< > <= >=) passes/typecheck_expr.go checkComparison TestExprComparisonOfBooleanRejected ✅ Faithful
Disjoint ==/!= operands (warning; '==' is declared over Anything) passes/typecheck_expr.go checkEquality TestExprEqualityAcrossDisjointTypesWarns ✅ Faithful
validateTransitionFeatureMembershipGuardExpression — a transition guard is a Boolean expression (SysML v2 8.3.18.8), in every spelling that is a TransitionUsage: the state-body transition first a if g then b, accept … if g then b, if g then b, transition if g then b, and an action body's guarded successions (first a if g then b, succession s first a if g then b, a decision's if g then b) passes/transition_guard.go TransitionGuardPass at LevelType, element-scoped, visiting ast.TransitionMember, ast.InitialNode and ast.ControlFlowEdge guards through passes/typecheck_expr.go exprChecker.checkBoolean passes/transition_guard_test.go TestTransitionGuardNonBooleanInEveryTransitionForm (String, Natural, Rational, enumeration, part-typed, non-Boolean calc and chain), TestTransitionGuardBooleanFormsAreSilent (literal, attribute, Boolean specialization, [0..1], untyped, null, operators, constraint reference, Boolean calc and chain), passes/typecheck_expr_test.go TestExprTransitionGuardMustBeBoolean; corpus semantic/s81-guarded-succession-guard-not-boolean.sysml ✅ Faithful (adjudicated for the specification: the pinned pilot with the standard library loaded reports none of these because a guard implicitly redefines TransitionPerformance::guard, whose result is already Boolean — its own Xpect fixture expects the error; drafted in omg-issues.md. The OCL's guard.result.multiplicity.hasBounds(1,1) clause is checked by neither tool)
Boolean-valued contexts (constraint/assume/require, if/while/until, guards) passes/typecheck.go checkBehaviorMember (recursing into loop and branch bodies, so a nested condition is checked too), passes/typecheck_expr.go checkBoolean/checkNonScalarCondition with semantics/exprtype.go Model.CouldHold; a bare name or feature chain is typed by inferQualified/featurePrimType as the effective scalar type of the feature it resolves to — declared, given by its value (a non-default one beside no generalization, typingValue, as §8.3.3.3 checkFeatureValuationSpecialization has a value type a feature), or reached through the features it redefines or subsets and the types it inherits, an alias followed to its target (KerML 1.1 §8.3.4.8.5 binds a feature reference's result to the referent, §8.4.4.9.3 has the result specialize it so its type is the referent's; §§7.3.4.3–7.3.4.5 typing, subsetting, redefinition; §7.4.11 feature values) TestExprTransitionGuardMustBeBoolean, TestTypeCheckNonBooleanControlFlowConditions, passes/w6d_boolean_context_test.go (a condition typed by a part, an item, an enumeration or a non-Boolean calc result; a Boolean specialization and a constraint reference are not reported), passes/typecheck_feature_reference_test.go TestBareFeatureReferenceIsTypedByItsFeature (declared, subsetting, valued, alias, chains through inheritance and redefinition, valued redefinitions and subsettings), TestBareFeatureReferenceComparisonIsJudged, TestBareFeatureReferenceConditionIsJudgedStatically (while n over n : String is reported with its type; an untyped n is left to the executor); conformance action_succession_guard_not_boolean (a guard of statically unknown type is refused by the executor) ✅ Faithful (every condition and operand the scalar lattice can type — a literal, an operator, a scalar-typed feature or chain — is judged statically, and one typed by something no Boolean can come from — a structure, an enumeration, a calc whose result is one — is reported here too, before execution; only a condition whose type is genuinely unknown, an untyped feature, an unresolved chain or a behavior with no declared result, is caught by the executor (runtime/action_statements.go evalCondition), the one path left to it. Stricter than the reference, which reports neither while total { } nor while e { } and accepts -x over x : String and x == 1 between a String and a Natural (kerml-examples/Simple Tests/Expressions.kerml, adjudicated in pilot-differential.md) — so KerML 1.0 §7.4.9's Boolean-valued condition and the operand types the Kernel Function Library declares are the authority for this check, not the pilot)
Change-event conditions (accept when <expr>, transition ... when <expr>) passes/typecheck_trigger.go TriggerArgumentPass (LevelType, ElementScoped: visits every accept and transition trigger in action, state entry/do/exit, transition-effect and succession bodies once, each gated by Context.DownstreamOfFailure on its own argument, so an unrelated unresolved name elsewhere in the document does not hide an invalid trigger) TestExprAcceptWhenConditionMustBeBoolean, typecheck_trigger_test.go:TestTriggerOnAcceptActions, typecheck_element_scope_test.go:TestTriggerArgumentIsElementScoped, typecheck_trigger_test.go:TestTriggerTypesSurviveWorkspaceDuplicates ✅ Faithful (accept when is always a condition; after transition ... when a bare name is a signal, so only expressions are checked there)
The argument of an after trigger is a DurationValue: a quantity literal in a DurationUnit (after 5 [s], after 5 [ms]), a feature typed by ISQBase::DurationValue or a subtype, an invocation returning one, or arithmetic over quantities whose dimension is time (after t2 - t, after d + 5 [s]); a unitless number, a String, a Boolean, a length or a time instant is refused, as is arithmetic with a plain-number operand (after 2 * d selects NumericalFunctions::'*', whose result is a NumericalValue), %, and if/??, whose library functions return Anything (SysML v2 §7.16 TriggerInvocationExpression checkTriggerInvocationExpressionAfterArgument; pilot validateTriggerInvocationActionAfterArgument) passes/typecheck_trigger.go checkTimeEvent over semantics/valuetype.go Model.ExprConformsToLibrary (FQNDurationValue) and semantics/dimension.go (unit and scale dimensions); code trigger-after-duration typecheck_trigger_test.go:TestTriggerAfterAcceptsDurations, TestTriggerAfterRejectsNonDuration, TestTriggerUnresolvedArgumentIsSilent; refereed pilot-reject/…/semantic/s48-after-trigger-not-duration.sysml ✅ Faithful
The argument of an at trigger is a Time::TimeInstantValue: a feature so typed (or by a subtype), a chain or invocation reaching one, or a time-dimensioned sum of an instant and a duration; a duration, a number or a Boolean is refused (SysML v2 §7.16 checkTriggerInvocationExpressionAtArgument; pilot validateTriggerInvocationActionAtArgument) passes/typecheck_trigger.go checkTimeEvent (FQNTimeInstantValue); code trigger-at-time-instant typecheck_trigger_test.go:TestTriggerAtAcceptsTimeInstants, TestTriggerAtRejectsNonTimeInstant; refereed semantic/s49-at-trigger-not-time-instant.sysml ✅ Faithful
The argument of a when trigger is Boolean: a comparison, a Boolean combination, a feature typed by ScalarValues::Boolean, a chain or invocation reaching one; a number, a String, a quantity or an arithmetic expression is refused, and an untyped or unresolved feature draws nothing (SysML v2 §7.16 checkTriggerInvocationExpressionWhenArgument, whose Boolean test we take through the expression's result type rather than requiring a FeatureReferenceExpression, as the pilot does; pilot validateTriggerInvocationActionWhenArgument) passes/typecheck_trigger.go TriggerArgumentPass → passes/typecheck_expr.go checkCondition/checkNonScalarCondition (FQNBoolean); code trigger-when-boolean typecheck_trigger_test.go:TestTriggerWhenAcceptsBooleans, TestTriggerWhenRejectsNonBoolean, TestTriggerBareWhenTransition; refereed semantic/s50-when-trigger-not-boolean.sysml ✅ Faithful
Division/exponentiation result types (a whole-number quotient — Natural/Natural or Integer/Integer — is Rational, as the reference evaluates it; the library's declared Natural return is recorded in omg-issues.md) passes/typecheck_expr.go divisionResult TestExprWholeNumberDivisionAndPowerOK, TestExprDivisionIsRational ✅ Faithful
An invocation writes at most one argument per effective input parameter, inherited and partially redefined (:>>) parameters and the arrow-form receiver counted (KerML 1.0 §8.3.4.8.8 validateInvocationExpressionParameterRedefinition; pilot Must correspond to one input parameter of the invoked type, arity.sysml:38:32 for F(1.0, 2.0, 3.0) against two inputs): F takes 2 argument(s), found 3 is an error, at a bare call and at an invocation heading a feature chain (M(1, 2).r.inner) alike passes/typecheck_expr.go effectiveInParameters/checkArguments, inferFeatureChain TestExprPartiallyRedefinedParametersKeepInheritedSignature, TestExprInvocationTooManyArguments; duplicate_binding_test.go:TestInvocationHeadingFeatureChain ✅ Faithful
An invocation that leaves a default-less input parameter unbound — positionally (F(1) against in x; in y;), by name (F(y = 2)) or with an empty list (F()) — is well-formed: KerML 1.0 §8.3.4.8.8 lists no InvocationExpression constraint on the count of arguments (validateInvocationExpressionParameterRedefinition and …NoDuplicateParameterRedefinition bound the arguments written, not the parameters left out), and the pinned pilot (2026-07, jupyter-sysml-kernel 0.61.0) validates every omission form clean — plain call, chain head (A().y, kerml-examples/Simple Tests/Behaviors.kerml:14, whose ParsingTests_Behaviors.kerml.xt declares no error), behavior, calc and constructor, [1] and [1..*] — and evaluates F(1.0) to the unreduced OperatorExpression +. The parameter is unbound, so the call cannot be evaluated: OpenSysML reports the advisory F leaves parameter y unbound, so the call cannot be evaluated (code unbound-parameter, severity warning in every conformance mode, -strict included, since it judges the model and not the notation) once per omitted parameter, identically at a bare call and at a chain head; a parameter a default reaches along its redefinitions or whose effective multiplicity admits no value draws nothing. The runtime keeps refusing the evaluation with ErrUnboundParameter (calc F parameter "y" has no argument and no default) wherever it reaches the unbound input, so the advisory predicts exactly the runtime's verdict passes/typecheck_expr.go inferInvocation/inferNodeInvocation (one path for both positions), checkArguments, checkNamedArguments, parameter.required (CodeUnboundParameter); runtime/invoke_calc.go bindCalcParameter, runtime/action_frame.go checkInputsBound, runtime/invoke_operation.go (ErrUnboundParameter) passes/typecheck_expr_test.go:TestExprInvocationTooFewArguments; passes/duplicate_binding_test.go:TestInvocationHeadingFeatureChain (plain/chain parity); passes/invocation_test.go (TestInvocationOverloadRedeclaredLibraryInputs, TestInvocationPerformedActionOptionalInputs); cmd/sysml/strict_test.go:TestStrictConformanceKeepsUnboundParameterAdvisory; runtime invocation_selection_test.go, invoke_action_test.go, compile_constructs_test.go; pilot-corpora ratchet kerml-examples/Simple Tests/Behaviors.kerml (1, the adjudicated advisory) ✅ Faithful (an advisory the reference does not report, recorded as the one only-ours row of Simple Tests/Behaviors.kerml in the differential)
Invocation argument types and named-argument names passes/typecheck_expr.go checkArguments TestExprInvocationArgumentTypeMismatch ✅ Faithful
An invocation binds each parameter at most once (KerML 1.1 §8.3.4.8 validateInvocationExpressionNoDuplicateParameterRedefinition, pilot Parameter already bound): the duplicate is found by the parameter a name resolves to — F(x = 1, x = 2), a positional argument followed by x = …, an alias or a redefining name of the same parameter, in a KerML function call, a calc usage, action a = A(…), perform action a = A(…) and send new Sig(…) alike, and an invocation heading a feature chain (F(x = 1, x = 2).r, under one segment or several, where every argument check applies exactly as at a bare call, the unbound-parameter advisory included) — and reported at the second binding, F binds parameter "x" twice; the runtime binds a named argument to the same parameter the checker resolved it to (Model.BoundParameter), so a short name, an alias or a qualified inherited name that validates also executes passes/typecheck_expr.go checkNamedArguments, namedParameter, inferFeatureChain; semantics/binding.go Model.LookupBinding, Model.BoundParameter; runtime/eval.go boundParameterNames passes/duplicate_binding_test.go (TestInvocationDuplicateParameterBinding…, TestInvocationOverloadSelectedByResolvedParameterName, TestInvocationHeadingFeatureChain); runtime/invocation_selection_test.go (…names_a_parameter_as_the_checker_does), runtime/compile_constructs_test.go TestCompiledNamedArgumentSpellings; corpus semantic/k33-parameter-bound-twice.kerml; census probe validateInvocationExpressionNoDuplicateParameterRedefinition.kerml ✅ Faithful
Each argument of an invocation corresponds to one in parameter of the invoked type (KerML 1.1 §8.3.4.8 validateInvocationExpressionParameterRedefinition, pilot Must correspond to one input parameter of the invoked type): a named argument naming an out or return parameter, a feature that is no parameter, or a general's parameter the invoked function's own redefines by position; and a positional argument past the last in parameter, in a function, a calc def, a step or a feature typed by a behavior, with inherited and redefined parameters counted through effectiveInParameters passes/typecheck_expr.go checkArguments, checkNamedArguments (msgInvocationParameterRedefinition) passes/duplicate_binding_test.go TestInvocationParameterRedefinitionKerML; census probe validateInvocationExpressionParameterRedefinition.kerml; corpus semantic/k32-too-many-arguments.kerml ✅ Faithful; the pilot's wording heads ours, followed by the cause (…: F has no parameter named "y", …: F takes 1 argument(s), found 2) at the offending argument. A required parameter left unbound (F()) draws OpenSysML's own unbound-parameter advisory (warning), a check the pilot does not make
A constructor expression binds each feature of the instantiated type at most once (KerML 1.1 §8.3.4.8 validateConstructorExpressionNoDuplicateFeatureRedefinition, pilot Feature already bound): new C(x = 1, x = 2), an alias or a qualified name of the same feature, and an inherited feature bound under its own and its redefining name all count; reported at the second binding, x of C is already bound by an earlier argument, and the same rule reaches a constructor nested in a send payload (under a conditional, a sequence, a feature chain, a call argument or a body expression) or in a feature chain's operand passes/typecheck_expr.go inferConstructor (the constructor row below), inferFeatureChain passes/duplicate_binding_test.go (TestConstructorDuplicateFeatureBindingKerML, TestConstructorDuplicateFeatureBindingSysML); corpus semantic/k34-constructor-feature-bound-twice.kerml; census probe validateConstructorExpressionNoDuplicateFeatureRedefinition.kerml ✅ Faithful (a call's named argument is not indexed as a member reference of the called type, so editor rename and find-references do not reach f(F::x = …) — an editor follow-up; constructor labels are)
Invocation overload selection: a name visible as several function/calc declarations (owned, inherited, imported or re-exported — a library function only where the model imports its package) selects the one whose effective input parameters take the arguments — each argument, by position or name, to a distinct parameter it conforms to; a candidate the arguments bind every default-less parameter of is preferred to one they leave a parameter of, which stays applicable and draws the unbound-parameter advisory, so pick("a") against pick(in x : Integer) and pick(in s : String; in y : Real) binds to the second rather than reporting a mismatch against the first, pick(2) against pick(in x : Real; in y : Real) and pick(in x : Integer) runs the complete one, and pick() against two candidates it fits alike is invocation-ambiguous — and by type conformance (the ScalarValues lattice, strings, booleans, collections, quantities and declared types through Model.Conforms — a non-literal argument's type only bounds its values, so a broader declared type fits a narrower parameter, while declared types neither of which conforms to the other, MassValue to a VolumeValue parameter, never bind, as the pinned validator judges a binding; a Collections parameter takes any sequence, as the runtime reads it, and a KerML::Root::Element parameter the element any argument names, as DocumentQueries bind it, ranked between a declared type and Anything); among several fits the most specific wins, a tie or incomparable pair is reported as invocation-ambiguous naming the tied candidates (InvocationSelection.Tied, the applicable ones none is more specific than, so a broader overload the arguments also fit is not among them), and no fit keeps the argument diagnostic against the first candidate, naming the others considered. The selection is memoized in a side table keyed by the invocation node and scope, the runtime dispatches the declaration selected there, and a named argument written twice is reported (binds parameter twice for a calc call, ErrDuplicateArgument for a nested action call) and dispatched by none. A feature typed by a behavior is a candidate performing that behavior, so ref pick : Twice; is selected beside a same-named calc when only its signature fits. An expression evaluates to a calc's result, so a call in expression context whose name denotes a calc selects among the calcs alone, whatever the import order and however closely a same-named action's inputs fit — the arguments are then checked and dispatched against the calc, and the action is never evaluated — while action call = tag(3); and perform tag(3); select among actions only. A bodiless calc specializing a library function (calc def Renamed :> sqrt { in y :>> x; }, or a feature typed by one) is computed by that function through its own effective signature: the runtime binds the written arguments to the specialization's parameters as any calc's are bound — renamed, defaulted, optional; each default evaluated where it is declared with the earlier parameters in scope (in y :>> y = x + 1.0;), each value checked against the effective type and multiplicity the specialization states (in n : Integer :>> x;, [1]) before the library computes it — and lays them out in the library's order (runtime/invoke_calc.go libraryCalcPerformed, effectiveParameter, invokeLibraryPerformance), for an expression, a send and the InvokeCalc/InvokeCalcNamed API alike; a calc with a body of its own is never redirected. A nested action call with an explicit empty argument list (action call = tag();) binds nothing — a required input is unbound, a defaulted one takes its default — where only the bare perform tag; and action call : Tag; forms read the caller's same-named values resolve/invocation.go Resolver.InvocationCandidates (all matches of the winning visibility category), semantics/invocation.go Model.SelectInvocation, passes/typecheck_expr.go inferInvocation, passes/invocation.go argument, runtime/eval.go invocationTarget and runtime/invoke_action.go resolveActionSymbol for a nested action call = A(...) or perform A(...), whose receiver x->A() is the first argument (ErrAmbiguousInvocation, ErrReceiverWithNamedArgs), model/workspace.go calledDeclaration and Workspace.AmbiguousInvocationInDoc for editor navigation (a call selecting one overload navigates to it; a tied call lists every tied overload under go-to-definition and hover, and is left out of find-references and rename, which refuses to start from it), semantics/invocation.go Model.SelectCall (the checker's argument typing installed by passes.NewArgumentTyper) for queryplan/compiler.go compileInvocation and columns.go compileColumn (a tie is ErrorAmbiguousInvocation) and runtime/signal.go invokesCalc (a send names a signal unless the call selects a calc) passes/invocation_test.go (TestInvocationOverloadRedeclaredLibraryInputs, TestInvocationOverloadOmittedInputSelectsTheFittingCandidate, TestInvocationOverloadCompleteCandidateBeatsOmission, TestInvocationOverloadOmittedInputsAreAmbiguous), runtime/invocation_selection_test.go (calc_call_binds_a_library_function_through_redeclared_inputs, calc_call_selects_a_calc_over_a_more_specific_action, calc_call_omitting_an_input_runs_the_fitting_candidate, calc_call_omitting_every_input_is_ambiguous), queryplan/overload_test.go, runtime/signal_test.go:TestActionSendCallsACalcSharingASignalsName, lsp/invocation_navigation_test.go, lsp/ambiguous_invocation_navigation_test.go, conformance calc_library_overload_by_argument_type, calc_library_overload_partial_import, calc_library_overload_ambiguous, calc_library_function_shadowed_by_model, action_node_invocation_overload_inherited_input (the winning action's matching input is inherited, and the runtime frame binds it), lsp/library_invocation_test.go:TestPublishDiagnosticsSelectsLibraryOverloadByArgumentType, pilot-exec-diff w6d:complex-abs, :complex-is-zero ✅ Faithful. An argument whose type is statically unknown (an unresolved parameter type, an untyped feature, a sequence of mixed element types) keeps every candidate it could bind to applicable — a known argument beside it still rules out the candidates it does not fit, and specificity settles the rest only where every candidate types the unknown argument alike — so the call selects the one candidate left, or is InvocationSelection.Undetermined: Ambiguous, its Tied the candidates left open, reported as the invocation-ambiguous warning call of abs is undetermined between … (a tie between candidates known types fit incomparably stays the error, and so does one no value could break — the tied candidates typing the unknown argument alike, valuesMaySettle — since the run would only report it again) and typed Unknown, so no binding is judged from it; null and () have no element to type and fit every candidate alike (Argument.Empty), as before. The runtime settles such a call by the types of the values it is given — a scalar typed as the literal spelling it would be (runtime/eval.go spelledPrim: a nonnegative integer a Natural, a negative one an Integer, a fraction a Rational, a collection the type its elements share), so the value settles on the overload the checker selects for that literal — pick(in n : Natural) over pick(in i : Integer) for a positive value, as pick(1) does; an object by every type it is classified by (Argument.Also), so one fitting a candidate exactly through a classifier is not bound loosely to the one found first; an argument some candidate takes as an expr parameter is left unevaluated and unknown to the selection, so a short-circuiting built-in never runs a branch it would not have — for a calc call (runtime/eval.go evalUndeterminedInvocation, semantics/invocation.go Model.SelectAmongArguments over the tied candidates) and for a nested action call alike (runtime/invoke_action.go settleAction, the arguments evaluated once and bound by position or name to the action settled on; until then the node holds the pins of every tied action so a delivery to any is accepted, and once settled those of the action performed alone — runtime/action_frame.go pinsOf — so the node is read as a value by that action's result, never by a pin another candidate declares), and reports ambiguous invocation when they tie still (passes/invocation_test.go:TestInvocationOverloadUnknownArgumentType, :TestInvocationOverloadSelectsByCollectionLiteralElementType, queryplan/overload_test.go:TestCompileFollowsTheArgumentTypingInstalledLast, runtime/invocation_selection_test.go:calc_call_undetermined_statically_is_settled_by_the_values, :calc_call_undetermined_statically_keeps_a_deferred_argument_unevaluated, :calc_call_undetermined_statically_is_settled_by_every_classifier, :calc_call_undetermined_statically_types_a_value_as_its_literal, :action_call_undetermined_statically_is_settled_by_the_values, :action_call_undetermined_statically_reports_a_tie_the_values_leave, runtime/robustness_test.go:settled_node_read_as_a_value_by_another_candidates_result, conformance calc_library_overload_undetermined_argument, action_node_invocation_overload_undetermined_argument, action_node_invocation_overload_undetermined_result_name). The called name is resolved as any name is — KerML 1.1 §8.2.3.5.3 makes a namespace's owned, imported and inherited memberships its local resolution and §8.2.3.5.4 the first namespace outward that has one the full resolution, §7.2.5.4 hiding an imported membership behind an owned one of the name — and an invocation expression names its function by a qualified name resolved so (§7.4.9.4), the inputs of the function named being bound to the arguments (§8.3.4.8.8): the namespace owning a calc of the name hides the library function imported into it, while a nested namespace's import is that namespace's local resolution, ahead of the enclosing model's calc; the library has no standing of its own either way (resolve/invocation.go unqualifiedCandidates; TestInvocationOverloadModelShadowsLibrary, both agreeing with the pinned pilot's resolution of the same names). The pinned pilot evaluates none of the probes (ToInteger, abs, isZero over their overload sets come back as the unevaluated InvocationExpression), so the most-specific rule is self-assessed against KerML 1.1 §8.3.4.8 (a decimal literal is a LiteralRational, so ToInteger(7.9) selects RationalFunctions::ToInteger over RealFunctions'). A parameter's [m..n] multiplicity decides only whether a call may omit it: one admitting none, or one a default reaches along its redefinitions (Model.OptionalParameter, Model.ParameterDefault), is optional to the checker and to runtime/action_frame.go checkInputsBound alike, and the omitted input is read as empty or as the inherited default (passes/invocation_test.go:TestInvocationPerformedActionOptionalInputs, runtime/invocation_selection_test.go:action_call_omits_optional_inputs). Not done: a value's element count is not checked against the multiplicity
A collection operation's static result is typed by the argument the library's declaration hands through, not by the Anything[0..*] it declares (KerML 1.1 §8.3.4.8 checkSelectExpressionResultSpecialization: a select's result subsets the collection's; §9.2 Kernel Function Library ControlFunctions.kerml, collect's result "the collection of results" of mapper, select/reject/selectOne elements of collection, reduce the reducer's result, forAll/exists Boolean[1]): xs->collect {in x : C; x.mass} and xs.{in x : C; x.mass} are typed MassValue, xs->collect {in x : C; x.name} String, a nested collect by the innermost body, a body answering a sequence (x.mass, x.name) by every element type, xs->collect Mass by the named function's result, xs->select {…}/->reject {…}/->selectOne {…} as xs is, ->reduce '+' by the reducer's result — and, as the runtime hands a one-element collection back unreduced, by the element too unless the collection is known to hold two or more (a [2..*] feature, a sequence of two literals), and by the element alone where it holds one at most ([1], [0..1]), the reducer never applied — and ->forAll/->exists Boolean; the operation is the resolved library declaration, in the receiver, plain and named-argument notations alike. A body parameter declaring no type is bound to each element of the collection the body is applied to (KerML 1.1 §8.3.4.8: the body is evaluated once per element, its parameter bound to that element; grammar KerMLExpressions.xtext:309-317 PrimaryExpression's '->' … BodyExpression, '.' BodyExpression and '.?' BodyExpression operands, :361 BodyExpression, :370 ExpressionBody's BodyParameterMember, whose BodyParameter may declare no type — see docs/reference/grammar for the pinned grammars), so it takes the element type(s) that collection is statically known to hold — xs->collect {in x; x.mass} and xs.{in x; x.mass} are typed MassValue as the in x : C forms are, x.nosuch in such a body is reported as an unresolved member, and x.mass is checked — in every notation, in a nested body (xs.{in x; x.parts.{in p; p.mass}} types p by what x.parts holds) and for a reducer's second parameter. A reducer's first parameter holds an element on the first fold only and the reducer's own result on every later one (ControlFunctions::reduce folds its result back in), so it takes the element types only where the reducer's result — typed with that parameter assumed to hold them — conforms to every element type, the fixed point the fold keeps: cs->reduce {in a; in b; a}, {… b}, {… a.next} over a next : D of Ds type a by the elements, while cs->reduce {in a; in b; a.mass} hands a MassValue back and so leaves a untyped as before, its a.mass refused (no scope for member lookup in a) rather than passed to fail at run time from the second fold on — as is a conditional's result, which the typer holds to the Anything the library's 'if' declares, so {in a; in b; if a.mass > b.mass ? a else b} needs in a : C to read a.mass; the assumption is answered from a side table for the one query that judges it, that query cut short so nothing derived under it is memoized, and the decision itself is memoized as the parameter's supertypes; the inferred types are the parameter's supertypes in the semantic model's side tables, the AST untouched, and are consulted by member resolution as a declared type is. Only a body over a collection whose elements cannot be typed leaves its parameter untyped and the result the library's Anything, never a guess. The multiplicity stays the declaration's (collect/select/reject/reduce [0..*], selectOne [0..1], forAll/exists [1]). A collection value bound to a typed feature or passed as an argument is judged by these element types, each element a sequence-valued body answers on its own, so part b : Boat = vs->select {in v : Vehicle; true} and Sail(vs.{in v : Vehicle; v}) are refused where the declared Anything would bind. A body reading the feature it values (total = cs->collect {in x : C; total}->reduce '+') terminates under the same guard as an argument that does semantics/collection.go Model.CollectionResultTypes, CollectionElementTypes, sourcesOf, invocationSources, holdsAtLeastTwo, collectSources, appliedSources, bodySources, argumentTo, sourcesElementTypes; semantics/bodyparam.go Model.BodyParameterElementTypes, reducerFeedsBack, bodyApplicationOf, appliedOver, appliesOverElements, reached from semantics/model.go Model.DirectSupertypes (assumedSupers), over the body applications symbols/bodyscopes.go ExprWalker.Applied reports; resolve/document.go Resolver.memberless; reached from semantics/valuetype.go Model.ExprResultType, exprConformance, invocationConformance and semantics/operator_conformance.go resultTypes; semantics/invocation.go SelectCall/callArguments (typingArgs); passes/typecheck_value.go exprChecker.checkValueConformance, unboundElementTypes, invocationResultTypeSymbol, invocationResultParameter (selects under the chains being typed) semantics/collection_test.go TestCollectionResultTypes, TestCollectionResultMultiplicity, TestCollectionNestedAndSequenceBodies, TestCollectionArgumentNotations, TestCollectionUntypedBodyParameterTakesElementType, TestCollectionReduceUntypedParametersTakeElementType, TestCollectionReduceGuardMemoizesDecision, TestCollectionNestedUntypedBodyParameters, TestCollectionUntypedBodyFallsBackToLibraryResult, TestCollectionSelfReferentialBodyTerminates, TestCollectionResultConformance, TestCollectionReduceMayReturnTheElement; passes/typecheck_index_test.go:TestBodyParameterTakesElementType, :TestReducerFirstParameterTypedOnlyWhereResultFeedsBack; passes/typecheck_value_test.go:TestValueCollectionResultIsJudged, :TestValueReduceResultIsJudged, :TestArgumentCollectionResultIsJudged; passes/typecheck_dimension_test.go:TestRecursiveRollupThroughACall, :TestRecursiveRollupThroughACollectBody; passes/typecheck_trigger_test.go:TestCollectAndSelectTriggerArguments; passes/w7g_one_type_test.go:TestW7GASelectedEnumeratedValueKeepsItsOperandType; conformance calc_collect_untyped_body_reads_element; grammar PrimaryExpression ('->' … BodyExpression, '.' BodyExpression, '.?' BodyExpression) ✅ Faithful for select/reject/selectOne/forAll/exists (the pinned pilot subsets a select's result from its collection and types the rest by the declared result); ⚠️ Approximate for collect/reduce (self-assessed: the specification declares their result Anything[0..*] and the pilot types it so, giving xs.{in x : C; x.mass} no static type; typing it by the body is stricter than the reference, so a diagnostic it raises — a when trigger over a collect of Integers, an enumerated value collecting Reals — the pilot stays silent on; typing an untyped body parameter by the collection's elements is stricter than the pilot too, which leaves it the library's Anything and so neither types nor resolves x.mass in such a body)
An unqualified call to a Kernel Function Library function the model does not import is unresolved for the checker and the runtime alike: the library packages are members of the root namespace but not implicitly imported, so a bare sqrt(4.0) with no import RealFunctions::*; draws the unresolved diagnostic offering the imports that would resolve it and fails to evaluate with the same text, RealFunctions::sqrt(4.0) resolves anywhere, and under the import the call checks clean, is selected among the imported overloads and evaluates; the OpenSysML extension functions (exp, ln, log, atan2) are gated by the same rule resolve/invocation.go ResolveInvocationName (the name resolves as any other), runtime/eval.go invocationTarget (dispatch by the declaration resolved, passes.SelectInvocation choosing among several) passes/invocation_test.go:TestInvocationUnimportedLibraryFunction, :TestInvocationQualifiedLibraryFunctionArgumentsChecked, :TestInvocationOverloadOnlyImportedPackagesContribute, runtime/library_functions_test.go:TestLibraryFunctionUnqualifiedNames, :TestLibraryFunctionQualifiedCallNeedsNoImport, conformance calc_library_function_unimported, calc_library_function_imported, lsp/library_invocation_test.go:TestPublishDiagnosticsAgreesWithRuntimeOnLibraryCall, :TestPublishDiagnosticsKeepsExtensionFunctionImportGated, lsp/invocation_navigation_test.go:TestDefinitionReachesImportedLibraryFunction ✅ Faithful. Observed against the pinned pilot: it reports Couldn't resolve reference to Element 'sqrt' for the unimported call and resolves the qualified one
No false positives on the shipped library and examples corpus guard model/typecheck_expr_corpus_test.go ✅ Faithful
[ is not an index in KerML (pilot validateOperatorExpressionBracketOperator, Use #(...) for indexing; the specification gives [ no KerML function — BaseFunctions::'[' is abstract): every bracket in a .kerml document warns at the operator expression, a .sysml document is judged by the quantity rule instead passes/typecheck_expr.go exprChecker.checkBracket; code bracket-operator typecheck_operator_test.go:TestBracketOperatorInKerML, TestBracketOperatorIsKerMLOnly; census probe validateOperatorExpressionBracketOperator.kerml ✅ Faithful (warning, as the pilot)
The unary ~ is the operator KerML 1.0 §8.2.5.8.1 leaves undefined — it maps to DataFunctions::'~', declared abstract and defined by no library the runtime applies, and "a tool should give a warning if this operator is used": every ~x warns at the operator, in a value, a filter condition or a multiplicity bound, in .kerml and .sysml alike, and stays a warning in strict conformance since the specification asks for a warning, not a rejection passes/undefined_operator.go UndefinedOperatorPass (syntax tier over ast.Inspect, so an unresolved name elsewhere does not suppress it); code undefined-operator undefined_operator_test.go:TestUndefinedOperatorWarns, TestUndefinedOperatorSurvivesUnresolvedReference ✅ Faithful (the warning §8.2.5.8.1 asks for; the pilot raises none, so one-sided — see bitwise-complement.md)
A cast's argument type conforms to its target in either direction (KerML as; pilot validateOperatorExpressionCastConformance, Cast argument should have conforming types): every type of the argument — declared, inherited, redefined, an alias, a chain's or an invocation's result — against the target, in KerML and SysML alike; an argument whose type is not statically known (an untyped feature, null, a body, a sequence, a conditional) draws nothing passes/typecheck_expr.go exprChecker.checkCast, semantics/operator_conformance.go Model.CastConformance; code cast-conformance typecheck_operator_test.go:TestCastConformanceUnrelatedTypes, TestCastConformanceRelatedTypes, TestCastConformanceInSysML; census probe validateOperatorExpressionCastConformance.kerml ✅ Faithful (warning, as the pilot)
The unit of a quantity x [u] is a TensorMeasurementReference (SysML; pilot validateOperatorExpressionQuantity, Should be a measurement reference (unit).): a unit, an alias, a feature typed by a measurement reference (a frame, a custom unit, an in parameter), an element # selects from a sequence of them, and — as the pilot judges them — an operator, cast or sequence over them whose declared result is wider (m * s, m * 2, km / h, (m, 3)) when an operand it is passed by value is one; a number, a String, a quantity value (2 [m] too), a dimensionless computation, an untyped feature or a conditional (its branches are expression bodies, so its result is Anything) warns at the unit, wherever an expression is typed (values, guards, triggers, assignments, payloads, calc and constraint bodies); the operators inside the unit are type-checked on their own, so a non-conforming cast there warns as well passes/typecheck_expr.go exprChecker.checkBracket, semantics/operator_conformance.go Model.UnitOperandConformance; code quantity-unit typecheck_operator_test.go:TestQuantityUnitMeasurementReferences, TestQuantityUnitNotAMeasurementReference, TestQuantityUnitInEveryExpressionPosition; census probe validateOperatorExpressionQuantity.sysml ✅ Faithful (warning, as the pilot; seq#(i) is judged by seq, where the pilot accepts either argument of #; a quantity value nested as the unit warns, where the pilot's Anything-typed [ accepts it — drafted in omg-issues.md)
Non-scalar conformance of bound values (specialization hierarchy, enumeration literals) passes/typecheck_value.go checkValueConformance, invocationResultTypeSymbol with semantics/redefinition.go Model.ResultParameterOf (an invocation is typed by the result parameter of the behavior it names, inherited ones included); a computed value — an invocation's result or an operator's (computesValue) — is typed by semantics/valuetype.go Model.ExprResultTypes and judged against a scalar-typed feature too by boundTypesConform over semantics/exprtype.go Model.ClassifiesTypes, the classification the runtime's write conformance applies (runtime/eval.go valueConforms/classifyValue), so only a result no value of the feature's type can be — ClassifiesNone — is refused and the two verdicts cannot disagree; checkBoundValue hands the elements the lattice typed to the lattice alone (latticeTyped), so a scalar mismatch is reported once. passes/typecheck_expr.go inferInvocation types a call's result by its declaration whether or not the effective input signature can be determined — a result-only or parameterless behavior is typed as any other; only the arguments go unchecked when the signature cannot be TestValueUnrelatedInstanceDoesNot, TestValueEnumerationLiteralOfOtherEnum, passes/w6d_value_type_test.go (a calc result binding to an unrelated feature, through specialization, and inherited; a behavior with no result stays unjudged), passes/typecheck_feature_reference_test.go TestComputedValueConformanceToScalarFeature (attribute s : String = GetReal(), through a bodiless specialization, a + 1.0, a structured result), TestComputedValueConformanceLeavesOverlapAndUnknownAlone (Real = GetInt(), Integer = GetReal() conform along the lattice as the values may; a behavior declaring no result, or an action, stays unjudged) ✅ Faithful (a value is judged by its static type whether it is a name, a literal, an invocation of a behavior declaring a result or an operator expression, against a scalar-typed feature and a structured one alike; a behavior that declares no result has no static type and leaves the binding to the runtime, which refuses the write there)
Multiplicity conformance of bound values passes/typecheck_value.go checkValueCount, effectiveRange, wording shared with the runtime through semantics/multiplicity.go Range.CountViolation TestValueTooManyValuesForUpperBound, TestValueTooFewValuesForLowerBound, TestValueEmptyCollectionForLowerBound, TestValueCountAgainstRedefinedMultiplicity, TestValueNestedCollectionCountsItsElements, TestValueCollectionOfReferencesIsNotCountedStatically ⚠️ Approximate (stricter than the reference, which reports no static count violation for attribute x : Real[1] = (1.0, 2.0) at all — matched validator runs in the PR — so KerML 1.0 §7.4.5 is the authority for this check, not the pilot. Only a literal, or a collection literal of them, has a statically known element count — flattened as binding flattens it; a reference may itself be multi-valued, so it and any collection holding one are left to the runtime check when the feature value materializes)
Collection element types (each element against the feature's type) passes/typecheck_expr.go checkUsageValue TestValueCollectionElementTypes ✅ Faithful

View and Viewpoint Members (SysML v2 §8.3.20 Views, §8.3.26 Viewpoints; SysML.xtext ViewRenderingMember, FramedConcernMember, StakeholderMember, ActorMember)

Each of these keywords owns a usage through a dedicated membership, and each usage is written either as a reference to an existing element or as a declaration introduced by the kind keyword the notation spells out. A reference declares no name of its own: the name it answers to is its reference's, derived by ast.EffectiveName (KerML §7.3.4.5), which is why a reference to an inherited element is not a name conflict.

Semantic Rule Implementation Test Case Status
render owns a RenderingUsage through a ViewRenderingMembership: render asTree; references the rendering the view uses, render rendering r : AsTree; declares one. No ValuePart, and no definition form. The declaration's UsageDeclaration is optional, so it may be anonymous (render rendering : AsTree;) ast/defusage.go UsageViewRendering; parser/defusage.go parseDefUsage (render/frame dispatch) and parseReferenceMemberUsage; symbols/builder.go (SymbolRenderingUsage), passes/typecheck.go (typed by a rendering def), semantics/implicit.go (Views::Rendering), export/kinds.go (ViewRenderingMembership), export/rdf_in.go usageHead (which form to write back is decided by the reference, not the name, so an anonymous declaration keeps its kind keyword) parse/view_members.golden, export/testdata/convert/kind_keyword_synonyms.golden.sysml, parser/negative_test.go (render_definition, render_reference_value), passes/nameres_test.go TestRenderReferenceToInheritedRenderingIsNoConflict, TestRenderDeclarationOfInheritedNameConflicts, corpus 42. Views/* ✅ Faithful (parse and naming; what a view renders is read from this member by view/view.go Renderer.KindOf — see the rendering rows)
frame owns a ConcernUsage through a FramedConcernMembership: frame 'system breakdown'; references the concern framed, frame concern c : SafetyConcern; declares one, possibly anonymously (frame concern : SafetyConcern;). Its body is a requirement body ast/defusage.go UsageFramedConcern; parser/defusage.go (same dispatch, body parseRequirementBody); symbols/builder.go (SymbolConcernUsage), passes/typecheck.go (typed by a concern def), semantics/implicit.go (Requirements::ConcernCheck), resolve/document.go parameterizedByName, export/kinds.go (FramedConcernMembership) parse/view_members.golden, parser/negative_test.go (frame_definition), corpus 42. Views/Viewpoint Example.sysml ⚠️ Approximate (frame concern SafetyConcern; follows the grammar's ConstraintUsageDeclaration and declares a concern usage named SafetyConcern; the reference to a concern of that name is frame SafetyConcern;. Framing is checked against the viewpoint's concerns by semantics/conformance.go Model.ViewConformance — see the viewpoint conformance row)
stakeholder and actor own a PartUsage through a StakeholderMembership / ActorMembership; both are declarations (stakeholder se : Engineer;, actor driver : Person;) and neither has a definition form ast/defusage.go UsageStakeholder, UsageActor (the former ast.ActorMember node is gone); parser/defusage.go parseDefUsage; symbols/builder.go (SymbolPartUsage), passes/typecheck.go (typed by a part def), semantics/implicit.go (Parts::Part), runtime/context.go memberBindings (actor binding, name via ast.EffectiveName), export/kinds.go parse/view_members.golden, parse/requirement_members.golden, parser/behavior_test.go TestParseRequirementBody_Actor, parser/negative_test.go (stakeholder_definition, actor_definition, stakeholder_no_declaration, actor_no_declaration), runtime requirement_actor.sysml, corpus 41. Use Cases/*, 42. Views/* ✅ Faithful (a stakeholder or actor definition is rejected: the notation has only the usage, typed by the party's definition)
satisfy names the requirement satisfied (satisfy vehicleSpecification by vehicle;) or declares the satisfaction (satisfy requirement r : Req1 by v;); a view body's satisfy viewpoint; is the same form parser/defusage.go parseUsage UsageSatisfy branch (RequirementUsageKeyword UsageDeclaration?, then ValuePart?, then by) parse/satisfy_reference.golden (incl. the anonymous forms satisfy requirement by vehicle; and satisfy requirement : VehicleSpecification by vehicle;), parse/view_members.golden, parser/satisfy_subject_test.go ⚠️ Approximate (the reference is recorded as a Subsetting rather than a ReferenceSubsetting, so passes/typecheck.go can require the target to be a requirement usage; a satisfy reference therefore takes no effective name, so semantics/conformance.go Model.SatisfyTarget reads the target from the Subsetting relationship instead. Settled as a cross-package migration, not a parser fix. SysML.xtext:2119 and :2272 both own an OwnedReferenceSubsetting here, so ast.RelReferences is the faithful kind; recording it that way was tried and moves the reading of the relationship in passes/typecheck.go:399, passes/constraint.go:230 and runtime/satisfy.go:134 — the last owned by another slice — so it is left for a coordinated change rather than half-migrated)
frame and render are keywords of SysML.xtext only, so they are ordinary names in a .kerml file (the Kernel Semantic Library writes in frame : SpatialFrame[1]) and syntax words in a .sysml one parser/defusage.go atMemberKeywordUsedAsKeyword (the language decides; in KerML the following token does) parse/w7c_sysml_only_words_name_kerml_features.kerml, parse/view_members.golden, parser/negative_test.go (frame_no_concern, frame_concern_no_declaration, render_no_rendering), parser/modifier_kind_ambiguity_test.go:TestFrameAndRenderNameKerMLFeatures, libs/reserved_keyword_name_test.go, TestStdlibConformance ✅ Faithful (matched: viewpoint def V { frame; } and view def V { render; } are rejected by us and by build/pilot-validator/validate-sysml, where main declared a feature named after the keyword; frame c1; references a concern and frame concern c1 … declares one, both clean in ours and in the reference; the same words name KerML features, clean in build/pilot-kerml-validator/validate-kerml. Where the two still differ we are the wider: part frame : T; in a SysML part body is no viable alternative in the reference and a reserved-keyword-name warning in ours, on the normative-library precedent of that row)
A view owns at most one view rendering: a second render member is an error, in a view definition and in a view usage alike (SysML v2 §8.3.26; validateViewDefinitionOnlyOneViewRendering / validateViewUsageOnlyOneRendering) passes/w8d_view_rendering.go W8DViewRenderingPass passes/w8d_view_rendering_test.go; see the SysML-half validation section for the reference verdicts ✅ Faithful
expose in a view body is an Import see the Name Resolution section's expose rows parser/expose_test.go, resolve/expose_test.go, parse/view_expose.golden ✅ Faithful (validateExposeOwningNamespace reports an expose outside a view usage — see the Name Resolution section's expose rows)
A view usage's expose admits only what satisfies the filter conditions of the usage and of its view definition and that definition's supertypes (SysML v2 §7.24 Views: a view usage inherits its definition's element filters) resolve/filter.go Resolver.importAdmits, inheritedViewConditions (memoised per view scope, cycle-guarded), viewOwnConditions semantics/expose_test.go TestExposedElementsSatisfyInheritedViewConditions, cmd/sysml/render_test.go TestRenderAllOOSEMViews (library-backed view definitions) ✅ Faithful

Structural, Interface and Analysis Notation (SysML v2 §7.12 Ports, §8.2.2.14 Interfaces, §8.2.2.19 Analysis Cases, §8.3.9.11 Occurrences)

Notation exercised by the Open-MBEE corpus models (starkit, Dragon, DesertKite/OOSEM, the spacecraft example notebooks). Conjugation is a semantic relationship, not parser sugar: the ~ is kept on the typing relationship (ast.Relationship.Conjugated) and the reversal of in/out is computed in the semantics layer over the conjugation parity of the typing/specialization chain.

Semantic Rule Implementation Test Case Status
port p : ~P types a port by the conjugated port definition P::'~P' (§7.12.3); its features are P's with in/out reversed and inout unchanged, and conjugation composes, so a conjugate of a conjugate has P's directions ast/defusage.go Relationship.Conjugated; parser/defusage.go parseRelationshipClauseTarget; semantics/conjugation.go ConjugateDirection, superEdges, typeEdge, featureEdges, conjugatedSupertypes, PortFeatures, IsConjugated (a declaration's feature typing carries the conjugation, so it is read ahead of a redefinition clause written before it) parse/conjugated_port_type.golden, semantics/conjugation_test.go TestConjugationReversesDirections, TestDoubleConjugationRestoresDirections, TestConjugationOnRedefiningPort, parser/negative_test.go (conjugated_no_type, conjugated_no_type_after_name) ✅ Faithful
A port usage conforms to the definition it conjugates, and two ports match when each named feature of one has a feature of the other with a conforming type and the conjugate direction (§7.12.2) semantics/conjugation.go PortsConform, featuresMatchConjugate, featureTypesConform semantics/conjugation_test.go TestConjugatedPortConformance ✅ Faithful
The ports at the two ends of an interface must have conjugate directed features: what one end sends the other receives semantics/conjugation.go InterfaceEndPortMismatch, endPortFeatures; passes/constraint.go checkInterfaceEndConjugation (code port-conjugation) passes/constraint_test.go TestConstraintInterfaceEndConjugation, TestConstraintInterfaceFlowPairsDirectedFeatures, semantics/conjugation_test.go TestInterfaceEndConjugation, passes/w8g_notation_residue_test.go TestW8GInterfaceConjugationStaysAWarning ⚠️ Approximate (re-adjudicated: the pinned validator is silent on the same fixture, so this is a one-sided check kept as a warning on the precedent set for our other deliberately wider checks, and it fires only for an interface whose two ends both declare a resolvable port type; undirected features carry no flow, so they are not required to match; a feature the interface's own flow pairs with a complementary-direction, type-conforming feature of the other end is paired by that declaration rather than by name, so the name match is not required of it; the ends of a connect/flow clause take their types by implicit redefinition of the interface's ends, which is checked as end identity, not as direction)
A connect, interface usage or flow joining two ports whose definitions neither specialize one another nor a common definition of the model, and neither of whose directed features all match the other's with the conjugate direction and a conforming type (§7.12.2), is reported as the lint port-type-mismatch; a connector typed by a definition whose ends are port-typed is left to that definition's port-conjugation check semantics/conjugation.go ConnectedPortsMismatch; passes/lint_port_type.go PortTypeMismatchPass passes/lints_test.go TestPortTypeMismatch*; the false-positive scan over examples/ and tests/testdata/ ⚠️ Approximate (a lint: the specification states no such constraint and the pinned validator reports none, so it is a warning in every mode and can be switched off)
Only a port usage or a connector end may be typed by a conjugated port definition, and ~ must name a port definition. The rule is a SysML one — ~ there occurs only in a ConjugatedPortTyping (SysML.xtext FeatureTyping: OwnedFeatureTyping \| ConjugatedPortTyping) — so it is scoped to a conjugated typing: a KerML declaration conjugation is a Conjugation between any two Types (KerML 1.1 §7.4, ClassifierConjugationPart/FeatureConjugationPart) and demands no port passes/typecheck.go checkConjugatedTyping, reached from checkRelationships for ast.RelTyping alone passes/typecheck_test.go TestTypeCheckConjugatedTyping; passes/f90_conjugation_scope_test.go (TestF90KerMLConjugationIsNotAPortTyping over testdata/passes/f90_conjugation.kerml, TestF90KerMLConjugationFormsAreClean, TestF90SysMLConjugatedPortTypingIsClean over testdata/passes/f90_conjugation_ports.sysml, TestF90SysMLConjugatedPortTypingStillChecked) ✅ Faithful (matched runs: the pinned validate-kerml is silent on every KerML conjugation form in the fixture, and the pinned validate-sysml rejects ~Q for a non-port Q as an unresolvable ConjugatedPortDefinition, so the SysML check stands)
An interface body may declare a default end with no declaration at all: end; is an anonymous port usage (§8.2.2.14.1 DefaultInterfaceEnd: isEnd ?= 'end' Usage) parser/parser.go bodyContext/pushBodyContext; parser/defusage.go parseAnonymousEnd, parseAnonymousEndUsage, anonymousUsageKind parse/end_usages.golden, resolve/analysis_test.go TestResolveDragonStructures ✅ Faithful
Anywhere else a bare end; is not standard notation: only DefaultInterfaceEnd makes the usage declaration optional, and every other end form (ReferenceUsage, EndUsagePrefix + a kind keyword) requires an Identification or a specialization part parser/defusage.go parseAnonymousEndUsage (typed diagnostic naming the fix, end ref;) parser/negative_test.go (end_outside_connector, end_outside_connector_package) ✅ Faithful (see the known limitation on Dragon.sysml below)
require Q::r; / assume Q::r;, braced or not, subset a requirement by a qualified reference, and the body may redefine a feature of it by its qualified (:>> R::f = expr) or its plain name (:>> f = expr), which the member inherits through the reference subsetting ast/behavior.go RequireMember.Reference, AssumeMember.Reference (a full *ast.QualifiedName, not a final segment); parser/behavior.go parseRequireMember, parseAssumeMember; parser/behavior.go tryParseConstraintReference (the bodyless spelling, read as a reference when the name is qualified or specialized); resolve/document.go resolveConstraintReference, walkConstraintBody, lookupConstraintRefFeature; runtime/condition.go appendReferencedConditions (a referenced requirement's conditions are evaluated with the referring one) parse/require_qualified_requirement.golden, parser/w8g_constraint_reference_test.go, runtime/testdata/conformance/requirement_reference_subsetting, resolve/analysis_test.go TestResolveQualifiedRequirement, TestResolveRequiredRequirementFeatureByPlainName, TestResolveRequiredRequirementUnknownPlainName, TestResolveQualifiedRequirementUnresolved, TestResolveQualifiedRedefinitionUnresolved, parser/negative_test.go (require_qualified_malformed_body, require_qualified_trailing_colons, require_reference_no_specialization_type, require_reference_unclosed_body) ✅ Faithful (require Q::r; with no body is read as a reference too, and the referenced requirement's conditions reach the runtime, so the reference is visible where satisfaction is decided rather than only in resolution)
A require/assume body is a namespace of its own, resolved to whatever depth it is written to: a declaration nested in it has its own body walked recursively, what the body declares is visible inward but does not leak outward, and the referenced requirement's features are offered to the body's direct members, which is what the reference subsetting inherits them to symbols/builder.go buildConstraintBodyScope, ConstraintBodyScope (a body-local child scope keyed by the member), read by symbols/bodyscopes.go, resolve/document.go walkConstraintBody and resolve/references.go resolve/constraint_body_test.go TestResolveTypoAtDepthTwoInARequireBody, TestResolveTypoAtDepthThreeInAnAssumeBody, TestResolveDeepRequireBodyResolves, TestResolveRequireBodyNamesDoNotLeakOutward, resolve/analysis_test.go TestResolveNestedRedefinitionPrefersOwnType ✅ Faithful
Every tier reads a require/assume body in that same body scope, so name resolution, type checking and condition evaluation agree on which declaration a name in the body denotes (a body-local name shadows one of the same name outside it) passes/typecheck.go checkBehaviorMember (AssumeMember/RequireMember route through symbols.ConstraintBodyScope), runtime/condition.go appendConditions, Condition.Owner (nearest owning scope, since a body-local scope owns no symbol) passes/typecheck_constraint_body_test.go TestTypecheckReadsARequireBodyLocalName, TestTypecheckReportsAMismatchNestedInARequireBody, TestTypecheckRequireBodyLocalNameShadowsTheEnclosingOne, TestTypecheckRequireBodyLocalValuesStayClean, runtime/condition_test.go TestRequireBodyConditionReadsABodyLocalName, runtime/conditions_of_test.go TestConditionsOfCarryScopeAndOwner ✅ Faithful
snapshot and timeslice are the two portion kinds of an occurrence usage (PortionKind, §8.3.9.11 OccurrenceUsage::portionKind); either prefix makes the declaration an occurrence usage whatever kind keyword follows. PortionUsage ends in Usage, whose declaration is optional, so an anonymous portion (timeslice;) is standard notation and is accepted without a diagnostic. Both keywords are read by the same prefix, so they agree on every declaration spelling, including a quoted name (snapshot 'launch event';) ast/defusage.go PortionKind, Usage.Portion; parser/defusage.go (portion prefix), parser/behavior.go (portion-prefixed behavior parameters); ast/dump.go; passes/typecheck.go declKind.portion, isOccurrenceUsage; export/rdf_out.go/rdf_in.go parse/occurrence_portions.golden, parser/occurrence_modifier_test.go, resolve/analysis_test.go TestResolveDragonStructures, parser/negative_test.go (timeslice_no_subject, timeslice_usage_no_type, timeslice_unterminated) ✅ Faithful (parse, naming and resolution; a portion is not related to its whole occurrence at runtime — see below)
A structural body's two-ended first <a> then <b>; is a SuccessionAsUsage over its members (SysML.xtext SuccessionAsUsage, §8.2.2.13.3), so ordering two portions in time leaves both readable — an initial-node member named a would shadow the portion it names. The one-ended first a; stays an initial node, and an action-carrying body's first still opens its InitialNodeMember; a state body is a structural body here, not an action-carrying one (see the state-machine row on first a then b;) parser/succession.go atTwoEndedFirst; parser/defusage.go defBodyContext/usageBodyContext; parser/parser.go (body-member dispatch, bodyContext.carriesActions) parse/occurrence_portion_succession.golden, conformance/snapshot_succession_portion_reads.sysml + .expected.json ✅ Faithful (snapshot … at <time>, happens before as a body member, and event-bounded timeslice ranges do not parse — a portion is declared by name and ordered by successions)
The standard view/diagram library is part of the vendored stdlib, so view v : StandardViewDefinitions::gv; resolves libs/stdlib/Systems Library/StandardViewDefinitions.sysml (already vendored: the eight standard view definitions with their short names) model/standard_views_test.go TestStandardViewDefinitionsBundled, TestStdlibConformance ✅ Faithful
A qualified reference whose first segment names no loaded namespace is reported as such, naming the declarations that do carry the trailing name resolve/qualified.go (unresolved-namespace diagnostic), symbols/index.go FQNsEndingIn model/standard_views_test.go TestVendorViewNamespaceDiagnostic, resolve/analysis_test.go TestResolveMissingStandardViewNamespace ✅ Faithful
Every usage element subsets the most general base usage Base::things, whose that feature is therefore visible in a usage body (§7.6, [KerML 8.4.2]) semantics/implicit.go implicitBaseUsage, semantics/reference.go contributors semantics/implicit_test.go TestImplicitBaseUsageContributesThat, model/that_constraint_test.go TestThatResolvesInAssertedConstraint, model/scope_names_test.go TestVisibleNamesClassImplicitMembersNeedOccurrences ✅ Faithful (a member-contribution edge only: it is deliberately not a direct supertype, so conformance and DirectSupertypes are unchanged; a KerML type declaration — class, struct, assoc, behavior, predicate, interaction, which the parser records as a usage node — is excluded, since it specializes a base type instead, so it no longer reached Base's members where the resource set omits the library file its real supertype lives in)
The notation the Open-MBEE corpus models write stays clean at every validation tier: a conjugated interface and connection end, connect/flow between two such ends, and a portion prefixed onto a kind keyword and named as its whole occurrence is (item item1 { timeslice item item1; }) the rules above (parser/defusage.go end and portion prefixes, semantics/conjugation.go, passes/typecheck.go checkConjugatedTyping) passes/integration_test.go TestPassesGoldenCorpusNotation over testdata/passes/corpus_notation.golden ((no diagnostics)), parser/negative_test.go (conjugated_end_no_type) ✅ Faithful
A succession may be written with no keyword at the start of a namespace member: first a::b then c; (SuccessionAsUsage) parser/namespace.go parseMember, parseSuccessionAsUsage parse/succession_as_usage.golden ✅ Faithful
Each end of a binding connector is a ConnectorEnd, so it names a feature by a QualifiedName or by a feature chain each of whose chaining features is itself a qualified name (§8.2.2.9.2 BindingConnectorAsUsage/ConnectorEndMember, [KerML 8.3.3.2] OwnedReferenceSubsetting, OwnedFeatureChaining): bind A::b.C::d.e = F::g;. The chain is recorded segment by segment (nested ast.FeatureChainExpr whose Member is the full qualified name), and the end is a ReferenceSubsetting, so it resolves outside the connector rather than as an inherited redefinition parser/defusage.go parseBindingEnds, parseConnectorEnd (each end an ast.ConnectorEnd of Usage.ConnectorEnds), parseRelationshipTarget; resolve/document.go resolveFeatureChain (a qualified chaining feature resolves outward when the previous element has no such member) parse/binding_qualified_ends.golden, resolve/analysis_test.go TestResolveQualifiedBindingChain, parser/negative_test.go (binding_end_qualification_no_name, binding_end_chain_trailing_dot, binding_end_chain_trailing_dot_qualified, binding_end_unterminated, binding_end_no_target) ✅ Faithful
A binding connector's end may declare its own name: bind e1 ::> a = e2 references b;, KerML binding of e1 ::> a = e2 references b; (§8.2.2.9.2 BindingConnectorAsUsage → ConnectorEndMember, [KerML 8.3.3.2] ConnectorEnd: an optional crossing multiplicity, an optional Name ReferencesKeyword, then the OwnedReferenceSubsetting). The name belongs to the end — a ReferenceUsage/Feature the binding owns through an EndFeatureMembership — never to the binding, so bind e1 ::> a = b names no connector; a following [mult], ::> or references decides, as it does for successions and connectors. The end's reference subsetting resolves in the binding's owner scope, the semantic binding joins the two referenced features, and the RDF graph states the end through sysml:connectorEnd, EndFeatureMembership, sysml:declaredName/sysml:name, and sysml:references (with sysx:endReferencesKeyword where the source wrote the word) ast/defusage.go ConnectorEnd (DeclaredName, AttachedTarget, ReferencedTarget), Usage.ConnectorEnds; parser/defusage.go parseBindingDeclaration, parseBindingEnds, parseConnectorEnd; symbols/builder.go buildConnectorEnds; resolve/document.go, resolve/references.go (end relationships resolve in the owner scope); semantics/connector.go clauseEndTarget; lower/binding.go lowerBinding; export/rdf_out.go bindingEnds, export/end_forms.go endShape, connectorEndText, relatedEnds, endNameText parse/binding_connector_ends.golden, parse/kerml_binding_connector_ends.golden, parser/negative_test.go (binding_named_end_no_target, binding_named_second_end_no_target, binding_named_end_no_eq, binding_ends_then, binding_named_end_expression, binding_declaration_bind_no_ends, binding_of_named_end_no_target), symbols/builder_kinds_test.go:TestBuildBindingConnectorEndsAreTheBindingsMembers, resolve/w12e_visibility_test.go:TestBindingConnectorEndNamesAreEndsAndTheirTargetsResolve, lsp/hover_test.go:TestHoverBindingConnectorEnds, lsp/definition_test.go:TestDefinitionBindingConnectorEnds, lsp/rename_test.go:TestRenameBindingConnectorEnd, export/binding_connector_ends_test.go (TestBindingConnectorEndsAreStatedLikeSuccessionEnds, TestKerMLBindingConnectorEndsCarryTheRoundTripWithoutSourceText, TestBindingEndsWithoutANotationAreRefused) ✅ Faithful (every fixture form is accepted by the pinned pilot validator and every negative is rejected by it; a qualified end name, binding of P::a = b, is written back from the graph alone by its shortest name, as every reference is)
A connection usage may state its ends where its own name would go and then carry an ordinary body: connect x.p to r.p { … }, connect (a, b, c) { … } (§8.2.2.9.2 ConnectionUsage → ConnectorPart UsageBody). The keyword-less form is read by the same parseUsage path every declaration is, so the body's members are the usage's own members — nothing is parsed and discarded — and each end is a feature chain of any depth parser/defusage.go atDefUsageStart, parseDefUsage (connect branch), parseUsage (skipIdentification), parseTierBEnds, atConnectorShorthandEnds, atEndThenKeyword parse/connector_usage_body.golden, parser/negative_test.go (connect_body_unclosed, connect_to_no_target_before_body, connect_no_ends, connect_body_no_ends) ✅ Faithful (the keyword is the ConnectorPart, so it states at least one end: connect; and connect { … } are reported. A multiplicity written after the keyword is the first end's, connect [1] a to [1] b)
A KerML binary connector is named only where from follows the declaration: connector c from a to b;, connector all from a to b;. Without from, what follows connector is the first ConnectorEndMember, itself possibly a multiplicity and a name that reference-subsets the feature it attaches to (KerML.xtext:836 BinaryConnectorDeclaration, :856 ConnectorEnd): connector eng to t; declares no name and relates eng, connector a ::> eng to t; declares an end a of the connector that references eng, connector [0..1] eng to [1..*] u; puts the multiplicities on the ends parser/defusage.go atConnectorBinaryEnds, parseConnectorEnds, parseConnectorFromTo; ast.ConnectorEnd (DeclaredName, AttachedTarget, ReferencedTarget); symbols/builder.go buildConnectorEnds (an end's name is the connector's member, in a scope of its own); export/rdf_out.go connectorEnd; export/end_forms.go standardEnds, connectorEndText (binary connectors add sysml:sourceFeature/sysml:targetFeature, every arity carries sysml:relatedFeature) parse/kerml_binary_connector.kerml (every form: bare, chained, multiplicity-bearing, ::>/references ends, from, all, all from, n-ary), parser/negative_test.go (binary_connector_no_first_end, binary_connector_no_to, binary_connector_no_second_end, binary_connector_named_end_no_target, binary_connector_end_multiplicity_no_target, binary_connector_name_without_from), symbols/builder_kinds_test.go:TestBuildKerMLBinaryConnectorEndsStayOutOfTheFeaturingType, resolve/w12e_visibility_test.go:TestKerMLBinaryConnectorFirstEndIsAnEnd, lsp/definition_test.go:TestDefinitionKerMLBinaryConnectorFirstToken, lsp/hover_test.go:TestHoverKerMLBinaryConnectorFirstToken, export_test.go:TestKerMLBinaryConnectorEndsCarryTheRoundTripWithoutSourceText, export/testdata/convert/connector_ends.kerml ✅ Faithful (the pinned pilot parses the three corpus files that use these forms clean, and so do we; the graph carries the end's name structurally and, where the source spelled it references, that word as sysx:endReferencesKeyword; a graph stating neither is written back with ::>)
An interface usage may likewise state connector-style ends where its name would go, with or without a body: interface b1.p to b2.p { … }, interface differential.leftDiffPort to rearAxle.leftHalfAxle.axleToDiffPort; (§8.2.2.14 InterfaceUsage → InterfacePart), while interface named : Coupling connect a to b keeps stating its ends after connect parser/defusage.go parseUsage (skipIdentification, skipMultiplicity), parseTierBEnds, atConnectorShorthandEnds parse/interface_usage_shorthand.golden, parser/negative_test.go (interface_ends_no_target, interface_ends_no_to, interface_ends_unclosed_body) ✅ Faithful
A flow usage may carry a body after its ends, and its ends may be feature chains with the to on a continuation line: flow s1.x to s2.x { … } (§8.2.2.10 FlowUsage → FlowDeclaration UsageBody) parser/defusage.go parseUsage (skipIdentification via atFlowShorthand/from), atEndThenKeyword (the shorthand is recognized past a chain of any depth) parse/flow_usage_body.golden, parser/negative_test.go (flow_ends_no_target_before_body, flow_ends_unclosed_body) ✅ Faithful
A requirement-like body admits usage elements, so a connector, flow or message written with its kind keyword (connection connect r to x;) is a member of a requirement, constraint, concern, objective, use case or view body exactly as it is at package level (§8.2.2.19 RequirementBody → DefinitionBodyItem → UsageElement) parser/behavior.go parseRequirementMember, usageIsSubstantive (a connector or flow usage declares no name, so its ends are what make it substantive) parse/requirement_body_prefixed_usages.golden ✅ Faithful

Known limitations of this notation

  • A body on a succession or on a control node is not read yet: SuccessionAsUsage ends in DefinitionBody and MergeNode/DecisionNode/JoinNode/ForkNode in ActionBody, so first start then continue { … } and merge continue { … } are standard notation, but OpenSysML requires ; there. These are the outstanding false positives on the pilot validation corpus's 3a-Function-based Behavior-1 and 5-State-based Behavior files (a transition body is the same gap). Reading them needs body members on ast.InitialNode, the four control nodes and ast.SuccessionEdge/ControlFlowEdge, and lowering that executes them, so it is a feature of its own rather than a parser tweak, and no member is dropped in the meantime — the notation is reported, not silently accepted.
  • Dragon.sysml declares bare end; members inside connection def, flow def and nested connection def bodies (6 sites). This is not standard notation: a connection def/flow def body is an ordinary DefinitionBody, whose members are NonOccurrenceUsageElement/OccurrenceUsageElement — neither includes DefaultInterfaceEnd (only InterfaceBodyItem does), and the only other keyword-less end, DefaultReferenceUsage, requires a UsageDeclaration. OpenSysML reports these with a typed diagnostic naming the conforming form (end ref;, which ReferenceUsage does allow) rather than inventing grammar.
  • Dragon.sysml, OOSEM.sysml and DesertKite.sysml type their views by 'SysML Standard Diagrams'::gv (7, 3 and 10 sites). No such namespace exists in the OMG release library or the pilot implementation — it is a tool-specific package, not part of the standard library — so it is not vendored under that name and no alias to StandardViewDefinitions is fabricated. The diagnostic says the namespace is not loaded and points at StandardViewDefinitions::gv.
  • The two notations that had been suspected of being our false positives are adjudicated as legal and are accepted: a conjugated end (end spacePort : ~CommunicationPort, spacecraft-example-model.sysml) and a portion prefixed onto a kind keyword (timeslice item item1, Dragon.sysml). ConjugatedPortTyping specializes FeatureTyping (Systems Library/SysML.sysml:100), so any feature typing — a connection or interface end among them — may name a conjugated port definition; and PortionKind is an enumeration of timeslice/snapshot (Systems Library/SysML.sysml:291) held by OccurrenceUsage::portionKind (:262), which an ItemUsage is. Both parse, resolve and check clean over every tier, pinned by testdata/passes/corpus_notation.golden, so neither notation is outstanding. What the models do still report is other notation: OOSEM.sysml (Open-MBEE/DesertKite.sysml, default branch) reports the three 'SysML Standard Diagrams'::gv errors above and nothing else, and DesertKite.sysml — which lives only on that repository's InitialDesign branch — reports 7 errors that are ours, not the model's: a qualified name refused as a bind end (3 sites, 6 errors) and connection connect … ; refused inside a requirement body (1 site). Both are parser defects owned by a separate session; they are not adjudicated here and no verdict above depends on them.
  • Only the braced spelling of a requirement-constraint reference sets RequireMember.Reference/AssumeMember.Reference. CalculationBody also allows ;, so standard require Q::r; is a reference too, but OpenSysML reads a body-less require/assume member as a condition expression, which the runtime evaluates as Boolean. Distinguishing the two spellings needs the name's resolution, not its syntax, and no spelling requires the referenced requirement's own conditions at runtime yet (runtime/condition.go appendConditions walks only the member's own body), so the body-less form is left on the expression path rather than made a silent no-op.
  • Conjugation is not a runtime concept here: nothing is executed differently for a conjugated port, because ports carry no transfer semantics in the runtime yet (see "Major Features Not Implemented").
  • A snapshot/timeslice portion is recorded on the usage and resolves like any occurrence usage, but the runtime does not relate a portion to the occurrence it is a portion of, and no time ordering between portions is derived.

Usage Prefixes and KerML Classifier Declarations (SysML v2 §7.6 Usages, §8.3.9.11 Occurrences; KerML §8.2 Classifiers; SysML.xtext RefPrefix, IndividualUsage, PortionUsage, ForVariableDeclaration)

Semantic Rule Implementation Test Case Status
A lone occurrence modifier declares the kind of the usage it prefixes: individual i : V is an individual usage, snapshot s/timeslice ts/event e occurrence usages, and the modifier reaches the symbol kind rather than being recorded and ignored parser/defusage.go modifierImpliedKind (the kindless fallback of parseDefUsage), parser/behavior.go parseDirectionParameter; symbols/builder.go classifyUsage → usageSymbolKind parse/occurrence_modifier_anonymous.golden, parse/occurrence_individual_snapshot.golden, parse/occurrence_portions.golden, parser/occurrence_modifier_test.go:TestParseUsageOccurrenceModifiers, symbols/occurrence_modifier_kind_test.go:TestOccurrenceModifierDecidesSymbolKind ✅ Faithful
A modifier followed by a kind keyword and no name (individual part : Vehicle, ref item : Integer, individual part : 'Gus Grissom' :> crew) declares an anonymous usage of that kind — SysML reserves the keyword, so it is the kind and never the name, individual part and individual item stay distinct, and the nameless form is well-formed and draws no diagnostic, as the pinned validator parses it clean (Usage: UsageDeclaration? UsageCompletion) parser/defusage.go parseDefUsage, parser/behavior.go parseDirectionParameter parser/modifier_kind_ambiguity_test.go:TestAnonymousModifiedUsagesDoNotWarn, :TestFrameAndRenderNameKerMLFeatures, parse/occurrence_modifier_anonymous.golden ✅ Faithful
A parameter is a usage prefixed by its direction, and its declaration is optional, so in snapshot ; is an anonymous occurrence parameter that is registered as a member rather than dropped — but a direction with no declaration at all (in ;) declares nothing and is an error, as the pinned validator reports it (no viable alternative at input ';') parser/behavior.go parseDirectionParameter, parser/defusage.go parseBodyMember/atDirectionKeyword; symbols/builder.go (anonymous member registration) parse/parameter_occurrence_modifiers.golden, parser/negative_test.go (direction_without_feature_action, direction_without_feature_part), pilot-reject grammar/g36-direction-without-feature.sysml both-reject, symbols/occurrence_modifier_kind_test.go:TestAnonymousParameterBuildsSymbol, export/w8g_classification_test.go:TestKindlessParameterIsAReferenceUsage ⚠️ Approximate (Adjudicated: the two readings are aligned. A declaration with no kind keyword is one thing wherever it is written — SysML.xtext reaches it through DefaultReferenceUsage in a parameter list as much as outside one — so a kindless parameter now reads the same kind as a kindless member instead of a part usage (parser/behavior.go parseDirectionParameter defaults to modifierImpliedKind). The export consequence is taken and pinned: every kindless parameter exports as sysml:AttributeUsage rather than sysml:PartUsage (export/testdata/convert/views_flows_parameters.golden.ttl), and the reading is pinned by parser/w7c_kindless_parameter_test.go:TestKindlessParameterReadsSameKindAsKindlessMember. Resolved: the kind exists now — a usage declared without a kind keyword is SymbolReferenceUsage (symbols/builder.go classifyUsage), so the symbol kind and the metamodel @type agree with the export: referenceUsage, sysml:ReferenceUsage (SysML v2 §7.6.4; §7.6.3 makes a directed usage referential whatever it declares). Every consumer that treated it as an attribute usage still does (SymbolKind.IsAttributeLike); only the metaclass name maps apart)
A for loop's variable is a usage declaration, so a keyword may name it (for step in c), with the reserved-keyword warning any keyword-named declaration gets, and a short name alone identifies it (for <v> in c) parser/behavior.go parseForAction (parseIdentificationStopping("in") rather than an Identifier token) parse/action_for_keyword_variable.golden, parser/for_loop_variable_test.go:TestForLoopVariableMayBeKeyword, :TestForLoopVariableMayBeShortNameOnly, :TestForLoopKeywordVariableWarns, :TestForLoopMalformedRecovers ✅ Faithful
A datatype declares a KerML DataType — a definition — so a feature can be typed by it whether or not it specializes anything (datatype D; as much as datatype Real specializes Complex;), as class, struct, assoc, behavior and interaction already were symbols/builder.go classifyUsage (the datatype keyword decides, ahead of the relationship-driven attribute classification) symbols/kerml_type_kind_test.go:TestKerMLTypeDeclarationsAreClassified, export/w8g_classification_test.go:TestKeywordDecidedMetaclasses, :TestKeywordMetaclassesRoundTrip ⚠️ Approximate (a datatype is classified as an attribute definition, the SysML mapping of a DataType; a function stays a calcUsage, since the runtime resolves an invocation through it, so a function definition and a calc usage are still one kind. Adjudicated: the pinned validator accepts both keywords and builds a DataType and a Function, so the distinction is now carried where it is observable — the export names the KerML metaclass the keyword declares (export/kinds.go keywordMetaclass), leaving only the internal kind conflated)
A classifier declares a plain KerML Classifier, and every definition is a Classifier — a DataType among them — so classifier C specializes D is well-formed whatever kind D is declared with, while the narrower class, struct and part def rows stay constrained to their own kind (KerML §8.3.2, §8.4.4.1; SysML v2 §8.4.5.1) passes/typecheck.go compatMessage (the declKind.isPlainClassifier row, told apart by the written keyword since classifier and class are both ast.DefClass) passes/typecheck_classifier_test.go:TestTypeCheckClassifierSpecializesAnyDefinitionOK, :TestTypeCheckPartDefSpecializesDataTypeStillRejected, :TestTypeCheckClassifierSpecializesUsageStillRejected ✅ Faithful
KerML has no definition/usage distinction — every declaration is a Type and a Specialization relates two Types (KerML §8.3.3) — so in a .kerml document class Person specializes Object is well-formed, and the SysML rule that only a definition may specialize does not apply. The target must still be a type: a package or an annotation is not one passes/typecheck.go compatMessage (the declKind.isKerML row of ast.RelSpecializes), declKind.lang from source.KindOf — the same file-kind mechanism the kerml-notation warning reads passes/typecheck_kerml_language_test.go:TestTypeCheckKerMLSpecializationClean, :TestTypeCheckSysMLSpecializationStillFires, :TestTypeCheckKerMLSpecializesNonTypeStillFires ✅ Faithful
A KerML FeatureTyping's type is any Type, a Feature among them (KerML §8.3.4.4), so in a .kerml document feature yy : y is typed by a feature and the SysML usage-kind taxonomy — which requires a definition — does not apply. A non-type target is still reported passes/typecheck.go compatMessage (the declKind.isKerML row of ast.RelTyping), isTypeKind passes/typecheck_kerml_language_test.go:TestTypeCheckKerMLTypingByFeatureClean, :TestTypeCheckSysMLTypingByFeatureStillFires, :TestTypeCheckKerMLTypedByNonTypeStillFires ✅ Faithful
Only a Type may be a KerML specialization or typing target, and a Type is enumerated rather than assumed: every definition and usage kind and a KerML type declaration, but not a namespace, a dependency, an annotation or an alias — a resolvable alias is resolved to its target upstream, so one reaches the check only when it is cyclic and names no type. A kind added later is rejected until it is classified passes/typecheck.go typeSymbolKinds, isTypeKind; checkTypeTarget (alias resolution) passes/typecheck_kerml_language_test.go:TestIsTypeKindIsAnAllowlist, :TestTypeCheckKerMLSpecializesAliasOfTypeClean, :TestTypeCheckKerMLSpecializesCyclicAliasStillFires ✅ Faithful
A KerML ClassifierDeclaration takes a multiplicity before its superclassing part (classifier B [1] specializes A;, KerML.xtext:468-470), for every classifier keyword — and a SysML DefinitionDeclaration (SysML.xtext:508) does not, so attribute def A [1]; stays rejected parser/defusage.go parseDefinition (defKeywordConsumed plus isKerMLClassifierDefinitionKeyword), ast.Definition.Multiplicity parse/F83-classifier-multiplicity.kerml, parser/f50_f70_f81_f82_f83_test.go:TestF50F70F81F82F83AndF62F63Parse ✅ Faithful (every classifier keyword takes the multiplicity, type S [1] specializes A; among them once the type keyword was added; attribute def A [1]; stays rejected)
A KerML type declaration takes differences in the same relationship position as intersects and unions (DifferencingPart, KerML.xtext:359), and a Disjoining (:426) stands alone as a namespace or body member (disjoint B from A;), with the ordered ends, optional disjoining <id> identification and relationship body of the keyword-first relationships in the row below parser/defusage.go relationshipKeywords (ast.RelDifferences); parser/f86_relationship_member.go relationshipMemberForms["disjoint"], reached from parser/namespace.go and the body-member path; export/kinds.go parse/F81-differences.kerml, parse/F82-namespace-disjoint.kerml, parse/kerml_disjoining_member.kerml, parser/f50_f70_f81_f82_f83_test.go (a SysML document still rejects disjoint B from A; at member position) ✅ Faithful
A feature's modifiers combine in any order and var is one of them (FeaturePrefix), so abstract var feature x [0..*]; declares a variable abstract feature, and a type body member may be prefixed by member (TypeFeatureMember, a type body only — the reference rejects it in a namespace); ( 'rep' Identification? )? 'language' STRING REGULAR_COMMENT (TextualRepresentation, KerML :103) is a body member wherever a member is expected, including a constraint body, named or anonymous, and its identification may be a short name parser/defusage.go parseFeatureModifiers (isVariable), parseTypeFeatureMember, atTextualRepresentationStart reached from parseBodyMember; parser/namespace.go parseTextualRepresentation/parseRepresentationIdentification; symbols/builder.go (SymbolTextualRepresentation, defined in the namespace it represents); ast.Usage.IsVariable, ast.Membership.IsTypeFeature parse/F50-variable-member.kerml, parse/F70-textual-representation.kerml, parse/textual_representation_forms.sysml, parse/textual_representation_kerml_forms.kerml, parser/f50_f70_f81_f82_f83_test.go, parser/textual_representation_test.go, symbols/textual_representation_test.go, model/f50_f70_resolution_test.go ⚠️ Approximate (parsing, naming and membership are faithful: the declared name and short name are defined in the owning namespace, the anonymous form is an anonymous member of it, a sibling and a qualified name reach the representation, and a missing language string or comment body is diagnosed and recovered from. The boundary: the represented text itself is never interpreted — an "ocl" or "alf" body is carried as its source span and is not parsed, type-checked or evaluated, so a defect inside it is invisible. TextualRepresentation::representedElement (KerML §7.2.5) is the owning element, which the RDF export writes as sysml:owningNamespace rather than as its own predicate)
A KerML declaration needs no kind keyword — Feature is ( EndFeaturePrefix \| BasicFeaturePrefix ) FeatureDeclaration (KerML.xtext:542) — so a member declared only by its name is a feature wherever a member is expected: a : Integer; and x; in a namespace, composite e1 redefines V::m; specializing without a typing, p5[1] : Real; with the multiplicity first, and the var/const prefixes (BasicFeaturePrefix:515). type (:319) declares a Type, whose specialization is mandatory, so type A; is rejected as the reference rejects it parser/defusage.go atKeywordlessFeature, keywordlessFeatureAt, atVarPrefixedFeature, featureModifierKeywords (const); parser/f85_type_declaration.go usageKind/definitionKind (the type keyword is KerML-only, so a SysML parameter named type is unaffected) and checkTypeDeclarationSpecialization parse/F84-F95-kerml-declarations.kerml, parser/f84_f95_kerml_declarations_test.go:TestF84F95KerMLDeclarations, :TestF84F95DeclarationFields, :TestNegativeF84F95KerMLDeclarations, :TestNoPanicF84F95KerMLDeclarationsTruncated ✅ Faithful (kerml-examples reports no syntax diagnostic at all)
A relationship is also written keyword-first, with its own optional identification and both ends named: specialization/subtype (KerML.xtext:390), subclassifier (:486), typing (:665), subset (:683), redefinition (:712), conjugation (:408), inverse/inverting (:634), featuring (:652) and disjoint/disjoining (:426); typed by is the long spelling of : (TypedBy:600); a declaration conjugates with conjugates or ~ (:468, :730); const precedes end (EndFeaturePrefix:511) ast/namespace.go RelationshipMember (the first-class element, its ends ordered Source then Target); parser/f86_relationship_member.go relationshipMemberForms, parseRelationshipMember, parseTypeFeaturingMember; symbols/builder.go SymbolRelationship; export/rdf_out.go relationshipEnd, export/kinds.go relationshipElementForm parse/F84-F95-kerml-declarations.kerml, parser/f84_f95_kerml_declarations_test.go, symbols/w7b_relationship_element_test.go, resolve/w7b_relationship_ends_test.go, export/w7b_relationship_graph_test.go (TestKeywordFirstSpecializationDescribesTheSameGraph, TestKeywordFirstRelationshipEndsAreOrdered, TestKeywordFirstRelationshipRoundTrips, TestDisjoiningRoundTripsFromTheGraphAlone, TestDisjoiningOrientationIsLoadBearing, TestDisjoiningClauseStaysOnItsDeclaration); semantics/w7b_relationship_metaclass_test.go; lsp/definition_test.go TestDefinitionKerMLDisjoiningEnds ✅ Faithful (a keyword-first relationship is now its own element with ordered, queryable ends — the RDF export of specialization Gen subtype A specializes B and of classifier A :> B describe the same specialization edge, which the anonymous-usage representation did not (it wrote Gen :> A and Gen :> B); a Disjoining likewise states typeDisjoined then disjoiningType, where the anonymous usage stated two disjointFrom and came back from the graph as disjoint from X, Y;, which is not KerML. Matched run: the pinned validate-kerml is silent on the fixture of all six keyword-first forms. Not refereed: no diagnostic surface, ours or the reference's, derives conformance from a relationship element — specialization Gen subtype A specializes B followed by feature q : A :> p with p : B is silent in both implementations with and without the specialization, so the semantic consumption of the element (internal/semantic/semantics) is unobservable rather than proven)
Both ends of a keyword-first relationship must be instances of the metaclasses its ends relate, as the ends of a declaration clause already must: a Specialization (subtype), Conjugation (conjugate) and Disjoining (disjoint) relate two Types (KerML §8.3.3.1, §8.3.3.4, §8.3.3.3), a Subclassification (subclassifier) two Classifiers (§8.3.3.2), a Subsetting (subset), Redefinition (redefinition) and FeatureInverting (inverse) two Features (§8.3.4.5, §8.3.4.6, §8.3.4.8), and a FeatureTyping (typing) and TypeFeaturing (featuring) a Feature and a Type (§8.3.4.4, §8.3.4.3). A package, comment or import is none of these; a feature — a named multiplicity among them, Multiplicity specializing Feature — is a Type but not a Classifier; a class is a Type and a Classifier but not a Feature. Only a resolved end of the wrong kind is reported — an unresolved end is the name-resolution tier's finding, and an unclassified kind constrains nothing passes/typecheck_relationship_ends.go relationshipMemberEnds (the per-keyword end table), endKind.admits (Type over isTypeKind, Classifier over SymbolKind.IsDefinition, Feature over SymbolKind.IsFeature), typeChecker.checkRelationshipMember/checkRelationshipEnd, reached from typeChecker.walk; typecheck.go compatMessage reads the same endType.admits for a KerML declaration's specialization and typing targets passes/typecheck_relationship_ends_test.go (TestRelationshipMemberSubtypeEnds, …SubclassifierEnds, …TypingEnds, …SubsetAndRedefinitionEnds, …ConjugateAndDisjointEnds, …InverseAndFeaturingEnds, …EndThroughAlias, …UnresolvedEndIsSilentAtTypeTier, …EndsInsideATypeBody, …DeclarationClauseWordingUnchanged); tools/referee/reject/testdata/negative/semantic/k55–k73 ✅ Faithful (refereed: the pinned validate-kerml fails to link each of the nineteen wrong-kind ends — Couldn't resolve reference to Type|Classifier|Feature '…', its cross-references being typed by metaclass — and accepts every end the table admits: a feature or a named multiplicity as either end of subtype, conjugate and disjoint and as the type of typing and featuring, and a data type as either end of subclassifier. Admitting the named multiplicity as a Type in the shared typeSymbolKinds, and as a Feature in checkChainSegments (which now reads endFeature.admits), also clears the declaration clauses feature g : M; and feature g :> M.x;, which the pilot accepts and which were reported as type must be a type, found multiplicity and feature chain segment must be a feature, found multiplicity before (TestRelationshipMemberNamedMultiplicityIsAType). The message follows the house style, <keyword> source|target must be a type|classifier|feature, found <kind>, since the reference has no validator message for an unlinkable reference. KerML only: the parser admits these members in a .kerml document alone and SysML.xtext has no keyword-first relationship, so a .sysml document has nothing to judge. Not covered: a package-level feature as the redefined end — redefinition g redefines f with both at package level — which the reference rejects as A package-level feature cannot be redefined, a rule about the ends' owners rather than their kinds)
A connector end may be a feature chain in either position (ConnectorEnd:854 → OwnedReferenceSubsetting:699), binding/succession take an optional name, typing and of/= ends with member ends otherwise (:875, :891), a filter-package import takes more than one filter bracket (FilterPackage:200), prefix metadata stands in for the feature keyword, and a named expr takes a brace-enclosed expression body with no ; parser/defusage.go (connector-end chains, binding/succession declarations, ast.UsageExpr body, prefix-metadata members), parser/namespace.go (repeated filter brackets, anonymous locale comments and doc with a short name) parse/F84-F95-kerml-declarations.kerml, parser/f84_f95_kerml_declarations_test.go ✅ Faithful (repeated filters combine into one and expression, as the reference conjoins them)
A binding/succession written without of/first has no declaration of its own (KerML.xtext:875 BindingConnectorDeclaration, :891 SuccessionDeclaration, second alternative), so a multiplicity that leads it is the first end's crossing multiplicity (ConnectorEnd:854 OwnedCrossingMultiplicityMember): binding [1] a = [1] b; and succession [1] a then [*] b; put both multiplicities on the ends and none on the connector, while binding [1] of a = b;, binding bb [1] of a = b;, succession [1] first a then b; and succession sn [1] first a then b; keep [1] as the connector's parser/defusage.go parseBindingDeclaration, parseBindingEnds, parseConnectorEnds, parseConnectorEnd; ast.ConnectorEnd.Multiplicity carries the end multiplicities, ast.Usage.Multiplicity the connector's; export/rdf_out.go writes each end's sysml:lowerBound/sysml:upperBound on the end node parse/kerml_connector_end_multiplicity.kerml (every form, accepted by the pinned KerML validator), parser/negative_test.go (binding_end_multiplicity_no_end, binding_end_multiplicity_unclosed, binding_second_end_multiplicity_no_end, binding_end_multiplicity_no_terminator and the four succession_*_multiplicity_* counterparts), export_test.go:TestKerMLConnectorEndMultiplicitiesAreStatedAsStructure ✅ Faithful (the pinned pilot reads the six Kernel Semantic Library sites — Occurrences.kerml:207, :458, ControlPerformances.kerml:78, :125, StatePerformances.kerml:43, TransitionPerformances.kerml:31 — with no connector multiplicity and the stated multiplicities on the ends, as we now do; Occurrences.kerml written back from its graph alone keeps each binding [1] … = [1] … where it was, instead of moving [1] behind the second end. A connector's own multiplicity in the of/first forms is still written after its ends by the RDF mapping, which does not read back; that rendering is the mapping's, not the parser's)
A metaclass is a Class (KerML §8.4.4), so metaclass AtomMetadata specializes Metaobject is a metaclass specializing a metaclass and is well-formed in either language; an unrelated definition kind is still a mismatch passes/typecheck.go defSymbolKind (ast.DefMetaclass → symbols.SymbolMetaclass, absent before, which made every metaclass declaration incomparable with its own kind) passes/typecheck_kerml_language_test.go:TestTypeCheckMetaclassSpecializesMetaclass, :TestTypeCheckMetaclassSpecializesPartDefStillFires ✅ Faithful

Name Resolution

Semantic Rule Implementation Test Case Status
Inherited feature resolution document.go:199 resolveRedefinition flow_payload_test.go ✅ Faithful
Declaration named with a keyword (action flow { ... }, attribute item : Integer) parser/defusage.go atKindPrefix, atSecondaryKind parser/namespace_keywords_test.go TestParseKeywordAsNameAfterKindKeyword, model/behavior_body_resolve_test.go TestKeywordNamedDeclarationIsReferenceable ✅ Faithful (the name is kept and is referenceable)
Keywords reserved in name position (only an unrestricted name may spell one) parser/namespace.go parseIdentification → Parser.Warnings, surfaced as diag.SeverityWarning code reserved-keyword-name by model/workspace.go parser/namespace_keywords_test.go TestParseKeywordAsNameIsReported, model/behavior_body_resolve_test.go TestReservedKeywordNameWarning, libs/reserved_keyword_name_test.go TestStdlibReservedKeywordNames ⚠️ Approximate (reported as a warning, not an error, because the normative OMG library itself uses unquoted keyword names — step entry[1];, part done : Part;, attribute type : String[0..1]; — and must keep parsing clean; those eleven sites — including the Kernel Semantic Library's in frame : SpatialFrame[1], whose name SysML reserves for a view/viewpoint member — are pinned by the libs test so the set cannot grow silently. Reservation is now per-grammar. The lexer still tokenizes the union of both languages' words, and reservation is decided against the grammar of the file being read (source.IsKeywordIn, parser/namespace.go reservedWord): a word only KerML.xtext spells is an ordinary name in a .sysml file and the reverse. Matched runs: part chains : T; — and differences, disjoint, inverse, type, multiplicity in the same position — was error: expected a name on main, is clean in ours and in build/pilot-validator/validate-sysml; feature frame : SpatialFrame[1]; and feature render : Rendering; in a .kerml file were warned about on main, are clean in ours and in build/pilot-kerml-validator/validate-kerml. This is why the eleven pinned library sites in libs/reserved_keyword_name_test.go are now none: every one of them named a declaration with the other language's word. part all : T; stays clean in ours and rejected by the reference — all is a SysML literal in one position only, and narrowing it would lose the KerML all prefix that the same lexer token carries)
A keyword left in a parameter's name position names the parameter, quoted as KerML §7.2.4 requires (in 'type': Anything;, which the Kernel Function Library itself writes) or bare with the reserved-keyword warning, so the parameter is declared and its name resolves rather than the declaration failing to parse parser/behavior.go parseDirectionParameter (parseIdentificationStopping for the name, stopping on the post-multiplicity modifiers, rather than requiring an identifier token) parse/parameter_keyword_names.golden, resolve/parameter_keyword_name_test.go:TestResolveKeywordNamedParameter, :TestResolveKeywordNamedParameterUnresolved, parser/negative_test.go (keyword_named_parameter_no_type, parameter_default_no_value, parameter_redefines_no_target), conformance/calc_keyword_named_parameters.sysml, runtime/robustness_test.go:testCalcUnboundKeywordNamedParameter ✅ Faithful (a keyword the parameter grammar reads as something else — a kind keyword, a relationship keyword, default, ordered, nonunique — still reads as that, not as the name)
An event occurrence declares a feature like any other, so its name resolves from a value expression and from the trigger of a transition or an accept that names it; event <name> without the occurrence keyword references an existing occurrence (SysML v2 §8.3.13, SysML.xtext EventOccurrenceUsage), so a name that declares nothing is unresolved there parser/defusage.go parseUsage (the reference form is an ast.RelReferences relationship), resolved by resolve/target.go ResolveTarget and resolve/unqualified.go like any feature reference resolve/event_feature_test.go:TestResolveEventOccurrenceFeature, :TestResolveEventOccurrenceReferenceUnresolved ✅ Faithful
Keyword qualifying a kind keyword (var feature x, assert constraint { ... }, item part Shape) parser/defusage.go atKindPrefix, parseDefUsage parser/namespace_keywords_test.go TestParseKeywordBeforeKindKeywordIsNotAName ✅ Faithful
Words the grammar uses in one position only are not reserved: var marks a variable feature (KerML.xtext BasicFeaturePrefix, isVariable ?= 'var') and on is a literal in none of the pilot's grammars, so both name a feature everywhere else, as point does; chain is the feature chain modifier only when a name follows it (attribute chain x : T;, feature chain link;); before a word the file's grammar reserves it names the declaration, so attribute chain = 1;, attribute chain default 1;, attribute chain ordered :> pt;, metadata chain about x;, KerML class chain specializes Base; and the library's own feature chain chains source.target; (ControlFunctions::'.') declare, and chain + 1 reads, a feature named chain source/keywords.go (none is in keywordList), parser/defusage.go atVarPrefix, atVarDeclaration, atChainModifier (reservedWord) lexer/lexer_test.go:TestContextualWordsAreIdentifiers, parser/contextual_keyword_name_test.go (TestParseChainIsANameBeforeAnythingButAName, TestParseChainKeepsTheOrderingModifiersAfterIt, TestParseKerMLTypeNamedChainKeepsItsName), parse/contextual_keyword_name_on.golden, parse/contextual_keyword_name_var.golden, parse/contextual_keyword_name_chain.golden, parse/contextual_keyword_name_chain_types.golden, libs/snapshot_test.go:TestSnapshotIndexMatchesFreshLoad, parser/negative_test.go (state_named_on_no_semicolon, var_prefixed_declaration_no_type, var_prefix_without_kind_keyword) ✅ Faithful (Adjudicated: matched runs — KerML var a : Amount; with the kind keyword left out is clean in build/pilot-kerml-validator/validate-kerml and in ours, and SysML attribute on : A; attribute var : A; is clean in build/pilot-validator/validate-sysml and in ours: passes/w6c_row_adjudication_test.go:TestW6CVarWithoutAKindKeywordParses, :TestW6CContextualKeywordsInNamePosition. The words that do stay reserved are the row above's stated gap, not this row's)
Binding connector ends (binding [1] bind [0..*] a.b = [0..*] c) parser/defusage.go parseUsage UsageBinding libs/reserved_keyword_name_test.go (the library's ShapeItems.sysml sites) ✅ Faithful (bind is the ends keyword, formerly read as the connector's name)
A directional parameter's FeatureSpecializationPart is the ordinary feature's (KerML.xtext:574: FeatureSpecialization+ MultiplicityPart? FeatureSpecialization* \| MultiplicityPart FeatureSpecialization*), so its specializations may follow its multiplicity and the ordered/nonunique of the MultiplicityPart: in x : Integer[1] redefines A::x;, in y : Integer[1] :>> A::x;, in xs : Integer[*] ordered nonunique :>> M::xs;, return : Integer[1] ordered :>> C::r;, for in, out, inout and return alike parser/defusage.go parseFeatureSpecializationPart, parseSpecializationsAfterMultiplicity (one loop, shared by parseUsage, behavior.go parseDirectionParameter and parseResultMember) parse/parameter_multiplicity_before_specialization.sysml (accepted by the pinned SysML validator), parser/negative_test.go (parameter_multiplicity_redefines_no_target, parameter_multiplicity_redefines_symbol_no_target, parameter_multiplicity_ordered_subsets_no_target, parameter_multiplicity_unclosed_before_redefines, parameter_multiplicity_redefines_no_terminator, out_parameter_multiplicity_redefines_no_target, inout_parameter_multiplicity_redefines_no_target, return_multiplicity_redefines_no_target) ✅ Faithful (formerly expected ';' or '{' after parameter; the multiplicity-first order the Systems Library writes, in transitionLinkSource[1] : StateAction :>> …, is unchanged)
Named argument resolution document.go:205 (no name resolution) requirement_invocation_test.go ✅ Faithful
Control flow node registration builder.go InitialNode/FinalNode transition_first_test.go ✅ Faithful
An end a succession or a decision branch names is a reference, resolved where the name is written (succession first start then zzz;, then zzz;, if c then zzz;, else zzz;) resolve/edge.go resolveSuccessionEdge/resolveControlFlowEdge, reached from resolve/document.go resolveDecl and surfaced by passes/nameres.go (code unresolved) passes/succession_endpoint_test.go TestEndpointNamingNoMemberIsReported, :TestEndpointsThatResolveStaySilent, :TestEndpointDiagnosticAgreesWithLowering ✅ Faithful (an end the notation supplies rather than the author naming it — the source a one-name then <target>; takes from the member before it, and either end bound by position — names no reference: ast/behavior.go SourceImplied/TargetImplied and SourceMember/TargetMember record that, and lowering reads the member itself. start and done resolve as the features Actions::Action declares, the names lowering also treats as implicit)
Redefinition name resolution (KerML 8.2.3.5.2): a :>> target owned by a feature of a type is looked up in the generals of that owning type — its explicit and implicit specializations, a usage's typing, subsetting and featuring, and everything those generals inherit or publicly import — and, failing that, from the enclosing namespace outward by the ordinary rules; the owning type's own members, imports and aliases are never consulted, so a sibling x, a same-scope import Lib::* or alias y for x leaves the target unresolved. A qualified target (C::x) or a feature-chain target (w.x) resolves its first segment the same way and walks the tail through that element's members, and a general that offers the first segment shadows the enclosing namespaces for the tail. A package-owned redefinition, a prefix-metadata body and a redefinition reached by a subsetting resolve as before resolve/document.go resolveRedefinition (generals via generalsOf, findImplicitSpecializations, featureOf, lookupContributedMember; the fallback starts at scope.Parent()), resolveRedefinedChain (a chain's leading name through resolveRedefinition, its members through chainFrom), resolve/target.go ResolveRedefinitionTarget/Reference.Redefines, resolve/qualified.go walkQualifiedTail; the semantic model's semantics/model.go generalizationTarget and the document walk agree resolve/redefinition_target_test.go TestRedefinitionTargetsFollowTheGeneralsThenTheEnclosingNamespace (every case over the relationship walk and the semantic model), resolve/shadowing_test.go TestQualifiedRedefinitionFallbackDoesNotReportSpeculativeFailure, TestEnclosingDeclarationShadowsANestedImport, passes/w10b_structural_test.go TestW10BSiblingRedefinitionIsUnresolved, semantics/uniqueness_test.go TestIsUniqueTerminatesOnRedefinitionCycle, semantics/w11e_masked_reference_test.go TestRedefiningNamesakeStillNamesItsTarget, semantics/redefinition_test.go TestChainRedefinitionTargetStartsInTheGenerals, passes/w8c_metadata_annotation_test.go TestNestedMetadataBodiesRedefineTheNestedFeatureMembers; tools/referee/reject semantic/s94–s98 (both-reject) ✅ Faithful (the pinned pilot's KerMLScope.resolve searches only the generals for a redefinition and its outer scope is the enclosing namespace; every listed verdict is refereed against it)
References in behavioral bodies (calc return, constraint/assume/require, assignment, entry/do/exit, transition guard and effect) resolve/document.go resolveDecl model/behavior_body_resolve_test.go TestBehaviorBodyReferencesAreResolved ✅ Faithful
State def bodies are state bodies whatever their first member is parser/defusage.go DefState case (always parseStateBody) parse/state_def_region_pseudostate.golden (a state def whose first member is an attribute, followed by regions) ✅ Faithful
States a region declares with a body (state x { ... }) lower/state_graph.go collectRegionStates (memberships unwrapped, region and initial recorded) state_region_choice.sysml, region_pseudostate_test.go ✅ Faithful
Substate, region, and named-pseudostate declarations symbols/builder.go StateNode/StateRegion/PseudostateNode model/behavior_body_resolve_test.go TestBehaviorDeclarationsAreVisible ✅ Faithful
Region-scoped state names (sibling regions may reuse a name) symbols/builder.go StateRegion TestBehaviorDeclarationsAreVisible/sibling_regions_reuse_state_names ✅ Faithful
Requirement actor declaration symbols/builder.go (*ast.Usage of kind UsageActor) TestBehaviorDeclarationsAreVisible/requirement_actor_binding ✅ Faithful
Inherited member through a qualified-name segment (engine::'4cylEngine') resolve/qualified.go walkQualified → semantics.Model.LookupMember model/inherited_scope_resolve_test.go TestInheritedMembersAreVisible ✅ Faithful
Redefinition target that the redefinition shadows (part redefines engine) semantics/model.go inheritedFeature TestInheritedMembersAreVisible/nested_redefinition, TestRedefinitionDoesNotShadowItsTarget ✅ Faithful
Loop body as a namespace (loop { action a; } until a.x, for x in c { ... }) symbols/builder.go WhileLoopActionNode (including a for loop's iteration variable), resolve/document.go, passes/typecheck.go, resolve/references.go; at execution runtime/action_statements.go stmtEnv (a frame per entered block) TestBodyLocalDeclarationsAreVisible, TestBodyLocalNamesDoNotEscape, runtime/robustness_test.go:loop_body_declaration_does_not_leak ✅ Faithful
Body-expression parameters (c->forAll { in i : Positive; f(i) }) symbols/bodyscopes.go buildBodyScopes (scope linked into the document tree), read back by symbols.BodyExprScope in resolve/document.go and resolve/references.go TestBodyLocalDeclarationsAreVisible/body_expression_parameter, lsp TestRenameLeavesBodyExpressionParameters, TestRenameBodyExpressionParameterFromDeclaration, TestDefinitionBodyExpressionParameter ✅ Faithful
Declarations inside expression bodies are members of a body-local namespace: forward references and nested-body lookup work, body-local names shadow enclosing features, and feature values are evaluated lazily symbols/bodyscopes.go newBodyExprScope/bodyScopesInExpr, symbols/bodylocal.go LookupBodyLocal, runtime/collections.go applyBody, runtime/eval.go evalName symbols/f99_body_declarations_test.go, tests/parser/testdata/parse/f99_body_declarations.sysml, runtime/testdata/conformance/f99_body_bare_result, f99_body_local_result, f99_body_forward_reference, f99_body_shadowing, runtime/robustness_test.go (f99_body_member_without_value, f99_unsupported_body_member, f99_cyclic_body_declaration) ✅ Faithful
Features of the stdlib base type of an untyped usage (state normal; → States::StateAction::done) semantics/implicit.go implicitUsageBases, implicitUsageBaseFeature, baseFeatureTypes, and Model.implicitBase via semantics/model.go DirectSupertypes model/implicit_typing_test.go TestImplicitUsageBaseTypes, TestInheritedMembersResolveThroughUntypedUsage, semantics/implicit_test.go, lsp/implicit_typing_test.go ✅ Faithful (an untyped usage subsets the standard-library base feature for its kind, derives its type from that feature, and contributes the feature's members; recorded specialization edges keep the feature path available in the library index)
Implicit redefinition of behavior/step parameters by position (out item image; in action focus : Focus redefines Focus::image), KerML 7.4.7.2/7.4.7.3, SysML v2 7.17.2 semantics/redefinition.go Model.implicitParameterRedefinitions, Model.parametersOf, reached from semantics/model.go DirectSupertypes semantics/redefinition_test.go, model/implicit_typing_test.go TestParameterRedefinitionAccompaniesTheImplicitBase, TestImplicitRedefinitionSuppliesInheritedMembers, passes/typecheck_expr_test.go TestExprRedeclaredParametersMatchByPositionNotName (the invocation signature matches by the same rule) ✅ Faithful (owned parameters in lexical order redefine the parameter at the same position of each general behavior or step, matching direction; parameters a single general behavior leaves un-redefined are inherited after the owned ones; the kind's standard library base still applies alongside the redefinition, since the redefined parameter may itself be untyped)
Implicit redefinition of a result parameter (return redefines the general calculation's result whatever its position), SysML v2 7.19.2 semantics/redefinition.go Model.implicitParameterRedefinitions (ast.Usage.IsResult, set by parser/behavior.go parseResultMember) semantics/redefinition_test.go TestImplicitResultParameterRedefinition ✅ Faithful
A nested usage that is not a parameter and shares a name with an inherited feature resolve/document.go Resolver.checkInheritedNames, conflictable, parameterizedByName, surfaced by passes/nameres.go (code name-conflict); the usage still only gets the standard library base of its kind from semantics/implicit.go passes/nameres_test.go TestNameResolutionPassReportsInheritedNameConflict, TestRedeclaredInheritedNameIsNoConflictWhenRedefined, TestInheritedNameConflictExemptsRedefiningFeatures, TestDistinctNestedNameIsNoConflict, model/implicit_typing_test.go TestLikeNamedUsageIsNotAnImplicitRedefinition, resolve/inherited_names_test.go TestRedeclaringAnInheritedNameConflictsUnlessItRedefines, TestRequirementParametersAreNotNameConflicts, TestRedefinitionTargetFoundTwoSupertypesUp (the target is searched up the whole specialization chain, not only the direct supertype) ⚠️ Approximate (SysML v2 7.6.1 and KerML 7.3.2.1 make this a name conflict, reported at the name-resolution tier where inheritance is known; a feature that redefines what it shares the name with — explicitly, or implicitly by parameter position — does not conflict. The subject, actors and stakeholders of a case or requirement redefine the inherited ones by name (SysML v2 7.18.4, 7.19.4), which is not modelled and is not distinguishable from an ordinary feature at this tier, so the rule is not applied inside a case or requirement body at all (a concern and a viewpoint are requirements too) — a genuine conflict on an ordinary feature there goes unreported too. Adjudicated: the rule and its severity now match the reference. It is name distinguishability, reported as a warning, in the reference's own wording (Duplicate of other owned member name, Duplicate of owned member name, Duplicate of other alias name, Duplicate of inherited member name), over owned-vs-owned, alias-vs-owned, alias-vs-alias, owned-vs-inherited and inherited-vs-inherited pairs, skipping imports, constructed expressions and binding connectors: resolve/distinguishability.go, resolve/inherited_names_test.go, passes/nameres_test.go:TestNameResolutionPassReportsInheritedNameConflict. A name two supertypes contribute is reported on the subtype itself (:TestNameInheritedFromTwoSupertypesIsReportedOnTheSubtype), a feature an intermediate supertype redefines is still inherited under its name by that supertype's own subtypes (:TestNameRedefinedByAnIntermediateSupertypeStillConflicts), and a parameter arriving twice because one owner specializes the other is one feature, so it stays silent (:TestParameterInheritedThroughItsOwnSupertypeIsNotAmbiguous, against the warning its unrelated-supertype twin draws in :TestParameterInheritedFromTwoUnrelatedSupertypesIsAmbiguous) — each matched against the pinned validator. Members inherited from library supertypes are outside this rule; a separate pass covers them, passes/w9c_inherited_name_conflict.go. Adjudicated: a supertype's non-private imported memberships are inherited too, as KerML 8.3.3.1 and 8.4.3.2 read together require — Resolver.importedMembers, resolve/inherited_names_test.go:TestNameImportedByASupertypeIsInherited)
Effective name of an unnamed redefining feature (in item; in action shoot : Shoot is named image), KerML 7.3.4.5, SysML v2 7.6.5 symbols/builder.go effectiveIdent for a declared redefinition; for an implicit one resolve/unqualified.go Resolver.implicitlyNamedMember (with impliesNamingFeature), reached from walkUnqualifiedHiding and resolve/target.go memberChain, over symbols/scope.go Scope.AnonymousMembers and semantics/model.go DirectSupertypes passes/nameres_test.go TestImplicitlyRedefiningParameterBindsRedefinedName, TestImplicitlyRedefiningParameterDoesNotBindItsKeyword, symbols/builder_test.go TestUnnamedRedefinitionTakesRedefinedName ✅ Faithful (the name is bound in the owning scope, so a sibling resolves it by simple name; an implicit redefinition's target is known only to the semantic model, so that binding is resolved lazily rather than when scopes are built)
Implicit redefinition of connection/association ends by position (connection : PressureSeat connect bead references t.bead to ... redefines PressureSeat::bead), SysML v2 7.13.2, 7.14.2, KerML 7.4.6 semantics/connector.go Model.implicitEndRedefinitions, Model.endsOf, reached from semantics/model.go DirectSupertypes; the ends themselves are declared by symbols/builder.go buildConnectorEnds (ast.ConnectorEnd.DeclaredName) and the arity check is passes/constraint.go checkConnectorEndRedefinition (Model.UnmatchedConnectorEnds) semantics/connector_test.go (including TestImplicitEndRedefinitionOfAssociationUsage), model/connector_ends_test.go TestConnectorEndNamesResolve, TestConnectorEndArityMismatch ✅ Faithful (an end of a connect clause that reference-subsets what it attaches to declares an end of the connector, in lexical order, and redefines the end at the same position of each connector the usage specializes; positions count every end of the clause, including one that only names what it attaches to; an explicit :>> adds the ends it names to that positional redefinition rather than replacing it, so an end that names BinaryLinkObject::source still masks its general's end at its own position, and an end past the general connector's last position is reported. Connection, interface, allocation, flow, succession, association and binding declarations are matched — a general whose own ends are not enumerable, such as an unparsed library type, suppresses the arity check rather than reporting)
Reference subsetting contributes members (perform action takePhoto references takePicture;, perform providePower.generateTorque;) semantics/reference.go Model.ReferencedFeature, Model.MemberSources, consumed by semantics/members.go MembersOf/LookupMember; targets resolved by resolve/target.go ResolveTarget/ResolveReferenceTarget semantics/reference_test.go, resolve/target_test.go, model/perform_reference_test.go, parse/perform_reference.golden, runtime/testdata/conformance/action_perform_reference.sysml, runtime/robustness_test.go (perform_of_missing_action, perform_reference_cycle) ✅ Faithful (a member-contribution relation, deliberately not a generalization — see below)
Effective name of an unnamed feature that reference-subsets (perform providePower.generateTorque; declares generateTorque) or redefines (part :>> engine;, equivalently part redefines engine;, declares engine), KerML 7.3.4.5 Feature::namingFeature ast/namespace.go NamingFeature, EffectiveName, TargetName, IsFeatureChain; ast/defusage.go Usage.NamedByReference and ast/behavior.go DeclNamedByReference (the reference forms that name: perform, exhibit, include, require/assume, frame, render, a variant, a state's entry/do/exit action); symbols/builder.go effectiveIdent, namingIdent, namingTargetNode record the provenance in symbols/symbol.go Symbol.Naming (NamedByDeclaration, NamedByReference, NamedByRedefinition) and Symbol.NamingTarget, read by lower, runtime, passes and export; the reference that named a feature is hidden from its own resolution by resolve/target.go refFilter symbols/perform_test.go, symbols/builder_test.go TestUnnamedRedefinitionTakesRedefinedName, TestRedefinitionDoesNotOverrideDeclaredName, TestReferenceSubsettingOutranksRedefinitionAsNamingFeature, TestTwoRedefinitionsNameTheFeatureByTheFirst, TestShortNameSuppressesTheDerivedName, TestChainRedefinitionNamesNothing, TestNamingFeatureIsRecordedOnTheSymbol, resolve/document_test.go TestRedefinitionTargetSkipsTheNameItGaveAway, model/perform_reference_test.go ✅ Faithful (matched cell by cell against the pinned validators, 2026-07: a reference subsetting names the feature only in the forms whose membership the pilot derives a namingFeature for — assert q;, satisfy r;, an event, a plain ref ::> q; and a KerML feature :> q; are members with no name; otherwise the first owned redefinition names it, part :>> engine :>> motor; being engine and not motor; a declared name or declared short name governs and derives nothing, so part <e> :>> engine; is e alone; a feature chain is a nameless feature, so part :>> p.q; derives nothing while perform p.a; is a. A derived name is bound lazily: resolve/unqualified.go BindsName (LocalBindings, localBinding) answers it only when the naming target resolves to a feature, so an unresolved or non-feature target leaves the member anonymous, as in the pilot. The one remaining cell: the pilot also derives the naming feature's short name, so part def C { ref part :>> A::redefined; } answers C::r when A declares <r> redefined — we derive it only where A::redefined is inherited (semantics/masking.go Model.NamingRedefiner, via resolve/document.go inheritedAs); the shape is otherwise rejected by both tools (Must be an accessible feature). A value on a member redefining several features reaches only the first one's name, which passes/constraint.go checkUnnamedRedefinitionValue reports as a warning, code redefinition-no-derived-name, tested by passes/constraint_test.go TestConstraintUnnamedRedefinitionValue)
A reference subsetting resolves outside the name it contributes, while the members its owner inherits and imports stay visible (part v : V { perform 'provide power'; }) resolve/target.go refFilter, Resolver.ResolveReferenceTarget, threaded through resolve/unqualified.go walkUnqualifiedHiding and applied in resolve/document.go resolveRelationships, resolve/references.go refCollector.relationships (via model.Workspace.ResolveReferenceInDoc) and runtime/invoke_action.go resolveActionSymbol; the inherited half is semantics/members.go Model.LookupContributedMember resolve/target_test.go TestReferenceTargetSkipsSelfBinding, semantics/reference_test.go TestPerformOfInheritedAction, lsp/definition_test.go TestDefinitionPerformChainMember (a chain member resolves through its operand, resolve.Reference.Chain), semantics/reference_test.go TestReferenceFindsSiblingDeclaredAfterIt, model/perform_reference_test.go (perform shadowing the action it performs), lsp/definition_test.go TestDefinitionPerformReference, runtime TestPerformShorthandRunsTheReferencedAction, conformance/action_perform_shorthand.sysml ✅ Faithful
A member named by its reference is a member by that name for its owner and for every reader outside it (part h : H { perform a; } answers h.a with the performed use, and duplicates the inherited a, warned in both tools), while a member the pilot derives no name for (part h : H { assert q; }) is anonymous, so h.q written anywhere still names H::q; only a redefinition (constraint :>> q;) hides the redefined member (KerML 7.3.4.5, 8.2.3.5.5) symbols/builder.go effectiveIdent binds a derived name in the owner's scope like a declared one, and none for assert/satisfy/event/plain ::>; resolve/unqualified.go BindsName filters unqualified, qualified (resolve/qualified.go), chain (resolve/document.go chainMember, resolve/target.go memberChain) and invocation (resolve/invocation.go) lookups and the semantic model's member enumeration (semantics/members.go eachMember, eachContributedMember) alike, memoizing an answer only once the resolver's Enter/Leave frames report it settled; semantics/masking.go masks an inherited member for a true redefinition only, never for a subsetting or reference; the exact chain that named a member still skips it (Symbol.NamingTarget) probe part def H { part q; constraint c; } part h : H { assert q; } part ctx { assert h.q; assert h.c; }: the pinned validator (2026-07) reports 2:21 Must reference a constraint. and 3:19 Must reference a constraint. (ctx's h.q reaches H::q), and so do we (assert target must be a constraint usage, found partUsage at both); with constraint q : Q; in H both accept the whole model. tools/referee/reject semantic/s93 (both-reject), passes/typecheck_assert_reference_test.go TestAssertReferenceNamesNoMemberOfItsOwner, TestAssertReferenceThroughOwnChainNamesTheOwnersMember (the in-owner half), resolve/inherited_names_test.go TestReferenceDerivedNameIsAMemberInsideAndOut, semantics/w8b_masking_test.go TestSubsettingDoesNotMask, TestRedefinitionMasksTheRedefinedName, semantics/members_test.go TestMembersOfOmitsDerivedNamesTheirTargetsDoNotSupply, TestMembersOfKeepsARedefinitionWhoseTargetIsStillResolving, semantics/reference_test.go TestBareRequireAndAssumeReferenceTheNamedConstraint, TestChainedRequireAndAssumeReferenceTheChainsLastFeature (require q; and require h.q; with no body are references to q, named by the chain's last feature, not conditions reading it — ast/behavior.go ConstraintReferenceOf, runtime/condition.go appendRequirementConditions, conformance requirement_bare_reference.sysml, requirement_chain_reference.sysml) ✅ Faithful (formerly recorded as an open divergence in which the assertion was named q and shadowed H::q for every reader outside h; the pilot never named it)
The perform X; shorthand is an action node named X lower/action_graph.go getNodeName conformance/action_perform_shorthand.sysml (succession first start then increment; names the perform statement) ✅ Faithful
N-ary connector ends (connection link connect (a, b, c)), SysML v2 7.13.2, 8.3.13 parser/defusage.go parseConnectorEnds (parenthesized end list, reached by both the named declaration and the anonymous connect …; body member) over parseConnectorEnd, which takes exactly one target per end and only the relationship forms the ConnectorEnd production allows (::>, references, an explicit :>>), so a comma separates ends instead of being read as a further relationship of the first one; passes/constraint.go checkConnectorEnds (arity by kind, the interface case gated on semantics.Model.IsBinaryConnector, since Interfaces::Interface is n-ary — ref port :>> participant : Port[2..*] — and only BinaryInterface narrows it to two); semantics/implicit.go gives exactly two-ended untyped interface/connection definitions and usages their binary bases; lower/connection.go lowerConnections, PeerPorts parse/connection_nary.golden, parser/connector_ends_nary_test.go TestParseNaryConnectorEndsKeepsEveryEnd, parser/negative_test.go (nary_connect_unclosed, nary_connect_trailing_comma, nary_connect_empty), passes/constraint_test.go TestConstraintConnectionNaryEndCountReachesTheChecker, passes/w9c_rules_test.go TestW9CBinaryInterfaceEndDiamondWarns, TestW9CNonBinaryConnectorEndsStaySilent, TestConstraintBinaryInterfaceNaryFails, lower/connection_test.go TestLowerNaryConnectionKeepsEveryEnd and TestLowerAnonymousNaryConnectionKeepsEveryEnd, parser/connector_ends_nary_test.go TestParseAnonymousInlineConnectKeepsEveryEnd, conformance action_port_communication_nary.sysml and action_port_communication_nary_anonymous.sysml ✅ Faithful: SysML v2 §7.14.1 permits three or more ends on a general interface; §7.14.2 and §8.3.14.2 constrain BinaryInterface. Exactly two-ended untyped interfaces and connections receive binary typing, an explicit binary interface with too many ends errors, and a three-ended general interface remains general. The universal InterfaceUsage_Invalid.sysml.xt:49 expectation is a pilot limitation
Anonymous binary allocation (allocate torqueGenerator to powerTrain) parser/defusage.go atAllocateShorthand parse/perform_reference.golden ✅ Faithful (both names are connector ends; formerly the first was read as the usage's name)
An object of a connector usage holds the features it connects at its ends (connection link : Link connect a.p to b.q makes link.source be a.p), KerML 7.4.6, SysML v2 7.13.2 runtime/connector.go materializeConnectorFeatureValue, materializeConnector, attachConnectorEnd, bindEndFeatureValue, bindParticipants, reached from runtime/instance.go GetFeatureValue; end features synthesized by runtime/shape.go connectorEndFeatures; attachments and effective end names by semantics/connector.go Model.ConnectorEndAttachments, Model.IsConnectorUsage; inherited ends aliased by runtime/subsetting.go over Model.ImplicitEndRedefinitions connector_test.go (TestConnectorEndsAreTheConnectedFeatures, TestWritingAConnectedPortIsReadThroughTheEnd, TestConnectorEndFollowsAFeatureChain, TestConnectorEndAttachesToAPart, TestNaryConnectorKeepsEveryEnd, TestRedefinedEndSharesTheInheritedFeatureValue, TestEveryConnectorKindAttachesItsEnds), conformance/connector_end_identity.sysml (identity assertions), semantics/connector_test.go:TestConnectorEndAttachments, robustness_test.go:unattachable_connector_end, multiplicity_on_a_connector, connector_attached_to_itself, mutually_attached_connectors ✅ Faithful (an end holds the very object the connector attaches to, so writing the connected port is read through the end and two connectors on different ports are distinguishable; ends are attached in declaration order, including n-ary and nested feature chains and an end attached to a part; an end that names no reachable feature is a typed ErrConnectorEnd with a source location rather than a fresh object or <unknown>, an end naming the connector it belongs to — directly or through another connector — is ErrCyclicFeatureValue, and a connector usage holding more than one connector is reported with where it was written)
An untyped or anonymous connector usage materializes on the standard library base of its kind (interface iface connect a.p to b.q;, connect a.p to b.q;), SysML v2 7.13.2, 8.3.13 semantics/implicit.go implicitUsageBases (Connections::connections, Interfaces::interfaces, Allocations::allocations); runtime/connector.go connectorBaseOf, anonymousConnectors; symbols/builder.go usageSymbolKind (a KerML connector is a connection usage) and semantics/shape.go IsShapeFeature (an allocation usage is a feature) conformance/connector_end_identity.sysml, ballandchain_interface_connected.sysml, connector_test.go (TestUntypedConnectorUsageMaterializes, TestAnonymousConnectorJoinsItsEnds, TestAnonymousConnectorIsMaterializedOnce, TestAnonymousSuccessionIsNoConnector, TestEveryConnectorKindAttachesItsEnds), parse/connection_implicit_type.golden ✅ Faithful (a connection, interface, allocation or connector usage that names no definition is an object of its kind's library base with its ends attached, named form and anonymous form alike, and an anonymous one materializes once per object; binding and non-message flow usages are likewise materialized through the connector-object path, while message flows and one-ended bindings remain excluded)
A flow usage (flow f from a.out to b.in) and a binding usage (binding b bind a.p = b.p) are connectors of the kernel layer, but state their ends in their own syntax — Usage.FlowEnds, and a binding's two Usage.ConnectorEnds — rather than in a connect clause parser/defusage.go parseFlowEnds and parseBindingDeclaration/parseBindingEnds; resolve/document.go isImplicitCalcResult; lower/connection.go (flow ends reach routing through lowering); lower/binding.go ToBindings/lowerBinding; runtime/connector.go materializeConnectorFeatureValue/materializeConnectorAs/anonymousConnectors; runtime/binding.go objectBindings/resolveBindingValue/resolveBindingSet/attemptBinding/unmaterializedObjectEnd/resolveBindingLocation; runtime/instance.go materializeFeatureValue; runtime/invoke_calc.go resultBindingExpr; semantics/connector.go Model.IsConnectorUsage covers connect forms for views and passes, while Model.IsConnectorObjectUsage and Model.ConnectorObjectEnds cover binding and flow usages for the object model parse/connection_implicit_type.golden (flow f from a.p to b.p;, binding bnd bind a.p = b.p;), lower/connection_test.go, lower/binding_test.go (TestToBindingsKeepsMultipleContributors), runtime/testdata/conformance/binding_value_forward.sysml, binding_value_reverse.sysml, binding_nested_end.sysml, binding_nested_end_reverse.sysml, binding_expression_end.sysml, binding_multivalued.sysml, binding_calc_result.sysml, binding_calc_result_reverse.sysml, binding_object_end.sysml, binding_chained_object_ends.sysml, runtime/robustness_test.go binding cases (including exact collection-conflict, multiple-contributor, element-budget and distinct-object cases) ⚠️ Approximate (a flow between action nodes carries its value through lowering, and a named or anonymous non-message flow or two-ended binding is also materialized as a connector object whose ends hold the attached values; message flows and one-ended bindings remain outside that object model. The remaining limitation is that a flow object does not yet hold its payload. Binding propagation retains inherited and nested ends, exact sequence/set equality, multiplicity handling, element-budget charging, lazy adoption of unmaterialized composite endpoints, conflict/cycle errors, and calc result binding. Adjudicated: the resolution half agrees with the reference — flow f from a.p.o to b.p.i; with feature-chain ends is clean in the pinned validator and in ours (passes/w6c_row_adjudication_test.go:TestW6CFlowChainEndsAndOccurrenceModifiers); the residual is the runtime's binding merge, in runtime/, which this change did not own.)
An object of a connector usage holds the features it connects at its ends (connection link : Link connect a.p to b.q makes link.source be a.p), KerML 7.4.6, SysML v2 7.13.2 runtime/connector.go materializeConnectorFeatureValue, materializeConnector, attachConnectorEnd, bindEndFeatureValue, bindParticipants, reached from runtime/instance.go GetFeatureValue; end features synthesized by runtime/shape.go connectorEndFeatures; attachments and effective end names by semantics/connector.go Model.ConnectorEndAttachments, Model.IsConnectorUsage; inherited ends aliased by runtime/subsetting.go over Model.ImplicitEndRedefinitions connector_test.go (TestConnectorEndsAreTheConnectedFeatures, TestWritingAConnectedPortIsReadThroughTheEnd, TestConnectorEndFollowsAFeatureChain, TestConnectorEndAttachesToAPart, TestNaryConnectorKeepsEveryEnd, TestRedefinedEndSharesTheInheritedFeatureValue, TestEveryConnectorKindAttachesItsEnds), conformance/connector_end_identity.sysml (identity assertions), semantics/connector_test.go:TestConnectorEndAttachments, robustness_test.go:unattachable_connector_end, multiplicity_on_a_connector, connector_attached_to_itself, mutually_attached_connectors ✅ Faithful (an end holds the very object the connector attaches to, so writing the connected port is read through the end and two connectors on different ports are distinguishable; ends are attached in declaration order, including n-ary and nested feature chains and an end attached to a part; an end that names no reachable feature is a typed ErrConnectorEnd with a source location rather than a fresh object or <unknown>, an end naming the connector it belongs to — directly or through another connector — is ErrCyclicFeatureValue, and a connector usage holding more than one connector is reported with where it was written)
An untyped or anonymous connector usage materializes on the standard library base of its kind (interface iface connect a.p to b.q;, connect a.p to b.q;), SysML v2 7.13.2, 8.3.13 semantics/implicit.go implicitUsageBases (Connections::Connection, Interfaces::Interface, Allocations::Allocation); runtime/connector.go connectorBaseOf, anonymousConnectors; symbols/builder.go usageSymbolKind (a KerML connector is a connection usage) and semantics/shape.go IsShapeFeature (an allocation usage is a feature) conformance/connector_end_identity.sysml, ballandchain_interface_connected.sysml, connector_test.go (TestUntypedConnectorUsageMaterializes, TestAnonymousConnectorJoinsItsEnds, TestAnonymousConnectorIsMaterializedOnce, TestAnonymousSuccessionIsNoConnector, TestEveryConnectorKindAttachesItsEnds), parse/connection_implicit_type.golden ✅ Faithful (a connection, interface, allocation or connector usage that names no definition is an object of its kind's library base with its ends attached, named form and anonymous form alike, and an anonymous one materializes once per object; a flow or binding states its ends by other syntax and is not a connect connector — its ends reach routing through lowering, not through connector-end feature values)
A flow usage (flow f from a.out to b.in) and a binding usage (binding b bind a.p = b.p) are connectors of the kernel layer, but state their ends in their own syntax — Usage.FlowEnds, and a binding's two Usage.ConnectorEnds — rather than in a connect clause parser/defusage.go parseFlowEnds and parseBindingDeclaration/parseBindingEnds; resolve/document.go isImplicitCalcResult; lower/connection.go (flow ends reach routing through lowering); lower/binding.go ToBindings/lowerBinding; runtime/binding.go objectBindings/resolveBindingValue/resolveBindingSet/attemptBinding/unmaterializedObjectEnd/resolveBindingLocation; runtime/instance.go materializeFeatureValue; runtime/invoke_calc.go resultBindingExpr; semantics/connector.go Model.IsConnectorUsage deliberately covers only the connect forms parse/connection_implicit_type.golden (flow f from a.p to b.p;, binding bnd bind a.p = b.p;), lower/connection_test.go, lower/binding_test.go (TestToBindingsKeepsMultipleContributors), runtime/testdata/conformance/binding_value_forward.sysml, binding_value_reverse.sysml, binding_nested_end.sysml, binding_nested_end_reverse.sysml, binding_expression_end.sysml, binding_multivalued.sysml, binding_calc_result.sysml, binding_calc_result_reverse.sysml, binding_object_end.sysml, binding_chained_object_ends.sysml, runtime/robustness_test.go binding cases (including exact collection-conflict, multiple-contributor, element-budget and distinct-object cases) ⚠️ Approximate (a flow between action nodes carries its value through lowering; a binding declared in a materialized type/usage body is lowered to a bidirectional runtime value identity, including inherited and nested ends, exact sequence/set equality, multiplicity handling, element-budget charging during propagation, lazy adoption of an unmaterialized composite endpoint by the read-side object — the end being read adopts an object the other end already holds rather than building a fresh one, so a chain of bindings through nested assemblies stays one object whichever end is read first —, conflict/cycle errors, and calc result binding. A binding that states only one end — bind x;, binding bnd of x; — lowers to no runtime binding (binding bnd = x; states two ends, bnd and x, per KerML.xtext BindingConnectorDeclaration), and bindings owned directly by packages/namespaces are not applied until namespace objects are materialized. Several bindings supplying unequal scalar values report a typed conflict; multiple bindings contributing to a multi-valued end are not yet element-wise merged and report a typed ErrBindingEnd instead of silently selecting one. Adjudicated: the resolution half agrees with the reference — flow f from a.p.o to b.p.i; with feature-chain ends is clean in the pinned validator and in ours (passes/w6c_row_adjudication_test.go:TestW6CFlowChainEndsAndOccurrenceModifiers); the residual is the runtime's binding merge, in runtime/, which this change did not own.)
An object of a connector usage holds the features it connects at its ends (connection link : Link connect a.p to b.q makes link.source be a.p), KerML 7.4.6, SysML v2 7.13.2 runtime/connector.go materializeConnectorFeatureValue, materializeConnector, attachConnectorEnd, bindEndFeatureValue, bindParticipants, reached from runtime/instance.go GetFeatureValue; end features synthesized by runtime/shape.go connectorEndFeatures; attachments and effective end names by semantics/connector.go Model.ConnectorObjectEnds, Model.IsConnectorObjectUsage; inherited ends aliased by runtime/subsetting.go over Model.ImplicitEndRedefinitions connector_test.go (TestConnectorEndsAreTheConnectedFeatures, TestWritingAConnectedPortIsReadThroughTheEnd, TestConnectorEndFollowsAFeatureChain, TestConnectorEndAttachesToAPart, TestNaryConnectorKeepsEveryEnd, TestRedefinedEndSharesTheInheritedFeatureValue, TestEveryConnectorKindAttachesItsEnds, TestBindingConnectorIsAnObjectOfItsEnds, TestFlowConnectorIsAnObjectOfItsEnds, TestBindingConnectorEndFollowsAFeatureChain, TestBindingConnectorEndsHoldBoundValues), conformance/connector_end_identity.sysml, conformance/connector_object_binding_flow.sysml, semantics/connector_test.go:TestConnectorEndAttachments, semantics/connector_test.go:TestConnectorObjectEnds, robustness_connector_objects_test.go, robustness_test.go:unattachable_connector_end, multiplicity_on_a_connector, connector_attached_to_itself, mutually_attached_connectors ✅ Faithful (an end holds the very object the connector attaches to, so writing the connected port is read through the end and two connectors on different ports are distinguishable; the same object path now materializes connect, binding and non-message flow usages with declaration-order ends; message flows and one-ended bindings remain excluded; errors are typed ErrConnectorEnd with a source location and all-or-nothing rollback)
An untyped or anonymous connector usage materializes on the standard library base of its kind (interface iface connect a.p to b.q;, connect a.p to b.q;, bind a.p = b.q;, flow a.p to b.q;), SysML v2 7.13.2, 8.3.13 semantics/implicit.go implicitUsageBases; runtime/connector.go connectorBaseOf, anonymousConnectors; symbols/builder.go usageSymbolKind (bindings use SymbolBindingUsage) and semantics/shape.go IsShapeFeature conformance/connector_end_identity.sysml, conformance/connector_object_binding_flow.sysml, connector_test.go (TestUntypedConnectorUsageMaterializes, TestAnonymousConnectorJoinsItsEnds, TestAnonymousConnectorIsMaterializedOnce, TestAnonymousBindingAndFlowAreOwnedConnectors, TestAnonymousSuccessionIsNoConnector, TestEveryConnectorKindAttachesItsEnds), parse/connection_implicit_type.golden ✅ Faithful (a connection, interface, allocation or connector usage, two-ended binding, or non-message flow that names no definition is an object of its kind's library base with its ends attached, named form and anonymous form alike, and an anonymous one materializes once per object; anonymous successions remain excluded)
A flow usage (flow f from a.out to b.in) and a binding usage (binding b bind a.p = b.p) are connector objects of the kernel layer, but state their ends in their own syntax — Usage.FlowEnds, and a binding's two Usage.ConnectorEnds — rather than in a connect clause parser/defusage.go parseFlowEnds and parseBindingDeclaration/parseBindingEnds; lower/connection.go and lower/binding.go continue routing and binding semantics; runtime/connector.go materializeConnectorFeatureValue/materializeConnectorAs/anonymousConnectors; semantics/connector.go Model.IsConnectorObjectUsage/Model.ConnectorObjectEnds semantics/connector_test.go:TestConnectorObjectEnds, connector_test.go (TestBindingConnectorIsAnObjectOfItsEnds, TestFlowConnectorIsAnObjectOfItsEnds, TestAnonymousBindingAndFlowAreOwnedConnectors), robustness_connector_objects_test.go, routing_differential_test.go ✅ Faithful (the object model now materializes two-ended bindings and non-message flows through the same connector path as connect usages. IsConnectorUsage remains deliberately connect-only for views, document queries and passes; IsConnectorObjectUsage and ConnectorObjectEnds cover the wider object model. Flow objects do not yet hold their payload; message flows and one-ended bindings are not connector objects.)
The of clause of a binding (binding b of full = level) names the feature the binding binds, so it is a reference subsetting rather than a typing (KerML 8.3.3.3.9, SysML v2 8.3.13) parser/defusage.go (parseBindingEnds reads what follows of as two ast.ConnectorEnds, each attaching the feature it binds; the clause is never a typing) parser/binding_of_test.go:TestBindingOfTargetIsAReference, parse/constraint_parameterised_conditions.golden ✅ Faithful (formerly recorded as a typing, which reported the bound feature as "type must be a definition")
A declared name wins over an effective one in the same namespace (part v { perform p; action p; }) symbols/scope.go PreferDeclared, used by LookupLocal and resolve/qualified.go's segment walk; symbols/builder.go (Symbol.EffectiveName) semantics/reference_test.go TestReferenceFindsSiblingDeclaredAfterIt, TestQualifiedNameThroughEffectiveNameIsNotAmbiguous, TestRepeatedPerformResolvesToTheAction ✅ Faithful
individual def X :> PartDef, x : IndividualDef kind compatibility, SysML v2 7.9.4 passes/typecheck.go occurrenceDefSymbolKinds/isOccurrenceDefKind (specialization) and isCompatibleTyping (typing) passes/typecheck_individuals_test.go, corpus gate (Verification Case Usage Example now clean) ✅ Faithful (an individual def is an occurrence definition, so it may specialize an occurrence definition of any kind and may type a usage wherever an occurrence definition may; specializing a data type — an attribute or enumeration definition — stays an error per 8.4.5.1, and a usage kind that rejects an occurrence definition, such as a port usage, still rejects an individual definition)
individual / snapshot usage modifiers (individual testSystem : TestSystem, snapshot occurrence takeoff : Flight), SysML v2 7.9.4, abstract syntax 8.3.9.11 (OccurrenceUsage::isIndividual, OccurrenceUsage::portionKind) ast/defusage.go Usage.IsIndividual/Usage.IsSnapshot, stored by parser/defusage.go parseUsage and parser/behavior.go parseDirectionParameter; consulted by passes/typecheck.go declKind.isOccurrenceUsage, compatMessage and isCompatibleTyping parser/occurrence_modifier_test.go, parse/occurrence_individual_snapshot.golden, parser/negative_test.go (individual_modifier_no_member, individual_usage_no_type, individual_usage_no_body, snapshot_usage_no_type, individual_parameter_no_type), passes/typecheck_individuals_test.go TestTypeCheckOccurrenceModifierWidensTypingOK, TestTypeCheckOccurrenceModifierRejectsDataType, TestTypeCheckDataTypeTypingWithoutModifierOK ⚠️ Approximate (the modifier is orthogonal to the keyword that declares the usage, so individual part p is a part usage that is an individual, and either modifier makes the usage an occurrence usage: it may be typed by an occurrence definition of any kind and may not be typed by a data type — an attribute or enumeration definition — per 8.4.5.1. An individual occurrence takes Occurrences::Life as its implicit base (semantics/implicit.go implicitBase). The modifier is not yet reflected in the usage's symbol kind, so individual testSystem is still indexed as an attribute usage — the typing widening compensates. Adjudicated: stays approximate; the referee cannot see the gap. individual occurrence def F1 :> Flight; with snapshot occurrence takeoff : F1; is clean in the pinned validator and in ours (passes/w6c_row_adjudication_test.go:TestW6CFlowChainEndsAndOccurrenceModifiers), so the symbol-kind approximation produces no diagnostic difference to adjudicate; the fix is in internal/semantic/symbols, which this change did not own)
if/else branch bodies as namespaces ast/behavior.go IfBranchNode (parsed by parser/behavior.go parseIfBranch), symbols/builder.go IfActionNode/IfBranchNode, resolve/document.go, symbols/bodyscopes.go, resolve/references.go TestBodyLocalDeclarationsAreVisible/if_branch_body_reads_its_own_declaration, /else_branch_reuses_the_then_branch's_name, TestBodyLocalNamesDoNotEscape/if_branch_member_from_outside, /else_branch_member_from_the_then_branch, parse/action_if_branch_body.golden, lsp/if_branch_test.go, resolve TestImportRecursiveSkipsBodyLocalNames, repl TestLookupInScopeTreeSkipsBodyLocalNames ✅ Faithful (each branch owns a body-local scope: names declared in a branch resolve inside it, do not escape to the enclosing behavior or to the sibling branch, and — like loop bodies — are excluded from recursive imports and the REPL scope-tree search; the condition is evaluated before either branch is entered, so it resolves in the enclosing scope only)
Transition source/target names resolve/transition.go (*Resolver).ResolveEndpoint; resolve/edge.go and resolve/document.go for transition and succession spellings; passes/state_transition.go and passes/action_endpoint.go; lower/action_nodes.go ActionNodes/ActionEndpointAccepted and lower/state_graph.go endpoint consumption resolve/transition_test.go, passes/succession_endpoint_test.go:TestResolvedStateEndpointNotVertexIsReported, :TestActionEndpointPassReportsResolvedNonNodes, :TestActionEndpointPassDiagnosticAgreesWithLowering, :TestStateSuccessionEndpointSpellingsAcceptVertices ⚠️ Approximate (resolved as references at the name-resolution tier, so a misspelled endpoint reports there with a suggestion and lowering consumes the resolved declaration; the leniencies are listed with the state machine row above — an unqualified endpoint falls back to the first vertex of the machine whose name path ends in it, and an endpoint naming no vertex leaves its edge out of the graph rather than failing the lowering. Adjudicated: the resolution half is matched — transition first a then bb; with a misspelled target is Couldn't resolve reference to Feature 'bb' in the pinned validator and one unresolved diagnostic in ours (passes/w6c_row_adjudication_test.go:TestW6CTransitionEndpointNameIsResolved); the leniencies above are lower/'s, which this change did not own)
Signal trigger names (accept X) and the payload parameter an accept declares parser/behavior.go parseTriggerEvent builds a typed payload Usage; resolve/document.go resolveTrigger resolves its typing; lower/state_graph.go classifyTrigger carries the resolved type into the accept event internal/syntax/parser/behavior_test.go:TestParseTransitionAcceptPayloadTyping, tests/parser/testdata/parse/transition_accept_payload_typing.golden, internal/semantic/resolve/accept_trigger_test.go:TestAcceptTriggerReferences, internal/check/passes/w6c_row_adjudication_test.go:TestW6CSignalTriggerNameIsResolved ✅ Faithful (bare and qualified accept names, named payload typings and subsetting targets resolve as type references, matching the pinned validator; unqualified misspellings receive ordinary unresolved-reference diagnostics with suggestions where applicable; OpenSysML's when <name> spelling remains an injected signal and is not resolved)
Payload feature a flow/message declares in its of clause (message m of fuelCommand : FuelCommand) parser/defusage.go parseFlowEnds (declaration recorded as FlowEnds.PayloadDecl and kept as a member of the flow), resolve/document.go (the of name resolves in the flow's own scope) parse/flow_payload_declaration.golden, model/flow_payload_resolve_test.go TestDeclaredFlowPayloadIsAMember, TestFlowPayloadReferenceStillResolvesOutward ✅ Faithful (the declared payload is a member of the message, so the of name and m.payload both resolve; the reference form of Type still resolves in the enclosing scope)
Accept-parameter visibility to sibling action nodes runtime/action_executor.go the action's shared feature space action_accept_message.sysml ⚠️ Approximate (the executor binds the payload into the action's shared feature space, which scoping does not model: a sibling node reading the parameter by simple name is reported unresolved) — Adjudicated: matched, and the divergence is ours by choice. On action consume { attribute z : Text = msg.payload; } beside accept msg : Msg; the pinned validator reports Couldn't resolve reference to Element 'msg' and … 'payload', while we resolve both so the executor's shared feature space and the scope agree (passes/w6c_row_adjudication_test.go:TestW6CAcceptParameterIsVisibleToSiblingNodes). Stated as a deliberate, tested divergence rather than promoted
Unqualified library names in files that do not import their library (Boolean, Real) — only the public top-level members of a root namespace are globally visible, and a library member is not one ([SysML, 7.2] over [KerML, 8.2.3.2, 8.2.3.5]); the same for a qualified name whose first segment resolves to nothing (VerificationMethodKind::test unimported) resolve/suggest.go unresolvedMessage (the diagnostic names the qualified spelling and, when exactly one importable candidate matches, the private import X::*; that makes the bare name visible) and importCandidate (that single-candidate rule, shared with the qualified path), resolve/qualified.go unresolvedNamespace (the first-segment spelling of the same diagnostic, with a fix rewriting the segment to the candidate), and resolve/fixes.go importFix (the offered fix writes private import X::*;, an import that serves the importing namespace without re-exporting onward) model/suggestion_test.go, model/examples_test.go, resolve/fixes_test.go TestTheImportFixWritesAPrivateImport, lsp/codeaction_test.go TestCodeActionImportsResolvableFQN, resolve/verification_method_lookup_test.go (the import sentence on both forms and on a first segment, both fixes, and the imported and fully qualified spellings resolving), passes/verification_method_test.go (the imported bodies type-check clean; a literal outside the enum and values of other types are refused), tests/export/verification_method_metadata_test.go ✅ Faithful (such a name is genuinely unresolved: a model imports the library or qualifies the name, as OMG's own training files do; every surface says which qualified name was meant, offers both spellings, and names the import that would make the bare name visible. Observed against the pinned pilot: VerificationMethod/VerificationMethodKind::test unimported are rejected by both (tools/referee/reject semantic/s100), accepted by both with private import VerificationCases::*;)
A member chain from that reads the object featuring the value being written, not the library declaration's own members ([SysML, 7.2] over [KerML, 8.4.2]) resolve/document.go featuringOf (a chain whose operand is Base::things::that resolves its members against the usage enclosing the expression, so both the usage's own members and those it inherits are reached through lookupMember), runtime/eval.go evalName (that evaluates to the object being evaluated) model/that_test.go TestThatChainsThroughTheFeaturingType, runtime/that_test.go TestThatReadsTheFeaturingObject, TestThatReadsTheInnermostFeaturingObject ✅ Faithful (that.a resolves and evaluates through the featuring object, the innermost enclosing usage wins, an unowned member is unresolved member, and a that written where no usage encloses it stays unresolved rather than resolving to the library feature)
A namespace re-exports what it imports with import X::*, transitively and wherever the name X resolves (KerML::Element, where KerML imports Kernel::*, which imports Core::*, which imports Root::*; KerML 7.2.5, 8.2.3.5) symbols/index.go ExpandWildcardImports (repeats expandRound to a fixpoint over the importers in name order, deriving the re-exports of the ones a change reached and dropping those its imports no longer support) and resolveWildcardTarget (searches the importing package's enclosing namespaces before the global one) symbols/index_test.go TestExpandWildcardImportsChainsAndIsOrderIndependent, TestExpandWildcardImportsPrefersTheEnclosingTarget, TestExpandWildcardImportsFollowsAReexportedTarget, TestExpandWildcardImportsIgnoresAnAmbiguousTarget, libs/loader_cache_test.go TestParsedAndRestoredIndexesAreEquivalent, model/corpus_gate_test.go TestCorpusGatesCacheStateIndependent ✅ Faithful (a chain of imports is followed to its end, and the result does not depend on iteration order or on whether the library was parsed or restored from the on-disk index cache; a target name resolves against the importing namespace's own imported memberships — wildcardTargetAt follows a name an earlier import re-exported to the FQN it was declared under — before the global namespace)
The names a document's own root-level import X::* surfaces are visible in the scope tree that document builds for the editor, whether or not it declares anything else — a bare import at the REPL prompt included symbols/members.go SetDocName / DocNameOf (the tree carries the document name itself, no member being needed to hold it), read by resolve/filter.go documentOf resolve/imports_test.go TestRootImportInDocumentDeclaringNothingElse ✅ Faithful
A membership import surfaces the imported member under both of its names — the declared name and the short name, so import SI::kilogram makes kilogram and kg resolve — and under no other member's short name: what the import adds is the target's Membership, which carries a memberName and a memberShortName (KerML 8.2.4) resolve/unqualified.go matchImport (the target is found by member lookup, which knows both names, instead of by comparing the written last name part with the name being resolved) with importPrefixAvailable, which keeps that wider match from re-entering an import whose own prefix has no binding to resolve through resolve/imports_test.go TestMembershipImportAcceptsDeclaredAndShortNames (kg resolves; the sibling <g> gram the import does not name stays unresolvable), TestImportMembership, TestImportDoesNotLeakNonImported ✅ Faithful
An import carries a visibility indicator: the pinned OMG pilot grammars make it mandatory (fragment ImportPrefix : visibility = VisibilityIndicator 'import' ..., KerML.xtext:169-172, SysML.xtext:241-244, with no ? unlike the sibling MemberPrefix), and all 574 imports in the 254 OMG-authored corpus files carry one passes/import_visibility.go ImportVisibilityPass at LevelSyntax, code syntax/import-visibility, span on the import keyword; no parser or AST change — ast.Import.Visibility already records VisibilityDefault for the bare form passes/import_visibility_test.go, testdata/passes/import_no_visibility.sysml + golden, lsp/diagnostics_test.go TestPublishDiagnosticsReportsBareImportAsWarning ⚠️ Approximate (reported as a warning, not an error: the bare form is unambiguous to parse, so hard-failing would reject existing models over notation. It does not gate the name-resolution, type or constraint tiers, and an expose is exempt — the pilot grammar gives it implicit protected visibility, SysML.xtext:2366-2372. Adjudicated: stays a deliberate divergence, matched. package P { import Q::*; … } is a parse error in the reference and an import-visibility warning in ours, which is the recorded maintainer decision, tested by passes/w6c_row_adjudication_test.go:TestW6CNotationNoPinnedProductionAdmitsIsWarned/import_without_a_visibility_indicator; the indicator itself is still not honoured for visibility filtering, which is the remaining gap)
A namespace declaration is KerML notation: namespace is a literal in KerML.xtext only (:125, NamespaceDeclaration), and a SysML file's root is RootNamespace : PackageBodyElement* (SysML.xtext:38), which admits no namespace declaration passes/nonstandard_notation.go NonstandardNotationPass at LevelSyntax, code kerml-notation, span on the namespace keyword; source/kind.go Kind/KindOf tells a .sysml file from a .kerml one passes/nonstandard_notation_test.go TestNamespaceInSysMLIsKerMLNotation, TestNamespaceInKerMLIsSilent, TestNamespaceInAnUnnamedDocumentIsKerMLNotation, repl/notation_test.go ⚠️ Approximate (reported as a warning, not an error, and not in .kerml: both namespace N; and namespace N { … } are legal KerML, so the construct stays parsed and silent there. The REPL and CLI buffer is one document of no file kind, so it takes the SysML reading its prompt takes, and repl/session.go dropKerMLNotationOfKerMLFiles drops the finding for a snippet loaded from a .kerml file. Adjudicated: stays a deliberate divergence, matched — namespace N { part def Q; } in a .sysml file is a parse error in the pinned validator and a kerml-notation warning in ours: passes/w6c_row_adjudication_test.go:TestW6CNotationNoPinnedProductionAdmitsIsWarned/namespace_in_sysml)
Notation OpenSysML accepts that no pinned production admits — the state pseudostates (choice, junction, the history forms), defer, and two placement rules: assume/require outside a requirement-style body (SysML.xtext:2039 admits it there alone) and a one-ended first <node>; outside an action body (:1376) — is diagnosed rather than silently accepted. The spellings that were pure aliases of standard notation are parse errors instead: a named final node written done <name>;, transition <src> to <tgt>;, initial <state>;, final <state>;, the orthogonal-region member region <name> { … }, bind <feature> = <expression>;, and a computed calculation result written return <expression>; (:1961 makes return a result parameter declaration and :1967 makes a computed result the keyword-less trailing expression); a keyworded inline condition (assert <expression>;, assume <expression>;, require <expression>;) is no longer accepted at all: :2007 and :2066 admit a reference or a constraint declaration after the keyword, never an expression, so the parser rejects the expression form and the standard spellings — a keyword-less condition in a constraint body, require constraint { … } in a requirement-style body — are the only ones; the audit with citations is reference/grammar/conformance-audit.md passes/nonstandard_notation.go NonstandardNotationPass at LevelSyntax, code nonstandard-notation, span on the word; ast.PseudostateNode.Keyword records which pseudostate spelling was written passes/nonstandard_notation_test.go TestStateExtensionsAreReported, TestOneEndedFirstOutsideAnActionBodyIsAnExtension, TestRequirementConstraintOutsideARequirementBodyIsAnExtension, TestResultParametersAndTrailingExpressionsStaySilent, TestFeatureValuedBindingIsSilent, TestStandardTransitionStaysSilent, TestStandardConstraintAndRequirementConditionsStaySilent, parser/keyworded_condition_test.go TestKeywordedConditionIsRejected, TestNotationWarningsPointAtTheirKeywords, TestStandardNotationIsSilent, passes/nonstandard_notation_strict_test.go TestExtensionInventoryIsAnErrorUnderStrictMode, libs/nonstandard_notation_test.go TestStdlibHasNoNonstandardNotation, libs/nonstandard_notation_strict_test.go TestStdlibIsConformingUnderStrictMode ⚠️ Approximate (a warning by default, an error under the opt-in strict conformance mode (sysml -strict, %strict on, the strictConformance LSP setting, ParseFileRequest.strict_conformance, diag.ConformanceMode): the notation is a documented OpenSysML extension existing models use, so by default it keeps parsing and does not gate a higher tier. done, fork and join are silent, and return (a); is silent because the parser collapses a single parenthesized expression to its inner node, so the pass cannot tell it from the legal return a; — see the audit's judgment calls. Adjudicated: this row cannot be promoted, and that is the honest outcome. Strict mode does not promote it either: strict mode is opt in, so a conformance claim resting on it says only that the check exists and passes when asked, not that the default pipeline rejects the notation — weaker evidence than a default check, and no external suite adjudicates it. What changed is that the finding can now be made fatal on request, and the rejection oracle uses that to reach agreement on four extensions/ cases under strict mode alone (docs/project/pilot-rejection.md) — agreement when asked strictly, not four gaps that disappeared. Only those four moved here; of the 14 gaps the oracle carried earlier, the other nine were closed by the KerML and SysML declared-rule work above. And since we authored all 34 of that oracle's cases, its gap count's denominator measures our coverage of the rejection surface, not our conformance. Nothing external tests that we correctly reject notation, so the deliverable is a negative suite of our own: passes/w6c_row_adjudication_test.go:TestW6CNotationNoPinnedProductionAdmitsIsWarned pins four such constructs against the reference's own rejection messages (mismatched input 'of' expecting 'bind', no viable alternative at input 'N', no viable alternative at input 'featured', mismatched input 'import' expecting '}'). part all : T;, clean in ours and no viable alternative at input 'all' in the reference, is a further instance of the same thing and belongs to the reserved-name row above)
A private import X::* is not re-exported by its namespace, and the names it brings in are visible only within that namespace (KerML 8.2.3.3) symbols/index.go applyReexportMarks / exportedChildren (a re-export is hidden while every document that surfaced it did so with a private import, and left out when that namespace is itself wildcard-imported) and LookupQualifiedFrom (a hidden name answers a lookup only when the referring namespace is the one that hid it, or is nested in it); resolve/qualified.go referringNamespaceFQN supplies that context for a qualified reference and HiddenFrom stops its member-lookup fallback, which reaches a cached symbol's children without consulting the marks, from resurfacing a hidden name; resolve/alias.go resolveCachedAliasTarget supplies the context for the target of a cached alias; resolve/unqualified.go matchImport enumerates a wildcard import's target through symbols/index.go LookupDirectChildrenFrom, which reads the same marks from the referring namespace unless the import is import all symbols/index_test.go TestExpandWildcardImportsDoesNotCarryOnAPrivateImport, TestLookupQualifiedFromSeesAPrivateImportOnlyFromWithin, TestHiddenFromReportsOnlyPrivatelySurfacedNames, TestLookupQualifiedReachesAPubliclyImportedName, TestLookupQualifiedAcrossAChainedPrivateImport, TestLookupDirectChildrenFromDropsPrivatelyImportedNames; resolve/qualified_test.go TestResolveQualifiedRejectsAPrivatelyImportedName, TestResolveQualifiedRejectsAPrivatelyImportedNameThroughMemberLookup, TestResolveQualifiedFromInsideAnUnnamedElement, TestResolveQualifiedReachesAPubliclyImportedName; resolve/visibility_test.go TestNamespaceImportSkipsAPrivatelyImportedName, TestNamespaceImportSkipsAPrivatelyImportedCachedName; resolve/alias_test.go TestAliasResolvesAPrivatelyImportedTargetFromCache, TestAliasResolvesAPrivatelyImportedTargetWhenParsed; model/visibility_reach_test.go TestPrivateWildcardImportIsNotReExportedAcrossDocuments ✅ Faithful (neither a qualified nor an unqualified reference reaches a privately imported name from outside the importing namespace, whether the index was parsed or restored from cache; an import all still takes the target's private memberships, and a reference inside the importing namespace still sees them)
A name an alias introduces reaches the aliased element, not the alias: an alias declares a Membership whose memberElement is an existing element — "the memberNames of a Membership are effectively aliases within the membershipOwningNamespace for an Element with a separate OwningMembership in the same or a different Namespace" (KerML 8.2.3.2; the pinned KerML.xtext AliasMember returns SysML::Membership) — so a reference through the alias resolves to that element and reports the element's own qualified name, while the alias name stays a member of its namespace resolve/alias.go Resolver.AliasedElement over ResolveAliasTarget (an alias whose target is unresolvable, a cycle included, stays itself so its own diagnostic still fires); resolve/resolver.go resolvedPart canonicalizes every qualified-name segment and the memoized ResolveName, resolve/qualified.go walkQualified and resolve/target.go memberChain route through it, so a segment written as an alias walks the target's scope and registers the target's FQN; resolve/resolver.go PartAlias retains the alias membership a segment wrote, which resolve/unqualified.go matchImport uses to keep a membership import naming the alias, edit/rename.go renameOccurrences and model/workspace.go ResolveReferenceNameSegmentsInDoc (read by lsp/rename.go and lsp/references.go) use to keep renaming an alias distinct from renaming its target — an editor rename edits the name that was written — and lsp/references.go unions with the canonical segment so a reader asking for references of the element still sees the uses written through the alias resolve/w6b_alias_identity_test.go TestW6BReferenceThroughAliasReachesTheTarget, :TestW6BAliasStaysAVisibleMember, :TestW6BAliasQualifiesItsTargetsMembers, :TestW6BAliasChainAndCycle, :TestW6BImportedAliasNameResolvesToTheTarget; lsp/w6b_alias_consumers_test.go TestW6BDefinitionThroughAliasLandsOnTheTarget, :TestW6BHoverOnTheAliasDeclaration, :TestW6BCompletionStillOffersTheAliasName, :TestW6BMembersThroughAliasAreTheTargets, lsp/w6b_alias_rename_test.go TestW6BRenameAliasRewritesItsUses, :TestW6BRenameTargetLeavesAliasUsesAlone, :TestW6BReferencesOfTargetIncludeAliasUses, :TestW6BReferencesOfAliasAreItsNameOccurrences; symbols/w7b_alias_identity_test.go (SameElement over an alias chain, the alias name still a member), resolve/w7b_alias_identity_test.go TestW7BImportOfAnAliasMembershipNamesTheMembership; the pilot's own Xpect suite as referee: go run -C tools ./cmd/pilot-xpect linkedName 194 of 194, from 151 of 194, the 41 moved rows all an alias naming itself (build/pilot-xpect-corpus/kerml/testsuite/MemberNameTests_LocalNamedMember.kerml.xt:37 declares test.A, we reported test.A_alias) ✅ Faithful (the alias is not an element, and its name still exists: it is enumerated in its namespace's members and offered in completion, go-to-definition through it lands on the target, and hover on the declaration still reads alias Box. An audit of the symbol half found one defect: import defs::Car where Car is an alias surfaced the target's name Vehicle in the imported membership list — resolve/filter.go ImportedElements now keeps the alias membership's own name, which TestW7BImportOfAnAliasMembershipNamesTheMembership pins and which fails on the parent commit; the rest — completion, member lists and symbols.SameElement over a chain of aliases — was already correct)
A root-level import X::* surfaces its names in the importing document's own root namespace, so they are not names of another document's root (KerML 8.2.3.3, 8.2.4) symbols/index.go ReexportVisible (a root-level re-export answers a lookup only in a document holding a claim on it; a name under a namespace is visible wherever that namespace is), read by resolve/filter.go Resolver.admitsUnderName on the whole-index routes for a top-level name symbols/index_test.go TestARootReexportIsVisibleOnlyInItsOwnDocument; resolve/filter_test.go TestARootImportSurfacesNamesInItsOwnDocumentOnly; model/that_test.go TestRootImportServesItsOwnDocumentOnly ✅ Faithful (a filter on a root-level import therefore hides what it rejects from every document, and the importing document keeps what it admits; the importing document keeps them at every visibility, private included — LookupQualifiedFrom leaves the private-import hiding of a root-level name to ReexportVisible, which decides it per document, rather than hiding it from the document that wrote the import)
Visibility of the members a recursive import surfaces (import X::**, KerML 7.2.5) resolve/unqualified.go matchImport (both the membership and namespace branches filter through resolve/visibility.go visibleThroughImport) resolve/visibility_test.go TestRecursiveMembershipImportSkipsPrivate, TestNamespaceImportSkipsPrivate, TestImportAllReExportsPrivate ✅ Faithful (a recursive membership import hides private members of the subtree it walks unless it is import all)
expose in a view body is an Import (SysML v2 8.3.26.2 Expose, 8.3.26.3 MembershipExpose, 8.3.26.4 NamespaceExpose) parser/defusage.go (expose shares parser/namespace.go parseImportTail, so ::* yields a NamespaceExpose and ::** a recursive MembershipExpose; ast.Import.IsExpose, IsAll, protected Visibility) parser/expose_test.go TestParseExposeImportKind, TestParseExposeIsImportAllAndProtected, resolve/expose_test.go ⚠️ Approximate (an Expose always imports all elements regardless of visibility — validateExposeIsImportAll — so its exposed elements resolve inside the view body, in views that specialize it, and not outside; validateExposeOwningNamespace is implemented — see the row below. Adjudicated: stays approximate for a stated reason, not for a known defect. The refereeable half agrees — expose Inner::Hidden; on a private member is clean in the pinned validator and in ours (passes/w6c_row_adjudication_test.go:TestW6CExposeOfAPrivateMemberIsClean) — but the part that would promote the row, that exposed names are not visible outside the view, produces no diagnostic in the reference and so has no external check)
Protected import visible in specializations of the importing definition or usage (SysML v2 7.5.3) resolve/visibility.go inheritedThroughSpecialization (a protected or public import reaches specializations, a private one does not — KerML 8.2.3.3) and lookupInheritedImports (walks semantics.Model.DirectSupertypes upward from the referring scope's owner, breadth-first and cycle-guarded, matching each supertype's inherited imports through the same matchImport); resolve/unqualified.go walkUnqualifiedHiding consults it after the imports declared in the scope itself resolve/protected_test.go TestProtectedImportReachesADirectSpecialization, TestProtectedImportReachesATransitiveSpecialization, TestProtectedImportReachesAUsageTypedByTheImporter, TestProtectedImportDoesNotReachAnUnrelatedNamespace, TestPrivateImportDoesNotReachASpecialization, TestProtectedImportAllReachesASpecializationWithPrivateMembers, TestExposeReachesASpecializingView, TestInheritedImportWalkTerminatesOnASpecializationCycle; model/visibility_reach_test.go TestProtectedImportReachesSpecializationsAcrossDocuments, TestProtectedImportDoesNotReachAnUnrelatedDocument, TestExposeReachesASpecializingViewAcrossDocuments ✅ Faithful (an expose is protected, so it reaches a specializing view the same way; a feature typing is a generalization edge — KerML 8.3.4.6 — so an import declared in a definition is also reached from a usage typed by it, and an unrelated namespace sees nothing)
A protected import is re-exported only to what specializes the importing definition or usage: under part def Base { protected import Lib::Pub; }, part def Sub :> Base { public import Base::*; } reaches Pub and an unrelated part def Other { public import Base::*; } does not (SysML v2 7.5.3, KerML 8.2.3.3) resolve/unqualified.go importVisibleFrom (a protected import answers a lookup through another namespace's re-export only when the referring scope's owner specializes the namespace that declared it) over resolve/visibility.go specializes / specializationChain, one breadth-first cycle-guarded walk of semantics.Model.DirectSupertypes shared with lookupInheritedImports resolve/protected_test.go TestProtectedImportReexportFollowsSpecialization, TestProtectedImportDoesNotReachAnUnrelatedNamespace, TestProtectedImportReachesATransitiveSpecialization ✅ Faithful (a re-export widens who may reach a protected import, never what it imports: the visibility test is applied at the referring scope, so the same name is answered for a specialization and refused for a sibling)
validateExposeOwningNamespace — the importOwningNamespace of an Expose must be a ViewUsage (SysML v2 8.3.26.2) passes/expose.go checkExposeOwners / exposeOwnerDiagnostic, run by passes/constraint.go ConstraintPass at LevelConstraint; code expose-owning-namespace passes/expose_test.go TestExposeOwningNamespace, model/expose_owner_test.go TestExposeOwnerAcrossDocuments ✅ Faithful (usage-only reading, per maintainer decision: an expose owned by a view usage is legal, one in a view def body is a warning since OpenSysML resolves it — resolve/expose_test.go TestExposeInViewDefinitionBody — and any other owner is an error; a package or namespace body rejects expose in the parser)
A namespace's filter restricts the imported memberships it re-exports (package P { public import Q::*; filter @Safety; }, KerML 8.2.4, SysML v2 7.4.4) symbols/filter.go NamespaceFiltersIn and symbols/index.go reexportGated (each way a name is re-exported records the conditions along it as one route; a name with no route is ungated) — the index records the gate and never evaluates it; resolve/filter.go Resolver.admitsUnderName evaluates a candidate against the routes through semantics.Model.SatisfiesElementFilter, and resolve/unqualified.go matchImport, resolve/qualified.go and symbols/index.go LookupDirectChildrenFrom share that one admission test; a filter member of a definition or usage body — a view narrowing what its expose lines surface — reaches the same test through resolve/filter.go Resolver.importAdmits, which composes symbols.NamespaceFiltersIn(scope) with the import's own clause; a filter at a document's root gates that document's root-level imports alone, since each document owns its root namespace (symbols/filter.go namespaceFiltersGating, keyed per document by symbols/index.go gateKeyOf) symbols/index_test.go TestExpandWildcardImportsGatesAFilteredReexport, TestExpandWildcardImportsKeepsAnUnfilteredRoute, TestExpandWildcardImportsRecordsAGateOnce; resolve/filter_test.go; model/element_filter_test.go (including TestFilterMemberOfADefinitionBodyRestrictsItsImports, TestFilterConditionResolvesThroughTheImportsItFilters, TestARootFilterRestrictsOnlyItsOwnDocument), symbols/index_test.go TestRootNamespaceFiltersGateOnlyTheirOwnDocument; libs/loader_cache_test.go TestFilteredImportsSurviveCacheRestore ✅ Faithful (a filter restricts only what the namespace re-exports, per maintainer decision: its own directly declared members are never hidden, only the condition's own names resolve unfiltered — otherwise a condition could not name a metadata type the namespace itself imports, since the condition's own names would be filtered by the condition (resolve.Resolver.InCondition) — and a name reached by an unfiltered route as well as a filtered one stays visible: the routes are alternatives, and the conditions along one route are a conjunction)
An import or expose filter restricts what that import brings in (import P::*[@Safety];, expose vehicle::**[@Safety];, SysML v2 7.4.4, 8.3.26) ast.Import.FilterExpr reaches the index through symbols/index.go wildcardImport.filter, gating the memberships that import surfaces; the same admitsUnderName decides them, so the qualified and the unqualified route agree, including the whole-index fallback for a top-level name (resolve/qualified.go lookupGlobalTop, which a root-level filtered import would otherwise bypass); the condition's own names are resolved as references like a filter member's, so a typo in the clause is an unresolved reference and editor navigation over it works (resolve/document.go, resolve/references.go) resolve/filter_test.go, model/element_filter_test.go (a filtered expose in a view surfaces a strict subset, an element the condition rejects is unresolvable by either route, TestAFileLevelFilteredImportHidesWhatItRejects, TestAnUnresolvedNameInAnImportFilterIsReported), parse/element_filters.golden ✅ Faithful (an import's condition and the filters of the namespace it imports compose: the intersection is what the importer sees)
A view's exposed elements are queryable (SysML v2 7.24 Views and Viewpoints, 8.3.26 Expose) semantics/expose.go Model.ExposedElements (a view's own expose relationships, then those of the views it specializes since an Expose is protected, in declaration order and once each) and Model.NestedViews (the views in its body, to walk a view tree), enumerated by resolve/filter.go Resolver.ImportedElements — the same admission, visibility and filter gating a lookup through that import makes, so the exposed set is what the view body actually resolves semantics/expose_test.go (TestExposedElementsNamespaceWildcard, TestExposedElementsRecursiveExpose, TestExposedElementsWithAnElementFilter, TestExposedElementsWithAViewBodyFilter, TestExposedElementsOfNestedViews, TestExposedElementsExposingAnotherView, TestExposedElementsInheritedFromAViewDefinition, TestExposedElementsOfAViewExposingNothing, TestExposedElementsOfANonView) ✅ Faithful (an empty exposed set is no error; asking a non-view is semantics.ErrNotAView. The REPL surface is %view — repl/view.go doView)
A view's exposed set is rendered as the rendering its render member states, and as a containment tree where it states none (SysML v2 7.24 Views and Viewpoints, §10.2 — the rendering is tool-defined) semantics/rendering.go Model.ViewRenderings (the render members of the view and of the views it specializes) and Model.RenderingTarget (the rendering the member references or declares); view/view.go Renderer.KindOf, Renderer.Render (the kind, then the exposed set from Model.ExposedElements) and Renderer.RenderExposed (an arbitrary selected set); view/pseudo.go derives the #<kind> vocabulary from the kinds this build supports; view/tree.go, view/interconnection.go (the model's own connector and flow ends, not source text), view/behavior.go (the lowered lower.StateGraph/lower.ActionGraph, never a re-parse of symbol.Decl), view/table.go (the exposed elements, the elements declared in them and the nested views, as rows), view/sequence.go Renderer.renderSequence (the occurrences an interaction declares as lifelines, the model's own flow ends as directed messages, ordered by the successions between the events those messages run between), view/text.go, view/mermaid.go and view/markdown.go (the forms, chosen per kind by Kind.MachineForm in view/form.go) view/render_test.go TestGoldenRenderings (text and machine-readable goldens for tree, interconnection, state, action, a filtered view and a table, from .sysml fixtures), TestTreeRenderingShowsNestedViewsAndDefaults, TestInterconnectionRenderingDrawsConnections, TestStateRenderingComesFromTheLoweredGraph, TestActionRenderingComesFromTheLoweredGraph, TestRenderingUsesFilteredAndInheritedExposure, TestTableRenderingRows, TestTableFormsAreMarkdownNotMermaid, TestMermaidLabelsAreEscaped; view/sequence_test.go TestSequenceRenderingDrawsLifelinesAndMessages, TestSequenceRenderingFromTheShortViewName, TestSequenceRenderingReportsWhatItCannotShow, TestSequenceRenderingHonoursStatedOrder, TestSequenceRenderingReportsASuccessionCycle, TestSequenceMermaidDeclaresParticipantsFirst; view/pseudo_test.go ✅ Faithful to the notation, tool-defined in output (the kinds produced are a tree, an interconnection diagram, a state machine, an action flow, a sequence diagram and a table; state and action renderings read the graphs the runtime executes, so a rendering cannot drift from what runs. Mermaid is the machine-readable form of the graph-shaped kinds and Markdown that of a table; SysML §10.2 specifies no artifact)
A rendering this build does not produce, a name that is no view, a view exposing nothing, and an exposed element a rendering cannot represent are each explicit view/view.go UnsupportedKindError (wrapping view.ErrUnsupportedKind, naming the kind, the view and the rendering it stated), Renderer.Render (semantics.ErrNotAView for a non-view, as %view answers), Rendering.Empty and view/text.go (an empty artifact saying whether the view exposes nothing or nothing exposed was representable), Rendering.Notices (what a kind could not draw); cmd/sysml/render.go reports and skips unsupported declared views and incompatible forced forms during -render-all, prefixing each notice with its source view view/render_test.go TestUnsupportedRenderingKinds, TestRenderingSomethingThatIsNoView, TestRenderingAViewExposingNothing, TestRenderingReportsWhatItCannotRepresent; repl/view_render_test.go TestRenderOfAnUnsupportedKindNamesIt, TestRenderOfANonViewIsTyped, TestRenderOfAViewExposingNothingSaysSo; cmd/sysml/render_test.go TestRenderReportsWhatItCouldNotDo, TestRenderAllSkipsUnsupportedKindsAndWrongForcedForms, TestRenderAllPrefixesRenderingNoticesWithTheirView ✅ Faithful (a stated kind that is not produced is a typed error naming it, never a substituted rendering; a form the kind is not written in is a WrongFormError naming the one it is; an element that cannot be drawn is reported, not dropped. A one-view render stops on either typed error; a render-all run skips only that view and renders the rest)
A graph-shaped rendering (tree, interconnection, state, action) is also written as Graphviz DOT, an alternative to Mermaid for Graphviz toolchains and large-graph layouts, without a Graphviz installation: a digraph with // view:, // kind:, // stated:, // not represented:, // canvas: and // layout: header comments, rankdir from the direction, containment as subgraph "cluster_<id>" (a tree as arrowhead=none edges, as its Mermaid form), an edge at a cluster drawn to a node inside it and clipped with lhead/ltail, state pseudo-states as point/circle/doublecircle, states as rounded boxes, regions as dashed clusters, transition labels as the state writer's trigger/guard/effect text, and edge kinds parallel to the Mermaid arrows (connection arrowhead=none, flow style=dashed, transition and succession solid); every identifier and label is quoted through one helper; the DiagramLayout geometry is written as Graphviz reads it, one pixel to one point with y flipped from the library's y-down origin (inputscale=72, dpi=72; y measured from the canvas's bottom edge, negated with no canvas height): a positioned node pinned at the centre of its box with pos="x,y!", pin=true, its size as width/height in inches — a stated size with fixedsize=true and the label fitted to it (the head wrapped at the width and shrunk from 14 pt to 8 pt until it fits, the keyword and detail lines kept only while height remains, an overrunning head ellipsized), an unstated one fitted to the label so the box's corner stays where the Layout put it — collapsed as comment="collapsed", a positioned cluster's bb stated (its stated box, or the one from its corner round its positioned members) and its anchor pinned at the centre, a Route as the edge's pos B-spline through its waypoints (a route of one waypoint drawn as no line and noticed as // not represented:), a sized Canvas as an invisible point pinned at each corner so the drawing's bounding box is the canvas, and the // layout: header naming neato -n2 when every node is placed and any edge routed, neato -n when every node is placed and none routed, neato when some nodes are, dot when none; sequence and table have no DOT form and are the same WrongFormError the other forms raise view/dot.go Rendering.DOT, Rendering.DOTWith, dotWriter (engine, graphAttributes, dotNodeAttributes, dotPin, dotBox, dotClusterAttributes, dotAnchorAttributes, clusterBox, dotEdgeAttributes, dotSpline, flipY, dotInches), dotQuote; view/form.go FormDot, Forms, DiagramForms, Kind.SupportsForm, Options, Write, WriteWith; cmd/sysml/render.go (-render-form dot, .dot under -render-all); repl/meta.go (%render <name> dot); lsp/render.go renderForm (form: "dot") view/dot_test.go TestGoldenDOT (testdata/*.dot.golden beside the Mermaid goldens, each checked by an in-test DOT syntax walker: balanced braces, every edge endpoint declared as a node or cluster, quoted identifiers), TestDOTFormSupport, TestDOTQuotesEveryIdentifierAndLabel, TestDOTNestedClusters, TestDOTDirections, TestDOTEdgeKinds, TestDOTStateShapesAndLabels, TestDOTEmptyAndNotices, TestDOTWritesTheGeometry (testdata/layout.dot.golden beside the Mermaid and text goldens of the layout fixture; the flipped axis with and without a canvas height; states and transitions placed), TestDOTPinsEveryNode (neato -n and neato -n2, the one-waypoint notice, a stated, a member-fitted and a corner-only cluster's bb and anchor, a tree's positioned parent, pseudo-state centring, the zero-extent, unit-only and unpositioned canvas); the syntax walker parses every pos and bb; cmd/sysml/render_test.go TestRenderDotForm; repl/view_render_test.go TestRenderWritesDotWhenAskedFor; lsp/render_test.go TestRenderWritesDotWhenAskedFor ✅ Faithful to the notation, tool-defined in output (Mermaid stays the machine-readable form Kind.MachineForm chooses; DOT is written on request. Producing the DOT needs no dot binary, and the goldens are validated by the in-test syntax walker rather than by dot -Tsvg; the one place Graphviz runs is the PDF backend, drawing the block on request when a Graphviz is installed (the document-rendering rows below). A Route is written as the polyline through its waypoints, not smoothed; a node with no stated size is given the writer's estimate of its label's extent (8.4 pt a glyph, 16.8 pt a line), not fixedsize, so Graphviz may grow the box for its own font and move the corner by the difference. The gRPC API has no view-render RPC — RenderDocument alone, to Markdown — so no wire contract carries a form)
The DOT form draws in the Standard B&W style of the OMG SysML v2 Pilot Implementation's PlantUML visualizer (SysML v2 §8.2.2.2, the graphical notation's rendering being tool-defined), after the sysmlbw PlantUML skin by Hisashi Miyashita (Mgnite Inc.) shipped with the Pilot and the edge rules of its SysML2PlantUMLStyle.java, reproducing the skin's visual parameters rather than its text: Helvetica text at 14 pt on nodes and 13 pt on edges, white fills, #181818 lines at penwidth=0.5 on nodes and 1 on edges, a definition (… def, or a KerML classifier keyword) square and a usage style="rounded,filled", the name in bold over the «keyword» line in italics at its 10 pt size, clusters unfilled with black borders — penwidth=1.5 for a package, 0.5 for an element's cluster and a region (which keeps style=dashed) — a connection at penwidth=3 with arrowhead=none, flow, succession and transition as before, and an unnamed initial or final pseudo-state as the filled black UML dot (shape=circle/doublecircle, fillcolor=black, label="", width=0.2 unless a Layout sizes it) while a named one keeps its labelled ring unless a Layout sizes it; a decision, merge, choice, fork, join, initial, final or port a Layout sizes drawn as its symbol with no inner text, its name as an xlabel unless the view IR marks it synthesized; every style attribute precedes the geometry in a node's list, and geometry, node IDs, label text, escaping, routes, cluster anchors, lhead/ltail, the header comments and the order of nodes and edges are the ones the DOT form always wrote view/dot.go dotNodeDefaults, dotEdgeDefaults, dotControlKinds, dotNodeAttributes, dotPseudostateAttributes, dotClusterAttributes, dotClusterPenwidth, dotEdgeAttributes, dotLabel; view/palette.go isDefinitionKind, kermlClassifierKinds view/dot_style_test.go TestDOTStandardDefaults, TestDOTDefinitionsSquareUsagesRounded, TestDOTPseudostateRules, TestDOTClusterBorders, TestDOTConnectionPenwidth, TestDOTEscapesNamesInStyledLabels; every view/testdata/*.dot.golden, reviewed so that only style attributes and the italic keyword markup moved; docrender/testdata/*.golden.*, repl/view_render_test.go, cmd/sysml/render_test.go, lsp/render_test.go ⚠️ Approximate (the skin's 20-unit UsageRoundCorner is Graphviz's fixed rounded radius; its Shadowing 0, hide circle and wrapWidth 300 have no Graphviz counterpart and nothing to turn off; the skin's plain-weight state title is not followed — a state's name stays bold like every other kind's, so the text, Mermaid and DOT forms read alike; the Pilot's -[thickness=5]- binding connectors are not drawn apart from connections because the interconnection rendering has no edge kind for them; the skin's notes, sequence, gantt, mindmap and wbs sections are out of the DOT form's scope. Producing DOT still runs no Graphviz binary; the goldens are checked by the in-test syntax walker, and a Graphviz installation is used only by hand to look at them)
A graph-shaped rendering (tree, interconnection, state, action) and a sequence are also written as PlantUML, the language the OMG Pilot's own visualizer draws with, without Java or a PlantUML jar: an @startuml … @enduml file with the ' <view> — <kind> rendering (<stated>) header comment and one ' not represented: line per notice and per loss the writer itself incurs, the Standard B&W style inline as a <style> block plus skinparam wrapWidth 300 and hide stereotype, top to bottom direction/left to right direction from the direction (a reversed BT/RL takes the nearest forward one under a notice, since PlantUML draws no reversed direction; a sequence ignores it), and the DiagramLayout geometry as ' canvas:, ' layout: and ' route: comments in the Mermaid form's shape under a notice naming the dot form for pinned positions; a tree as a class diagram (hide circle, hide empty members, containment as parent -- child edges as the Mermaid and DOT trees draw it), an interconnection as nested rectangle blocks with a port a nested rectangle, a state or action rendering as the state grammar (composite states as nested blocks, a body's start as [*] --> inside it as the Mermaid writer's starts map places it, control nodes as PlantUML's <<start>>, <<end>>, <<fork>>, <<join>>, <<choice>>, <<history>>, <<history*>> pseudostates — a merge and a junction as <<choice>>, PlantUML having no round junction; an action takes the state grammar uniformly, PlantUML's activity syntax being procedural), a sequence as participants in root order and -> messages in edge order (an empty rendering as one participant, rectangle or state carrying EmptyReason()); edges as the Pilot draws them — a connection -[thickness=3]-, a flow -[dashed]->, a transition and a succession -->, labelled as the DOT form labels them; every node's keyword a stereotype (<<part def>>, <<state>>) with a <<usage>>/<<package>> shape stereotype beside it for the style to select on, and the rendering's identifier-safe node IDs as the aliases; a table, textual or geometry rendering is refused with WrongFormError view/plantuml.go Rendering.PlantUML, Rendering.PlantUMLWith, plantumlWriter (writeStyle, writeClassDiagram, writeRectangleDiagram, writeStateDiagram, writeSequenceDiagram, writeEdge, writeArrow, decoration), plantumlDirection, plantumlArrow, plantumlPseudostates, plantumlShapeStereotype, plantumlLabel, plantumlQuote, plantumlText, Rendering.countGeometry; view/mermaid.go writeGeometryComments, writeLayoutComments (the comment prefix a parameter, shared with the Mermaid form); view/form.go FormPlantUML, Forms, DiagramForms, Kind.SupportsForm, WriteWith; cmd/sysml/render.go (-render-form plantuml, .puml under -render-all); repl/meta.go (%render <name> plantuml); lsp/render.go renderForm (form: "plantuml") view/plantuml_test.go TestGoldenPlantUML (testdata/*.plantuml.golden beside every Mermaid golden, the sequence-* and layout fixtures included, each checked by checkPlantUMLSyntax, an in-test walker: @startuml/@enduml bracketing, balanced braces, a closed <style> block, every quoted label closed, every alias an arrow names declared), TestPlantUMLDrawsEveryNodeAndEdge (every node and edge of every golden model, against the rendering), TestPlantUMLTreeIsAClassDiagram, TestPlantUMLInterconnectionNestsRectangles, TestPlantUMLStateDiagram, TestPlantUMLActionUsesStateGrammar, TestPlantUMLSequenceDiagram, TestPlantUMLFormSupport (the five kinds written, the three refused), TestPlantUMLDirection, TestPlantUMLEscapesLabels, TestPlantUMLLabelShape, TestPlantUMLHeaderAndGeometryComments (the Mermaid geometry comments unchanged byte for byte); checkPlantUMLRenders runs java -jar … -checkonly over each golden only when OPENSYSML_PLANTUML_JAR names a jar, and is skipped otherwise; cmd/sysml/render_test.go TestRenderPlantUMLForm; repl/view_render_test.go TestRenderWritesPlantUMLWhenAskedFor; lsp/render_test.go TestRenderWritesPlantUMLWhenAskedFor ✅ Faithful to the notation, tool-defined in output (Mermaid stays the machine-readable form Kind.MachineForm chooses; PlantUML is written on request. Producing the PlantUML needs no Java, and the goldens are validated by the in-test syntax walker, the jar check being opt-in by hand; the one place the jar runs is the PDF backend, drawing the block on request when a jar and a Java are installed (the document-rendering rows below). Not honoured: a DiagramLayout position or route, which PlantUML cannot pin — kept as comments and noticed, the dot form being the one that draws them; a reversed direction. Not written: hyperlinks, since no writer derives a stable URL from Origin; a port on the boundary — portin/portout is a component-diagram construct and a port is a nested rectangle instead; an action as PlantUML activity syntax, whose procedural start/:action;/fork/if shape cannot hold an arbitrary graph of successions and flows losslessly, so every action rendering takes the state grammar. The gRPC API has no view-render RPC, so no wire contract carries the form)
The PlantUML form draws in the Standard B&W style of the OMG SysML v2 Pilot Implementation's PlantUML visualizer (SysML v2 §8.2.2.2, the graphical notation's rendering being tool-defined), after the sysmlbw PlantUML skin by Hisashi Miyashita (Mgnite Inc.) shipped with the Pilot and the edge rules of its SysML2PlantUMLStyle.java, translated into rules the file carries itself since PlantUML proper does not ship the skin: a <style> block with root (white background, SansSerif 14 pt black text, #181818 lines, left alignment), element (white fill, LineThickness 0.5, RoundCorner 0, Shadowing 0.0), arrow (#181818 at thickness 1, 13 pt text), note (#FEFFDD, 13 pt), .usage { RoundCorner 20 }, .package { LineThickness 1.5 } and .region { LineStyle 4 }, then skinparam wrapWidth 300 and hide stereotype; a definition (… def, or a KerML classifier keyword) square and a usage rounded by its <<usage>> stereotype, the name in bold creole over the «keyword» line italic at 10 pt inside the label (the stereotypes hidden, so one guillemet line is printed, not two), pseudostates and containers black and white under every palette, a connection at thickness=3 undirected view/plantuml.go writeStyle, the plantuml* style constants, plantumlUsageStereotype, plantumlPackageStereotype, plantumlShapeStereotype, plantumlLabel, plantumlKeywordFontSize, plantumlArrow, decoration; view/palette.go isDefinitionKind, kermlClassifierKinds view/plantuml_test.go TestGoldenPlantUML, TestPlantUMLLabelShape, TestPlantUMLTreeIsAClassDiagram, TestPlantUMLInterconnectionNestsRectangles, TestPlantUMLStateDiagram, TestPlantUMLEscapesLabels; every view/testdata/*.plantuml.golden, reviewed by eye ⚠️ Approximate (the skin's three rules the DOT form could not honour — the 20-unit usage corner radius, shadows off and the 300-pixel wrap — are honoured here; the skin's plain-weight state title is not followed, a state's name staying bold like every other kind's so the text, Mermaid, DOT and PlantUML forms read alike; the Pilot's -[thickness=5]- binding connectors are not drawn apart from connections because the interconnection rendering has no edge kind for them; the skin's monochrome mode, notes, gantt, mindmap and wbs sections are out of the form's scope; the guillemet line is the label's, not PlantUML's stereotype rendering, so its size is the label's <size:10> rather than the skin's stereotype rule. Producing PlantUML runs no jar; the goldens are checked by the in-test syntax walker, and a jar is used only by hand to look at them)
A view.Palette names a colourblind-safe palette the DOT and PlantUML forms fill nodes with by keyword family, the same hex per node in both, the way the Pilot visualizer's STDCOLOR mode colours by element kind: okabe-ito (Okabe & Ito 2002), tol-bright, tol-muted, tol-light (Paul Tol, SRON note 3.2), brewer-set2, brewer-dark2 (ColorBrewer, Apache-2.0 notice kept), viridis and cividis (matplotlib's ramps at 16 stops). The families are ordered part, item, port, attribute, action, state, requirement, constraint, connection, interface, use case, case, allocation, analysis, verification, enum, occurrence, flow, other; a kind is placed by the first of its words with a family, def set aside, so a definition and its usages share a hue; a qualitative palette is indexed by the family's fixed rank (wrapping past its last colour), a sequential one sampled evenly across the families present, darkest first. A definition is filled with the family colour, a usage with it blended 60 % toward white, both bordered in the colour at penwidth=1; every fill is lightened toward white until black text on it reaches the WCAG 2 AA ratio of 4.5:1; pseudo-states, control nodes and cluster borders stay black and white. The palette travels with the direction in one view.Options: Diagram::palette in DocumentQueries.sysml, -render-palette, %render <name> dot <palette> (completed), the palette field of opensysml/render, the document plan and IR, and data-palette on the HTML figure. Mermaid notes a palette as %% not represented:; text and Markdown ignore it; a name that is no palette is one typed *view.UnknownPaletteError naming the palettes there are, and a document block's is invalid-palette or, on a kind with no DOT form, unsupported-palette view/palette.go Palette, Palettes, ParsePalette, PaletteNames, UnknownPaletteError, ErrUnknownPalette, Palette.check (a Palette value outside the registry is the same error at DOTWith), paletteColors, Palette.Color, Palette.Sequential, paletteFamilies, paletteFamily, familyRank, paletteFill, legibleFill, contrastWithBlack, Kind.SupportsPalette, Form.TakesPalette, paletteForms, familyFills, paletteNotice; view/form.go Options, WriteWith; view/dot.go dotNodeAttributes; view/plantuml.go plantumlWriter.decoration (#hex;line:hex on the element, a sequence participant's fill without the border); view/mermaid.go MermaidWith; libs/stdlib/OpenSysML Libraries/DocumentQueries.sysml Diagram::palette; docplan/compiler.go compileDiagram, docplan/errors.go ErrorInvalidPalette, ErrorUnsupportedPalette; docir/ir.go Content.Palette, Content.Options; docrender/markdown.go, docrender/html.go; cmd/sysml/render.go renderOptions; repl/view.go, repl/complete.go atPaletteArgument, renderPalettes; lsp/render.go renderPalette view/palette_test.go TestPaletteRegistry, TestUnknownPaletteError, TestPaletteColorsAsPublished, TestPaletteColorForCategory, TestPaletteFamilyIndex, TestPaletteFillsAreLegible, TestPaletteNotice, view/dot_style_test.go TestDOTPaletteFills, TestDOTRefusesAnUnregisteredPalette, TestDOTSequentialPaletteSpansFamiliesPresent, TestPaletteOnOtherForms, TestGoldenDOTPalettes (testdata/interconnection.okabe-ito.dot.golden, state.okabe-ito.dot.golden, tree.viridis.dot.golden); view/plantuml_test.go TestPlantUMLPaletteParityWithDOT (the same fill per node as DOT over every golden model and every palette), TestGoldenPlantUMLPalettes (testdata/interconnection.okabe-ito.plantuml.golden), TestPlantUMLSequencePaletteFillsParticipants, TestPlantUMLRefusesAnUnregisteredPalette; docplan/diagram_test.go (accepted, invalid and unsupported palettes), docir/diagram_test.go, docrender/html_diagram_test.go; cmd/sysml/render_test.go, repl/view_render_test.go, lsp/render_test.go (accepted and refused palettes, the Mermaid notice) ✅ Faithful to the notation, tool-defined in output (a kind supports a palette when a form that fills nodes writes it — DOT or PlantUML — so a sequence takes one for its PlantUML participants while a table takes none; colouring is by keyword family only; colouring by a data attribute or query result, and Mermaid theming, are not built, though Palette.Color(i, n) is shaped for a later caller to colour categories without knowing families)
A diagram node's label follows the graphical notation's header (SysML v2 §8.2.2.2 — the tool-defined drawing of a view, §10.2): the element's name first, with : Type after it for a typed usage (pump : Pump), the kind on the next line in guillemets («part», «part def»), the notes (initial, already shown, own flow) after that; an anonymous element leads with its kind and has no keyword line. The declared type is a field of the node (Node.Type, type on opensysml/render) spelled as the notation writes it — every typing in declaration order, a conjugated one behind ~, a global name behind $::, each segment quoted as needed — the detail carries the notes alone, and no writer parses the type out of the detail. Mermaid joins the lines with <br> in the flowchart, stateDiagram-v2 and sequenceDiagram grammars alike, and a flowchart whose cluster title spans several lines opens on a subGraphTitleMargin frontmatter block reserving their height; DOT writes an HTML-like label with the name in bold and the keyword line in italics at 10pt, the same for a cluster, with &, <, >, " and ' as entities; the text form keeps the notation's keyword-leading declaration order, part pump : Pump, a note parenthesised after it. Edge labels, sequence message lines, geometry comments and notices are unchanged view/label.go labelHead, labelLines; view/mermaid.go mermaidLabel, writeFlowchartFrontmatter; view/dot.go dotLabel, dotEscape, dotLabelExtent (the label-fitted box of an unsized positioned node, bold head and 10pt keyword line included); view/plantuml.go plantumlLabel, plantumlText (creole bold head, italic 10 pt keyword line; a quote, backslash, angle bracket, tilde and doubled creole marker written as <U+XXXX>); view/text.go writeNodeText; view/view.go Node.Type, filled by view/tree.go, view/interconnection.go, view/behavior.go, view/sequence.go from declType, nodeType, typingOf, referenceText; view/data.go NodeData.Type; lsp/render.go renderNode.Type; editors/vscode/src/protocol.ts RenderNode.type view/label_test.go (typed usage, untyped usage, definition, anonymous node, node with notes; the text form; the flowchart, state and sequence Mermaid fragments); view/render_test.go TestMermaidLabelsAreEscaped; view/dot_test.go TestDOTLabelShape, TestDOTQuotesEveryIdentifierAndLabel, TestDOTNestedClusters, TestDOTWritesTheGeometry, TestGoldenDOT (the syntax walker reads an HTML-like label as one balanced string); view/plantuml_test.go TestPlantUMLLabelShape, TestPlantUMLEscapesLabels; view/testdata/*.{mermaid,dot,plantuml,text}.golden; lsp/render_test.go TestRenderNodesCarryTheTypeApartFromTheDetail; repl/view_render_test.go, repl/view_layout_test.go, cmd/sysml/render_test.go, docrender/testdata/telescope_report.*.golden.* ✅ Faithful (the notation's header, name over kind, is what the graphical notation draws in a node's compartment; how a line is broken is the diagram language's — the pinned mermaid-cli breaks <br> in all three grammars with htmlLabels on and off, and Graphviz dot/neato draw the HTML-like label, verified outside the repository, which runs neither)
%render <name> [text\|mermaid\|markdown\|dot\|plantuml [palette]] renders a declared or pseudo-view at the prompt; sysml -render <view> [-render-form <form>] [-render-palette <name>] [-o file] renders one outside it, and sysml -render-all <dir> [-render-form <form>] writes every declared view separately repl/view.go Session.ViewRendering, renderPseudoView, Views, renderLines, viewRenderer (a semantic model and resolver of its own over the session's symbol index, so no runtime is built); model/selection.go shares declaration-order view enumeration and targetless top-level selection across model and session paths; repl/meta.go (the %render arm, help entry), repl/complete.go (the form completion); cmd/sysml/render.go runRender, runRenderAll, loadRenderingModel, writeArtifactFile (one artifact on stdout or -o, or one file per declared view with render-all reporting on stderr); cmd/sysml/main.go (rendering modes are mutually exclusive with conversion and checks) repl/view_render_test.go TestRenderDefaultsToATree, TestRenderWritesMermaidWhenAskedFor, TestRenderOfATabularView, TestRenderOfAnUnknownNameReports, TestPseudoViewsRenderThroughTheSession, TestPseudoViewErrorsUseTheSessionLookupAndListAlternatives, TestTargetlessPseudoViewSpansLoadedDocuments, TestViewsListsSessionViewsInDeclarationOrder, TestRenderMisuseShowsUsage, TestRenderIsInHelpAndCompletion, TestRenderBetweenStepsDisturbsNothing; cmd/sysml/render_test.go TestRenderWritesTheArtifactOnStdout, TestRenderTextFormAndOutputFile, TestRenderOfATabularView, TestRenderAllWritesOneMachineArtifactPerView, TestRenderAllForcedTextUsesTxtAndUnboundedWidth, TestRenderAllWithoutViewsIsUnevaluable, TestRenderAllMutualExclusions ✅ Faithful (%render is a read: it materializes nothing, adds no object to the session, leaves the submission buffer alone and leaves an %action/%state debugging session stepping the same graph and objects — pinned by TestRenderBetweenStepsDisturbsNothing. A targeted pseudo-view uses the session's normal simple or qualified lookup; a targetless one renders loaded documents in order. %view's report is unchanged)
Diagram geometry in notation — an OpenSysML extension, not a specification rule: DiagramLayout::Layout (x, y, optional width, height, collapsed) positions the element it annotates, DiagramLayout::Route (points, a flattened x0, y0, x1, y1, … sequence, ordered nonunique since a segment repeats a coordinate) steers the edge whose declaring element it annotates, DiagramLayout::Canvas (unit, width, height) sizes a view's drawing surface; pixels, y down, origin top-left. For a (view, element) pair an about annotation declared in the view's body applies first, the element's own inline annotation second, none third; a view's second about annotation of one kind for one element is ignored in favor of the first. The geometry rides the rendering tree as values (view.Node.Geometry, view.Edge.Route, view.Rendering.Canvas) in every graph-shaped rendering — tree, interconnection, state, action — through Render and RenderExposed (no view, so element fallback only); the sequence diagram and the table carry none. The writers keep it visible: Mermaid as %% canvas:, %% layout: and %% route: comments after the header, text as at (x, y), size w×h, collapsed and via (x, y) … suffixes, opensysml/render as optional x/y/width/height/collapsed/route/canvas; a model without annotations renders byte-identically libs/stdlib/OpenSysML Libraries/DiagramLayout.sysml; semantics/layout.go Model.LayoutSitesOf, LayoutOf, RouteOf, CanvasOf (lazy, memoized, over the metadata side table ElementMetadataOf/AnnotationSitesOf); semantics/masking.go directRedefinedFeatures → implicitMetadataBodyRedefinitions (a metadata body's points = (…) is judged by the redefined nonunique feature); ast/metadata.go DeclaredMetadata (transition and succession bodies); lower/state_graph.go StateGraph.DeclOf, lower/action_graph.go (a succession body of annotations only); view/geometry.go Point, Geometry, Canvas, Renderer.geometryOf/routeOf/canvasOf, view/place.go Renderer.Draws/DrawsAnywhere, view/clone.go, view/mermaid.go, view/text.go; lsp/render.go; passes/diagram_layout.go DiagramLayoutPass at LevelConstraint (diagram-layout-unplaced warning for a Layout/Route on an element the judged rendering draws no node/edge for, diagram-layout-value error for odd points or a non-constant binding, diagram-layout-canvas error outside a view, diagram-layout-duplicate warning; source constraint) parse/view_layout_metadata.golden; semantics/layout_test.go TestLayoutOfPrefersTheViewBodyOverTheInlineAnnotation, TestLayoutOfPlacesAnElementInOneViewOnly, TestLayoutOfReadsAnAboutAnnotationNestedInTheViewBody, TestLayoutOfFirstViewLocalAnnotationWins, TestRouteOfReadsWaypointPairs, TestRouteOfReportsAnOddPointCount, TestLayoutOfReportsANonConstantBinding, TestCanvasOfReadsTheViewsCanvas; semantics/uniqueness_test.go TestIsUniqueInheritsIntoMetadataBody; view/geometry_test.go (view-local over inline, one view of two, connection/transition/succession routes, canvas, writers, RenderExposed, clone, no-annotation byte identity) with view/testdata/layout.{text,mermaid}.golden; lower/action_succession_test.go; lsp/render_test.go TestRenderCarriesLayoutGeometry; passes/diagram_layout_test.go (one test per diagnostic); cmd/sysml/check_test.go TestCheckReportsDiagramLayoutFindings; repl/view_layout_test.go ⚠️ Approximate (self-assessed: nothing in the specification to be faithful to — the notation is standard user-defined metadata, the meaning is OpenSysML's, proposed for standardization in omg-issues.md. No writer of this build lays a diagram out from the positions: Mermaid and text keep them visible, a layout-honoring form is pending, as is write-back from a graphical editor. Positions are absolute, so a container's children are not repositioned with it)
A view's conformance to the viewpoints it satisfies is evaluated (SysML v2 7.24 Views and Viewpoints; a concern is a requirement, so its conditions are evaluated by the requirement engine) semantics/conformance.go Model.ViewConformance (per satisfy claiming conformance — IsViewpointSatisfy, since a satisfy stating a subject asserts its requirement of that subject instead, as the stdlib View does: Model.SatisfyTarget, then every concern Model.FramedConcernsOf reports for the viewpoint, matched against the view's own, inherited and nested framings, and evaluated against the exposed elements the concern's subject admits through the ConcernEvaluator the caller supplies — implemented in repl/view.go concernEvaluator over runtime.Context.CheckSatisfactionOn, so condition evaluation is not reimplemented); passes/constraint.go checkViewSatisfyTarget (code view-satisfy-viewpoint) semantics/conformance_test.go (framing direct, inherited, nested and inherited-from-a-viewpoint; a missing concern; a false condition; an unevaluable one; no admissible subject; an unresolved stakeholder; a satisfy that is no viewpoint; determinism), passes/constraint_test.go TestConstraintViewSatisfyNonViewpointRequirement, TestConstraintViewSatisfyViewpointOK, TestConstraintSatisfyOutsideAViewIsNotChecked, TestConstraintViewSatisfyRequirementBySubjectOK, conformance/viewpoint_framed_concern_conditions.sysml, viewpoint_concern_without_condition.sysml ⚠️ Approximate (the structural question — is every framed concern framed by the view — and the conditions are evaluated faithfully; the verdict rules are tool-defined, since SysML v2 leaves verification verdicts non-normative: a concern must hold of every exposed element its subject admits, a concern framed by a nested view counts for its container, and what cannot be evaluated — no condition, no admissible subject, an unresolved party, a framing whose concern reference does not resolve, a viewpoint framing no concern at all — is reported as unevaluable with a reason, never as a pass. Adjudicated: left as found, deliberately. This row states no name-resolution rule; its verdict rules are non-normative, so no referee can promote it, and its implementation is in semantics/, which this change did not own)
A filter condition is a model-level predicate over one candidate element, with the candidate as the implicit self (@Safety, @@Safety, and/or/xor/not/implies, and a comparison of an annotation's feature) semantics/filter.go Model.CompileElementFilter / EvalElementFilter / SatisfiesElementFilter (compiled once per condition, memoized per candidate) over the annotations semantics/annotations.go collects — prefix metadata (#Safety part def P;), a metadata member of the element's body, and metadata m about X; — with conformance through Model.AllSupertypes, so @Safety matches a metadata type specializing Safety, and a metaclass classification (@SysML::PartUsage) reads the candidate's own kind semantics/filter_test.go, model/element_filter_test.go, semantics/cached_library_test.go TestNamespaceFilterOverALibraryIsTheSameParsedAndRestored (the condition as parsed and the same condition compiled into an index-cache record classify alike), TestAnnotationFactsOfALibraryElementSurviveTheCache ✅ Faithful (evaluated against a symbol, not a value: there is no instance at name-resolution time, which is what a model-level condition is defined over. A KerML declaration keyword implies its metaclass (struct → Structure, class → Class, assoc → Association, datatype → DataType, and the rest of KerML §8.2: semantics/annotations.go kermlMetaclassNames), and a metaclass feature (Element::name, Type::isAbstract, Feature::isComposite) is answered from the candidate element itself (reflectiveFeatureValue), from an annotation only when one binds it. A feature of a metaclass the candidate is an instance of but whose value no declaration gives is ErrFilterUnevaluable — reported through filter-not-evaluable, never decided false, which is the distinction that was dropping candidates and reporting their names unresolved (kerml-examples Simple Tests/Filtering.kerml:38,47,48, now clean, as the pinned validate-kerml is). Refereed: the pinned validators are silent on testdata/passes/f93_element_filter.{kerml,sysml} and on Filtering.kerml, and so are we (passes/f93_element_filter_scope_test.go). @/@@ in the runtime value evaluator remains unimplemented, line 214 — a separate layer, runtime/)
A filter condition that is not boolean-valued, or that OpenSysML cannot evaluate passes/filter.go ElementFilterPass at LevelType, over semantics/filter.go Model.CheckElementFilter; codes filter-not-boolean (error — a condition that is not a predicate can select nothing) and filter-not-evaluable (warning). Each fault is reported once, on the membership stating the condition (KerML §8.2.4), however many operands carry it passes/filter_test.go TestFilterNotBooleanIsReported, TestFilterNotEvaluableIsReported, TestFilterConditionsInTheSupportedSubsetAreClean ✅ Faithful (an unevaluable condition is reported and not applied, so the elements it would have selected from all stay visible: a verdict OpenSysML could not reach never silently hides model content — maintainer decision, recorded here because the spec does not say). A feature bound to nothing is not that case: reading it yields the empty sequence, which an ordering propagates and ==/!= decide against any value (KerML DataFunctions, where an ordering takes DataValue[1] and equality [0..1]), so @Safety and Safety::isMandatory == true does not hold of an element annotated @Safety alone and does not surface it — a verdict, not a reported failure. An annotation inherits the values its metadata type declares, so a default the type gives the feature is what the comparison reads (semantics/annotations.go addTypeDefaults; semantics/filter_test.go TestFilterUnsetAnnotationFeature, TestFilterAnnotationFeatureDefault, model/element_filter_test.go TestFilteredImportRejectsAnUnboundAnnotationFeature)
A KerML declaration specializes the library type its keyword implies (class → Occurrences::Occurrence, struct → Objects::Object, assoc/association → Links::Link, behavior → Performances::Performance, function/predicate → the matching evaluation, interaction → Links::Link as an association (KerML 1.1 §8.3.5.4; Links::BinaryLink with two ends), metaclass → Metaobjects::Metaobject, datatype → Base::DataValue, classifier/type → Base::Anything), so the library members it implies are inherited (KerML 1.0 §8.4.2) semantics/implicit.go implicitKerMLBases, read by implicitBase when source.KindOf says the document is KerML (the same file-kind mechanism, no second notion) — a bare feature is left with no base rather than being given the SysML attribute one; libs/record.go (cache format 17) stores the semantic supertype edges so a restored library symbol inherits alike semantics/implicit_test.go:TestKerMLImplicitDefinitionBases, :TestSysMLImplicitDefinitionBasesRemainKindBased, semantics/cached_library_test.go:TestInheritedImplicitBaseIsTheSameParsedAndRestored, libs/loader_cache_test.go:TestLoaderCachePreservesRedefinitionMemberEdges ✅ Faithful, self-assessed (the keyword table covers the KerML §8.2 declaration kinds, and the implied feature bases §8.4.2 also defines are now modelled: implicitKerMLFeatureBases maps a feature keyword to the base feature it subsets — step/behavior → Performances::performances, expr/function → Performances::evaluations, bool/predicate → booleanEvaluations, inv → trueEvaluations, class → Occurrences::occurrences, struct → Objects::objects, datatype → Base::dataValues, assoc/connector → Links::links, binding → Links::selfLinks, succession → Occurrences::happensBeforeLinks, interaction → Transfers::transfers, flow → Transfers::flowTransfers, metaclass → Metaobjects::metaobjects — contributed as members, like the base usage, not as conformance. Every name in both tables is checked against what the bundled library declares (semantics/w7a_library_bases_test.go:TestW7AImplicitBaseTablesNameLibraryElements) and the member contribution by implicit_test.go:TestW7AKerMLFeatureBaseContributesMembers. Evidence is KerML §8.4.2 plus these tests, not a referee: an implicit base surfaces in no reference diagnostic — a probe declaring the keyword forms is accepted by validate-kerml and by us alike — so no matched run can settle it. The feature table is read for feature declarations only: a keyword that declares a type — class, struct, assoc, behavior, predicate, interaction — takes its base from the type table above and nothing from this one)
A declared generalization suppresses the implicit one only when it already reaches that base, directly or indirectly (KerML 1.0 §8.4.2), so struct MyWheel specializes Wheel still specializes Objects::Object when classifier Wheel; reaches only Base::Anything semantics/implicit.go declaredGeneralizationReaches (declared generalizations only, and cycle-guarded, so it cannot re-enter the closure the implicit base feeds), consulted for KerML documents; a SysML declaration keeps taking its supertypes from its own declaration semantics/implicit_test.go:TestImplicitBaseOfExplicitSupertypeIsTransitive, :TestSysMLImplicitBaseOfExplicitSupertypeIsTransitive ✅ Faithful, self-assessed (the rule is now the same in both languages and for a usage as for a definition: implicitBase consults declaredGeneralizationReaches alone, and Model.DirectSupertypes no longer skips the implicit base of a SysML usage that declares any generalization, so part p :> Frames::Frame; still specializes Parts::Part while part q :> Parts::Part; takes only what it declares. The implied feature base is suppressed by the same test. A metadata keyword is the one exception: it supplies the kind itself, so its baseType stands in for the kind's base (SysML v2 §7.27.4, model/metadata_test.go:TestSemanticMetadataKeywordSubsetsBaseType). Tests: semantics/implicit_test.go:TestW7AKerMLFeatureBaseSuppressedWhenDeclared, :TestW7ASysMLSuppressionMatchesKerML, :TestImplicitBaseOfExplicitSupertypeIsTransitive, :TestSysMLImplicitBaseOfExplicitSupertypeIsTransitive. Evidence is KerML §8.4.2 plus these tests, not a referee — suppression is unobservable in a reference diagnostic; what the move was checked against is the corpus ratchet, which does not change for any file because of it)
A public membership import is re-exported to importers of the importing namespace, a root-level import is visible from a nested package of the same document, and an imported name may prefix a qualified name (KerML 1.0 §8.2.4, SysML v2 §7.4.4) resolve/unqualified.go lookupImportedMember, matchImport (recursion-guarded), resolve/qualified.go qualifiedSegment (the members the current namespace inherits hide what it imports, which comes before the global fallback), resolve/filter.go (a namespace's children include what its public imports re-export), symbols/builder.go (the root scope keeps its document node, so root-level imports are found from a nested scope) resolve/visibility_test.go:TestPublicMembershipImportIsReexported, :TestPrivateMembershipImportIsNotReexported, :TestCyclicMembershipImportsTerminate, resolve/imports_test.go:TestRootImportVisibleInNestedPackage, :TestImportPrefixResolvesThroughSiblingImport, passes/invocation_test.go:TestInvocationOverloadQualifiedInheritedHidesImported, lsp/invocation_navigation_test.go:TestDefinitionQualifiedInheritedHidesImported ✅ Faithful (private stays unexported, and the traversal is lazy and cycle-safe)
A qualified name through an import at evaluation: the evaluator resolves a multi-segment name through the same ResolveQualified the checker uses, so a segment answered by a public import (Bq::x, ISQ::speed), a recursive import, a short name or an alias evaluates, and a private import or a missing name fails with the checker's unresolved reference: …, a name several members answer to with its ambiguous reference: … (N candidates) (see the Expression Evaluation row of the same name for the full contract) runtime/eval.go evalNameGeneral, unresolvedQualifiedName over resolve/reading.go ReadQualified, resolve/qualified.go walkQualifiedTail runtime/qualified_import_test.go:TestQualifiedNameThroughImportEvaluates, :TestQualifiedNameThroughImportRejectedAsChecked, :TestQualifiedNameEvaluatedInSeveralScopes, conformance/calc_qualified_name_through_import.sysml, conformance/calc_qualified_name_private_import.sysml, conformance/calc_qualified_name_ambiguous.sysml ✅ Faithful
A name written in a declaration's header (featured by, crosses, a subsetting) resolves against the members and imports of that declaration's own body before the enclosing scope, and those members stay reachable from outside by qualified name and by feature chain resolve/document.go resolveHeaderRelationships, headerHasName, findFeaturedByTargets (a featuring type is traversed for inherited members like a supertype); resolve/resolver.go lookupMember (contributed members, then the scope's own imports) resolve/document_test.go:TestResolveDeclarationBodyMembersFromHeaders, :TestResolveFeatureChainThroughImportedBodyMember, resolve/inherited_names_test.go:TestInheritedMembersThroughGeneralFeature ⚠️ Approximate (the body is preferred only for a name it declares or imports itself, not for one it inherits, and a typing in the header is excluded — TestValueTypeNameNotShadowedByOwnMembers fixes that a value type is not shadowed by a member of the body it types. Adjudicated: half closed, with the other half deliberate. A featured by or crosses name is now also looked up among the members the declaration inherits from its type (headerHasName, which takes the relationship kind), tested by passes/w6c_header_scope_test.go:TestW6CHeaderNameInheritedByTheDeclarationsBody. A subsetting or redefinition target is deliberately not looked up there — it would resolve to the declaring feature itself — pinned by :TestW6CSubsettingTargetDoesNotSeeInheritedMembers and :TestW6CRedefinitionTargetIsNotResolvedInTheHeaderScope)
The body of a feature that redefines another sees the features nested under the redefined feature, at any depth, including through the redefinition an association end takes implicitly from the end it specializes (KerML §7.4.7, §8.4.4.6) resolve/redefined_nesting.go nestedInRedefined (reached only after ordinary lookup fails, so it shadows nothing), over semantics.Model.ImplicitEndRedefinitions resolve/f72_redefined_nesting_test.go, Association Examples/ProductSelection_N_ary.kerml:93,101,109 ✅ Faithful (a plain specialization and the specializing association's own body stay unable to see nested members, as the reference has them)
A succession usage is a redefinition target like any other feature (succession redefines named : T [1] first a then b;) symbols/builder.go usageSymbolKind classifies ast.UsageSuccession as SymbolSuccessionUsage; resolve/ resolves the target resolve/f52_succession_redefines_test.go, symbols/w7b_succession_kind_test.go, tests/parser/testdata/parse/succession_declared_multiplicity.sysml, tests/parser/testdata/parse/kerml_succession_declaration.kerml ✅ Faithful (the root cause is fixed — a succession usage has its own symbol kind, so it is a first-class redefinition target rather than an unclassified symbol, and every consumer that read the kind (semantics/filter.go, semantics/annotations.go, grpc/query.go, libs/record.go, whose record version is bumped) names it. Matched run recorded when the row was adjudicated: succession redefines named first a then b; in a specializing action is clean in the pinned validator and in ours)

Design note: references is a member-contribution edge, not a generalization

A perform action usage relates the action it performs through a ReferenceSubsetting, written references or ::> (SysML v2 §7.17.6; the derived PerformActionUsage::performedAction comes from that owned reference subsetting, §8.3.17.14). KerML makes ReferenceSubsetting a syntactically distinguished kind of Subsetting (§8.3.3.3.9), which is why the referenced feature's members are visible on the referencing one.

It is nevertheless kept out of semantics.Model.DirectSupertypes. Subsetting in this implementation drives conformance and implicit typing, and a perform statement is not a subtype of the action it performs for those purposes: making it one would give perform action takePhoto references takePicture; the type of takePicture and silently change conformance results elsewhere. Instead Model.MemberSources — the union of the generalization edges and the reference subsetting, breadth-first and cycle-guarded — is what member lookup consumes, so takePhoto.focus resolves while AllSupertypes(takePhoto) stays free of takePicture.

Two consequences of the spec's naming rules fall out of this and are implemented alongside it: an unnamed feature takes the effective name of the feature it references (KerML Feature::effectiveName), so perform providePower.generateTorque; declares generateTorque; and because that name is bound in the same scope the reference resolves in, the reference is resolved outside its own binding: a refFilter hides just that borrowed binding for the duration of the lookup, leaving each scope's declarations, inherited members and imports intact, so a perform of an action the owner inherits from its type still resolves.


Validation Rules Declared by the Reference's Own Xpect Suites — KerML half

Rule families the pinned reference (pilot 2026-08) declares in its Xpect suites and SysMLValidator.xtend / KerMLValidator.xtend, established from those sources plus a matched run of build/pilot-sysml-validator/validate-sysml-batch against bin/sysml -validate on a minimal model. The severity in each row is the reference's own severity; where the two implementations were run on the same file the diagnostics quoted below were byte-identical apart from the path.

Semantic Rule Implementation Test Case Status
validateSubsettingMultiplicityConformance / validateRedefinitionMultiplicityConformance (KerMLValidator.xtend): a subsetting or redefining feature should not have a larger multiplicity upper bound, and a redefining feature should not have a smaller lower bound — both warnings, separately reported, and skipped for an end/non-end pair or a bound that is not evaluable. A 1..1 default is assumed only for the explicitly-typed usage keywords (attribute, item, part, port), not for a generic KerML feature, an action or a state passes/multiplicity_conformance.go checkMultiplicityConformance passes/w7g_multiplicity_conformance_test.go (eight cases: subsetting upper bound, both redefinition bounds as separate warnings, bounds within range, the default-multiplicity keywords, the keywords that take no default, end/non-end, an unbounded subsetted upper bound, and that a subsetting lower bound is not diagnosed); matched run on attribute :>> a[0..5] redefining a[1..2] — both report both warnings at 7:17 ✅ Faithful
validateFeatureEndFeatureMultiplicity (KerMLValidator.checkFeature; KerML 1.1 §8.3.3.3): an end feature has a multiplicity of exactly 1..1 among its own multiplicity and those it inherits — through subsetting, redefinition, typing, a reference, a feature chain, or the implicit ends of a binary association or connector (warning, End feature must have multiplicity 1). A bound that is not evaluable at model level counts as omitted, so [n..1] is silent; a SysML end usage defaults to 1..1, so only a declared own non-1..1 multiplicity warns, and the [m] of end [m] item x : A is the cross feature's passes/end_multiplicity.go checkFeatureEndFeatureMultiplicity, over semantics.Model.EndMultiplicityIsOne and Model.ConnectorEndMultiplicityIsOne (semantics/end_multiplicity.go, cycle-safe over the general features); the anonymous crossing multiplicity is an unnamed ast.CrossFeatureMember (parser/defusage.go) whose bounds semantics.UsageMultiplicityOf and Model.MultiplicityOf report as the cross feature's own, never the end's; the RDF mapping exports it as a feature the end owns through an OwningMembership (export/rdf_out.go crossFeature, export/rdf_in.go ownedCrossFeature) passes/end_multiplicity_test.go TestKerMLEndOwnMultiplicityNotOne, TestKerMLEndInheritedMultiplicity, TestKerMLConnectorEndMultiplicity, TestSysMLEndUsageMultiplicity; parser/end_feature_test.go; semantics/multiplicity_test.go TestMultiplicityOfEndIsNotItsCrossFeatures; export/cross_feature_test.go; census probe validateFeatureEndFeatureMultiplicity.kerml; matched run on assoc L { end feature a : A [0..*]; end feature b : B [1]; } — both report 5:13 warning: End feature must have multiplicity 1 ✅ Faithful
validateReturnParameterMembershipOwningType (KerMLValidator.xtend; KerML 1.1 §8.3.4.7): a return parameter membership is owned by a function or expression (error, Return parameter membership not allowed). The reference grammar rejects returns outside calculation, constraint, case, function and expression bodies; OpenSysML rejects requirement-like body returns at parse time and retains the owning-type check for other non-function bodies such as classifiers, classes, structs, behaviors, steps and SysML definitions/usages that are not calculations, constraints or cases passes/return_parameter.go checkReturnParameterOwner passes/return_parameter_test.go TestReturnParameterOutsideFunctionIsReported, TestReturnParameterInFunctionIsClean, TestReturnParameterKerMLOwners; corpus semantic/k51-return-parameter-in-classifier.kerml (pilot: 5:9 no viable alternative at input 'return'; ours: 5:9 Return parameter membership not allowed) ✅ Faithful
validateTypeAtMostOneConjugator (KerMLValidator.xtend; KerML 1.1 §8.3.3.1): a type has at most one conjugator (error, Cannot have more than one conjugator). The reference grammar admits one ~ per declaration, so a second is a syntax error there; our parser rejects it likewise on a classifier or definition, and on a feature or usage parses it and reports each conjugation past the first at its own ~ passes/conjugator.go checkAtMostOneConjugator passes/conjugator_test.go TestSecondConjugatorIsReported, TestSingleConjugatorIsClean; corpus semantic/k52-two-conjugators.kerml (pilot: 5:21 no viable alternative at input '~'; ours: 5:21 parse error, one specialization part per classifier) ✅ Faithful
checkAtMostOneRelationship (SysMLValidator.xtend): a state owns at most one entry, one do and one exit action, and a requirement or case at most one subject; every membership after the first is an error, and only owned memberships accumulate, since an owned subject redefines the inherited one passes/at_most_one_member.go checkAtMostOneMember passes/w7g_at_most_one_member_test.go (only the extra subaction/subject is reported; one of each kind is silent); matched run — both report 12:3 A state may have at most one entry action. and 16:3 Only one subject is allowed. ✅ Faithful
checkSubjectParameter (SysMLValidator.xtend): the subject of a requirement or case must be its first parameter (error), reported on the owned subject where there is one and on the declaration otherwise; a declaration with no parameter at all is silent, since the reference constructs the subject implicitly passes/at_most_one_member.go checkSubjectParameterPosition, judging position in lexical declaration order and falling back to the semantic member order only where no local parameter can be inspected passes/w7g_at_most_one_member_test.go (a subject after another parameter, a subject first, an input-only requirement, a requirement with no parameter, and TestW7GLocalResultSuppressesInheritedParameterFallback for the fallback boundary); matched run — both report 5:3 and 11:2 Subject must be first parameter. ✅ Faithful
validateCalculationUsageType_ and its siblings for constraint, requirement, case, analysis/verification/use case, enumeration, rendering, viewpoint, view and metadata usages, plus validateAttributeUsageEnumerationType_: those usages are typed by exactly one definition (error), while a part or item may name several passes/one_type.go checkOneType, dispatched from passes/typecheck.go walk; the kind half of validateMetadataUsageType_ (a metadata usage typed by one non-metadata definition, semantic/s23-metadata-typed-by-part-def.sysml) is passes/w8c_metadata_type.go MetadataTypePass, with the same wording and passes/w8c_metadata_type_test.go TestW8CMetadataUsageTypeMustBeOneMetadataDefinition passes/w7g_one_type_test.go (one case per promoted keyword, plus at-most-one-type, a part with two types, an enumeration-typed attribute); matched run on enum e : E1, E2; and calc k : K1, K2; — both report the rule at 7:2 and 10:2. The metadata rule is unreachable in the reference, whose grammar rejects a second type on a metadata usage outright (4:17 no viable alternative at input ','); we parse it and report the rule ✅ Faithful
validateCaseDefinitionOnlyOneObjective / validateCaseUsageOnlyOneObjective: the reference allows a case at most one objective (error) passes/at_most_one_member.go checkAtMostOneObjective, dispatched by objectiveOwnerDecl for a case, verification or use case declaration. A declaration is judged on the objectives a single type owns, since an objective redefines the objective role of the types above it: owned objectives after the first are reported, and where nothing is owned, one supertype owning several is reported on the declaration inheriting them (library-declared objectives, Cases::Case::obj, are the frame and never compete). An inherited objective : R; is enumerated from the declaring type's scope, since semantics.Model.MembersOf walks members by name and an anonymous one binds no name. Analysis cases are exempt: OpenSysML's solver improves several objectives lexicographically in the order declared (internal/exec/solve, examples/solver-demo.sysml), and no other case kind has that semantics passes/w7g_at_most_one_member_test.go (case def, the inherited conflict, TestW7GVerificationAndUseCaseReportTheExtraObjective, TestW7GAnalysisCaseAdmitsSeveralObjectives, TestW7GOwnedObjectiveRedefinesTheInheritedOne, TestW7GAnonymousObjectivesCompete); matched run on case def C, verification def V and use case def U, each with two objectives — both report Only one objective is allowed. on the second. On analysis def A the reference reports it at 5:38 and we are silent, which is the extension. Refereed matched runs also cover the redefinition shapes the reference leaves silent — one owned objective under an inherited one (case def Sub :> Base, case c : Base) and a chain where each type owns one (Base → Middle → Leaf) — against the reported one where a single type owns two and a usage inherits both. (analysis case def A is not a reproducer: the reference's grammar rejects case after analysis, no viable alternative at input 'case') ⚠️ Faithful except for analysis cases, where the extension is deliberately wider
A feature that subsets itself (part p4 :> p4;) participates in a one-element specialization cycle, the same defect a longer cycle is (KerML 8.3.3.1: specialization is a strict order). Our own self-consistency, not a reference rule — the reference is silent on every cycle length (one-sided by design) passes/constraint.go selfSpecialization, read from the declaration: the specialization graph drops a same-named single-segment subsetting whose only candidate is the feature itself, so HasSpecializationCycle never sees the edge. inheritsFeatureNamed mirrors semantics.inheritedFeatureNamed so a redefinition of an inherited feature of the same name stays silent passes/w7g_self_specialization_test.go (:>/subsets self-loops on a part and an attribute; a same-named subsetting and redefines of an inherited feature, and a differently-named one, are not cycles); pilot-examples/Simple Tests/PartTest.sysml line 53 is the corpus instance, 3 → 4 only-ours ✅ Faithful (a redefines n with nothing to redefine is an unresolved reference at the name tier instead, and stays there)
Duplicate of inherited member name '<n>' from <A>, <B> on a usage whose declared type is of another kind than its keyword — most of the declared warnings expectations are on such a usage Implemented as a warning over library bases, passes/w9c_inherited_name_conflict.go (W9CInheritedNameConflictPass, LevelType), beside the own-document rule in resolve/distinguishability.go. The union is built from the implicit base of the declaration's kind and the base its declared type reaches, and each conflicting name is attributed to the types that declare it. The pass also compares the declaration's own member and alias names against those library bases (checkOwnedNames), so state start; inside a state is reported against StatePerformances::StateAction::start; a member that redefines or subsets the inherited feature, one whose declared redefinition target does not resolve, an implicit redefinition (a positional parameter, a case or requirement role, an assignment in a metadata usage body) and an anonymous final node are all silent. A short name is a name here too (KerML 7.2.2): an inherited member is reached under both its identifiers (ownMembers), and an owned member or alias is compared under each of its own (ownedKeysOf), reported at the identifier that repeats passes/w9c_rules_test.go TestW9CActionPartDiamondWarns (cold and warm library cache), TestW9CInheritedShortNamesConflict (an owned h or <h> against CylindricalPosition3dVector::<h> height, a diamond through <h>, the redefinition escape hatch under either identifier, an alias <h> H or alias <hh> height for another feature, and the silence of an alias for the inherited feature itself; the pinned pilot agrees line and column for line), TestW9CConformingTypingStaysSilent, TestW9CRedefinedUntypedFeatureStaysSilent, TestW9CVariantStaysSilent, TestW9CBinaryInterfaceEndDiamondWarns, TestW9CPassesAreRegistered, TestW9COwnedNameAgainstOneLibraryBaseWarns, TestW9COwnedNameSpecializingItsLibraryBaseStaysSilent, TestW9CMetadataBodyNamesStaySilent, TestW9CInheritedNameThroughUserSupertypesWarns (a library base reached through two user supertypes, with the redefinition escape hatch beside it), TestW9CInheritedNameLibraryBaseFixtures over testdata/passes/inherited_name_library_base.sysml and ..._clean.sysml; matched runs include the action/part diamonds, part def Q { attribute portions; }, state S { state start; } and InterfaceUsage_Invalid.sysml.xt:78 ✅ Faithful — one warning is reported per duplicated name, at the declaration and at a referenced feature chain, with candidates canonicalized so one feature reached twice is not a conflict. Step 3 supplies the binary interface end's inherited port type and closes the former interface-end gap
Duplicate of other owned member name where two members differ only by a short name, and User library packages should not be marked as standard passes/w9c_owned_name_and_library.go (W9CShortNameDistinguishabilityPass, W9CUserStandardLibraryPass, both LevelNameResolution): a short name is a name for distinguishability, and standard library package outside the standard library is the pilot's warning passes/w9c_rules_test.go TestW9CShortNameDistinguishability, TestW9CUserStandardLibraryPackage; reproducers ShortNameTests_Distinguishibility1/2.kerml.xt:20,22, LibraryPackage_invalid_notStandard.kerml.xt:15 ✅ Faithful (warning severity and location match the declared expectations)
Bound features should have conforming types passes/w9c_bound_feature_types.go (W9CBoundFeatureTypesPass, LevelType): both feature endpoints of a binding are resolved — the feature each of its two ast.ConnectorEnds attaches to (AttachedTarget), whether the end is named or not — and their types compared with semantics.Model.Conforms passes/w9c_rules_test.go TestW9CBoundFeatureTypes ✅ Faithful for binding-connector feature endpoints. BindingConnector_Invalid2.sysml.xt:42 on the operator expression rearWheel+1 is the argument binding of the next row
Bound features should have conforming types on the binding each operator or invocation argument implies to the parameter it fills (KerML 1.1 §8.3.4.8.3): the argument's static result types against the selected function's corresponding input parameter — positional, named and receiver arguments alike — under the same symmetric conformance test the explicit bind rule uses, at the argument of an invocation and at the whole operator expression, where the pilot's active binding-conformance rule reports it over the connectors it synthesizes (its separate argument-level check is commented out) passes/w9c_argument_bindings.go exprChecker.judgeOperatorBindings, judgeArgumentBinding, exprChecker.diagnostics; semantics/operator_function.go Model.OperatorFunction, Model.InputParametersOf passes/w9c_argument_bindings_test.go TestW9CArgumentBindingsSysML, TestW9CArgumentBindingsKerML, TestW9CArgumentBindingLocations, TestW9CArgumentBindingYieldsToTypeError, TestW9CArgumentBindingsNotJudged, TestW9CArgumentBindingsNeedASelectedOverload, TestW9CArgumentBindingsInStateAssignment; census probes validateBindingConnectorTypeConformance.argument.{kerml,sysml}; Xpect BindingConnector_Invalid2.sysml.xt:42 and examples/disposal-team-demo/team.sysml:29 agree with the pilot column for column ✅ Faithful. Silent, as the pilot is, where the callee is unresolved or has no single selected overload, where the argument's type is unknown, where the argument is collection-valued or the parameter typed by a Collection or Element, and where a precise type error of ours already covers the argument; team.sysml:117 stays one-sided because the two implementations resolve the assigned accepted to different features, not because the rule differs
Bound features should have conforming types on the bindings the language implies: a function's or calc's result expression to its result parameter (KerML 1.1 §8.4.4.7), a nested requirement's or case's subject to the subject of the non-abstract requirement or case it is declared in, a subject's own value, and the by operand of a satisfy … by to the satisfied requirement's subject (SysML v2 §8.3.19.7); an invocation result (F()) is typed by the invoked function's result, a body expression by its own result, so return : Boolean; { c == c } warns and return : BooleanEvaluation; { c == c } does not, as the pilot has it passes/w9c_bound_feature_types.go checkResultExpressions, checkSubject, checkSatisfySubject, valueConforms; semantics/roles.go Model.SubjectParameterOf; semantics/valuetype.go ExprResultType on an InvocationExpr passes/w9c_rules_test.go TestW9CResultExpressionBindingKerML, TestW9CImplicitBindingsSysML; census probes validateBindingConnectorTypeConformance.{result.kerml,satisfy.sysml,subject.sysml}; the pinned pilot agrees line for line on both test models ✅ Faithful for these shapes; the implicit binding of a feature value (x : D = c;, return : D = c;) stays the typechecker's error cannot bind a value of type C to a feature typed by D at the value where the pilot warns at the feature, and a predicate's or bool's non-Boolean result expression the typechecker's error constraint expression must be Boolean — severities the census row records as ⚠️ rather than changes
validateBindingConnectorTypeConformance on a usage's value binding whose value is a feature chain (part x : D = a.b.c;): the chain is typed by its last feature, each segment a member of the one before it (KerML §8.3.3.3 Feature::typingFeatures(), the pilot's Feature_typingFeatures_InvocationDelegate), so it conforms or fails exactly as a plain name does passes/typecheck_value.go valueTypeSymbol resolves a FeatureReference, QualifiedName, FeatureChainExpr or a#(i) element selection through resolve.Resolver.ResolveTarget — the resolver's own chain walk, including inherited and redefined segments and conjugated ports — and reads the last segment's declared type; every consumer benefits (checkValueConformance, checkNonScalarCondition, checkValueDimension) passes/typecheck_value_chain_test.go (probe, conforming chain, inherited segment, typed and untyped redefinition, port and conjugated port, connection end, collection elements, indexed segments and elements a#(1).b#(1).c, unresolved segment, if a.b); the pinned pilot reports every negative shape as Bound features should have conforming types and none of the positive ones ✅ Faithful for the value's type; ours is an error as the plain-name binding already was, the pilot's a warning, so no semantic/ rejection case (the pilot does not reject). A value naming an untyped redefinition (part :>> b; … = b) still reads no type — declaredTypeSymbol follows only an explicit typing, on chained and plain values alike

KerML Validation Rules the Reference Declares and We Did Not Report

The KerML half of the silent declared-error families in the pinned reference's Xpect suites (pilot 2026-08), clustered from build/pilot-xpect/pilot-xpect.txt. Each row's scope comes from the pilot's own KerMLValidator.xtend constraint, not from its message string. Xpect errors agreement moved 563 → 590 and the silent declared-error rows 266 → 229; the rejection oracle moved 20 → 24 both-reject. The pilot differential is unchanged at 306 fully agreeing with 125 only-ours (byte-identical to the parent commit): none of these rules fires on a corpus file the reference disagrees about, which is the false-positive result that work wanted.

Semantic Rule Implementation Test Case Status
validateTypeOwnedDifferencingNotOne / …UnioningNotOne / …IntersectingNotOne and their …NotSelf siblings, plus validateFeatureChainingFeaturesNotOne / validateFeatureChainingFeatureNotSelf (KerMLValidator.xtend): a type may not name exactly one unioning, intersecting or differencing type, none of them may be the type itself, and a feature chain may not have a single chaining feature or contain the chained feature (errors) passes/w8c_type_relationships.go TypeRelationshipsPass (constraint tier) passes/w8c_type_relationships_test.go (one of each count rule and each self rule, the legal two-operand forms, and the single-chaining-feature case with its span) ✅ Faithful
validateMultiplicityRangeBoundResultTypes (KerMLValidator.xtend): each bound of a multiplicity range must be model-level evaluable to a Natural (error), reported on the offending bound passes/w8c_multiplicity_bounds.go MultiplicityBoundsPass (constraint tier), evaluating bounds through w8cEvalConst passes/w8c_multiplicity_bounds_test.go (a string, a real and a non-evaluable bound fire; literal, * and constant-feature bounds stay silent) ✅ Faithful
validateMultiplicityRangeResultTypes (KerMLValidator.xtend, KerML 1.1 §8.3.3.6): a bound that is not model-level evaluable must still have an Integer-conforming result type (error, Must have a Natural value); a bound naming a feature typed by a class has no such type passes/w8c_multiplicity_bounds.go multiplicityBoundsChecker.boundIsInteger, reading the bound's declared type through semantics.Model.DeclaresResolvedType when no primitive type is inferable (a kind's implicit library base counts when no Natural can be of it, so a bare part or step is rejected while an attribute or feature stays untyped); integer literals, * and arithmetic over integer operands are accepted structurally passes/w8c_multiplicity_bounds_test.go TestW8CMultiplicityBoundResultTypeNotNatural (a class-typed or bare kind-typed feature bound fires), TestW8CMultiplicityBoundImplicitKindTypeSysML, TestW8CMultiplicityBoundResultTypeSilent (Natural- and Integer-typed, untyped and unresolved bounds stay silent); corpus semantic/k37-multiplicity-bound-not-natural.kerml ✅ Faithful
validateAssociationRelatedTypes (KerMLValidator.checkAssociation, KerML 1.1 §8.3.3.5): a concrete association has at least two related types (error, Must have at least two related elements), counting inherited ends passes/w10b_related_elements.go W10BRelatedElementsPass (constraint tier), classifying KerML assoc/assoc struct/interaction and SysML connection def, interface def, allocation def and flow def through w10bClassify; ends counted by semantics.Model.ConnectorEndCount; an interaction implicitly specializes Links::Link (Links::BinaryLink with two ends), as any association does passes/w10b_related_elements_kerml_test.go TestW10BKerMLAssociationEnds, TestW10BKerMLInteractionEnds (one-end assoc, assoc struct and interaction fire; two-end, inherited-end and abstract shapes stay silent); corpus semantic/k25-assoc-one-end.kerml ✅ Faithful
validateConnectorRelatedFeatures (KerMLValidator.checkConnector): a concrete connector has at least two related features (error), whether its ends are positional ((x, y), from x to y), declared end features or inherited passes/w10b_related_elements.go W10BRelatedElementsPass, counting through semantics.Model.RelatedFeatureCount; KerML connector x to y without from now parses as a two-ended connector (parser/defusage.go atConnectorBinaryEnds) passes/w10b_related_elements_kerml_test.go TestW10BKerMLConnectorRelatedFeatures, TestW10BSysMLRelatedFeatures; parser golden tests/parser/testdata/parse/kerml_binary_connector.kerml and parser/negative_test.go (binary_connector_*) ✅ Faithful
validateAssociationBinarySpecialization / validateConnectorBinarySpecialization (KerMLValidator.checkAssociation, checkConnector; KerML 1.1 §8.3.3.5, §8.3.4.7): an association or connector that conforms to Links::BinaryLink cannot have more than two ends (error, Cannot have more than two ends), counting positional ends, declared end features and inherited ends; positional ends redefine the inherited binary ends by position rather than adding to them passes/constraint.go checkBinaryConnectorEnds, over semantics.Model.BinaryConnectorExcessEnds and Model.IsBinaryConnector; each end past the second is reported at its own span passes/binary_connector_ends_test.go TestBinaryConnectorPositionalEnds, TestBinaryConnectorDeclaredAndInheritedEnds, TestBinaryConnectorSysMLShapes; corpus semantic/k24-binary-assoc-three-ends.kerml, semantic/k26-binary-connector-three-ends.kerml ✅ Faithful
validateReferenceSubsettingIsOne (KerMLValidator.xtend): a feature has at most one reference subsetting (error), every one after the first reported on its target passes/w8c_reference_subsetting.go ReferenceSubsettingPass passes/w8c_structural_rules_test.go (two references fire on the second; one reference and a binding's two ends stay silent) ✅ Faithful (a binding is exempt: our AST encodes its two ends as reference subsettings)
validateFunctionResultExpressionMembership / validateExpressionResultExpressionMembership (KerMLValidator.xtend; KerML 1.1 §8.3.4.8 Function::result, Expression::result): a function, predicate, expression or boolean expression — and so a SysML calculation, constraint, requirement, concern or viewpoint definition or usage — owns or inherits at most one result expression (error, Only one (owned or inherited) result expression is allowed); a calculation, function or expression body lists at most one bare expression (calc def C { in x; x + 1 x + 2 } states two, where the pilot's CalculationBodyPart/FunctionBodyPart stop parsing), and a specialization or redefinition of a type that owns a result expression inherits it and may not state a second (constraint def Sub :> Base { x > 1 }, require constraint :>> c { x > 1 }, calc def D :> C { x + 2 }, calc c : C { … }, constraint d ::> c { x > 1 }), while the conditions of one constraint body are one result, an empty or documentation-only redefinition keeps the inherited one and a nested assert constraint { … } is a separate constraint rather than a result expression; reported on the second body stated, on the newly owned body when the type states one over an inherited one, on the declaration when two are inherited semantics/result_expression.go Model.ResultExpressionsOf, Model.ResultExpressionMemberships, Model.ResultExpressionConflict (owned bodies, then those of every member source through Model.MemberSources — specialized or reference-subsetted, a diamond counted once; a constraint body's conditions collapse to one membership) consumed by passes/w8c_result_expression.go ResultExpressionPass (constraint tier, ElementScoped); the runtime refuses the same shapes with ErrConflictingResultExpressions instead of choosing a body (runtime/invoke_calc.go calcShapeOf, runtime/condition.go Condition.Conflict, solve/translate.go) and collects the bodies it does evaluate over the same member sources (runtime/invoke_calc.go calcChain, runtime/context.go chainMembers), so a bodiless reference-subsetting calculation or constraint inherits the referenced one semantics/result_expression_test.go, passes/result_expression_test.go (specialized definition, redefining, typed and reference-subsetting usages, two inherited generals on a constraint, requirement, concern or viewpoint, two bare expressions in one calculation/function/expression body and a bodiless type inheriting them, KerML predicate/function/expression forms fire; an inherited-only body and many conditions in one constraint body stay silent), passes/w8c_structural_rules_test.go, runtime/robustness_test.go calc_states_second_result, calc_body_states_two_results, runtime/invoke_calc_body_test.go TestReferencedCalcBodyIsInherited, runtime/conditions_of_test.go TestConditionsOfReferencedConstraint (bodiless ::> inherits and evaluates the referenced body, a diamond once), repl/replaced_result_check_test.go, repl/owned_constraint_check_test.go (empty, braced and nested-assertion redefinitions inherit and execute the redefined check); corpus semantic/k29-, k30-, k43-, k44-, s82-, s83-, s84-, s85-…-result-expression (both reject at the same positions), grammar/g69-, k20-…-second-result-expression (the pilot's grammar stops at the second expression where we report it) ✅ Faithful (the reference's separate validateResultExpressionMembershipOwningNamespace owning-namespace half is not implemented)
checkAssociationEndTypes (KerMLValidator.xtend): an association end must have exactly one type (error) passes/w8c_association_end_types.go AssociationEndTypesPass passes/w8c_structural_rules_test.go (a two-typed end fires; single-typed, untyped and redefining ends stay silent); tools/referee/reject xpect/p02-association-end-two-types.kerml — both-reject (ours 1 error(s), pilot 1 error(s)) ⚠️ Approximate (we report only the two-or-more case: an end with no declared or inherited type is accepted, because the reference's own parsing fixtures — ParsingTests_Associations, AssociationTest_EndRedefinitionGoodCase — supply that type implicitly, and reporting it produced false positives on them)
validateImportTopLevelVisibility (KerMLValidator.xtend): an import owned by a root namespace must be private (error) passes/w8c_import_visibility.go TopLevelImportPass at the name-resolution tier passes/w8c_structural_rules_test.go (a public and a protected root import fire at the import's span; a private root import, an expose, and a public import inside a package stay silent); tools/referee/reject xpect/p01-public-root-import.kerml — both-reject (ours 1 error(s), pilot 1 error(s)) ✅ Faithful (deliberately not at the syntax tier: parser-recovered root members in the SysML corpora would otherwise be diagnosed)
validateFeatureOwnedVariable (KerMLValidator.xtend): a variable feature must be owned by an occurrence type (error) passes/w8c_variable_feature.go VariableFeaturePass, resolving Occurrences::Occurrence and testing conformance of the owning type passes/w8c_structural_rules_test.go (a variable feature in a datatype fires, one in an occurrence definition stays silent); tools/referee/reject xpect/p03-variable-in-datatype.kerml — both-reject (ours 1 error(s), pilot 1 error(s)) ✅ Faithful
validateFeatureValueIsInitial and validateFeatureConstantIsVariable (KerMLValidator.xtend): only a variable feature may carry an initial value (:=, Initialized feature must be variable) or be constant (Only a variable feature can be constant) (errors); a feature is variable per KerML 1.1 §8.3.3.1 Feature::isVariable — declared by var or by const in KerML, and redefined by SysML Usage::mayTimeVary for a usage: owned by an occurrence type, not a portion, and not a composite action semantics/usage.go Model.FeatureIsVariable (over Model.UsageMayTimeVary); passes/w8c_variable_feature.go VariableFeaturePass (constraint tier, diagnostics initial-value-not-variable on the := value part and constant-feature-not-variable on the usage) passes/w8c_variable_feature_test.go (KerML: := on a plain, default := and redefining feature, a behavior parameter and a root feature fire, var/const and = stay silent; SysML: := on a data-type attribute, a portion, a composite action and a root usage fire, var attribute … := 1 in an item def, := in a part def, a default = stay silent; constant on a data-type attribute, a composite action and a root usage fire, constant attribute and constant part in a part def stay silent); tools/referee/reject semantic/k11-initial-value-nonvariable.kerml and semantic/s80-constant-attribute-not-variable.sysml — both-reject, moved from pilot-only; tools/census/validation/testdata/probes/validateFeatureValueIsInitial.kerml, …/validateFeatureConstantIsVariable.sysml ✅ Faithful (Adjudicated: the pinned validate-kerml/validate-sysml-batch report the same messages at the same positions for every fired and silent shape of the test models, except var attribute x : Integer := 1;, which the pinned SysML grammar does not parse at all and which stays silent here)
validateFeatureReferenceExpressionReferentIsFeature and FeatureUtil.canAccess applied to a feature reference (not only a subsetting): a referent that is not a feature is Must be a valid feature, and one whose featuring types do not feature the referring context is Must be an accessible feature (use dot notation for nesting) (errors) passes/w8c_feature_reference.go FeatureReferencePass, reusing the existing canAccess walk of the subsetting rule above; passes/constraint.go featuringContexts derives a featuring type only for a feature, so a definition nested in a type — an owned member of it, not a feature featured by it — has none, and a body written inside calc def E under part def P { attribute n; … } does not reach n by its bare name, while a nested usage (calc e { n + 1 }) does passes/w8c_feature_reference_test.go (an inaccessible v1::n fires once, at the reference's span; two invalid referents fire; enum literals and legal dotted references stay silent; TestW8CFeatureReferenceBodyInaccessible fires once for a nested calc def, constraint def, state def guard and action def assign/if reading the enclosing definition's feature, and for a nested calc def chaining from the enclosing definition's part; TestW8CFeatureReferenceBodyAccessible analyses cleanly — no error of any tier — the nested-usage forms, a nested definition reading its own, inherited or redefined feature or one of a definition it specializes, a package-level feature, and a package-level definition chaining through its own part; each shape matched line-and-column against the pinned validator in both directions) ⚠️ Approximate (accessibility is not checked on a chain expression's member: the reference resolves that member against the preceding feature and reports validateFeatureChainExpressionFeatureConformance instead, which we do not implement. Variants and variation/enumeration members are exempt — they are owned members of their variation, not features of it — so the remaining FeatureChain_invalid row stays silent rather than risking a false positive)
via route feature accessibility in nested definitions: a bare route target is accessible only from the definition's context, while a usage body may resolve a feature of its owner passes/w8c_feature_reference.go featureReferenceChecker.checkVia, called for transition routes, accept-action relationships and send-via statements; usage routes use resolve.Resolver.RelationshipScope so targets owned by the usage resolve in the same scope as the resolver, and the checker inspects only the route head while preserving chained route semantics passes/w8c_feature_reference_test.go:TestW8CFeatureReferenceViaBoundaries; migration and runtime context-qualified-port fixtures ✅ Faithful
validateMetadataFeatureMetaclassNotAbstract (KerMLValidator.xtend): the metaclass an annotation names must not be abstract (error) passes/w8c_metadata_type.go MetadataTypePass at the type tier, over prefix and body annotations passes/w8c_metadata_type_test.go (a locally-declared abstract metaclass annotation fires; a concrete one stays silent); tools/referee/reject xpect/p24-metadata-abstract-type.sysml — both-reject (ours 1 error(s), pilot 1 error(s)), closed once a library metaclass carried its declaration and its abstractness on every load path ⚠️ Approximate (the remaining limit is tiering: the four @A; rows of MetadataTests_SemanticMetadata_invalid.kerml.xt and SemanticMetadata_invalid.sysml.xt are never reached because both files also carry a name-resolution error, adjudications.md)
validateFeatureHasType — FeatureUtil.getAllTypesOf(f) empty is Features must have at least one type (error) passes/w11e_implicit_base.go implicitBaseChecker.checkFeatureHasType (ImplicitBasePass, constraint tier): a feature no type reaches — directly, through what it specializes, through the base feature its kind implies, or, for a conjugated feature, through the types of the feature it conjugates — is reported; the implicit base counts only when the library declaring it is in the resource set, and a conjugation (feature f ~ D;) replaces the implicit subsetting that would supply it (semantics/implicit.go, matching the pilot's TypeAdapter.computeImplicitGeneralTypes). With the library present every ordinary feature f; keeps its implicit type, so the corpora and the stdlib gate stay silent Feature_invalid_noType.{kerml,sysml}.xt — all four rows agree, including the Must directly or indirectly specialize Base::Anything half the same pass owns; tools/census/validation/testdata/probes/validateFeatureHasType.kerml; tools/referee/reject semantic/k45-conjugated-feature-without-type.kerml — both-reject (a nested class C { feature g ~ D; }, a step and an expr conjugating a class, with feature f2 ~ f1 of a typed f1 staying silent) ✅ Faithful
validateClassifierDefaultSupertype (KerMLValidator.checkClassifier): a classifier specializes the default supertype of its kind, Must directly or indirectly specialize {supertype} (error); observable only on a conjugated classifier, which the pilot leaves without its implicit specialization passes/w11e_implicit_base.go implicitBaseChecker.checkDefaultSupertype over semantics/implicit.go Model.KindBaseFQNs (the generic base, and the binary base for a two-end association or interaction, per KerML 1.1 §8.3.4.7 checkAssociationBinarySpecialization) — a conjugated declaration owns no specialization of its own, so it reaches the base only through the type it conjugates passes/w11e_implicit_base_test.go; tools/census/validation/testdata/probes/validateClassifierDefaultSupertype.kerml; tools/referee/reject semantic/k46-conjugated-structure-not-an-object.kerml — both-reject ⚠️ Approximate (wider: on assoc A ~ B { end feature a; end feature b; } we require Links::BinaryLink where the pilot's check reads only the generic default and says Links::Link, and on a two-end assoc struct AS ~ Objects::LinkObject we require Objects::BinaryLinkObject where the pilot is silent; the binary requirement is the spec's, drafted against the pilot in omg-issues.md)
validateBindingConnectorIsBinary (KerMLValidator.checkBindingConnector): a binding connector's related features number exactly two, Binding connector must be binary (error) passes/w10b_related_elements.go W10BRelatedElementsPass.Run over semantics/connector.go clauseEndTarget / Model.ConnectorEndTargets — the two ConnectorEnds of the of x = y / bind x = y clause, an owned end, a usage's = value binding and the ends a binding inherits from what it specializes all count passes/w10b_binding_binary_test.go (a third owned end, one end, none, an abstract binding without ends; a binding subsetting a binary one stays silent); tools/census/validation/testdata/probes/validateBindingConnectorIsBinary.kerml; tools/referee/reject semantic/k49-binding-connector-with-three-ends.kerml — both-reject ✅ Faithful
validateFeatureChainingFeatureConformance (KerMLValidator.checkFeature): every chaining feature after the first is featured within the one before it, Must be a valid feature (error), on the chaining membership passes/w8c_feature_reference.go featureReferenceChecker.checkDeclaredChains / checkChainTarget / chainMemberFeaturedWithin: the chains a usage's header writes as a references, chains or subsetting target and as a connector, binding, succession or flow end are walked, and a member reached through an alias or import of the previous segment's type but featured elsewhere is reported at its span passes/w8c_feature_chaining_test.go; tools/census/validation/testdata/probes/validateFeatureChainingFeatureConformance.kerml; tools/referee/reject semantic/k47-feature-chain-through-alias-to-another-type.kerml — both-reject (four target diagnostics agree, a chain through a genuinely featured member stays silent) ✅ Faithful
validateAnnotationAnnotatedElementOwnership (KerMLValidator.checkAnnotation): an annotation owned by its annotated element owns its annotating element, Must own its annotating element (error) — textually, an annotating element whose about names itself passes/annotation_ownership.go AnnotationOwnershipPass.Run (constraint tier) over the resolver: a comment, doc or metadata whose about resolves (directly or through an alias) to the annotating element itself is reported once per such target; element-scoped, so an unrelated lower-tier error elsewhere in the document does not hide it and only an about that itself failed to resolve stands down passes/annotation_ownership_test.go (KerML and SysML self-annotation, beside other elements, through an alias, as a metadata usage; annotations of other elements stay silent; reported beside an unresolved name elsewhere in the document); tools/census/validation/testdata/probes/validateAnnotationAnnotatedElementOwnership.kerml; tools/referee/reject semantic/k48-annotating-element-annotates-itself.kerml — both-reject ✅ Faithful (the pilot's other branch, Must be owned by its annotated element, has no textual spelling)
validateFeatureEndNoDirection and validateFeatureEndNotDerivedAbstractCompositeOrPortion (KerMLValidator.checkFeature): an end feature has no direction and is not derived, abstract, composite or portion (errors) passes/end_feature.go EndFeaturePass.Run (constraint tier, element-scoped): SysML end in/out/inout a, end derived a, end abstract a, end variation a (a variation is abstract); parser/defusage.go now carries the modifier flags of an anonymous usage so an unnamed end is judged alike. KerML's EndFeaturePrefix admits none of these spellings, and a SysML end part a is not composite, so the composite and portion halves have no textual spelling passes/end_feature_test.go; tools/census/validation/testdata/probes/validateFeatureEndNoDirection.sysml, …/validateFeatureEndNotDerivedAbstractCompositeOrPortion.sysml; tools/referee/reject semantic/s86-end-with-direction.sysml, semantic/s87-end-derived-or-abstract.sysml — both-reject ✅ Faithful
checkMetadataBodyFeature's Must be model-level evaluable half, and validateMetadataFeatureAnnotatedElement (Cannot annotate Classifier) passes/w8c_metadata_annotation.go MetadataAnnotationPass over semantics/metadata_body.go Model.MetadataBodyInevaluableValues (the evaluability half) and semantics/annotated_element.go Model.AnnotatedElementViolation (the annotated-element half) — the two SysML-half rows below record each in detail MetadataTests_MetadataFeature_invalid.kerml.xt — all nine rows agree, including Must be model-level evaluable on x = ~3;, z = f((as A).y); and filter f((as A).y); and Cannot annotate Classifier ✅ Faithful
validateMetadataFeatureMetadata (KerMLValidator.xtend, Must have exactly one metaclass): the type of a metadata feature is a metaclass (error) passes/w8c_metadata_type.go MetadataTypePass (type tier), over prefix annotations, @M about … and KerML metadata … : M usages, deciding the kind with semantics.IsMetadataType after alias resolution passes/w8c_metadata_type_test.go TestW8CMetadataTypeMustBeAMetaclass (a class, a struct and a datatype fire; a concrete metaclass and an alias of one stay silent); tools/referee/reject semantic/k40-metadata-typed-by-class.kerml — both-reject ✅ Faithful (the pilot reports Must have a concrete type on that case: its reference to a non-metaclass does not link, so only the implicit abstract Metaobject type is left; we report the constraint itself)
validateMetadataFeatureAnnotatedElement over the metaclass's effective annotatedElement features: the annotated element's metaclass conforms to the types of every declared, inherited or redefined annotatedElement feature of the metadata type — non-abstract ones when there are any — for the @M and @M about … forms alike (error) semantics/annotated_element.go Model.AnnotatedElementViolation / Model.AboutAnnotatedElementViolations, reported by passes/w8c_metadata_annotation.go; the annotated element's metaclass and the feature types are resolved through the reflective KerML::* library, never by kind passes/w8c_metadata_annotation_test.go TestMetadataAnnotatedElementReadsEffectiveFeatures (direct, inherited, narrowed and subsetted annotatedElement features; each about target judged on its own) and TestMetadataAnnotatedElementInSysML; tools/referee/reject semantic/k35-metadata-annotates-wrong-kind.kerml — both-reject ✅ Faithful
validateMetadataFeatureBody (Must redefine an owning-type feature) in the KerML @M about C { :>> g; } spelling: a body feature, explicitly redefining or named after the feature it takes, redefines a feature of a type the metadata type conforms to, at every nesting depth (error) semantics/metadata_body.go Model.MetadataBodyViolationsOf, shared by passes/w8c_metadata_annotation.go (prefix and about annotations) and passes/w8d_metadata_usage.go (metadata … : M usages); resolve/metadata_scope.go Resolver.MetadataBodyOwner supplies the owning metaclass to a usage body too passes/w8c_metadata_annotation_test.go TestMetadataBodyRedefinitionMustNameAnOwningTypeFeature, passes/w8d_metadata_usage_test.go TestW8DMetadataUsageBodyRedefinitionMustNameAnOwningTypeFeature (a package-level feature, the annotated element's own feature and a nested outside feature fire; own, inherited and nested owning-type features stay silent); tools/referee/reject semantic/k36-metadata-body-redefines-outside.kerml — both-reject ✅ Faithful
AssociationTest_CrossFeatures_invalid.kerml.xt's four rows (checkCrossFeature-family: an association's cross features and their end featuring) three rows closed by passes/w10b_cross_features.go; the fourth, Must be the cross feature, closed once ast.CrossFeatureMember recorded the end's inline owned cross feature passes/w12d_rules_test.go, tests/parser/testdata/parse/w12d_end_cross_feature.{kerml,golden} ✅ Faithful (see adjudications.md E2)
validateCrossSubsettingCrossingFeature (Cross subsetting must be owned by one of two or more end features), validateCrossSubsettingCrossedFeature (Cross subsetting must chain through an opposite end feature), validateFeatureCrossFeatureSpecialization (Cross feature must specialized redefined-end cross features) and validateFeatureOwnedCrossSubsetting (at most one owned cross subsetting; KerML 8.3.3.3 Feature and CrossSubsetting) passes/w10b_cross_features.go checkW10BCrossFeatures over semantics/crossing.go (CrossSubsettings, Model.CrossedFeatureChain, Model.CrossFeature, Model.OwnedCrossFeature) and Model.EndFeatures/Model.ImplicitEndRedefinitions; a cross feature an end declares in its body or inline ahead of itself (symbols.SymbolCrossFeature, a member of the end) implicitly subsets the cross features of the ends its owner redefines (semantics/model.go DirectSupertypes), as the pilot's implied specializations do passes/w10b_cross_features_test.go (non-end and single-end owners, a crosses naming the opposite end or an unchained feature, two clauses, an assoc/connection def that specializes another and redefines its ends, with KerML and SysML spellings and clean n-ary, inherited-end, body-declared and inline-declared shapes); tools/referee/reject semantic/k16, k17, k19, k42 — all both-reject ✅ Faithful (the pinned pilot reports Error executing EValidator for k42 because its check indexes the reference subsettings instead of the cross subsettings — drafted in omg-issues.md; we report At most one cross subsetting is allowed)

Validation Rules Declared by the Reference's Own Xpect Suites — SysML half

The SysML families the pinned reference (pilot 2026-08) declares in build/pilot-xpect-corpus/sysml/…/tests/validation/invalid/ and we reported nowhere. Each row was established from the reference's own validator source (SysMLValidator / KerMLValidator, constraint name quoted) before implementing, and its scope and exemptions — not its message — decide the row's status. Every rule is at the constraint or type tier and none double-reports what a lower tier already errored on. Re-measured on the tree merged with the sibling declared-rule work: Xpect 599 → 629 agreeing rows, 0 rows newly disagreeing; the differential (125 only ours, 137 only the pilot's) and the rejection corpus (26 both reject) unchanged. Those three figures are as measured at that round and are not the current baseline, which the generated block in README and architecture states.

Semantic Rule Implementation Test Case Status
validateOccurrenceUsageType (INVALID_OCCURRENCE_USAGE_TYPE) and validateEventOccurrenceUsageReference (INVALID_EVENT_OCCURRENCE_USAGE_REFERENCE): an occurrence, item or part usage is typed by occurrence definitions (error), and an event names an occurrence, not an attribute or enumeration usage. A data type reached through a subsetting or redefinition counts, which is how the reference reports occurrence avalue :> aValue; passes/w8d_occurrence_typing.go W8DOccurrenceTypingPass (type tier), following typing and inherited subsetting/redefinition/reference edges; a directly declared data type on a part/item/individual/portion usage is left to passes/typecheck.go, which already errors on it, so the tiers do not double-report passes/w8d_occurrence_typing_test.go (a data-typed occurrence, one inheriting a data type through :>, a mixed typing list, and an event naming a ref usage; occurrence-typed occurrences and an event on a part stay silent). OccurrenceUsage_invalid.sysml.xt: 5 declared errors, all closed ✅ Faithful
validateConnectorTypeFeaturing (INVALID_CONNECTOR_TYPE_FEATURING, Must be an accessible feature (use dot notation for nesting)): each end of a connector, connection, interface, allocation, binding or flow must be accessible from the connector's featuring context — FeatureUtil.canAccess, the same predicate the subsetting-featuring check implements for :> passes/w8d_connector_featuring.go W8DConnectorFeaturingPass (constraint tier), reusing constraintChecker.featuringContexts/featuredWithin rather than a second accessibility helper; a variant connector (semantics.DeclaresVariant), a package-level feature and an enumeration literal are exempt, as in the reference passes/w8d_connector_featuring_test.go (ends naming another definition's feature and a nested feature of a sibling, on connect and on bind; dot-notation ends and the connections of a variation interface's variants stay silent). The named-end form (bead references t.bead) and enumeration bindings are covered by the pilot corpora gate, where they were the first false positives this rule produced. Measured on the tree with that sibling work merged: BindingConnector_redefine.sysml.xt goes from 4 disagreeing rows to 0 and Connector_Invalid.sysml.xt from 4 to 0 — the file's other declared rows already agreed there, closed by 8B's masking/conformance work ✅ Faithful (the redefinition/masking conformance predicate those fixtures also exercise is a separate row; this one takes only the accessibility diagnostic)
validateFlowEndSubsetting (INVALID_FLOW_END_SUBSETTING, Cannot identify flow end (use dot notation)) with validateConnectorRelatedFeatures (INVALID_CONNECTOR_RELATED_FEATURES): a flow end written as a qualified name rooted in a definition (flow from A::out to B::in) identifies no feature, and a flow that cannot identify an end has fewer than two related features passes/w8d_flow_end.go W8DFlowEndPass (constraint tier), resolving the root segment through resolve.Resolver.PartSymbol; one related-elements diagnostic per flow, as the reference reports it on the connector rather than per end passes/w8d_flow_end_test.go (both ends rooted in a definition — two end diagnostics and one related-elements diagnostic; dotted ends stay silent). Relationship_invalid_relatedElement1.sysml.xt: 5 declared errors, all closed ✅ Faithful (the subsetting row above covers that form; this closes the unidentifiable-end form the fixture declares)
validateDefinitionVariationMembership / validateUsageVariationMembership (An owned usage of a variation must be a variant.) and validateDefinitionVariationSpecialization / validateUsageVariationSpecialization (A variation must not specialize another variation.), both errors on the owned membership and on the specialization respectively passes/w8d_variability.go W8DVariabilityPass (constraint tier), keyed on semantics.IsVariation/DeclaresVariant so the two forms (definition and usage) share one check; a metadata M; written in the body is an annotating member of the variation (SysML.xtext AnnotatingMember, an owning rather than a feature membership), so it is not one of the owned usages the rule reads passes/w8d_variability_test.go (a non-variant usage owned by a variation, a variation specializing a variation, and a legal variation whose members are all variants and whose supertype is not a variation); passes/enumeration_body_test.go TestEnumerationBodyAdmitsValuesAndAnnotations (a body-level metadata usage in a variation stays silent, as in the pilot). Variability_invalid.sysml.xt: 4 declared errors closed ✅ Faithful
validateEnumerationDefinitionIsVariation (SysML v2 §7.6.4, §8.3.9: every enumeration definition is a variation and its enumerated values are its variants), so validateDefinitionVariationSpecialization / validateUsageVariationSpecialization reject an enum def specializing an enum def or a variation, a variation specializing or typed by an enum def (A variation must not specialize another variation.), and validateDefinitionVariationMembership rejects a keyword-less non-enumerated member of an enum def (An owned usage of a variation must be a variant.); an explicit variant in an enumeration body is rejected by the reference at parse time, and so is any member of an enumeration body that is not an enumerated value or an annotating element (SysML.xtext EnumerationBody: AnnotatingMember | EnumerationUsageMember), such as a nested definition, package, import or alias semantics/variation.go IsVariation (declared variation, or an enumeration definition); passes/w8d_variability.go W8DVariabilityPass reads it for the owner and for every specialization target, accepts the enumerated values as the variants (w8dSpecializationMessage spells out why and how to fix the enumeration form); passes/constraint.go checkVariantOutsideVariation reports a variant written in an enumeration body; passes/enumeration_body.go EnumerationBodyPass at LevelSyntax, code syntax/enumeration-body-member, reports every non-usage declaration an enumeration body owns as a non-blocking notation error naming the member and the fix (owned usages are left to the variation-membership constraint so each member is reported once); semantics/enumeration.go EnumerationDefinitionOwning; semantics/annotations.go reflectiveFeatureValue derives the metaclass features isVariation and isVariant from IsVariation / IsVariant, so element filters and queries read true for an enumeration definition and its values; parser/defusage.go atEnumeratedValueDeclaration parses every keyword-less EnumeratedValue form of SysML.xtext (uncl : Level = 0;, <s>;, : E = 1;) in an enumeration body as an enumerated value ; semantics/variation.go IsVariant, VariationOwning, Model.VariationPointOwning, VariantsOf, VariantOf, SelectsVariantOf, IsVariationFeature count the enumerated values as the variants their enumeration offers (a usage typed by an enumeration holds one value, as any attribute does, and is not a variation point); runtime/eval.go evaluates an enumerated value as its literal before asking whether it is a variant, so enum arithmetic, own attributes and defaults are unchanged; solve/reference.go valueOf/datatype keep the enumeration's finite sort distinct from a variation point's; export/rdf_out.go derives sysml:isVariation for an enumeration definition and sysml:isVariant (with sysml:variant/variantMembership) for its values, and export/rdf_in.go reads them as the derived facts they are, never as a variation/variant keyword the enumeration grammar cannot carry passes/w8d_variability_test.go TestW8DEnumerationDefinitionIsAVariation, TestW8DEnumerationDefinitionMembers, TestW8DLegalEnumerationStaysSilent; semantics/variation_test.go TestIsVariationIncludesEnumerationDefinitions, TestReflectiveVariationFeaturesOfEnumerations; queryexec/computed_test.go TestExecuteComputedEnumerationVariationFlag; parser/f61_keywordless_members_test.go typed_enum_values, short_named_and_nameless_enum_values; passes/enumeration_body_test.go (nested definitions, packages, imports and aliases at any depth are reported once; every enumerated-value form and annotating element stays silent; a parse-error member is not reported twice); pilot-reject semantic/s47-enumeration-specializes-enumeration.sysml and grammar/g68-definition-in-enumeration-body.sysml (both reject); MetadataTest.sysml in the pilot examples corpus stays clean; semantics/variation_test.go TestVariantsOfAnEnumeration (nested and qualified enumerations, VariantsOf/VariantOf/SelectsVariantOf through a usage typed by the enumeration, a value of another enumeration refused); solve/translate_test.go TestEnumerationIsAFiniteSort (an enumeration-typed feature is not in Query.Variations()); export/variation_rdf_test.go (enumeration definitions and values exported with the derived flags, imported without inventing a keyword, source-text-free structural round trip); runtime conformance enum_literal_* unchanged ✅ Faithful (validateEnumerationUsageType and validateAttributeUsageEnumerationType were already passes/one_type.go's An enumeration must be typed by one enumeration definition. and An enumeration attribute cannot have more than one type.; the pinned ontology declares no isVariant on EnumerationUsage, so that one derived triple is a recorded known violation in export/testdata/ontology-known-violations.txt)
validateRequirementVerificationMembershipOwningType (A requirement verification must be in the objective of a verification case., error): a verify satisfaction is legal only inside the objective of a verification case definition or usage passes/w8d_verification.go W8DVerificationPass (constraint tier), walking from the verify usage to its owning objective and that objective's owner passes/w8d_verification_test.go (a verify in a requirement definition, in a verification definition's requirement member and in a plain case's objective; a verify in the objective of a verification definition and of a verification usage, and a satisfy, stay silent). Verification_invalid.sysml.xt: 4 declared errors closed ✅ Faithful
validateAssignmentActionUsage requires referent.featureTarget.mayTimeVary (SysML v2 §8.3.17.5): an assignment referent must be a time-varying feature semantics/usage.go Model.UsageMayTimeVary derives SysML v2 §8.3.6.4 from the owning occurrence, portion status, SelfLink/HappensLink exclusions and composite-action exclusion; passes/w8d_assignment_referent.go AssignmentReferentPass is element-scoped at the constraint tier and resolves the assignment target before checking it passes/w8d_assignment_referent_test.go covers accepted time-varying features, rejected composite actions and package-owned features, unresolved/non-feature targets without cascades, nested/qualified targets, unrelated lower-tier errors, and cold/warm library configurations; AssignmentActionUsage_invalid.sysml.xt:44 agrees word-for-word ✅ Faithful
validateViewDefinitionOnlyOneViewRendering / validateViewUsageOnlyOneRendering (error on every view rendering after the first, with the definition and usage wordings the reference uses) passes/w8d_view_rendering.go W8DViewRenderingPass (constraint tier), counting only ViewRenderingMembership members — a plain rendering member is not one passes/w8d_view_rendering_test.go (a second render in a view definition and in a view usage, each carrying the reference's own wording; one render beside plain rendering members stays silent). ViewRendering_invalid.sysml.xt: 3 declared errors closed ✅ Faithful
checkMetadataBodyFeature (INVALID_METADATA_FEATURE_BODY, Must redefine an owning-type feature) and checkMetadataFeature's INVALID_METADATA_FEATURE_METACLASS_NOT_ABSTRACT (Must have a concrete type): a feature written in an annotation body must redefine a feature of the metadata definition annotated — recursively, for a nested body — and the definition annotated must be concrete passes/w8d_metadata_usage.go W8DMetadataUsagePass (type tier, beside MetadataAnnotationPass, so a violation in either spelling never gates the other's report), reading the metadata … : A usage form only, through semantics/metadata_body.go Model.MetadataBodyViolationsOf — the same query the KerML-half annotation pass uses — so an inherited feature counts and an explicit :>> must name a feature of a type the definition conforms to passes/w8d_metadata_usage_test.go (a usage body feature naming a package-level element and one naming nothing, at both nesting depths; an abstract and a concrete metadata definition; legal annotations, prefix and usage form). MetadataUsage_Invalid.sysml.xt: no row claimed — MetadataAnnotationPass reports the same body-feature rule for annotations written as prefixes or as members (metadata-owning-type-feature, moved there from RedefinitionConformancePass) and closed that file's rows, and this pass leaves an abstract type to the KerML-half's Must have a concrete type ⚠️ Approximate (the rule is faithful in isolation but only the metadata … : A form is left to it: annotation body features are MetadataAnnotationPass's and prefix concrete types the KerML half's)
checkMetadataBodyFeature's second half — a metadata feature value must be model-level evaluable (= ~3, = f((as A).z) in MetadataUsage_Invalid.sysml.xt lines 82 and 85) passes/w8c_metadata_annotation.go MetadataAnnotationPass (type tier) over semantics/metadata_body.go Model.MetadataBodyInevaluableValues, deciding each bound value with semantics/evaluable.go Model.ModelLevelEvaluable — the KerML §7.4.9 predicate as a walk over the expression, not the filter compiler passes/w8c_metadata_annotation_test.go (~3 fires, 1 + 2 stays silent, at both nesting depths); semantics/evaluable_test.go (literals, null, sequences, enumeration literals, constant features, constructors, casts, library versus user functions) ✅ Faithful (the span was the last gap and is now closed: the parser records the =/:= token of a feature value in Usage.ValueOperatorSpan and this pass reports from there through the value, which is the FeatureValue region the reference points at — adjudications.md, replacing adjudications.md's open row)
checkMetadataFeature's INVALID_METADATA_FEATURE_ANNOTATED_ELEMENT (Cannot annotate ItemUsage): the annotated element's metaclass must specialize what the metadata definition redefines annotatedElement to semantics/annotated_element.go Model.AnnotatedElementViolation, reported by passes/w8c_metadata_annotation.go on the annotation: the metaclass of the annotated element must conform to every typing of the annotatedElement feature the metadata type restates, declared or redefined passes/w8c_metadata_annotation_test.go TestMetadataAnnotatedElementMustConform (a metaclass restricted to KerML::Structure annotating a struct and a class), TestMetadataAnnotatedElementOnRequirementMembers (the same check on a subject, assume and require member's prefix as on a part's) ✅ Faithful (MetadataTests_MetadataFeature_invalid.kerml.xt Cannot annotate Classifier and MetadataUsage_Invalid.sysml.xt Cannot annotate ItemUsage both closed)
validateVariantMembershipOwningNamespace (A variant must be an owned member of a variation., error) — declared by Variability_invalid.sysml.xt lines 55, 57 and 73, where we report the same defect as a warning (variant b1 is not a variant of a variation …) passes/constraint.go checkVariantOutsideVariation (pre-existing; the severity divergence is unchanged) the rows are agree-adjacent, not silent: the reference errors where we warn. Raising the severity means editing an existing pass and re-adjudicating every corpus file that carries the warning, which was out of scope there ⚠️ Approximate (reported, at a lower severity than the reference)

Declared Validation Rules That Drew Nothing From Us

The rows the reference's own Xpect suites declare as errors and we reported nowhere: 47 such rows before this work, 18 after it (the 4 library-less Feature_invalid_noType rows closed since — see their row below). Of the 18 left, 12 are the protected-import visibility rows, 2 are extraneous input '}' expecting EOF parser rows, and 4 are name-resolution gaps rather than missing rules. Each rule below was read out of the reference's validator source (SysMLValidator / KerMLValidator, constraint name quoted) before implementing, and none of them fires anywhere in the four OMG corpora (examples/pilot-corpora, examples/sysml-v2-training, internal/workspace/libs/stdlib), scanned per file against the exact message set with a known-positive control.

Semantic Rule Implementation Test Case Status
validateUsageTyping family: a usage names the kind of definition it takes (An attribute must be typed by attribute definitions., and the allocation, connection, interface, port, action, state, flow connection and bare-usage wordings), reported at the declaration passes/w10b_usage_typing.go pilotTypingMessages/pilotTypingMessage, consumed by passes/typecheck.go and passes/one_type.go; our own kind rules still decide when a typing is wrong, this decides how it is said passes/f69_typing_test.go, passes/f61_reference_usage_typing_test.go, passes/typecheck_individuals_test.go. Closes 12 silent rows across AttributeUsage_invalid (5), AllocationUsage_Invalid (3), ConnectionUsage_Invalid (2) and InterfaceUsage_Invalid (2) ✅ Faithful (wording and location; the kind taxonomy behind it is unchanged)
validateIndividualUsage (An individual must be typed by one individual definition., At most one individual definition is allowed.) and validatePortionUsage (Must be owned by an occurrence definition or usage.) passes/w10b_individual_portion.go W10BIndividualTypingPass (type tier), W10BPortionOwnerPass (constraint tier); the parser now keeps the individual modifier on a definition (parser/defusage.go, ast/defusage.go) so the rule can see it passes/w10b_individual_portion_test.go, passes/typecheck_individuals_test.go ✅ Faithful
KerMLValidator.validateRelationship (Must have at least two related elements) passes/w10b_related_elements.go W10BRelatedElementsPass (constraint tier), counting owned and inherited ends through semantics.Model.DirectSupertypes so a connection definition specialising a two-end definition stays silent — including one whose base is index-only, where semantics.Model.ConnectorEndCount supplies Links::BinaryLink's two known ends, the case allocation def Allocation :> BinaryConnection needs passes/w10b_related_elements_test.go; examples/views-demo.sysml:34 corrected to declare its two ends in the same commit ✅ Faithful
validateRedefinition family: A package-level feature cannot be redefined, Featuring types of redefining feature and redefined feature cannot be the same, Redefining feature must be an end feature passes/w10b_redefinition.go, registered from constraint.go checkW10BRedefinition passes/w10b_redefinition_test.go; model/connector_ends_test.go adjusted where the end-feature rule now (correctly) also reports ✅ Faithful (the package-level form fires only when both features are package-level, which is the reference's owningNamespace test — an earlier reading rejected legal nested redefinitions)
validateAssociationCrossFeature family: Cross feature must have same type as feature, Cross subsetting must chain through an opposite end feature, Cross feature must specialized redefined-end cross features, Must be the cross feature passes/w10b_cross_features.go, registered from constraint.go checkW10BCrossFeatures passes/w10b_cross_features_test.go (including a named cross feature declared ahead of the end's kind keyword, end x1 : Sub1 feature x : C1, whose typing is its own and not the end's); semantics/crossing_test.go; tools/referee/reject semantic/k53-cross-feature-typed-narrower-than-end.kerml, semantic/s88-cross-feature-typed-narrower-than-end.sysml — both-reject. Closes 4 silent rows in Association_invalid*.kerml.xt ✅ Faithful
validateInterfaceDefinitionEnd / validateInterfaceUsageEnd (An interface definition end must be a port., An interface end must be a port.) and validateFlowConnectionDefinitionEnd (A flow connection definition can have at most two ends.) passes/w10b_ends.go W10BEndKindPass (its own type-tier pass: run in the constraint tier the interface-end rows were masked by the usage-typing error on the same declaration) passes/w10b_ends_test.go (a non-port end in a definition and in a usage, port ends and an n-ary association clean, malformed input) ✅ Faithful. Interface-end kind and flow-definition arity are separate from the pilot's stale universal interface-arity expectation; general interfaces remain n-ary
validateCalculationDefinitionOnlyOneResult / validateFunctionResultParameterMembership (Only one return parameter is allowed), validateStateDefinition{Entry,Do,Exit}Action (A state may have at most one entry/do/exit action.), validatePortDefinitionOwnedUsagesNotComposite / validatePortUsageNestedUsagesNotComposite passes/w10b_structural.go W10BStructuralPass (type tier; return counting uses the shared function/expression owner set, including constraint definitions/usages and KerML predicates/functions; the state subaction counting moved here out of at_most_one_member.go, which reads the same declaration but reported behind an error) passes/w10b_structural_test.go (TestW10BReturnParametersAcrossFunctionKinds: duplicate returns are reported on the extra member and one return stays silent for constraint definitions/usages and KerML predicates/functions; other structural tests cover one of each action and malformed input), passes/w7g_at_most_one_member_test.go ✅ Faithful (a usage is referential when it is ref, directed, an end, or reference-subsetting — the last is how 27. Occurrences/Interaction Realization-2.sysml writes a port's nested events)
validatePortUsageIsReference (A port usage must be referential.): a port usage whose owning type is neither a port definition nor a port usage is referential, which in the textual notation only a variant port under a variation port owned by one can violate (a variant has no owning type; the pilot's PortUsageAdapter clears isComposite for every other owner) passes/w10b_structural.go W10BStructuralPass (type tier), checkVariantPorts down a chain of variations; parser/defusage.go reads the usage prefix after variant (variant ref port a;, variant in port b;, variant end port c;) that the pilot grammar admits and that makes such a variant referential passes/w10b_structural_test.go (TestW10BVariantPortMustBeReferential: composite variants reported through nested variations, ref/directed/end/bare-reference variants and variants under a part owner silent), tests/parser/testdata/parse/variant_usage_prefix.sysml, tools/census/validation/testdata/probes/validatePortUsageIsReference.sysml, tools/referee/reject/testdata/negative/semantic/s52-variant-port-composite.sysml ✅ Faithful
validateElementFilterMembershipIsBoolean (Must have a Boolean result) and the evaluability half (Must be model-level evaluable) passes/filter.go filterDiagnostic, now the reference's two messages, both errors passes/filter_test.go, passes/f21_f23_validation_test.go ✅ Faithful (wording and severity; the rule's semantics are unchanged, this is the message gap that row named)
validateFeatureTyping on a library-less resource set: Must directly or indirectly specialize Base::Anything / Parts::Part and Features must have at least one type (Feature_invalid_noType.{kerml,sysml}.xt, 4 rows) passes/w11e_implicit_base.go ImplicitBasePass (constraint tier): a type whose supertypes do not reach the standard-library base its kind implies is reported — whether the library declaring the base is absent from the resource set or conjugation replaces the implicit specialization that would supply it — and a feature no type reaches draws Features must have at least one type under the same conditions Feature_invalid_noType.{kerml,sysml}.xt — all 4 rows agree in tools/referee/xpect ✅ Faithful
validateSubsettingFeaturingTypes (Must be an accessible feature (use dot notation for nesting)) passes/constraint.go checkSubsettingFeaturingTypes (pre-existing), skipping only a dotted feature-chain target (verify vehicleSpecification.vehicleMassRequirement), which is the notation the message itself prescribes, while a ::-qualified target of another type (satisfy R::nested by p) is still reported reproducer part def A { part b; } part def C :> A { part d :> b; } part e :> A::b; — the pinned validate-sysml-batch and we both report the diagnostic at 4:12, byte-identical. The follow-up entry that called this a gap is stale ✅ Faithful (verified against the pinned reference, not re-implemented)
The kind-mismatch, binding-type and conjugation false positives of ours passes/typecheck.go, passes/typecheck_value.go — no change needed reproducers run against the pinned reference: part p1 : ItemDef, use case uc : UseCaseDef, a bind whose value type specialises the target's, and KerML classifier B conjugates A / feature y ~ x are all silent on both sides today, and Simple Tests/{Conjugation,Types,Features}.kerml, ItemTest, IndividualTest, UseCaseTest carry no only-ours diagnostic in pilot-diff ✅ Faithful (both follow-ups' ours-side rows no longer reproduce; earlier changes closed them)
The three Must be a valid feature false positives on a KerML bool expression (ServerSequenceOutsideRealization-2.sysml, uncovered by 10F) symbols/builder.go — a KerML expr/bool declares the expression feature it names, not a plain feature reference passes/w10b_bool_expression_test.go; the file draws no Must be a valid feature diagnostic now that nothing masks its constraint tier ✅ Faithful
The warnings residue of 14 rows of 113 12 rows are closed; Step 3 closes the interface-end diamond; the last row, an inherited-import conflict, is closed by resolve/distinguishability.go Resolver.importedMembers (named in the duplicate-inherited-member section) warnings rows: all 14 agree now; passes/w11a_rules_test.go, passes/w9c_rules_test.go, resolve/inherited_names_test.go ✅ Faithful — the kind's last disagreement, BindingConnector_Invalid2.sysml.xt:42, is closed by the argument-binding row above; warnings is 113 of 113

Duplicate Inherited Members, Usage Typing and KerML Specialization

25 Xpect rows on two defects: the duplicate-inherited-member warning masking a set of unimplemented usage-typing and specialization constraints. Oracles, measured on this tree against a control build of main (07dc713c): Xpect 1172 → 1197 agreeing (154 → 129 disagreeing, 25 rows removed and none added), rejection 114 → 115 both rejecting (6 → 5 only the pilot). The differential does not move, and re-measured on the merged tree it measures 353 files, 311 fully agreeing, 142 only ours — byte-identical to the baseline taken before this work, so the 311 / 142 stands and the 312 / 139 this section first recorded was a mis-measurement.

Both of those differential figures were measured with a stale library index cache and are superseded. A fresh-cache run of the same tree measures 138 only-ours, and the current control is 317 fully agreeing / 119 only ours; see pilot-differential.md. The cache defect is fixed in internal/workspace/libs, which keys records by build identity and makes a library index-only on every load path.

The differential's Vehicle duplicate-inherited family was therefore still open here; 11F closed it by canonicalizing redefinition in the resolver's checkInheritedAmbiguity (Annex_A_VehicleViews.sysml 14 → 6 diagnostics, all 8 rows retired). After 11A the file still carried eight of these warnings (four names at each of lines 686 and 712, both :> vehicle_b where vehicle_b : Vehicle redeclares those four names), because this wording has two producers and 11A canonicalized only the pass-tier one: TestW11ADiamondRedefinitionIsNotDuplicate guards that producer rather than measuring the corpus family closed.

Semantic Rule Implementation Test Case Status
A name reached through two supertypes is a duplicate only where two distinct features survive: one feature reached over two paths is one member, a redefinition hides what it redefines, and a nearer subtype's declaration supersedes its supertype's (KerML 8.3.3.1 Specialization, 8.4.3.2 Redefinition; checkTypeDistinguishability) passes/w9c_inherited_name_conflict.go: candidates carry both the declaring type and the member, and conflictingBases drops a member another candidate redefines, a duplicate of the same member, and a candidate whose declaring type another candidate specializes passes/w11a_rules_test.go TestW11ADiamondRedefinitionIsNotDuplicate (the Vehicle/vehicle_b diamond of sysml-examples/Vehicle Example/Annex_A_VehicleViews.sysml; the corpus family itself still reproduces from the resolver-tier producer, as noted above, so this guards the pass tier rather than measuring that family closed), TestW11AImplicitValueTypingDiamond, passes/w9c_rules_test.go ✅ Faithful
One warning per duplicated name, at the declaration; a referenced feature chain (perform b.a;, exhibit s.sa;, event a.areal;) is an owned feature of its own and repeats them at the chain (KerML 8.3.3.2 feature chaining) passes/w9c_inherited_name_conflict.go check/chainSpans: names are reported individually and each conflict is reported at the declaration span and at every referenced chain passes/w11a_rules_test.go, passes/w10b_reference_bases_test.go (now asserting the chain's own warning). Closes ActionUsage_invalid.sysml.xt:61, StateUsage_invalid.sysml.xt:87, OccurrenceUsage_invalid.sysml.xt:59 ✅ Faithful
A feature's implicit Base::DataValue typing applies only where nothing else classifies it: a definition typing it, a subsetting or a redefinition supplies its type, but a feature typing it does not (KerML 8.4.2) passes/w9c_inherited_name_conflict.go libraryBases/typedByFeatureOnly passes/w11a_rules_test.go TestW11AImplicitValueTypingDiamond. Closes AttributeUsage_invalid.sysml.xt:47,52 ✅ Faithful
A specialization cycle leaves both ends their kind's implicit base: an edge on the path back to the declaration itself reaches no base (KerML 8.4.2 with 8.3.3.1) semantics/implicit.go declaredGeneralizationReaches skips a target already being visited passes/w11a_rules_test.go TestW11ASpecializationCycleKeepsImplicitBase. Closes the Specialization_invalid.kerml.xt cycle row ✅ Faithful
validateAttributeUsageType (An attribute must be typed by attribute definitions.): an attribute usage's types are data types, so an attribute or enumeration definition. A directed attribute is a parameter and keeps the structural leniency the parameter form needs passes/typecheck.go compatibleTyping passes/w11a_rules_test.go TestW11AAttributeTypedByStructure and TestW11AUsageTypingKindFamily, which pins all eight wordings of the family at once against matched reference runs; internal/check/edit/w6g4_rename_references_test.go's shadowing fixture now types its part-definition-typed feature as a part, the declaration the reference accepts ✅ Faithful
validateOccurrenceUsageType for the declared form on a part/item usage (An occurrence, item or part must be typed by occurrence definitions.): the types of an occurrence, item or part are classes, and a data type is not one passes/typecheck.go compatibleTyping; the inherited form stays passes/w8d_occurrence_typing.go so the tiers do not double-report passes/w11a_rules_test.go TestW11AUsageTypedByWrongKind and TestW11AUsageTypingKindFamily, passes/w8d_occurrence_typing_test.go ✅ Faithful
validatePartUsagePartDefinition (SysML v2 §8.3.11 partDefinition->notEmpty(), A part must be typed by at least one part definition.): a part usage's types must include a part definition or any definition kind specializing it (connection, interface, allocation, view, rendering), counting types reached through subsetting, redefinition and reference subsetting passes/part_usage_definition.go PartUsageDefinitionPass (constraint tier), reading semantics.Model.FeatureTypeSet, so an untyped part reaches Parts::Part and a part typed by a part definition among others stays silent; the pinned pilot declares the message constant but checkPartUsage is commented out, so it reports nothing and this row follows the specification rather than a pilot-observed diagnostic passes/part_usage_definition_test.go (an item-definition typing, direct and inherited through subsetting or redefinition; untyped, part-definition, mixed, unresolved and inherited part-definition typings stay silent) ✅ Faithful
validateActionUsageType, validatePortUsageType, validateStateUsageType (An action must be typed by action definitions., A port must be typed by port definitions., A state must be typed by state definitions.) over inherited types as well as declared ones passes/w11a_usage_typing.go W11AUsageTypingPass (type tier), following typing and subsetting/redefinition/reference-subsetting edges passes/w11a_rules_test.go TestW11AUsageTypedByWrongKind ✅ Faithful
validatePerformActionUsageReference (Must reference an action.) and validateExhibitStateUsageReference (Must reference a state.): the referenced feature of a perform/exhibit must be in that semantic family, definition or usage passes/w11a_usage_typing.go checkReference; an unresolved target stays name resolution's, so the type tier does not report it passes/w11a_rules_test.go TestW11AReferenceKinds. Closes ActionUsage_invalid.sysml.xt:57 ⚠️ Approximate (exhibit s; — a plain name rather than a feature chain — parses as a state usage named s rather than as a reference subsetting, so the rule never sees a target and StateUsage_invalid.sysml.xt:83 stays open. The fix is in the parser and was escalated rather than worked around here)
KerML specialization by metaclass family: a data type specializes neither a class nor an association, a class neither a data type nor an association, and a structure and a behavior do not specialize each other (KerML 8.3.3.1 with the classifier families of 8.3.2) passes/w11a_kerml_specialization.go W11AKerMLSpecializationPass (type tier, KerML documents only), mapping each declaration keyword to its families passes/w11a_rules_test.go TestW11AKerMLSpecializationFamilies. Closes the 4 Cannot specialize class or association / ... data type or association rows ✅ Faithful
A conjugated type is not the specific of a specialization, whatever the specialization: a standalone specialization subtype B specializes A, subset g subsets h, redefinition g redefines h or typing g typed by A whose specific B/g is declared ~ (KerML 1.1 §8.3.3.1 validateSpecializationSpecificNotConjugated; pilot Conjugated type cannot be a specialized type) passes/w11e_conjugated_specialization.go W11EConjugatedSpecializationPass, reading the specific of every standalone specialization member passes/w11e_conjugated_specialization_test.go TestConjugatedFeatureIsNotSpecializedThroughAnyRelationshipMember, TestConjugationDoesNotSpreadToOtherSpecifics; census probes validateSpecializationSpecificNotConjugated.kerml, ….subset.kerml ✅ Faithful; reported at the specific, where the pilot points for a subtype and falls back to the element's start for the other three
The classifier-family rules reach :> (parsed as subsetting) as well as specializes, and SysML definitions by their kind's metaclass: an attribute or enumeration definition is a data type, every other definition kind a class, a connection, interface, flow or allocation definition also an association (KerML 1.1 §8.3.3.1 validateClassSpecialization, validateDataTypeSpecialization; SysML v2 §8.3.5) passes/w11a_kerml_specialization.go W11AKerMLSpecializationPass, now on SysML documents too, with the pilot's SysML wording (Cannot specialize attribute definition, Cannot specialize item definition); passes/typecheck.go stands aside where the family pass reports the general passes/w11a_kerml_specialization_test.go TestW11ASpecializationFamiliesSubclassificationSpelling, TestW11ASpecializationFamiliesAssociationClasses, TestW11ASpecializationFamiliesSysMLDefinitions, TestW11ASpecializationFamiliesSilentShapes; census probes validateClassSpecialization.{kerml,subsets.kerml,sysml,connection.sysml}, validateDataTypeSpecialization.{kerml,subsets.kerml,sysml}; the pinned pilot agrees on every one of 17 KerML and 30 SysML definition-kind pairings tried ✅ Faithful
Duplicate of inherited member name 'self' from Part, Port on an interface end (end part ::> tankAssy.fuel;) semantics/implicit.go gives exactly two-ended interface definitions/usages the Interfaces::BinaryInterface base; existing positional implicit end redefinition then supplies the inherited port-typed end to the diamond pass passes/w9c_rules_test.go TestW9CBinaryInterfaceEndDiamondWarns, TestW9CNonBinaryConnectorEndsStaySilent; InterfaceUsage_Invalid.sysml.xt:78 agrees word-for-word ✅ Faithful: exactly two-ended interfaces and connections receive their binary bases, while three-ended general interfaces remain general and ordinary two-ended connections do not acquire the Part/Port warning
The duplicate-inherited-name warning stands beside a usage-typing error on the same element (timeslice t : A; with A an attribute def draws both An occurrence, item or part must be typed by occurrence definitions. and Duplicate of inherited member name 'self' from DataValue, Occurrence): the ill-typed declaration keeps its declared type, so the diamond through the kind's implicit base is real, not a consequence to suppress passes/w9c_inherited_name_conflict.go is deliberately not gated on a typing failure of its subject; the tier gating of passes/registry.go stays at tier granularity passes/w9c_rules_test.go TestW9CActionPartDiamondWarns, passes/w11a_rules_test.go TestW11ASpecializationCycleKeepsImplicitBase, passes/w10b_reference_bases_test.go; the pinned pilot reports both diagnostics on the probe and on every kind mismatch tried, and 76 declared Xpect warnings rows in 12 files sit at the anchor of a declared typing error — see pilot-xpect.md "The diamond beside a failed typing is not a consequence to suppress" ✅ Faithful (adjudicated against the pilot; a per-element suppression was proposed and declined because it would silence those 76 rows)
Duplicate of inherited member name 'B' from OuterPackage where a feature declares a name its subsetted feature imports (feature inner1 subsets inner { feature B redefines A }) resolve/distinguishability.go Resolver.importedMembers: a supertype's non-private imports are memberships it has (KerML 8.4.3.2 with 8.3.3.1), so an imported name is inherited exactly as an owned one is, under the same two limits as the owned side — a private import contributes nothing, and library elements stay with passes/w9c_inherited_name_conflict.go resolve/inherited_names_test.go TestNameImportedByASupertypeIsInherited; ShadowingTests_ImportAndInnerClassesNamesAreTheSameBadCase3_Rdef.kerml.xt:28 agrees word-for-word ✅ Faithful

Case, include, direction, qualification and state endpoints

These rules were implemented and tested together. Their citations are the SysML v2 case, include, port, name-resolution and state semantics; the bundled library supplies the specialized metaclasses and multiplicities.

Semantic Rule Implementation Test Case Status
A case definition or usage owns at most one objective requirement; inherited objectives participate in the same cardinality rule, and an inherited conflict is reported on the owner when no local objective is at fault (SysML v2 §8.3.22, especially ObjectiveMembership; Systems Library/Cases.sysml Case) passes/at_most_one_member.go objectiveOwnerDecl, checkAtMostOneMember for ordinary case definitions and usages passes/w7g_at_most_one_member_test.go TestW7GCaseObjectiveCompetesWithInheritedObjective, pilot Xpect CaseSubjectObjective_Invalid.sysml.xt ✅ Faithful for case; analysis, verification and use-case families intentionally permit multiple objectives in the bundled normative examples
An include reference must identify a use-case usage or use-case definition, not an arbitrary feature (SysML v2 include relationship and IncludeUseCaseUsage) passes/w11a_usage_typing.go include referent-kind validation in the consolidated 11A producer passes/w10b_performed_action_test.go, passes/pilot_p6_gaps_test.go, pilot Xpect CaseUsage_Invalid.sysml.xt ✅ Faithful
An included use-case usage must be typed by exactly one use-case definition (SysML v2 case/include semantics; use-case typing multiplicity in the normative library) passes/w11a_usage_typing.go one-definition validation and usage-declaration diagnostic placement passes/w10b_performed_action_test.go, pilot Xpect CaseUsage_Invalid.sysml.xt:101 ✅ Faithful
A redefining port feature has a direction compatible with its redefined feature; in, out, inout, absent direction and conjugation follow the general KerML redefinition relation (KerML §8.3.3.1 specialization/redefinition and SysML v2 port direction semantics) semantics/redefinition_conformance.go direction relation; passes/w8b_redefinition_conformance.go RedefinitionDirectionPass at LevelType passes/w8b_redefinition_conformance_test.go, pilot Xpect PortUsage_Invalid.sysml.xt:65,67 ✅ Faithful
Inherited state-action start and done vertices are valid transition endpoints, including the bare accept … then … succession form (SysML v2 state-machine and transition semantics, §8.3.17) resolve/transition.go endpoint vertex predicate shared by explicit transitions and accept-then successions passes/state_transition_test.go, resolve/transition_test.go, pilot Xpect StateTest.sysml.xt:49 ✅ Faithful

Parser recovery and the rules it unblocked

The Xpect rows where our parser was the reason we disagreed. Each rule below was unreachable because a form the pilot's grammar admits had no production here. The derivations, the before/after measurements and the rows still open are in adjudications.md.

Semantic Rule Implementation Test Case Status
A type has at most one multiplicity, so every multiplicity member after the first is an error (Only one multiplicity is allowed; KerML 8.3.3.1.1 Type::multiplicity is single-valued) — the surplus member is well-formed syntax, not a parse error parser/defusage.go accepts repeated multiplicity members; passes/at_most_one_member.go collects the declaration-level and member-level multiplicities and reports the extras passes/w12d_rules_test.go, pilot Xpect Type_Multiplicity_invalid.kerml.xt:20 ✅ Faithful
An association end that declares its cross feature inline must declare the feature crosses names (Must be the cross feature; KerML 8.3.4.5) ast/defusage.go CrossFeatureMember (immutable, with its own identification, multiplicity and relationships); parser/defusage.go preserves it; passes/w10b_cross_features.go checkDeclaredCrossFeature compares it with the crosses target passes/w12d_rules_test.go, parser/w12d_parser_test.go, golden w12d_end_cross_feature, pilot Xpect AssociationTest_CrossFeatures_invalid.kerml.xt:52 ✅ Faithful
A parallel state owns no successions or transitions — its substates are concurrent regions (A parallel state cannot have successions or transitions.; SysML v2 §7.16 StateDefinition/StateUsage::isParallel) ast/defusage.go IsParallel on definition and usage; parser/defusage.go (a body is required, so state def S parallel ; stays a parse error); passes/state_transition.go checkParallel; lower/state_graph.go parallelRegions synthesizes graph-only regions from direct substates, preserves their behaviors and deferred triggers, and reuses the extension region IR passes/w12d_rules_test.go, golden w12d_parallel_state, parser negative case, pilot Xpect TransitionUsage_invalid.sysml.xt:45,68; lower/state_graph_nested_test.go:TestToStateGraph_ParallelMatchesExplicitRegions, TestToStateGraph_ParallelStateBehaviorsAndDeferredEvents, runtime/state_parallel_standard.sysml, state_parallel_completion.sysml, state_region_completion.sysml, state_parallel_broadcast.sysml, state_parallel_accept_after.sysml, state_parallel_entry_behavior.sysml, state_nested_parallel_entry_exit_behavior.sysml, state_nested_parallel_region_owner_behavior.sysml ✅ Faithful for state definitions and usages with bodies, including nested parallel composites, nested region-owner behavior and deferred triggers, and the parallel state's entry/do/exit behavior; standard and extension region graphs complete only after every concurrent region completes, nested region sets included; typed behavior bindings reuse the lowered definition.
A transition with an accepter has a state as its source, since only a state is active over the interval in which the accepter waits (A transition with an accepter must have a state as its source.; SysML v2 §7.16) passes/state_transition.go checkAccepterSource, reported at the trigger's span (ast.TransitionMember.TriggerSpan) passes/w12d_rules_test.go, passes/state_transition_test.go, pilot Xpect TransitionUsage_invalid.sysml.xt:54 ⚠️ Approximate (fires only where the source resolves to a performed action; a transition out of a state whose entry action is named stays legal, and other non-vertex sources are left to the pre-existing endpoint rule)
A member only one body kind offers is rejected elsewhere, at the parser as in the pilot's grammar: subject and actor belong to a requirement or case body (RequirementBody, CaseBody; validateSubjectMembershipOwningType, validateActorMembershipOwningType), stakeholder to a requirement body (validateStakeholderMembershipOwningType), objective to a case body (validateObjectiveMembershipOwningType), entry/do/exit to a state body (StateBody; validateStateSubactionMembershioOwningType, spelled as the pilot spells it), render to a view body (ViewBody; validateViewRenderingMembershipOwningType), a transition first s1 then s2; member to a state body (TransitionUsageMember is a StateBodyItem alone), and expose to a view usage body (ViewBodyItem; ViewDefinitionBodyItem has no Expose). Definitions and usages of the owning kind admit them alike, a reference body (include x { … }, perform a { … }, frame c { … }) takes its kind's body, and a nested usage of another kind does not inherit its owner's body kind parser/parser.go bodyContext (one notation per body kind, pushed by parser/defusage.go defBodyContext/usageBodyContext around every definition, usage and reference body); parser/defusage.go ownedMembers, Parser.bodyAdmitsMember, Parser.misplacedMember (diagnostic and ErrorNode in place of the member; the member's own syntax is still consumed so the body recovers at the next member) parser/owned_member_test.go TestMemberOutsideOwningBodyIsRejected (each member in a part definition and usage, an action, a constraint, a package, a perform body, a subject's, required constraint's and rendering's own body, in the wrong owning kind — stakeholder in a case, objective and render in a requirement — and with visibility and metadata prefixes), TestMemberInsideOwningBodyStaysClean (requirement, concern, viewpoint, case, analysis, verification, use-case, view and state definitions and usages, frame, include, perform, exhibit and satisfy bodies, entry; then s;, transitions, inline and braced state actions, parallel and nested states); golden parse/owned_body_members; tools/referee/reject/testdata/negative/grammar/g07, g08, g61–g66, g77 (all both-reject); parser/grammar_prefix_test.go TestNegativeBodyContext; model/body_context_test.go TestBodyContextFindingsSurviveAnUnrelatedSyntaxError (the finding stays reported beside an unrelated syntax error in the same file, since the syntax tier is not gated); TestStdlibConformance and the four OMG corpora unchanged ✅ Faithful (the pilot rejects the same models with mismatched input '<keyword>' expecting '}'; ours names the owning body and the fix. Two placements the grammar likewise forbids stay accepted as documented extensions, so they draw the syntax-tier nonstandard-notation warning — an error under strict mode — instead of a parse error: expose in a view def body and require/assume outside a requirement-style body (passes/nonstandard_notation.go; tools/referee/reject/testdata/negative/extensions/x08, x09, both agreeing with the pilot under strict mode alone). variant outside a variation is well-formed syntax (VariantUsageMember is a DefinitionBodyItem) that the pilot rejects semantically, so it stays at the constraint tier as an element-scoped rule that a syntax error elsewhere no longer hides: passes/variant_owner.go after validateVariationMembershipOwningNamespace)
A metadata prefix may be written on any member declaration, including subject, actor and stakeholder members (KerML 8.2.4 PrefixMetadataMember; SysML v2 §7.20) ast/behavior.go Prefixes on the three member nodes; parser/behavior.go consumes prefix metadata before the name parser/w12d_parser_test.go, golden w12d_requirement_prefix_metadata, pilot Xpect SemanticMetadata_valid.sysml.xt:53 (a false positive on a valid file, now silent) ✅ Faithful
A #M prefix on a subject, assume or require member annotates the usage the member owns (SysML.xtext SubjectUsage is 'subject' UsageExtensionKeyword* Usage, RequirementConstraintUsage is UsageExtensionKeyword* ConstraintUsageKeyword ConstraintUsageDeclaration, and UsageExtensionKeyword is a PrefixMetadataMember), so it means what a prefix on an ordinary usage means: a SemanticMetadata keyword makes the member specialize its baseType (Metaobjects.kerml), and a metadata definition that redefines annotatedElement is checked against the member's metaclass ast/metadata.go DeclaredMetadata (the prefixes and body of every annotatable declaration, the three members included), read by semantics/metadata.go MetadataAnnotationsOf and semanticMetadataBases, semantics/annotations.go declaredAnnotations, passes/w8c_metadata_type.go w8cPrefixMetadata; semantics/model.go RelationshipsOf and semantics/multiplicity.go MultiplicityOf read the members' own typing, specializations and multiplicity semantics/requirement_member_metadata_test.go (MetadataAnnotationsOf on all three, a semantic keyword's baseType among their supertypes, plain metadata adding none), passes/w8c_metadata_annotation_test.go TestMetadataAnnotatedElementOnRequirementMembers (a SysML::Usage-only definition accepted on all three and on a part, a SysML::Definition-only one reported on all four), passes/w8c_metadata_type_test.go TestW8CMetadataAbstractTypeOnRequirementMembers ✅ Faithful
A semantic metadata definition that binds no baseType of its own inherits the binding of the metadata definition it specializes, so metadata def <k> K :> M; classifies its annotated elements as M does, while an own :>> baseType = … in K takes precedence (KerML Metaobjects::SemanticMetadata: baseType is an ordinary feature, redefined or inherited like any other; SysML v2 §7.27.4) semantics/metadata.go Model.baseTypeOf (own binding first, then direct supertypes, cycle-guarded) semantics/requirement_member_metadata_test.go TestSemanticMetadataInheritsBaseType, passes/mosa_test.go (#keyInterface as a modular system interface) ✅ Faithful
Prefix metadata on these members follows the member keyword — subject #M s : T;, actor #M a : A;, stakeholder #M k;, objective #M o : R;, variant #M part v;, assume #M constraint a : C;, require #M constraint r : C;, require #M <r> rc : C; (SysML.xtext SubjectUsage, ActorUsage, StakeholderUsage, ObjectiveRequirementUsage, VariantUsageElement, RequirementConstraintUsage, each 'keyword' UsageExtensionKeyword* …) — never precedes it; ahead of an ordinary usage or definition (#M part p;, abstract #M part p;) and of assert (#B assert not constraint c;, OccurrenceUsagePrefix) it stays where it is parser/defusage.go prefixMetadataFollowsKeyword (the one keyword set the pre-keyword check and the post-keyword parse share), reportMisplacedPrefixMetadata (a syntax error spanning the # run, naming the accepted spelling, with the quick fix that moves the run after the keyword; the member is then read as the accepted spelling reads it), parser/behavior.go parseKeywordedRequirementMember parser/member_prefix_metadata_placement_test.go (every rejected spelling: one diagnostic at the #, the fix yields the accepted spelling, the recovered AST equals the accepted spelling's; every accepted placement clean), golden parse/member_prefix_metadata_placement, parse/assert_prefix_metadata unchanged, negatives subject_prefix_metadata_before_keyword … require_prefix_metadata_before_keyword_bare, export/member_prefix_metadata_placement_test.go (the accepted forms round-trip with and without sysx:sourceText; the rejected ones do not convert), model/member_prefix_metadata_placement_test.go (the editor sees one syntax error with its fix and still indexes the member), semantics/requirement_member_metadata_test.go TestMisplacedMetadataOnRequirementMembersStillAnnotatesOnRecovery ✅ Faithful (matched: build/pilot-sysml-validator/validate-sysml-batch rejects each pre-keyword spelling with no viable alternative at input '#' — variant at the keyword — and accepts each post-keyword one; main accepted the pre-keyword spellings silently and its graph-only RDF round trip rewrote them to the accepted placement)
The constraint usage an assume or require member owns is a feature of the requirement (RequirementConstraintMember owns a ConstraintUsage): named by its name, typed by its : C, bounded by its multiplicity, valued, redefining what its :>> names, and looked up from the requirement like any usage; an anonymous one owns only its body symbols/builder.go buildRequirementConstraint (SymbolConstraintUsage over the AssumeMember/RequireMember declaration; an anonymous member keeps its body-local scope and no symbol); ast/behavior.go RequirementConstraint, RequirementConstraintOf; semantics/implicit.go kindBaseFQN (a constraint usage's implicit base); resolve/redefined_nesting.go (names nested under what the member redefines, KerML 8.3.4.4); runtime/context.go RequireConstraint, runtime/invoke_calc.go declMembers (the member is checkable and invocable as a constraint) symbols/builder_test.go TestRequirementConstraintMembersAreSymbols, TestRequirementMemberMetadataBodiesGetScopes, resolve/requirement_member_test.go, semantics/requirement_member_metadata_test.go TestRequirementConstraintUsageBases, lsp/require_reference_test.go TestDefinitionOfRequirementConstraintMember, runtime/condition_test.go (an anonymous body's conditions stay the requirement's) ✅ Faithful
A feature with a feature value and no declared type is typed by its value, so a chain through it reads the value's members (KerML 7.4.9 FeatureValue); a#(1) names one element of the sequence a, of a's own type resolve/document.go valueType (cycle-guarded by valuesInProgress, probing under aside so nested lookups are not attributed to the outer member); resolve/target.go resolves #(…) through its operand resolve tests, pilot Xpect ParsingTests_Indexing.kerml.xt:32 ⚠️ Approximate (bracket indexing a[i] is deliberately not routed this way: […] also carries quantity and selection meanings that are not “one element of”)
Every segment of a feature chain is a feature, and a subsets target is a feature (KerML 8.3.4.5, 8.3.4.7) passes/typecheck.go checkChainSegments, and the narrow KerML feature … subsets target check passes/typecheck tests, pilot Xpect ParsingTests_ScopeWithFourDotAndDot.kerml.xt:22 (was silence) ⚠️ Approximate (our name resolution is not filtered by metaclass, so where the pilot fails to resolve such a name we resolve it and reject its kind at the same offset — recorded as an adjudicated divergence, not admitted as wording-only)

Four Constraint Rules of the Reference, Adjudicated Against It

Four constraint-tier rules from the pinned reference implementation (pilot 2026-08) are covered here. They were adjudicated diagnostic by diagnostic in pilot-differential.md against minimal reproducers run through that reference. The subsetting-featuring and flow-end rules are implemented faithfully; element-filter accessibility is now covered for candidate-relative filter references, while evaluability and invocation-type rules remain approximate, with the divergences named in their rows. The rule text is its validator (org.omg.kerml.xtext/.../KerMLValidator.xtend) plus the constraint text on the generated metamodel.

Semantic Rule Implementation Test Case Status
validateSubsettingFeaturingTypes — subsettingFeature.canAccess(subsettedFeature) (KerML Subsetting): a feature of a type is not reachable by :: from outside it, since the subsetting feature's featuring types must feature the subsetted one (transitively, through featuring types that are themselves features) passes/subsetting_featuring.go checkSubsettingFeaturingTypes, walking the featuring context of the subsetting feature (canAccess) and skipping a subsetted feature with no featuring type, exactly as the validator's subsettedFeaturingTypes.isEmpty() guard does passes/w7g_subsetting_featuring_test.go (an inaccessible nested feature from a sibling type and from a package; a feature reachable through the featuring context; a package-level feature; :> between classifiers, which is a Subclassification the rule does not constrain); matched reference run on part a :> seatBelt where seatBelt is featured by vehicle — both report 8:12 Must be an accessible feature (use dot notation for nesting). The same run is why model/element_filter_test.go's visibility probes now read alias a for seatBelt rather than part a :> seatBelt: the reference rejects the subsetting form those probes used, so keeping it would lock in a false negative instead of testing a filter ✅ Faithful (the earlier false positives came from applying it to Subclassification and to subsetted features with no featuring type)
validateFlowEndSubsetting — a FlowEnd must subset a feature that is not merely redefined: each end names the feature the payload leaves from or arrives at, so it can redefine Transfer::source::sourceOutput / Transfer::target::targetInput (SysML v2 FlowEnd) passes/constraint.go checkFlowEndSubsetting, using semantics.Model.FlowEndAttachments passes/pilot_p6_gaps_test.go TestConstraintFlowEndSubsettingNotImplemented, TestConstraintDottedFlowEndsAreAccepted; parser golden tests/parser/testdata/parse/f21_flow_ends.sysml ✅ Faithful (the unidentifiable-end form of the same constraint — an end rooted in a definition rather than a feature — is passes/w8d_flow_end.go, which also reports the validateConnectorRelatedFeatures diagnostic the reference pairs with it)
validateElementFilterMembershipIsModelLevelEvaluable — condition.isModelLevelEvaluable: an invocation is evaluable when its function is a model-level-evaluable library function and every argument is; a feature reference when its referent is a self-reference, or owned by a Metaclass/MetadataFeature, or has no featuring type and an evaluable value expression semantics/filter.go compileCondition and compileReference; null compiles to the empty sequence and new T(…) to a constructed instance, both evaluable, so a condition built from either is reported for not being boolean rather than for not being evaluable passes/pilot_p6_gaps_test.go TestFilterModelLevelEvaluableFalsePositive, TestFilterModelLevelEvaluableFalseNegative; passes/f21_f23_validation_test.go TestF22ConstantFilterOperatorsRemainEvaluable ✅ Faithful (a chain rooted in a feature with no featuring type is evaluated through any number of hops — compileChainRead flattens E::root.inner.k and (E::root.inner).k alike — and through the read feature's value expression, which is evaluated where that feature is written so a sibling reference in it resolves (m = n + 1); a feature on the chain before the last that has its own value, a chain whose read feature has no value of its own, a chain rooted in or reading a feature of a metaclass (read reflectively from the candidate element), and a terminal value that is not a number or boolean, are reported as limitations rather than followed. A chain rooted in a featured feature still reports filter-not-evaluable naming the featuring type. Refereed: the pinned validate-sysml is silent on a probe declaring part p : P; filter E2::p.n > 0; and so are we. Evaluability is no longer conflated with falsity — a condition the reflective reader cannot answer is ErrFilterUnevaluable (row above). Tests: passes/f21_f23_validation_test.go TestF22ChainFromUnfeaturedRootIsEvaluable, TestF22FeatureChainMessageNamesLimitation, TestF22ConstantFilterOperatorsRemainEvaluable, TestFilterChainThroughNestedFeaturesIsEvaluable, TestFilterChainDerivedValueIsEvaluable, TestFilterChainThroughValuedHopReportsLimitation, TestFilterChainNonNumericValueReportsLimitation, TestFilterChainIntoMetaclassFeatureReportsLimitation, TestFilterChainUnvaluedTerminalReportsLimitation; semantics/filter_test.go TestFilterChainThroughNestedFeatures; passes/pilot_p6_gaps_test.go TestFilterModelLevelEvaluableFalsePositive, TestFilterModelLevelEvaluableFalseNegative)
validateInvocationExpressionInstantiatedType — instantiatedType must be a Behavior, or a Feature typed by exactly one Behavior passes/typecheck_expr.go inferInvocation and invocation-target classification, wording the diagnostic as the reference words it passes/pilot_p6_gaps_test.go TestTypeCheckInvocationInstantiatedTypeNotImplemented; passes/f21_f23_validation_test.go TestF23BehavioralTargetsRemainInvocable; passes/w7g_invocation_target_test.go (a non-behavior definition and a feature typed by one are reported at the same location and with the same text as the reference; an unresolved target is not; a name two imports both supply, and a calc def/action def target) ⚠️ Approximate (matched runs: on an ambiguous N supplied by a part definition and an action definition both implementations resolve the same one and report the rule at 6:16, and both are silent for F(1) on a calc def. On attribute b = P(1); attribute e = d(1); — both implementations report Must invoke a behavior or a behavioral feature at 3:16 and 6:16. The reference is not silent for an unresolved target: on attribute a = Missing(1) it reports the unresolved reference and this rule at 2:16, where our tiering reports only the unresolved reference, since the type tier is skipped for a name that did not resolve — a deliberate divergence, not a missing rule)

Send Action Arguments (SysML v2 §8.3.16 SendActionUsage, over Actions::SendAction / Performances::SendPerformance)

A send's payload, via and to arguments are expressions bound to the payload, sender and receiver parameters of the SendPerformance it performs, so the type tier now infers them like any other expression and checks the one constraint the specification places on the usage itself. passes/send_action.go SendActionPass (LevelType, ElementScoped, registered in passes/analyze.go so the CLI and the LSP report it alike) reaches every shape a send is written in: an action node, a bare statement in an action or state body, a state entry/do/exit subaction, a transition effect, an exhibited state, a nested action or state, a loop or branch body, and a payload bound in the send's body (send to r { in :>> payload = new Sig(); }).

Semantic Rule Implementation Test Case Status
validateSendActionUsagePayloadArgument — owningFeatureMembership.oclIsKindOf(StateSubactionMembership) or oclIsKindOf(TransitionFeatureMembership) implies payloadArgument <> null: a send that is a state subaction or a transition effect must name its payload passes/send_action.go checkPayload (send-payload-missing, error), over lower.SendPayload so a payload the body binds counts; a payload-less send outside those memberships is allowed, as the constraint's antecedent leaves it passes/send_action_test.go TestSendSubactionWithoutPayloadIsReported (entry, do, exit, transition effect, action-node form, nested and exhibited states), TestSendWellFormedShapesAreSilent (bound payload, payload-less send in an action def); refereed tools/referee/reject/testdata/negative/semantic/send-subaction-no-payload.sysml ✅ Faithful (the reference's grammar rejects the shape before its validator runs — SendNodeDeclaration requires the payload — so both reject; the reference's message, A send action must have a payload., is what checkSendActionUsage would report through the API)
The payload is an expression: validateInvocationExpressionInstantiatedType applies to send Def(args) passes/send_action.go check infers the payload through exprChecker.infer, so passes/typecheck_expr.go inferInvocation reports invocation-not-behavior on an item or attribute definition invoked as the payload; send new Def(args) constructs the payload instead and is silent passes/send_action_test.go TestSendPayloadInvokingNonBehaviorIsReported (attribute def, item def, bare send, state entry, transition effect, body-bound payload, nested action), TestSendWellFormedShapesAreSilent (behavior via port, constructors with positional and named arguments); refereed tools/referee/reject/testdata/negative/semantic/send-payload-non-behavior.sysml — both report 7:23 Must invoke a behavior or a behavioral feature ✅ Faithful (the probe action s send Sig() to target; with attribute def Sig reports the reference's diagnostic at the reference's span)
A constructor's arguments bind features of the instantiated type: validateConstructorExpressionResultFeatureRedefinition (each result feature redefines exactly one feature of the type) and validateConstructorExpressionNoDuplicateFeatureRedefinition (no feature bound twice), with the binding's type conformance (KerML ConstructorExpression; pilot checkConstructionExpression) passes/typecheck_expr.go inferConstructor, over semantics.Model.MembersOf of the constructed type (own features first, then inherited, excluding the library's) — a positional argument beyond those features, a label already bound, a label naming a member no constructor binds (a descriptor the library declares for every object of the kind, unless the type restates it), a qualified label resolving outside the type and an argument whose scalar type — the one it declares or inherits through a redefinition or subsetting (semantics.Model.PrimTypeOf) — cannot bind its feature, an object-valued argument (a feature, a constructor, an invocation's result, a literal) whose type conforms in neither direction to the feature's declared or inherited type (semantics.Model.DeclaredFeatureTypes, Conforms), and a value whose statically known count violates the feature's effective multiplicity are errors at the argument; the runtime refuses the same labels and an excess positional argument at the send, before any accept takes the message; the constructed name must be a type (validateInstantiationExpressionInstantiatedType): a package or other non-type is instantiation-not-type at the name, and the runtime rejects an unresolved or non-type new target at the send rather than posting a name-only message; a simple label is resolved as a member of the constructed type (resolve.Reference.Constructed), so an unknown one is the resolver's report and a rename of the feature rewrites the label passes/typecheck_constructor_test.go (silent: positional, inherited, labelled, calc-valued and qualified-label constructors, a constructed library type; reported: too many positional arguments, duplicate and qualified duplicate labels, an inherited library descriptor as a label, positional/labelled/inherited/redefined/subsetted/transition-effect type mismatches, object-valued arguments of a non-conforming type (positional, labelled, constructed, literal, redefined, per element of a collection), counts below and above the feature's multiplicity, a qualified label naming another type's feature, an unknown label reported once, a package as the constructed type; silent: a usage as the constructed type); runtime/signal_test.go TestSendNewRejectsNonTypeTargetsAtSend, TestSendNewConstructsAUsage; tools/referee/reject/testdata/negative/semantic/send-constructor-non-type.sysml; resolve/references_test.go TestSendReceiverAndConstructorLabelsAreReferences, TestConstructorLabelMustNameAFeatureOfTheConstructedType; edit/w6g4_rename_references_test.go TestRenameRewritesConstructorLabels ✅ Faithful (matched run on new Sig(zz = 1), new Sig(x = 1, x = 2), new Sig(1, 2), new Sig(x = "s") and new Sub(1) for item def Sub :> Base: the reference reports Must correspond to one feature of the instantiated type, Feature already bound and Bound features should have conforming types at the spans we report, and Must have an invoked/instantiated type on new Signals() for a package Signals, and binds a positional argument to the type's own features before the inherited ones as we do; the conformance finding is a warning there and an error here, as every binding type mismatch is)
validateSendActionUsageReceiver — a to argument whose referent, directly or through the last step of a feature chain, is a PortUsage should use via (pilot checkSendActionUsage; the specification places no such constraint, so the diagnostic is a warning as the reference's is) passes/send_action.go checkReceiver (send-to-port, warning), resolving the argument through Resolver.ResolveTarget/ResolveAliasTarget so a chain reports the port it ends in passes/send_action_test.go TestSendToPortWarns (own, chained and deeply chained port, via … to a port, transition effect); TestSendWellFormedShapesAreSilent (via an own, inherited or chained port) ✅ Faithful (the reference's validateSendActionUsageReceiver_ is marked non-normative by its trailing underscore; the wording spells out why via is the fix)
sender: Occurrence[1] and receiver: Occurrence[0..1] (Performances::SendPerformance): a via or to argument binds an Occurrence parameter, so a feature whose types are disjoint from Occurrence cannot be bound to it (KerML binding-connector type conformance) passes/send_action.go checkSender/checkReceiver (send-sender-not-occurrence, send-receiver-not-occurrence, warnings), over semantics.Model.Conforms and the binding checker's end typing (w9cBindingChecker.endTypes, w9cTypesConform); an untyped feature, a definition (the feature-reference rule's) and an unresolved name are left alone passes/send_action_test.go TestSendArgumentNotAnOccurrenceWarns (attribute, chained attribute, inherited attribute and user attribute-def receivers; attribute sender), TestSendWellFormedShapesAreSilent (part, item, action, untyped and ref receivers, inherited and redefined parts, chained occurrence), TestSendGatesOnUnresolvedArguments ✅ Faithful (the reference reports the same spans through its generic Bound features should have conforming types binding warning; a missing receiver is allowed, as receiver [0..1] and the specification's "determined by outgoing Connections" both say)

The element-filter entries above now include chain-shaped coverage in semantics/filter_pilot_test.go and passes/filter_test.go: metaclass-owned Boolean and non-Boolean chains, comparisons, user-struct-featured chains, library chains, and package-level feature chains. Evaluator-only limitations are warnings; specification faults remain errors, and a type-tier error can still gate the constraint-tier accessibility diagnostic.

Control-Node Successions (SysML v2 §7.17.3 Control Nodes, §8.3.17 ControlNode, DecisionNode, ForkNode, JoinNode, MergeNode)

The nine validation constraints on the successions of a control node, refereed against the specification text: the pinned pilot (2026-08) implements only validateControlNodeOwningType, and is silent on the other eight (adjudicated as pilot gaps in pilot-differential.md, drafted for upstream in omg-issues.md). A succession is any Succession the action declares or inherits — a succession usage, first a then b;, a member-attached then b;, and a guarded or default branch out of a decision (if g then b; / else b; — a TransitionUsage whose owned Succession has the decision as its sourceFeature, §8.3.17 checkTransitionUsageSuccessionSourceSpecialization); a connect, bind, or flow is not one. The count and end-multiplicity rules hold in the abstract syntax even where the notation omits the multiplicities (§7.17.3), so an end that writes none is taken to carry the required one and only a written multiplicity is judged. ControlNodeSuccessionPass (passes/control_node.go) reports a violation once, at the control node when the checked action declares it, else at the succession that action adds; a violation inherited whole is reported at the definition it comes from. A succession flow is a Succession as well as a Flow (SuccessionFlowUsage), so it counts too: the parser keeps the prefix (ast.Usage.IsSuccessionFlow), and each end relates the feature its dot notation names ahead of the payload (from a.out to f.in runs from a to f); an end written without one relates nothing here and is the flow-end rule's to report; a flow end writes no multiplicity (SysML.xtext FlowEnd), so only the count rules reach it. The runtime carries the kind on from the lowered ObjectFlow.Kind: a succession flow moves its value as the source completes, a plain flow streams each write (see the Action map's object-flow rows).

Semantic Rule Implementation Test Case Status
validateControlNodeOwningType — the owningType of a ControlNode must be an ActionDefinition or ActionUsage; a constraint body is a calculation body (SysML.xtext CalculationBody) and so admits an action node the rule then rejects passes/control_node.go controlNodeChecker.checkOwner (control-node-owner), semantics/action_succession.go ActionDeclaration; parser/behavior.go parseConstraintBody routes action nodes through parseActionMember passes/control_node_test.go:TestControlNodeOwningType (a fork in an occurrence definition, a decision in a succession body, a join in a constraint definition, a merge in a constraint usage), :TestControlNodeInEveryActionBodyIsSilent (nested actions, loop and branch bodies, a control node's own body, entry actions, calculations, cases, perform action); tests/parser/testdata/parse/constraint_control_node.sysml; tools/referee/reject/testdata/negative/semantic/cn05 (both reject) ✅ Faithful (matched reference run: both report the fork at 4:9 and the decision at 8:13)
validateControlNodeIncomingSuccessions — every Succession into a ControlNode has target multiplicity 1..1 passes/control_node.go controlNodeChecker.check → checkEndMultiplicity (control-node-incoming-multiplicity), semantics/multiplicity.go Range.HasBounds passes/control_node_test.go:TestControlNodeEndMultiplicities, :TestControlNodeSpecificationExamplesAreSilent (the §8.4.13.4 examples with every multiplicity written), :TestControlNodeUnboundedSideIsSilent; semantic/cn06 ✅ Faithful
validateControlNodeOutgoingSuccessions — every Succession out of a ControlNode has source multiplicity 1..1 passes/control_node.go checkEndMultiplicity (control-node-outgoing-multiplicity) passes/control_node_test.go:TestControlNodeEndMultiplicities; semantic/cn07 ✅ Faithful
validateForkNodeIncomingSuccessions — a ForkNode has at most one incoming Succession passes/control_node.go controlNodeChecker.checkCount (fork-incoming-successions), semantics/action_succession.go Model.ActionSuccessions (declared and inherited, redefinitions masking what they replace) passes/control_node_test.go:TestForkWithTwoIncomingSuccessions (shorthand then, first … then, succession s first … then, and a mix), :TestControlNodeInheritedSuccessionsCount, :TestControlNodeRedefinedSuccessionReplacesInherited, :TestControlNodeInheritedViolationReportedOnce, :TestControlNodeOtherConnectorsDoNotCount, :TestControlNodeSuccessionFlowsCount (a succession flow counts, a flow does not), :TestControlNodeSuccessionFlowEnds; tests/parser/testdata/parse/succession_flow.sysml; semantic/cn01 ✅ Faithful
validateJoinNodeOutgoingSuccessions — a JoinNode has at most one outgoing Succession passes/control_node.go checkCount (join-outgoing-successions) passes/control_node_test.go:TestJoinWithTwoOutgoingSuccessions, :TestControlNodeStaticRuleAgreesWithRuntime (the static rule reports the graph runtime/action_executor.go refuses at initialize() as join node … has multiple successors; the runtime check is kept), :TestControlNodeSuccessionFlowsCount; semantic/cn02 ✅ Faithful
validateMergeNodeIncomingSuccessions — every Succession into a MergeNode has source multiplicity 0..1 passes/control_node.go checkEndMultiplicity (merge-incoming-multiplicity) passes/control_node_test.go:TestMergeIncomingSourceMultiplicity; semantic/cn08 ✅ Faithful
validateMergeNodeOutgoingSuccessions — a MergeNode has at most one outgoing Succession passes/control_node.go checkCount (merge-outgoing-successions) passes/control_node_test.go:TestMergeWithTwoOutgoingSuccessions, :TestControlNodeSuccessionFlowsCount; semantic/cn03 ✅ Faithful
validateDecisionNodeIncomingSuccessions — a DecisionNode has at most one incoming Succession passes/control_node.go checkCount (decision-incoming-successions) passes/control_node_test.go:TestDecisionWithTwoIncomingSuccessions, :TestControlNodeSuccessionFlowsCount; semantic/cn04 ✅ Faithful
validateDecisionNodeOutgoingSuccessions — every Succession out of a DecisionNode has target multiplicity 0..1 passes/control_node.go checkEndMultiplicity (decision-outgoing-multiplicity) passes/control_node_test.go:TestDecisionOutgoingTargetMultiplicity; semantic/cn09 ✅ Faithful

SysML Notation the Reference Accepts and We Reject — the ten classes

The ten classes the 373 only-ours diagnostics on the two OMG SysML corpora fall into, adjudicated construct by construct against the pinned reference (pilot 2026-08) in pilot-differential.md (§"SysML corpora — only ours", follow-ups one per class). Nothing here is fixed yet, so "Implementation" names the site that rejects the notation, and "Test Case" names the corpus files that are currently the only coverage — each class needs its own golden and negative fixtures before its fix lands (§5.1 of AGENTS.md). The grammar production each row cites is in build/pilot-grammars/SysML.xtext.

Semantic Rule Implementation Test Case Status
ExtendedUsage (:730) — one or more prefix-metadata annotations may stand where a usage's kind keyword would (#M connect a to b;, end #original r1 : Req1;), and the member is a plain Usage, not an attribute usage parser/defusage.go parseDefUsage/parseBodyMember (prefix metadata accepted before usage-only keywords and after feature modifiers) parser/f60_prefix_metadata_test.go, tests/parser/testdata/parse/f60_prefix_metadata.sysml, passes/f61_reference_usage_typing_test.go, passes/f60_satisfy_reference_test.go, symbols/f69_function_kind_test.go ✅ Faithful (the notation parses, a keyword-less or metadata-prefixed member is typed as the ReferenceUsage it is rather than as an attribute usage (passes/typecheck.go isReferenceUsage), and a satisfy usage is classified (symbols/builder.go → SymbolSatisfyRequirementUsage), so end r1 ::> req1 resolves)
DefaultReferenceUsage (:632), EnumeratedValue (:786), ResultExpressionMember (:1967), Comment (:86) — a member needs no kind keyword: a declaration that is only a value, only a specialization or only a redefinition, identified by a name, a short name (<a> alpha = 1;, <b> :> alpha = 2;) or both, where a keyword of the other language is an ordinary name (<chains> links = 3; in SysML, <s> part = 1; in KerML); an anonymous enumerated value; a trailing expression; an anonymous comment carrying a locale parser/defusage.go atKeywordlessFeature/keywordlessFeatureAt/parseEnumBody/parseCaseBody, parser/namespace.go parseAnonymousLocaleComment parser/f61_keywordless_members_test.go, tests/parser/testdata/parse/f61_keywordless_members.sysml, parse/keywordless_short_name.golden, parse/kerml_keywordless_short_name.golden, parser/negative_test.go (calc_short_name_unclosed, calc_short_name_empty, part_short_name_unclosed) ✅ Faithful (enum followed by = or := is the anonymous value of EnumeratedValue, not a member named enum; the pinned validators accept keywordless_short_name.sysml and kerml_keywordless_short_name.kerml)
TransitionUsage, TargetTransitionUsage, SendNode, AcceptNode all end in ActionBody; ExhibitStateUsage takes OwnedReferenceSubsetting plus StateUsageBody — a transition target may be qualified and any of these may carry a body parser/behavior.go parseNodeBody (the shared optional body every node production ends in), parseChainedName (a dotted transition end), parseSendStatement; lower/state_graph.go transitionEffects, lower/action_graph.go lowerNodeBody, runtime/action_statements.go runNodeBody tests/parser/testdata/parse/f62_state_action_bodies.sysml, parser/f62_f63_node_body_test.go, runtime/testdata/conformance/f62_send_body_payload, f62_transition_body_dotted_target (+ trace), runtime/f62_f63_node_body_test.go ⚠️ Approximate (transition/send/accept bodies and dotted ends parse, lower and execute; exhibit vehicleStates.on { … } and a bare ref patient { … } now parse too (parse/F62-exhibit-chain.sysml, parse/F63-reference-body.sysml, parser/f50_f70_f81_f82_f83_test.go); send x via p to r parses, lowers with its receiver carried beside its port, and routes at runtime — see the send-through-a-port row. The last notation residue: a succession stating a guard between its ends is a GuardedSuccession (SysML.xtext:1719), which returns a TransitionUsage, so succession S first a if x == 0 then b; now builds an ast.TransitionMember rather than a two-ended succession connector (parser/defusage.go atGuardedSuccession, golden parse/w8g_guarded_succession.golden, parser/w8g_guarded_succession_test.go, negatives guarded_succession_no_guard, guarded_succession_no_target; the pinned validator accepts the same fixture). What remains is semantic, not notational: such a transition's ends are action nodes, which resolve/transition.go isVertex accepts only for states and pseudostates, so Simple Tests/DecisionTest.sysml trades its 2 syntax errors for 2 kind-mismatch ones — pinned by passes/w8g_notation_residue_test.go:TestW8GGuardedSuccessionEndpointsAreActions, owned by internal/semantic/resolve)
ControlNode alternatives are ControlNodePrefix isComposite ?= '<kw>' UsageDeclaration? ActionBody; ForVariableDeclaration (:1637) is a full UsageDeclaration; a body parameter needs only a FeatureSpecializationPart parser/behavior.go parseNodeDeclaration + parseNodeBody (every control node takes a declaration and an optional body), the for-variable path (relationships before in), parseNodeArgument (a body after a send target); symbols/builder.go buildControlNode, lower/action_graph.go lowerNodeBody, runtime/action_executor.go runNodeBody tests/parser/testdata/parse/f63_action_node_bodies.sysml, parser/f62_f63_node_body_test.go, runtime/testdata/conformance/f63_control_node_body (+ trace), f63_for_typed_variable, runtime/f62_f63_node_body_test.go ✅ Faithful (control node bodies, a quoted decide name, a typed for variable and a redefining body parameter parse, lower and execute; a bare ref patient { … } parses as a reference usage with its body — parse/F63-reference-body.sysml)
ReturnParameterMember (:1961) is 'return' UsageElement — a named, specializing, keyword-carrying usage, not an expression; a declaration is a legal member of an expression body; assert takes an OperatorExpression, so assert not c { … } negates rather than naming parser/behavior.go parseResultMemberIn/atReturnedUsage (a return whose declaration specializes is a usage), parser/expr.go parseBodyExpr/atBodyExprMember (declarations kept in ast.BodyExpr.Members); runtime/collections.go applyBody returns ErrUnsupportedBodyDeclaration parser/f64_return_usage_test.go, parser/f64_expr_body_declaration_test.go, tests/parser/testdata/parse/f64_return_usage.sysml, f64_expr_body_declaration.sysml, runtime/testdata/conformance/f64_calc_return_usage (+ _without_value), runtime/f64_body_declaration_test.go ⚠️ Approximate (returned usages parse, lower and execute, and a returned usage binding no value fails with the typed no-result error. The body-declaration half is closed: a declaration in an expression body is a member of the body's own scope, resolves there, is visible to a nested body and to nothing outside it, is collected as a reference and is type-checked in the body-local scope (resolve/w7b_body_scope_test.go, passes/w7b_body_declaration_test.go — both fail on the parent commit), and applying such a body evaluates its result. Self-assessed: the pinned pilot's execution surface returns an AST node rather than a value for such an expression, so the evaluation is not externally refereed — as is the negation added with it: assert not c { … } now parses as the negated reference form (OccurrenceUsagePrefix 'assert'? isNegated ?= 'not'), golden parse/w7c_f66_generalized_usage_declarations.sysml, negatives assert_not_no_condition, assert_not_no_body_end. Also: the unasserted negation is notation too — not satisfy r1 by p; is a SatisfyRequirementUsage whose isNegated is set with no assert before it (SysML.xtext:2093) — so a leading not satisfy parses as the negated satisfaction it is (parser/defusage.go atNegatedSatisfy, golden parse/negated_satisfy.golden, negatives not_without_satisfy, not_satisfy_no_subject), retiring the last diagnostic on Simple Tests/RequirementTest.sysml. not verify is not accepted: the pinned validator rejects it, and the grammar puts isNegated on satisfaction alone)
BindingConnectorAsUsage allows a UsageDeclaration before bind; a message's Payload after of is OwnedFeatureTyping ( OwnedMultiplicity )?; EventOccurrenceUsage ends in ValuePart? UsageBody parser/defusage.go parseUsage (a specializing declaration before bind, and a binding whose ends are body members), parseFlowEnds/atPayloadDeclaration (ast.FlowEnds.PayloadMultiplicity), parseDefUsage (an event occurrence reference goes through parseReferenceMemberUsage, so it takes a ValuePart) parser/f65_binding_message_event_test.go, tests/parser/testdata/parse/F65-binding-message-event.sysml ✅ Faithful (all three forms parse; 26 syntax diagnostics retired across Simple Tests/ConnectionTest.sysml, 17a/17b-Sequence-Modeling.sysml, AHFSequences.sysml, ServerSequenceModelOutside.sysml and Simple Tests/Connectors.kerml. Two diagnostics they had masked now surface in layers owned elsewhere: binding cannot be typed by connectionDef (kind mismatch) on ConnectionTest.sysml:24 (passes/typecheck.go) and 15 unresolved references to interface connect ends in AHFSequences.sysml:79-96 (resolve/))
RequirementConstraintMember (:2057) takes a full constraint usage whose body is optional; RequirementVerificationUsage may only redefine; UseCaseUsage is reachable from VariantUsageElement; FeatureSpecializationPart puts multiplicity after a specialization parser/behavior.go parseOwnedConstraintDecl (the declaration assume/require owns, named or anonymous, with or without a body); the verify, variant use case and ref redefines x[4] paths are parser/defusage.go parseUsage parser/f66_assume_constraint_test.go, tests/parser/testdata/parse/f66_assume_constraint_declaration.sysml ⚠️ Approximate (assume constraint c1 : C; and assume constraint c { … } parse and resolve, require likewise; all three now parse — verify r :>> massRequirement; (a reference form taking FeatureSpecialization*), variant use case uc11; (the two-word usage keyword reached from VariantUsageElement) and ref redefines cylinderBR[4]; (multiplicity after a specialization, FeatureSpecializationPart), golden parse/w7c_f66_generalized_usage_declarations.sysml, negatives verify_redefines_no_target, variant_use_case_no_type, ref_redefines_no_target, ref_redefines_unclosed_multiplicity. Step 2: verify vehicleMassRequirement :>> massRequirement; resolves through the enclosing objective's same-role implicit redefinition (semantics/roles.go, passes/w8g_notation_residue_test.go:TestW8GVerifyRedefinesInheritedObjective). Also: the member's prefix metadata was the other half — require #goal constraint { … } puts an ExtendedUsage's annotations after the member keyword, which the pinned validator accepts — so require/assume now carry their prefixes (ast/behavior.go RequireMember.Prefixes/AssumeMember.Prefixes, parser/behavior.go, golden parse/require_metadata_prefix.golden, negatives require_prefix_metadata_no_type, require_prefix_metadata_unterminated), retiring all 5 diagnostics on Metadata Examples/RequirementMetadataExample.sysml)
A name introduced into a namespace by an import is visible to a wildcard import of that namespace; a feature reachable by feature chain may be subset; a redefinition may introduce the type its own members are looked up through (item :>> shape : Box [1] { … }) resolve/ (lookup through imports and inherited members — the same traversal PR #331 fixed for length/width/height through ShapeItems::Box) Metadata Examples/RiskMetadataExample.sysml:3, Simple Tests/FeaturePathTest.sysml:24, Geometry Examples/CarWithShapeAndCSG.sysml:48, Variability Examples/VehicleVariabilityModel.sysml:71 ✅ Faithful (resolve/f67_import_reexport_test.go, model/f67_inherited_shape_test.go — all 12 corpus files' unresolved references now resolve)
Members of what a behavioral usage implicitly parameterizes are reachable through it (subscribing.sub, producer.publish_request), and rep … language "ocl" /* … */ (TextualRepresentation, :103) is a body member resolve/accept_payload.go triggerPayload (a trigger's payload is a parameter of its transition, TransitionUsage); resolve/target.go namedByReference (a feature that takes its name from what it redefines is a reference-subsetting target, unlike a borrowed binding); the textual-representation member parses as a body member (shared with the textual-representation row) resolve/f68_behavioral_member_test.go, Interaction Sequencing Examples/ServerSequenceRealization-2.sysml:42, Simple Tests/PartTest.sysml:25 ✅ Faithful (all 39 closed. semantics/implicit.go kindBaseFQN gives *ast.TransitionMember the base its usage kind already implies (Actions::TransitionAction, SysML v2 §7.19.2), and an action usage with an accept payload the Actions::AcceptAction an AcceptActionUsage is typed by (§7.16.5), so accepter, effect, acceptedMessage and receiver are reachable: Simple Tests/PartTest.sysml:25 and 10 of the 11 recorded diagnostics went with it (semantics/implicit_test.go:TestW7ATransitionMemberImplicitBase). The last two, one each in Interaction Sequencing Examples/ServerSequence{Realization,OutsideRealization}-2.sysml, were serverBehavior.delivering.effect.sentMessage reaching a scope-less Actions::TransitionAction::effect: a transition's own effect action is the effect it redefines, so the chain reads that action, and a library document is now parsed on every load path — a record carries derived facts only — so no restored symbol lacks a declaration or a scope. Both files are clean, and the chain is pinned cold and warm (model/w8g_f68_effect_member_test.go). The 6 diagnostics in Vehicle Analysis Demo.sysml are locals of a body expression, a separate class)
A part usage may be typed by any occurrence definition (the reference's own rule is An occurrence, item or part must be typed by occurrence definitions), a use case usage by a use-case definition, and a value binds when its type specializes the feature's passes/typecheck.go kind table; passes/typecheck_value.go:30 for the binding row passes/f69_typing_test.go, passes/f53_succession_typing_test.go (a succession or binding typed by a part/action/attribute/connection definition, a succession or binding typed by a usage rejected), passes/typecheck_value_test.go (TestValueSupertypeInstanceConforms), golden tests/parser/testdata/parse/f69_occurrence_typing.sysml, symbols/f69_function_kind_test.go ⚠️ Approximate (all 5 fixed; action d : OccurrenceFunctions::destroy now resolves, a KerML function being classified as the definition it declares (symbols/builder.go → SymbolCalcDef). Separately: a succession or binding types through a plain UsageDeclaration (SysML.xtext:1033 SuccessionAsUsage, :1020 BindingConnectorAsUsage), so any definition types it — the pilot enforces only A usage must be typed by definitions. The other 3 are one-sided checks the reference lacks — inherited-name conflict, interface conjugation, unit commensurability — and stay, on the precedent set for our other deliberately wider checks (the conjugation one re-adjudicated against the pin: see the interface-end row). Also: where a KerML keyword is still conflated internally, the export no longer conflates it — a datatype names sysml:DataType and a function sysml:Function in RDF (export/kinds.go keywordMetaclass, export/w8g_classification_test.go))

Independent Static Cross-Check — Sensmetry SysIDE

A third implementation (SysIDE, TypeScript, pinned at 0.9.1 with the 2024-12 standard library) gives an optional third verdict per file in tools/referee/diff, provisioned by scripts/download-syside.sh. It corroborates; it never adjudicates: the pinned OMG pilot (2026-08) stays the reference every conclusion in pilot-differential.md rests on, and the third column is additive — with SysIDE absent the report is byte-identical to the committed baseline.

What it can be evidence for: static checking only — parsing and notation acceptance, name resolution, static expression typing, and the KerML/SysML validation rules. SysIDE executes nothing, so it is not evidence for or against any behavioral row of this document (Action, State Machine, Classifier Behaviors, expression-body scope): those are execution semantics and no syside verdict speaks to them. Its standard library is also one release behind the pilot's, so a parse or resolution difference may be a 2024-12/2026-08 difference rather than a finding.

Semantic Rule Implementation Test Case Status
A third implementation's static verdict is reported per file beside ours and the pilot's — which tool said what is always visible, and no existing classification, total or bucket changes when it is present tools/referee/diff/syside.go attachSyside, compareSysideFile (a second, independent partition of the same tuples), categorizeSyside (deliberately under-mapped: an unrecognized SysIDE rule stays unmapped rather than manufacturing agreement) tools/referee/diff/f7_syside_test.go ✅ Faithful (static rows only — 349 files: all three agree exactly on 248, SysIDE corroborates all 20 of our agreed diagnostics, sides with us against the pilot on 7 and with the pilot against us on 37; behavioral rows are out of its scope by construction)

What We Don't (Yet) Support

Decisions to Reassess

Deliberate limitations whose current handling should be revisited once the feature they wait on lands (this repository has issues disabled, so follow-ups are tracked here):

Deferred until Reassess

Major Features Not Implemented (UML-referenced; no SysML v2 notation or KerML performance)

The remaining entries below are genuine implementation work or deliberate language/design boundaries; the landed Track E behavior is recorded in the execution rows and the roadmap.

Actions (Advanced): - Expansion regions — closed, not to be implemented (design record): the iterative form is for (§7.17.12, Actions::ForLoopAction), which runs in every body position; the parallel form is not SysML v2 — a multiplicity on a performed action usage with a flow delivering a collection to its input is not a standard spelling of per-element concurrent performance (SysML v2 §7.17.2, §8.4.13.2; KerML §7.4.7, Annex A.3.6; Performances.kerml, Transfers.kerml), no OMG corpus model writes one, and the pinned pilot performs no actions. The runtime performs such a node once per token (runtime/action_frame.go beginPerformance) and refuses a collection delivered to a one-valued pin (runtime/write_conformance.go checkTargetAs, ErrMultiplicityViolation); per-element concurrency is written as distinct nodes under a fork - Exception handlers — closed, not a SysML v2 construct (design record): UML's RaiseExceptionAction/ExceptionHandler have no spelling in §7.17 (no action kind raises, catches or propagates), no metaclass in §8.3.17 or the reflective SysML.sysml metamodel, no base type in Actions.sysml, and no counterpart in KerML — a Performance (Performances.kerml) ends but does not fail; no OMG corpus model writes one and the pinned pilot's SysML.ecore (175 classes) has none. A failure in SysML v2 is modeled, then handled with the ordinary constructs the runtime executes: an out result routed by decide (§7.17.3), or a failure signal send to an accept forked beside the work, whose branch terminates the work (§7.17.7, §7.17.8, §7.17.10 MonitoredActivity; runtime/action_terminate.go, runtime/action_executor.go acceptMatch, stepDecisionNode); a failure the model does not spell is a typed error at the boundary (ErrDivisionByZero, ErrAcceptDeadlock, …), never a panic, and an error verdict under a verification case - Structured activities with pin connectors

State Machines (Advanced): - Protocol state machines — closed by design record and not a SysML v2 construct; see protocol-state-machines.md. The order of receptions on a port or part is an ordinary exhibited state machine (§7.18.4, accept … via §7.17.8), which runs today on parts (Classifier Behaviors, state_transition_accept_via_port). ⚠️ The order it declares is enforced only for a directly injected event (StateExecutor.SendSignal: dropped and reported in AdvanceReport.Dropped; the REPL's %send refuses it): a message a model sends that the active state neither accepts nor defers stays on the context-wide bus (state_executor.go:takesMessage) and is taken by the first later state that accepts it, and a machine exhibited by a port definition does not take a model's messages routed to that port. The optional runtime follow-up is not a specification gap (discard-and-report on the bus path; port-machine routing; an optional typed error). UML's post-conditions, ProtocolConformance, static sequence checking and the gating of operation calls by state have no SysML v2 spelling and are not tracked as missing.

Object Model:

Type System — the four items once listed here were stale; the pilot's named specialization, redefinition and subsetting constraints are ✅ in validation-constraints.md and only the two rows below remain approximate: - Specialization validation — done. Kind restrictions on specialization (KerML 1.0 §8.3.3.2 class/datatype/structure/behavior: internal/check/passes/w11a_kerml_specialization.go:W11AKerMLSpecializationPass.Run, rejection cases k20–k23), binary association and connector specialization (constraint.go:checkBinaryConnectorEnds, k24/k26), the default supertype (w11e_implicit_base.go:implicitBaseChecker.checkDefaultSupertype, k46 — ⚠️ stricter than the pilot: a two-end association must also reach Links::BinaryLink, KerML §8.3.4.7), conjugation (validateSpecializationSpecificNotConjugated, validateTypeAtMostOneConjugator), SysML definition/usage kind typing (typecheck.go:compatibleTyping, one_type.go:checkOneType, w8d_occurrence_typing.go) and variation specialization (w8d_variability.go:W8DVariabilityPass.Run). "Generic" types do not exist in KerML or SysML v2 (no type parameters or templates), so nothing is missing under that heading. - Redefinition conformance — done. Featuring types (w10b_redefinition.go:checkW10BRedefinition, p28/p32), end conformance (same, k15), direction (w8b_redefinition_conformance.go:RedefinitionDirectionPass.Run, p31), constant and uniqueness (w8b_redefinition_conformance.go:RedefinitionConformancePass.Run, k14/p04), lower-bound narrowing as a warning like the pilot (multiplicity_conformance.go:constraintChecker.checkMultiplicityConformance, probe validateRedefinitionMultiplicityConformance), overriding a bound value (feature_value_overriding.go), plus the advisory redefinition-type-mismatch warning on a non-conforming declared type (constraint.go:checkRedefinition, constraint_test.go) that the pilot does not check. - Subsetting validation — done. Featuring types (subsetting_featuring.go:checkSubsettingFeaturingTypes, k13), upper-bound widening as a warning (multiplicity_conformance.go, probe validateSubsettingMultiplicityConformance), constant/uniqueness (as above), reference and cross subsetting (w8c_reference_subsetting.go, w10b_cross_features.go:checkW10BCrossFeatures, k09/k16–k19/k42/k54/s88), flow-end subsetting (w8d_flow_end.go:W8DFlowEndPass.Run, k38), and a feature-owned multiplicity's featuring types (multiplicity_domain.go:MultiplicityDomainPass.Run, probe validateFeatureMultiplicityDomain). validateSubsettingPortionConformance is a no-op in the pinned pilot, so there is nothing to referee; validateFlowEndImplicitSubsetting is ⚠️ — the pilot warns where OpenSysML rejects the same end as an inaccessible feature. - ~~Interface realization~~ — removed: UML's InterfaceRealization has no SysML v2 counterpart; an interface def is a connection between ports (§7.13.3, ends checked by w10b_ends.go:W10BEndKindPass.Run, typing by typecheck.go:compatibleTyping) and the two sides of an interaction are expressed by port conjugation (~PortDef), not by realizing a contract.

Advanced SysML v2: - Use case execution - Allocation execution semantics beyond materializing the allocation and its ends

What Can't Be Claimed for Spec Compliance

Intentionally Unspecified (No Normative Semantics): - Verification verdict evaluation (VerdictKind/PassIf) - SysML v2 §9.3.2: "evaluation... intentionally not specified normatively". OpenSysML does report a verdict — it runs the case body as it runs an analysis case body and reports the VerdictKind that run produced, computed by the library's own PassIf calculation where the body calls it, inconclusive where the body produces no verdict value and error where the run failed — but that reading of the body is this tool's, not a normative one, and it is reported beside the requirement-satisfaction verdicts rather than replacing them; nested subcases are reported individually because the library states no roll-up. See the Verification Case map - Variability/variation selection - SysML v2 §9.4: "Selection of variants is not specified normatively" — OpenSysML selects the variant a variation usage is bound to (attribute :>> cut = cut::cutIdeal;) and errors on an unselected, unknown, or multiply-selected variation; see the Variation and Variant map - View/viewpoint rendering - SysML v2 §10.2: "rendering semantics intentionally left to tools". OpenSysML does render a view — a tree, an interconnection diagram, a state machine, an action flow, a sequence diagram or a table, as text, Mermaid or a Markdown table (%render, sysml -render; see the rendering rows) — but the artifact itself is this tool's output, not a normative form, and the kinds it does not produce are a typed view.UnsupportedKindError rather than a substituted rendering. The viewpoint conformance verdicts OpenSysML reports are likewise tool-defined (see the viewpoint conformance row) - Allocation execution - SysML v2 §9.2.4: syntax defined, execution semantics not normative

Implementable But Not Yet Done: - None. The last entry here, exception handlers, closed as not a SysML v2 construct: the specification that has one is UML's, and "propagation" is a rule about that construct (design record, and the Actions (Advanced) bullet above).

No External Referee Exists (the limit of the evidence, not of the implementation): - Behavioral execution is self-assessed. The pinned OMG pilot implementation's only execution surface is model-level expression evaluation, so token-flow ordering, transition selection, concurrency and quiescence are checked against the specification text, the normative library and our own traces — not against another implementation. Sensmetry SysIDE is a checker and can corroborate static rows only. Every ⚠️ in the Action, State Machine and Classifier Behaviors maps should be read with that in mind. - The OMG corpora are demonstrations, not a conformance suite. They were written to show the notation off. Agreement over them means we agree where they happen to look; there is no official SysML v2 conformance test suite to run. - The differential is one-directional. It finds notation the reference accepts and we reject. Notation we accept and the reference does not is only visible when a corpus file happens to contain it — so our permissiveness is largely unmeasured, and examples/ still carries constructs no pinned production admits (see the ten-class map above).


Implementation Files

Runtime Execution (internal/exec/runtime/)

File Purpose Lines
context.go Execution context, constraint/requirement evaluation ~430
invoke_calc.go Calc invocation: parameter/result resolution across specialization, binding, recursion bound ~300
action_executor.go Token-flow semantics, control flow nodes, nested actions ~729
state_executor.go Event-driven state machines, transitions, hierarchical states, pseudostates ~1149
eval.go Expression evaluation (operators, literals, features) ~758
value.go Runtime value representation (ValConst, ValString, ValInstance) ~150
trace.go Deterministic execution and calc-evaluation trace recording, canonical value rendering ~290
conformance_test.go Conformance gate ~480
robustness_test.go, robustness_*_test.go Failure-mode tests, the shared cases and one file per feature ~830
trace_test.go Golden trace test infrastructure ~200
trace_calc_test.go Trace determinism and canonical rendering unit tests ~180

Runtime bounds: every limit a model can reach

A run is bounded, and a bound that silently changed a result would be the worst outcome, so each one is a typed error naming what it counts. The seven configurable bounds live in runtime/budget.go (Budgets, BudgetsFromEnv), and an unusable value for any of them is reported rather than silently replaced by the default.

Bound Sentinel Variable Default Robustness case
Expression evaluation steps ErrStepLimitExceeded OPENSYSML_MAX_STEPS 10,000,000 robustness_test.go:non_terminating_loop_performing_an_action, range_test.go:TestIntegerRangeSpendsTheStepBudget
Action token-flow steps ErrActionStepLimitExceeded OPENSYSML_MAX_ACTION_STEPS 1,000,000 budget_test.go:TestActionStepBudgetIsConfigurable
State machine events ErrStateEventLimitExceeded OPENSYSML_MAX_EVENTS 1,000,000 budget_test.go:TestStateBudgetsAreConfigurable, robustness_test.go:object_exhibited_machine_never_settles
Do action steps ErrDoStepLimitExceeded OPENSYSML_MAX_DO_STEPS 5,000,000 budget_test.go:TestStateBudgetsAreConfigurable
Collection elements one evaluation holds ErrElementLimitExceeded OPENSYSML_MAX_ELEMENTS 1,000,000 range_test.go:TestIntegerRangeExtremeBounds
Nested calc invocations ErrCalcRecursionLimit OPENSYSML_MAX_CALC_DEPTH 10,000 (ceiling 25,000) robustness_test.go calc recursion cases
Runs one parameter sweep or sample may make ErrSweepBudget OPENSYSML_MAX_SWEEP_RUNS 1,000 sweep_test.go:TestSweepBudgetIsRefusedBeforeRunning, :TestSweepBudgetBoundsTheProduct, :TestSamplesBeyondTheBudgetAreRefused

Three more bounds are not budgets and are stated here because they shape what a caller is told:

  • The objects one context holds are bounded where a host asks. Context.SetMaxInstances bounds the objects registered at once, nested ones counted; materialize refuses the one past it with ErrInstanceLimitExceeded, and the creation or read that reached it is abandoned whole (instance_limit_test.go:TestInstanceLimit_CountsNestedObjects, :TestInstanceLimit_RootFailsWhole). sysml-grpc sets it from OPENSYSML_GRPC_MAX_HELD_OBJECTS (default 10,000) on the runtime each cached model holds its objects in and answers the refusal as RESOURCE_EXHAUSTED (grpc/objects_test.go:TestHeldObjectsAreBounded); no other surface sets it.
  • Suspension is bounded by the executor. An accept can only be satisfied by a message the run can still receive: a nested action invoked synchronously, and an action driven by RunToCompletion, cannot wait for a message posted after the call begins, and a run whose every token is parked reports ErrAcceptDeadlock rather than hanging (robustness_test.go:routed_send_receiver_name_mismatch_deadlock, :injected_message_names_a_receiver_no_accept_has). A message no accept of the run ever takes is seen through the accept that waited for it; a run that completes with no accept waiting at all leaves it unconsumed in the queue, which is a limitation and not a verdict on the model.
  • The materialization check is bounded but says so. runtime/materialize.go walks an object's feature values to a depth of 8 and a budget of 1,000 and returns bounded when it did not read everything; repl/instantiate_report.go carries that flag into the report, so an incomplete check is presented as unchecked rather than clean (materialize_test.go:TestMaterializationErrorsBoundsAWideModel).

Symbol Resolution (internal/semantic/resolve/)

File Purpose Lines
document.go Name resolution, inheritance chain lookup ~750
qualified.go Qualified name resolution (A::B::C) ~200

Symbol Tables (internal/semantic/symbols/)

File Purpose Lines
builder.go AST → symbol table, control flow node registration ~380
scope.go Scope tree, member lookup ~250

Testing Infrastructure

See TESTING.md for complete test contract details.

Test counts: stated once, in the Test Coverage list near the top of this document — the per-prefix conformance breakdown and the trace, fixture, negative and robustness figures are all there, and every other page links here rather than restating them (CONTRIBUTING.md).

Quality Gates: - Parser: 103/103 stdlib files clean (94 vendored OMG, 9 OpenSysML extensions) - Execution conformance: every case passing, with known_failures.txt empty - Training examples: 100/100 clean (no files recorded in tests/corpus/testdata/training_examples_expected.txt) - No regressions: All tests pass on every commit

The training-example gate needs the corpus, which is not vendored: run ./scripts/download-training-examples.sh first. The gate skips while the corpus is absent, so run the script before claiming a change is clean locally. CI downloads it (.github/workflows/pr.yml) and sets OPENSYSML_REQUIRE_TRAINING_CORPUS=1, which turns an absent corpus into a failure, so the gate can no longer skip green there. The gate runs against an empty semantic cache (t.Setenv("XDG_CACHE_HOME", t.TempDir())), so it reports the same 100/100 on any machine.


Model Persistence and RDF Interchange

Implementation: internal/translate/rdf, internal/translate/export User surfaces: %save (internal/frontend/repl/meta.go), sysml -convert (cmd/sysml/main.go) Reference: the RDF mapping — the mapping, the CLI, and the limitations in full

Three representations are supported: SysML textual notation, RDF Turtle, and the SysML v2 API's JSON element form (api-json). The two graph forms are one mapping with two serializers — the JSON form is written from, and read into, the same graph Turtle is — so there is no second mapping that can drift from the first.

Notation is stable. RDF Turtle and the API element form are experimental as of 0.1.0: the statuses below report how faithful the mapping is to what it covers, not that the mapping covers a whole model or that its vocabulary is settled — see the mapping's status.

Capability Implementation Test Case Status
Save a model as notation, preserving comments and notes convert.Convert → format.Source (token stream, not an AST re-print) export_test.go:TestSaveKeepsComments, repl/save_test.go:TestMetaSaveSysML ✅ Faithful
Save a model as RDF Turtle export.ToRDF + rdf.WriteTurtle repl/save_test.go:TestMetaSaveTurtle, golden .golden.ttl fixtures ✅ Faithful
Notation → RDF for every definition/usage keyword the parser accepts export/kinds.go metaclass tables, rdf_out.go encode export_test.go:TestGoldenConversions (18 fixtures) ✅ Faithful
RDF → notation for the mapped subset rdf_in.go ToSysML export_test.go:TestGoldenConversions, TestConvertedNotationParses ✅ Faithful
Round trip preserves the graph (sysml→ttl→sysml→ttl is stable) both directions export_test.go:TestRoundTripIsLossless ✅ Faithful
Save a model as the SysML v2 API element form (api-json): one JSON object per element with @type (the metaclass), @id (the element id) and the metamodel properties as keys; a reference is {"@id": …}, a collection the graph annotates (SysML v2 API & Services, the element form the /elements endpoints serve) is an array, a scalar its JSON value; the sysx: properties keep their prefix as keys export/api_json_out.go WriteAPIJSON over the graph ToRDF builds, after rdf.ReconcileCollections tests/export/api_json_test.go:TestAPIJSONShapeOnTheInteropModel, :TestWriteAPIJSONRealLexicals, :TestWriteAPIJSONRefuses, :TestAPIJSONInverseOverAllFixtures (the JSON and Turtle of every fixture read back to the same triple set) ✅ Faithful — a collection of one member is written as an object, since the graph carries no multiplicity and the Turtle path annotates a collection only from its second member; the standard API serves every multi-valued property as an array
Read the API element form back into notation, as rdf_in.go's inverse over JSON export/api_json_in.go ReadAPIJSON → ToSysML; a name in an @id resolves within the subject's project scope or by its <qualifier>:<id> spelling (rdf.ReferenceIRI), an expression node's id by the same grammar the Turtle expr: IRIs use tests/export/api_json_test.go:TestReadAPIJSONValueForms, :TestReadAPIJSONScopedAndSingle, :TestReadAPIJSONExpressionClassification, :TestAPIJSONInverseOverAllFixtures (59 fixtures), internal/translate/convert/api_json_test.go:TestAPIJSONConvertRoutes ✅ Faithful — a string on a property the encoder writes expression text on (type, general, memberElement, the connector ends, …) is read as expression text unless it parses as a name; a string on any other property is a name literal
Malformed element form refused, never partly read (a scalar or null document, a duplicate @id, a missing or empty @id or @type, an unknown @ key, an object that is not a reference, a nested array, a null collection member, a key or @type in a prefix the mapping does not define) export/api_json_in.go tests/export/api_json_test.go:TestReadAPIJSONRejectsNonElements, internal/translate/convert/api_json_test.go:TestAPIJSONSyntaxError ✅ Faithful
The relationships the notation implies are written as the elements the metamodel defines for them — FeatureTyping/Subclassification/Subsetting/ReferenceSubsetting/Redefinition beside the collapsed type/general/subsets/redefines edges, EndFeatureMembership for a connector end, SubjectMembership for a requirement's subject, RequirementConstraintMembership for require/assume constraint, ResultExpressionMembership for a result expression, ElementFilterMembership for filter/filtered imports — minted under the owner's id (<S>_ft<i>, _sc<i>, _ss<i>, _rs<i>, _rd<i>, _subject, …) and carrying the edge's ends, and a declared relationship member is owned through an OwningMembership like any other member export/rdf_normative.go materializeNormative/emitRelationship, export/rdf_in_normative.go (accepts the materialized elements where the collapsed properties imply them, refuses where they contradict) export/rdf_normative_test.go, export_test.go:TestGoldenConversions, export/api_json_import_test.go ✅ Faithful
A [m..n] bound is a sysml:MultiplicityRange element (<S>_mult) whose lowerBound/upperBound expressions it owns, beside the collapsed bound properties export/rdf_normative.go export/rdf_normative_test.go, export/api_json_import_test.go ✅ Faithful
A conjugated port type port p : ~P; mints a sysml:ConjugatedPortDefinition (<S>_conjugated) and the sysml:PortConjugation (<S>_pc) joining it to P, so p's FeatureTyping types ~P export/rdf_normative.go export/rdf_normative_test.go, export/api_json_import_test.go ✅ Faithful
An expression's referent/targetFeature link is restated as a sysml:Membership minted beside it (<S>_referent, _preferent, _targetFeature), the shape interchange readers navigate export/rdf_normative.go materializeReferentMemberships export_test.go:TestFixtureElementIDsRoundTrip, export/api_json_import_test.go ✅ Faithful
sysml-toolkit interchange JSON imports both ways: convert --to compact-json and --to full-json decode to byte-identical notation, the toolkit's root Namespace+OwningMembership wrapper is transparent, {"@ref": <name>} and unresolved:-derived targets read as the name they spell, stated defaults on isImpliedIncluded elements collapse back, and ends the notation cannot place are refused export/rdf_in_toolkit.go, export/api_json_in.go ReadAPIJSON export/api_json_import_test.go (tests/export/testdata/interchange/ fixtures), :TestUUIDRoundTrip ✅ Faithful
-id uuid mints name-based uuids the way the library convention does (root: uuid5(NamespaceURL, elementIRI(root)); derived: uuid5(pkg, <the id it would carry by default)>), declared and normative ids are never re-derived, and the form reads back with its derived ids implied — no @ElementId annotations export/id_form.go IDUUID, export/rdf_identity.go subjectOf/minted/mintedNode, export/verbatim.go demoteStale export/api_json_import_test.go:TestUUIDIDs, :TestUUIDRoundTrip (byte-identical to the default-form round trip), :TestUUIDDeclaredElementID ✅ Faithful
Round trip through the element form pinned per file over examples/ tests/corpus/roundtrip_test.go TestCorpusAPIJSONRoundTrip, baseline testdata/api_json_roundtrip_expected.txt 354 of 356 models stable, 2 graph-diff — two pilot models spell a real as .1, which JSON cannot, so the JSON form carries 0.1 and the notation written back does too ✅ Faithful
The element form is what the SysML v2 API's own commit path accepts internal/translate/interop/flexo Measure, the api-json-commit side posts the emitted elements as DataVersion payloads TestFlexoInterop (opt-in, .agents/skills/flexo-interop), interop_expected.txt ✅ Faithful — the fixture's 59 elements and 505 of its 582 properties are served back, exactly the set the Turtle graph-load path delivers; the 77 lost are the sysx: properties, which the service drops on both paths
individual on a definition (OccurrenceDefinition::isIndividual, SysML v2 §8.3.9.11) carried structurally, so the modifier survives a round trip with sysx:sourceText stripped for every definition kind the parser lets it prefix (individual part def, individual item def, individual occurrence def, individual action def, …) and a usage typed by such a definition still validates; an individual def carries the same flag and reads back by its kind keyword alone; a definition without the modifier carries no flag export/rdf_out.go encode (the definition's flags, the same sysml:isIndividual a usage writes), export/rdf_in.go definitionHead export_test.go:TestFixturesComeBackFromTheGraphAlone, :TestGoldenConversions, :TestRoundTripIsLossless (fixture testdata/convert/individual_definitions.sysml), model/export_roundtrip_test.go:TestNotationFromTheGraphAloneAnalysesLikeTheOriginal (the notation written from the graph alone analyses exactly as the original) ✅ Faithful
Deterministic, reversible element IRIs keyed by qualified name, with ids in [A-Za-z0-9_-]+ rdf/vocab.go ElementIRI, rdf/ids.go EncodeElementID/DecodeElementID rdf_test.go:TestElementIRIRoundTrip, rdf/ids_test.go, export_test.go:TestElementIRIsEncodeQualifiedNames, TestFixtureElementIDsRoundTrip ✅ Faithful
Declaration order preserved across a format with no order sysx:memberIndex TestRoundTripIsLossless ✅ Faithful
Turtle writer/parser (prefixes, a, ;/, grouping, typed and language literals, long strings, escapes, @base) rdf/turtle_write.go, rdf/turtle_parse.go rdf_test.go:TestTurtleRoundTrip, TestParseTurtleForms, TestParseTurtleEscapes ✅ Faithful
Syntax errors rejected, never partially converted convert.SyntaxError, rdf.ParseError (with line) export_test.go:TestSyntaxErrorIsReported, cmd/sysml/convert_test.go:TestConvertErrors ✅ Faithful
Unsupported RDF reported, never silently dropped export.UnsupportedError rdf_test.go:TestParseTurtleRejects, export_test.go:TestUnsupportedTurtleConstructs/TestUnknownMetaclassIsUnsupported/TestForeignGraph ✅ Faithful
Expression-valued positions (values, bounds, guards, filters, conditions, payloads) export/rdf_expr.go — a tree of typed expr: nodes per position, with roots owned through OwningMembership/FeatureValue, operands owned through ParameterMembership and in Features with FeatureValue values, and sysml:operator; sysx:sourceText is optional and the decoder reads the structural graph without it export/w6g4_rdf_expr_test.go:TestExpressionValueIsATree, :TestExpressionPositionsAllEmitTrees, :TestExpressionIdentityIsPerPosition, :TestExpressionResourcesAreNotElements, :TestLiteralExpressionsStillDecode, :TestForeignExpressionTreeIsWrittenFromItsStructure, :TestUnsupportedExpressionShapesAreReported, :TestExpressionTreesKeepTheRoundTripExact, export_test.go:TestRoundTripIsLossless ✅ Faithful — queryable by SPARQL and source-free; legacy positional operands remain accepted (the mapping)
A constraint body's condition (require constraint { ready }, assert constraint { not x }, a bare constraint { a and b }, a named require constraint <'R-1'> ok { ready }, KerML's inv { ready }) written back from the graph alone sysx:condition carries the condition as an expression tree (rdf_out.go condition); rdf_in.go isTrailingCondition writes the keyword-less condition that closes its body bare, as a result expression is, since a lone name before ; is a kind-less feature declaration (SysML.xtext DefaultReferenceUsage) and not a reference; a condition others follow keeps its ; export/condition_references_test.go (TestConditionReferencesComeBackFromTheGraphAlone: every shape rebuilt from the graph with no sysx:sourceText, second hop equal as a triple set; TestConditionReferencesRevalidateFromTheGraphAlone: the rebuilt notation analyses as the fixture does), fixtures testdata/convert/condition_references.sysml and invariant_references.kerml, export_test.go:TestRoundTripIsLossless ✅ Faithful
End-binding heads (connect, bind, flow, succession, transition, accept, satisfy) heads use sysml:connectorEnd, EndFeatureMembership, end ReferenceUsages with sysml:isEnd, owned ReferenceSubsetting relationships and chain Features with sysml:chainingFeature, and sysml:relatedFeature; binary connectors additionally use sysml:sourceFeature/sysml:targetFeature, while TransitionUsage uses sysml:source/sysml:target (rdf_out.go connectorEnd/endReferences, end_forms.go standardEnds, behavior.go) export_test.go:TestVerbatimHeadsRoundTrip, :TestEndBindingHeadsComeBackFromTheGraphAlone, :TestEndBindingBodiesComeBackFromTheGraphAlone, :TestBehavioralHeadsComeBackFromTheGraphAlone, :TestEndsWithoutTheirFormAreReported, w6g4_rdf_expr_test.go:TestBindingEndsAreStatedAsStructure ✅ Faithful — standard end structure is source-free and legacy sysx: ends plus legacy transition predicates remain accepted (the mapping)
Accept-action shorthand (action X accept p : T [via Port]) parameter encoded structurally; printer rebuilds the shorthand export_test.go fixture testdata/convert/accept.sysml, tests/parser/testdata/parse/accept_action_shorthand.golden ✅ Faithful
then succession between members, with end multiplicities: the source end's (then [m] <member>, [m] then <ref>) and the target end's crossing multiplicity (then [m] <ref>;, then [m] <ref> { … }, then [m] done;, [m] then [n] <ref>;) ast.SuccessionEdge.SourceMultiplicity and TargetMultiplicity are set during parsing (parser/succession.go takeSuccession, parser/behavior.go parseSuccessionEdgeWithMultiplicity); semantics/action_succession.go carries each to its semantic end; passes/end_multiplicity.go and passes/behavior/control_node.go check the source end, as a crossing multiplicity on the target is no end feature's; sysml:SuccessionAsUsage carries the source multiplicity on the empty source connector end — sysx:sourceMultiplicityBeforeThen preserves which spelling was authored — and the target multiplicity on the target connector end, the same end succession first a then [m] b; states (export/behavior.go, export/rdf_out.go) parser/succession_test.go:TestSuccessionEndMultiplicityForms, parser/negative_test.go:TestActionBodyMultiplicityRequiresThen/TestSuccessionEndMultiplicityRejectsFormsWithoutAnEnd, parse/action_succession_multiplicity.golden, semantics/action_succession_multiplicity_test.go, passes/end_multiplicity_test.go:TestActionSuccessionSourceMultiplicity/TestNestedActionSuccessionSourceMultiplicity/TestActionSuccessionTargetMultiplicityIsNotAnEndMultiplicity, passes/behavior/control_node_test.go, export_test.go:TestActionSuccessionSourceMultiplicityRoundTripsWithoutSourceText/TestActionSuccessionTargetMultiplicityRoundTripsWithoutSourceText ✅ Faithful — the AST is annotated at parse time only, non-unit source ends receive the existing warning at the multiplicity span in top-level and nested action bodies, unit ends and target ends do not, and RDF reads back every form without sysx:sourceText. Two multiplicities on one end (then [m] [n] x;), a target multiplicity ahead of a member a member-attached then declares ([m] then [n] action x;) and one on a guarded then x if g; stay rejected, as the pinned pilot rejects them (SysML.xtext:878, 887 EmptySuccession, 994 ConnectorEnd, 1703-1706 TargetSuccession, 1708 GuardedTargetSuccession, 1714 DefaultTargetSuccession; formal/2026-03-02)
A control-node keyword after then that is followed by a name declares a node of that kind (then fork F;, then [m] fork F;, then fork F { … }, and join/merge/decide alike: SysML.xtext:1664-1682 'fork' UsageDeclaration? ActionBody), whether or not the body declares a member with the keyword's name — a keyword names no ConnectorEnd (SysML.xtext:1703 TargetSuccession), so the form is never a two-ended then; the multiplicity ahead of it is the source end's (EmptySuccession). Only the bare then <kw>; beside such a member references it (then done; beside action done;, as the pilot reads it; then fork; beside action fork;/action 'fork';, a member the pilot admits only quoted and beside which it declares an anonymous fork node), and then done D; stays rejected (a final node declares no name) parser/succession.go namesEdgeEnd (an edge end only before ;), atSuccession; parser/behavior.go parseForkNode, parseJoinNode, parseMergeNode, parseDecisionNode, parseFinalNode parser/succession_test.go:TestThenNodeKeywordWithANameDeclaresTheNode/TestThenNamedNodeDeclarationIsTheSameBesideADeclaredMember (the members declared beside action fork; are those declared without it)/TestThenTargetMultiplicityReferencesADeclaredNodeWordMember/TestThenNodeKeywordWithABodyDeclaresTheNode, resolve/succession_edge_test.go (the bare form resolves to the member, the named form to the node), parse/action_then_node_keyword_named_member.golden, tests/parser/negative_test.go:then_done_named_beside_declared_done/then_fork_two_names_beside_declared_fork ✅ Faithful (the pinned pilot accepts then fork F;, then [0..1] fork F;, then fork; and then fork F { … } beside action 'fork'; without diagnostics, and rejects action fork; itself as a keyword; pilot pin 2026-08)
Behavioral nodes of an action or state body (initial/final node, perform, send, accept, terminate, assign, fork/join/merge/decision, while/loop/for, if/else, states, substates, regions, entry/do/exit, defer, pseudostates, transitions) export/behavior.go metaclasses and sysx: properties, encoded and printed back; the conditions and expressions they carry are expression trees (rdf_expr.go) export/behavior_test.go:TestBehavioralModelsComeBackByteIdentical, :TestActionNodeMetaclasses, :TestLoopAndConditionalMetaclasses, :TestStateMachineMetaclasses, :TestStateMembersRoundTrip, w6g4_rdf_expr_test.go:TestExpressionPositionsAllEmitTrees, fixtures testdata/convert/action_nodes.sysml, loops_conditionals.sysml, state_machine.sysml ✅ Faithful — the nodes round-trip byte-identically and their expressions are queryable, a succession naming neither end included (it states them as positions; see the then row above). A succession whose ends the notation cannot express, or whose name needs quotes in the two-name form, is refused (export/behavior_test.go:TestUnsupportedBehavioralShapesAreReported)
Two members of one namespace sharing a name both export as separate elements: the first keeps the qualified name and each later one is identified by its position among the owner's members, the same name an element declared unnamed takes (export/rdf_out.go collect). identitySegment quotes a name that spells a position or contains ::, leaving other name spellings and existing IRIs unchanged; a named @2 is therefore Demo::'@2', distinct from positional Demo::@2 tests/resolve/distinguishability_test.go:TestDuplicateOwnedMemberNamesAreWarnings (two name-conflict warnings, no error), passes/nameres_test.go:TestDuplicateOwnedMemberNamesWarnInEveryConformanceMode (a warning in strict conformance too), export/names_identity_test.go, export/duplicate_names_test.go (API JSON, Turtle and uuid id form, declared-id and round-trip cases, toolkit reader, positional-name collision), export_test.go:TestDuplicateNameConverts/:TestPositionalNameCollisionConverts, cross_feature_test.go:TestCrossFeatureNameTakenByBodyMemberRoundTrips, cmd/sysml/convert_test.go:TestConvertDuplicateMemberNames, grpc/convert_duplicate_test.go:TestConvertDuplicateMemberNames, client/python/tests/test_conversion.py:test_duplicate_member_names_export_as_separate_elements/:test_positional_looking_name_and_position_export_separately ✅ Faithful — the pinned pilot's Element_qualifiedName_SettingDelegate names the first owned member with the name and leaves later members' qualified names unset, which positional identities mirror; the pilot validator warns on duplicates rather than rejecting them (warning: Duplicate of other owned member name, twice, exit 0), and this mapping does the same. Quoted name segments keep those names distinct from positions without changing ordinary names' identities
Ownership as the abstract syntax states it: sysml:owner and sysml:owningRelatedElement as element references, and a materialized OwningMembership — or FeatureMembership where a type owns a feature — between owner and member, each with its own IRI, sysml:elementId and member/owner wiring export/rdf_out.go owningMembership/relationshipOwnership, rdf/ids.go OwningMembershipID (the member's id plus _om, which no element id can be) export/ownership_graph_test.go:TestEveryElementCarriesItsElementID, :TestOnlyTheOutermostElementIsARoot, :TestMembersAreReachedThroughTheirMembership, :TestAFeatureIsOwnedThroughAFeatureMembership, :TestARelationshipOwnsItsMemberDirectly, :TestVisibilityIsStatedByTheMembership, :TestOwnershipComesBackFromTheMembershipsAlone, :TestWithoutAnyOwnershipPropertyTheTreeFlattens, :TestCompactOwnershipGraphStillConverts, :TestMembershipWithoutItsEndsIsReported ✅ Faithful for containment — one root per document, and the tree comes back from the memberships alone with sysx:sourceText and sysml:owningNamespace stripped. The compact sysml:owningNamespace shape earlier releases wrote is still written and still read; a membership missing an end is a typed error naming sysml:memberElement. Not claimed: a graph read back through a live Flexo API
Expression node identity: an id in [a-zA-Z0-9_-]+ and sysml:elementId, so a node is addressable by id like an element rdf/ids.go ExpressionNodeID (the owner's id, _p, and the encoded position, which no element or membership id can be), rdf/vocab.go ExpressionIRI, export/rdf_expr.go expressionNode rdf/ids_test.go:TestExpressionNodeIDRoundTripsAndCannotCollide, export/export_test.go:TestFixtureElementIDsRoundTrip, export/w6g4_rdf_expr_test.go:TestExpressionIdentityIsPerPosition, :TestExpressionTreesKeepTheRoundTripExact ✅ Faithful for addressing — the id is deterministic, reversible and accepted where an element id is; the . an earlier id held was refused by requireValidId before the store was read. A node is still not a model element: no sysml:qualifiedName, no ownership, and it is reached only from the position holding it
Reference-valued properties as element references, as the API defines them: sysml:type, importedNamespace, importedMembership (the imported element's owning membership, KerML §8.3.2.5 MembershipImport::importedMembership), sourceFeature/targetFeature, referent, function, and every declaration-head relationship link the element the name resolves to — an element of the graph by its id, a standard library element by its normative id whether or not the library is in the graph — and stay a literal only for a name that resolves to nothing the model declares or a body parameter, which is no element export/rdf_out.go reference, importedMembership; export/rdf_identity.go subjectForNode (normative ids through identity.LibraryCatalog); export/names.go resolver-checked shortest spelling; export/rdf_in.go referenceName, libraryElement (reads an IRI or a legacy literal) tests/export/reference_iris_test.go TestReferencePropertiesLinkElements (a library type, a current-document type, an inherited start, a feature chain, a membership import's owning membership, a metadata definition by short name, an unresolvable name kept as a literal), TestLegacyReferenceGraphsStillRead; tests/export/ontology_gate_test.go TestGoldenGraphsMatchOntology (the literal-for-object-property inventory shrank to the fixtures' unresolvable names) ✅ Faithful (a graph from a release that wrote the name as a literal reads back and gains the links on its next hop)
Every metaclass written is concrete in the metamodel, as every element the SysML v2 API returns is: an import is sysml:NamespaceImport or sysml:MembershipImport, never abstract sysml:Import; a KerML connector is sysml:Connector, never abstract sysml:ConnectorAsUsage; and no other abstract class of the pilot's SysML.ecore/kerml.ecore (ControlNode, Element, Expose, InstantiationExpression, LoopActionUsage, Relationship) is written export/rdf_out.go encodeMember (mNamespaceImport/mMembershipImport); export/kinds.go usageMetaclass (Connector), legacyConnectorAsUsage; export/rdf_in.go declarationHead (accepts sysml:Import and sysml:ConnectorAsUsage from older graphs) tests/export/reference_iris_test.go TestReferencePropertiesLinkElements, TestLegacyReferenceGraphsStillRead (abstract sysml:Import), TestLegacyConnectorAsUsageStillReads; golden imports.golden.ttl ✅ Faithful (audited by hand against the ecore models' abstract classes, which the pinned SysML.owl does not record)
Ownership cycle in an input graph refused: every element's owningNamespace chain must reach a root, or no root owns the element and printing would emit an empty document export_test.go:TestOwnershipCycleIsUnsupported ⛔ Rejected rather than emitting an empty file (deliberate). Ownership in the abstract syntax is a tree — an element has at most one owning relationship, and the graph is what a notation document nests — so a cycle is not an unrepresentable model but an inconsistent graph. The alternative, writing the elements a root does reach, would silently drop the cycle's members; the refusal names the elements in it
Lexical // and /* */ trivia across the RDF hop no element owns trivia; doc/comment are declarations and do convert export_test.go:TestCommentsThroughRDF ⛔ Not carried through .ttl (a direct .sysml save keeps it). Trivia belongs to no element in the abstract syntax, so there is no subject to hang it on; carrying it as a per-element sysx: blob would make the graph a source archive rather than a model. The surfaces say so: sysml -convert and %save print the experimental note, and the guide points at the notation save for round trips that must keep comments
Blank nodes, RDF collections, bare literal shorthands rejected by rdf.ParseTurtle rdf_test.go:TestParseTurtleRejects ⛔ Not supported (deliberate; see the mapping). This is a subset of Turtle, not all of it: an element of this graph is addressed by an IRI derived from its qualified name, and a blank node has no such name, so a blank-node subject could not be read back as an element or written twice to the same address. Collections and bare literal shorthands are unused by anything this mapping writes. A parse error names the line rather than dropping the statement, so a foreign graph using them fails loudly

Vocabulary: sysml: = https://www.omg.org/spec/SysML# and elmt: = urn:sysmlv2:element: match the Flexo MMS SysML v2 service's Namespaces.kt. The service's reader derives an element's @id from the substring after the final : and requireValidId permits only [a-zA-Z0-9_-]+, which the encoded ids satisfy; every element also carries the sysml:elementId paged listing and query select on, and the sysml:owner/sysml:owningRelatedElement and membership elements the roots endpoint and the API's payloads walk. The reader still ignores predicates outside sysml: and urn:sysmlv2:annotation:json:, so sysx: triples do not survive that path, and collection properties carry no JSON annotation — roadmap D3.4. Whether such a graph loads into a running Flexo triplestore has not been demonstrated. Properties the SysML metamodel does not define are confined to sysx: = urn:opensysml:sysml:, and the expression nodes an expression-valued position holds to expr: = urn:opensysml:expr:: memberIndex, hasBody and sourceText carry order, body presence and verbatim heads, operator, argumentIndex, relatedFeature, endIndex and endRole carry an expression's shape and a binding head's ends, prefixMetadata, filter, isNamespaceImport, isRecursive and isExpose carry notation the metamodel has no property for, and the behavioral properties (guard, expression, payload, subactionKind, …) carry the parts of a behavioral node the metamodel has no predicate for.

What can't be claimed: this is not a normative SysML v2 → RDF/OWL mapping. OMG's abstract syntax has no standard RDF serialization, so the property names follow the metamodel's own attribute names and the Flexo service's conventions. A model converted here is faithful to itself on a round trip; it is not guaranteed to be interpreted identically by an unrelated SysML RDF tool, and no round trip through a third-party triplestore has been demonstrated. The vocabulary may also change without a compatibility path, so a .ttl is an artifact to regenerate rather than the copy of record.


Source-Preserving Model Editing

Implementation: internal/check/edit, internal/frontend/grpc/edit.go (ApplyEdits) User surfaces: sysml-grpc — ApplyEdits; opensysml — Model.edit(), Editor.set_value, Editor.rename, Editor.add_member, Editor.delete, Editor.move, Editor.apply (client/python/opensysml/edit.py) Reference: the client guide

The standard defines an API for changing a model (SysML v2 API & Services commits); this is not that API. It is a source-level edit of the notation a model was parsed from: the AST stays immutable, the edit rewrites bytes of the source guided by the spans the parse recorded, and the result is read back before it is returned. Its operations include setting a value, renaming, adding members and usages, deleting, and moving declarations.

Rule Implementation (file:function) Tests Status
A verification case can receive verify <requirement>; in its owned objective, or have an objective created when it has none; metadata usages can be authored in SysML and KerML with optional about, feature values and @ shorthand, and new members can carry #M prefixes in grammar-admitted positions edit/verify.go Model.addVerifySplice, edit/metadata.go Model.addMetadataSplice, edit/add.go Model.addMemberSplice/writeMember, passes/w8d_verification.go shared objective predicates; grpc/edit.go capability gates and operation conversion edit/authoring_test.go (successes, structural and semantic refusals, prefix parse/reparse coverage), grpc/authoring_test.go (wire conversion, capability refusal, runtime verdict equivalence), client/python/tests/test_edit.py, client/opensysml/authoring_internal_test.go, Java PublicTypesTest/EditProtosTest, tests/grpc/conformance_test.go ✅ Faithful — semantic validity is decided by the edited model's re-analysis
A feature's value is set by replacing the expression of an existing = <expr>, or by adding one before the declaration's terminating ; when it has none edit/locate.go Model.valueSplice, Model.terminator (the ; from the token stream, not a search) edit_test.go:TestSetValueReplacesOnlyTheValueSpan, :TestSetValueAddsValueToValuelessFeature, :TestSetValueKinds (quantity, string, boolean, a feature reference, an expression, a feature nested three levels deep, a feature reached through redefines), grpc/edit_test.go:TestApplyEditsSetValuePreservesSource, client/python/tests/test_edit.py ✅ Faithful
A declaration is renamed by rewriting its own name token edit/rename.go Model.renameSplices, edit/locate.go declIdent edit_test.go:TestRenameRewritesTheNameTokenOnly, grpc/edit_test.go:TestApplyEditsAddsValueAndRenames ✅ Faithful
A rename rewrites every reference to the renamed element the model's source makes edit/rename.go Model.renameOccurrences (resolve.References + Resolver.PartSymbol, one splice per matching segment) edit_test.go:TestRenameRewritesReferences, :TestRenameSeesInheritedMembers, w6g4_rename_references_test.go:TestRenameRewritesQualifiedReferences, :TestRenameRewritesImportedReferences, :TestRenameLeavesWildcardImportAlone, :TestRenameRewritesAliasTargetNotAliasUses, :TestRenameAliasRewritesItsUses, :TestRenameLeavesShortNameReferencesAlone, :TestRenameReportsEveryRewrittenSpan, grpc/edit_test.go:TestApplyEditsRenameRewritesReferences, client/python/tests/test_edit.py ✅ Faithful for the model's own source — a qualified name's matching segment, an alias target and an import are rewritten; a wildcard import names the namespace and is left alone, and a reference written with the element's short name is left alone because the rename does not change the short name. References made from another file are not rewritten: an edit sees only the source of the model it was handed. FailureRenameReferenced is kept in the wire enum for compatibility and is no longer returned
Targets are named by the id a read reports (Symbol.id, an FQN), and only a declaration of the edited model's own source can be edited edit/locate.go Model.target (Index.LookupQualifiedFrom + GetFQN, DocName check) edit_test.go:TestRefusals (unknown target, target outside this source, target carries no value) ✅ Faithful — a name that resolves outside the edited source, the standard library included, is refused rather than editing a file the caller did not name
Edits apply in one pass, right-to-left by offset, and every byte outside an edited span is identical to the parsed source — an edited span covers the target's own tokens only, since a node's span runs on to the next token and so contains the whitespace and comments written after it edit/edit.go Apply, splice ordering, edit/locate.go Model.tokenSpan edit_test.go:TestSetValueKeepsWhatFollowsTheValue, edit_test.go:TestSetValuePreservesCommentsAndBlankLines, :TestApplyManyEditsInOnePass, the assertOnlySpanChanged check in every case (the original is rebuilt from the result by undoing each applied edit), client/python/tests/test_edit.py (byte comparison around each AppliedEdit) ✅ Faithful — nothing is reformatted, so format.Source is not run over the result
Two operations that would edit overlapping bytes are refused edit/edit.go Apply (FailureOverlappingEdits) edit_test.go:TestRefusals (overlapping edits), grpc/edit_test.go:TestApplyEditsRefusalIsAResponse ✅ Faithful
A new value that does not lex/parse as one expression, or a new name that is not an identifier, is refused before anything is spliced edit/validate.go Model.checkValue, checkName edit_test.go:TestRefusals (value does not parse, value is not one expression, value is empty, new name is not an identifier, new name is a keyword, new name is empty) ✅ Faithful
A rename that would capture or shadow a name at one of the references it rewrites is refused rename/rename.go Check, capturedAt (Resolver.ProbeReading of the reference with the segment respelled, Reference.Spelled — a trial reading by the path the document walk takes, keeping what each segment reached even where the rest of the name then fails, so a qualifier respelled onto an element without that member is still captured, and a segment that would name several members at once, Reading.Ambiguity, is refused as leaving the reference ambiguous: a scope lookup, a namespace member, a feature chain step in the operand's type, a redefinition target among the generals, a constructor label, an endpoint, an import; a segment that would write an alias name is captured by the alias, Resolver.PartAlias, even one for the renamed element) over the rename.Occurrences edit/rename.go Model.renameOccurrences collects, FailureInvalidName; the language server runs the same check over every workspace document (see Language Server) w6g4_rename_references_test.go:TestRenameCapturingANameAtAReferenceIsRefused, :TestRenameCapturingAFeatureChainMemberIsRefused, :TestRenameCapturingByAnAliasForItselfIsRefused, :TestRenameCapturingAQualifiedSegmentIsRefused, :TestRenameCapturingAQualifierWithoutTheSuffixIsRefused, :TestRenameLeavingAQualifiedSegmentAmbiguousIsRefused, :TestRenameShadowingAtAReferenceIsRefused ✅ Faithful — rewriting a reference where the new name already means something else would silently rebind that reference, which re-analysis cannot catch because the name still resolves. Checked per rewritten reference in addition to the declaration's own scope (next row)
A rename to a name that already means something where the element is declared is refused rename/rename.go Check, taken (Resolver.LookupNameExcluding from the element's own scope, with the element's own binding hidden), FailureInvalidName edit_test.go:TestRefusals (new name is a sibling's name), :TestRenameShadowingAnOuterNameIsRefused ✅ Faithful — a sibling of that name makes the qualified name ambiguous, and a name reached through an enclosing namespace, an import or inheritance would be shadowed, so every expression reading it would silently read the renamed element instead. Re-analysis cannot catch either, since the name still resolves. The refusal is conservative: a rename onto any name visible at the element's position is refused, even where shadowing would have been intended
A declaration is moved into another namespace of the same document by removing the span a delete removes and writing it where an add member would, re-indented; the references the move breaks are respelled by the shortest qualified name that still reaches the declaration, a reference inside the moved declaration included, and an import made redundant or dangling is dropped or respelled edit/move.go Model.moveSplices, Model.carried, mover.follow, mover.respell (Resolver.PartSymbol to find the segment the move breaks, mover.trial reading each candidate spelling), edit/delete.go Model.deleteSpan, edit/add.go Model.memberInsertion move_test.go:TestMove* (nested bodies and comments, a bodyless owner, the document root, qualified and local references, references inside the moved declaration, imports, quoted names), grpc/authoring_test.go, lsp/modeledit_test.go:TestApplyModelEditMoves*, client/python/tests/test_edit.py ✅ Faithful for one document — refused as owner-inside-target, illegal-kind (parser.BodyAdmitsMember), member-name-taken, move-referenced when no spelling reaches the moved declaration from a reference (a feature chain through it included), and referenced-elsewhere while another document refers to the target or anything within it; a cross-document move is not an operation
Documentation is written as doc [name] [locale "..."] /* body */, the first member of a new member's body (AddMemberEdit.doc) or of an existing declaration's (add_documentation), a declaration ending in ; gaining a body; the body is plain text written with * continuation lines under the owner's indentation, read back exactly as Comment::body under KerML §8.2.3.3.2's body processing (white space at either end and empty text included, a line whose text would be stripped written after an opening line break and a * margin), refused only when it contains */ — COMMENT_LINE_TEXT excludes it and the notation has no escape — or a carriage return, which the processing reads as a line break edit/add.go Model.addMemberSplice, edit/documentation.go Model.addDocumentationSplice, documentationText edit/documentation_test.go:TestAddMemberWritesDocumentationBody, :TestAddMemberWritesMultilineDocumentationUnderOwnerIndent, :TestAddDocumentationOpensBodyOfBodylessDeclaration, :TestAddDocumentationPrecedesExistingMembers, :TestAddDocumentationToRelationshipDeclarations, :TestAddDocumentationWritesNameAndLocale, :TestAddDocumentationRefusesExistingDocumentation, :TestAddDocumentationReplacesTheOneDocumentation, :TestAddDocumentationReplaceKeepsVisibility, :TestAddDocumentationReplaceRefusesAmbiguity, :TestAddDocumentationRefusals, :TestAddDocumentationBodyReadsBackExactly, grpc/authoring_test.go:TestApplyEditsDocumentationRoundTrip, client/python/tests/test_edit.py:TestEditRoundTripAgainstRealService.test_the_toaster_tutorial_documentation_is_built_by_the_editor ✅ Faithful
A comment is written as comment [name] [about a, b] [locale "..."] /* body */ where a new member of its owner goes, a declaration ending in ; gaining a body and an empty owner naming the document root; a name is refused when it is taken in the owner, an about name when it is not a qualified name or names nothing from the owner's scope, and the body follows documentation's rules edit/comment.go Model.addCommentSplice, edit/documentation.go commentBodyText, source/comment.go CommentText edit/comment_test.go:TestAddCommentAtTheRoot, :TestAddCommentInABodyWithNameAboutAndLocale, :TestAddCommentOpensABodylessOwner, :TestAddCommentInKerML, :TestAddCommentBodyReadsBackExactly, :TestAddCommentRefusals, grpc/authoring_test.go:TestApplyEditsCommentAndNoteRoundTrip, tests/export/comment_body_roundtrip_test.go, conformance apply_edits/comments_and_notes_are_written, client/python/tests/test_edit.py ✅ Faithful
A line note (// text, KerML §8.2.2.2 SINGLE_LINE_NOTE) is written on its own line above a named declaration at its indentation; the note is trivia, not a model element, so it reaches the edited source and sysx:sourceText but not the model, and it stays above its declaration through later renames, additions, moves and deletions of other members; text containing a line break is refused edit/comment.go Model.addNoteSplice edit/comment_test.go:TestAddNoteAboveAMember, :TestAddNoteBeforeAnInlineMember, :TestAddNoteAtTheRootAndOnPrefixedMembers, :TestAddNoteSurvivesReparsingAndLaterEdits, :TestAddNoteRefusals, grpc/authoring_test.go:TestApplyEditsCommentAndNoteRoundTrip, client/python/tests/test_edit.py ✅ Faithful
The edited source is re-lexed, re-parsed and re-analysed, and content is not returned when the edit introduced an error — a value that parses but names nothing included edit/validate.go validateResult (parser + passes.Analyze over the edited document in an index built for it), reported as FailureResultInvalid with the diagnostics edit_test.go:TestResultInvalidCarriesDiagnostics, :TestPreExistingErrorsDoNotRefuseAnEdit (only errors the edit introduces refuse it), :TestSemanticValidationSkippedWithoutIndexSource, grpc/edit_test.go:TestApplyEditsRefusalIsAResponse (empty content on every refusal) ✅ Faithful — the service never returns notation its own parser cannot read back
Every refusal is a typed failure kind, never a silent no-op, and a refused request applies nothing edit/errors.go Error/Failure, api/proto/sysml.proto EditFailure, client/python/opensysml/errors.py (EditError and its subclasses) edit_test.go:TestRefusedEditLeavesNothingApplied, grpc/edit_test.go:TestApplyEditsRefusalIsAResponse, client/python/tests/test_edit.py, test_wire_compat.py:test_edit_failure_kinds_keep_their_values ✅ Faithful
A model evicted from the service's cache is NOT_FOUND, as Convert reports it internal/frontend/grpc/edit.go Service.ApplyEdits grpc/edit_test.go:TestApplyEditsUncachedModelIsNotFound, client/python/tests/test_edit.py (ModelNotFoundError) ✅ Faithful
Building a model from the client — — ⛔ Not supported (deliberate): an edit changes the source of a model that already declares what it declares, and there is no AST printer, so a tree assembled client-side cannot be written out. The four operations are span splices over the bytes a parse recorded, which is what makes every unedited byte identical; an assembly API would need the printer instead, and a printer is a different guarantee (it reformats) from the one this surface makes. A client that wants to construct a model has one supported path today: write the RDF graph and Convert it to notation, within the mapped subset (the RDF mapping)
The client negotiates the capability before calling client/python/opensysml/capabilities.py CAPABILITY_APPLY_EDITS, connection.py Connection.apply_edits client/python/tests/test_edit.py (MissingCapabilityError, raised before any RPC) ✅ Faithful

gRPC Service Layer

Implementation: internal/frontend/grpc/service.go
Status: ✅ Functional, ✅ §5.2 test contract satisfied for the wrapper

Runtime RPC Handlers

RPC Implementation Status Tests
GetServerInfo service.go Service.GetServerInfo, capabilityAvailability ✅ Faithful — reports the build version (informational; a source build reports dev) and this service instance's capabilities in append-only canonical order: type_facts, convert, verification, query, oslc_query, enum_values, evaluate_subject, symbol_attributes, unset_value, feature_values, apply_edits, authoring, inline_language, strict_conformance, document_query, render_document, parse_sources, complex_values, structured_values, measurement_refs, function_values, set_values, tensor_values, verification_verdicts, infinity_value, diagnostic_codes, schedule, case_evaluations, schedule_explore, final_time, engines, metaobject_values, undetermined_value, engines_external, edit_documents, performer, render_document_html, connection_authoring, satisfy_authoring, requirement_constraint_authoring, member_modifiers, transition_authoring, verification_objective_authoring, metadata_authoring, sequence_authoring, implicit_parameters, constraint_body_authoring, state_action_authoring, import_authoring, documentation_authoring, comment_authoring. The same availability object drives request refusal and response population, so a capability cannot be advertised while withheld or withheld while advertised. A service predating this RPC answers UNIMPLEMENTED, which the client reads as supporting no capability service_test.go:TestGetServerInfo, capability_test.go:TestCapabilityAvailabilityDrivesAdvertisementAndRefusal, client/python/tests/test_capabilities.py
ParseFile service.go Service.ParseFile (parser + passes.Analyze + stdlib load) ✅ Faithful — the cache is keyed by the file name and content the service read, so repeated parses of an unchanged source hit it whatever the request's (ignored) content_hash says, a hash disagreeing with its content cannot serve another model, and identical content read under two names keeps a record each, since their diagnostics name different files. An explicit inline language requires inline_language, and strict_conformance=true requires strict_conformance; an unavailable requested field is UNIMPLEMENTED, while an unset field retains the default parse runtime_test.go:TestParseFile_*, service_test.go:TestParseFileCachesByContentRead, service_test.go:TestParseFileCachesPerFileName, capability_test.go, conformance parse cases
ParseFile (standard library) grpc/libindex.go libraryBase, buildLibraryIndex, indexPrewarmFromEnv (OPENSYSML_GRPC_INDEX_POOL, positive prewarms, 0 disables), symbols/layer.go, symbols/index.go Freeze/NewOverlay, libs/shared.go SharedBase/NewModelIndex, grpc/service.go Service.Prewarm/Close/ParseFile ✅ Faithful — the library does not depend on the model and is immutable once loaded, so it is built once, frozen, and read by every model through an overlay holding that model's own document. What a model resolves against is unchanged (same source list, same expansion, same persist step, same Index.Library marking); a model writes only into its overlay, so no two cached models see each other's documents and eviction removes only the evicting model's state; a request arriving before the shared index exists builds it, so a result never depends on prewarming. A cold ParseFile on examples/combined-behavioral-demo.sysml measures ~0.5–0.9 ms against ~100–128 ms building the library per model, and 100 cached models cost ~1.1 MiB rather than ~1598 MiB grpc/libindex_test.go:TestSharedIndexMatchesFreshlyBuiltIndex (identical diagnostics and identical qualified lookups over the whole index, shared vs built inline), :TestParseFileServesEveryModelFromOneLibraryIndex, :TestParseFileTakesNoIndexOnACacheHit, :TestCachedModelsOwnTheirIndex, :TestLibraryBaseFallsBackToBuildingInline, :TestLibraryBaseCloseReleasesTheIndexAndStillServes, :TestIndexPrewarmFromEnv, :TestLibraryBaseBuildsOnceUnderConcurrentDemand, symbols/overlay_test.go:TestOverlayEqualsAnIndexBuiltWhole, :TestOverlayAnswersEveryLookupAsAnIndexBuiltWhole, :TestOverlayRemovalOfABaseDocumentLeavesTheBaseIntact, :TestOverlaySuppressesAnAmbiguatedBaseImport, :TestConcurrentOverlaysDoNotSeeEachOther, :TestFrozenIndexRejectsWrites, libs/shared_index_test.go:TestModelsOverASharedLibraryAgreeWithModelsOverTheirOwn, :TestLibraryMarkingReadsThroughTheSharedBase, :TestAModelOverTheSharedBaseCostsFarLessThanItsOwnIndex, robustness_test.go:parse_with_unavailable_standard_library, BenchmarkParseFileColdShared/ColdInline
Convert export.go Service.Convert, conversion in internal/translate/convert and internal/translate/export ✅ Faithful for what OpenSysML writes — SysML/KerML notation, RDF Turtle and the API's JSON element form (api-json), from a loaded model named by its model_hash, a path the service opens, or inline content, with the format names sysml -convert takes and canonical names reported back. A model_hash converts the source that parse read, so a file edited since then does not change what is written, and a model evicted from the cache is NOT_FOUND rather than converted as something else; a file_path is read afresh, for a caller who does want the file as it stands. Notation to notation is source-preserving (comments and layout survive); a graph direction returns an equivalent model, not identical bytes, and drops comments, per docs/reference/rdf-mapping.md. A conversion that cannot be written faithfully returns error plus the diagnostics rather than partial output, and tolerate_syntax_errors is honored for notation to notation only, since a graph built from an unparsed declaration would lose it silently export_test.go:TestConvert*, TestConvertModelHashConvertsWhatWasParsed, TestConvertUncachedModelHashIsNotFound, client/python/tests/test_conversion.py
Query query.go Service.Query (SysML v2 API & Services Query) ✅ Faithful to the standard's query model and to every @type name it reports — see the construct map below query_test.go:TestQuery*, w6g4_query_type_test.go, client/python/tests/test_query.py
RunDocumentQuery docquery.go Service.RunDocumentQuery (queryplan.Compile + queryexec.Execute over heldObjects.queryContext), documentBindings, boundValue, rowSetResponse; objects.go heldObjects ✅ Faithful — runs a named document query (a calc def specializing DocumentQueries::Query) on a cached model, in the model's runtime and over the objects it holds, with typed parameter bindings, answering the projected column names and each row's element with its typed cell values (element by qualified name with its metamodel type, object by id, path and usage, string, integer, real, boolean, unbounded), in the engine's deterministic order. Requires document_query; an unknown model or query is NOT_FOUND, a non-query symbol, an unknown/missing/mistyped binding or an unnamed one is INVALID_ARGUMENT, an exhausted visit or invocation budget is RESOURCE_EXHAUSTED, and an operation the engine does not execute is FAILED_PRECONDITION — each with the engine's message, and provenance appended where the failure carries one docquery_test.go:TestRunDocumentQuery*, objects_test.go:TestInstantiateHoldsObjectsForQueries, :TestObjectsEnumeratesHeldObjects, :TestVerdictsOverHeldObject, conformance document_query_object_by_id, document_query_object_by_path, document_query_object_row, document_query_objects, document_query_verdicts, client/python/tests/test_document.py
RenderDocument docquery.go Service.RenderDocument (docplan.Compile + docir.Evaluate + docrender.Markdown or docrender.HTML) ✅ Faithful — compiles a named document definition (a part def specializing DocumentQueries::Document), runs its queries and answers the rendered CommonMark Markdown, byte-identical to %render-document and -render-document for the same model, or with form: html the HTML backend's standalone page in html, byte-identical to -doc-form html; another form is INVALID_ARGUMENT, and PDF is not offered, since it needs the CLI's converter toolchain. A document binds its queries' parameters in the model, so the name and form are the whole request. Requires render_document, and render_document_html for the HTML form; error mapping as RunDocumentQuery, with planning and evaluation failures mapped by their typed engine categories. The document's queries run over the objects the model holds, as -render-document runs beside -instantiate: after an Instantiate the document reports the objects' current values by path docquery_test.go:TestRenderDocument*, objects_test.go:TestRenderDocumentReadsHeldObjects, client/python/tests/test_document.py
GetSymbol service.go:126-145; static type facts in typefacts.go (SymbolInfo.type_info, .multiplicity, .specializations) computed by a per-model resolver + semantics context cached on the model and locked for the duration of a conversion ✅ Faithful — reports the declared and resolved type, the library scalar it reduces to, quantity/unit, the declared multiplicity, and every generalization edge (specializes, subsets, redefines, typing) in declaration order; an unresolved name is reported unresolved rather than guessed. SymbolInfo.attributes carries the attributes the element actually has — own and inherited, in that order, a redefinition masking what it redefines — each with the resolved type, unit and constant default the service resolves (following specializes/subsets/redefines for what a declaration leaves out); a default that is not constant is reported as absent rather than guessed. Advertised as the symbol_attributes capability, since an older service reports an empty set, which cannot be told from an element with no attributes service_test.go:TestGetSymbol_, typefacts_test.go:TestTypeInfo, TestSpecializations*, TestMultiplicity*, TestSymbolContextConcurrentConversion, attributes_test.go, conformance symbol_attributes, client/python/tests/test_symbol.py
GetDiagnostics service.go:148-169 (parser + semantic) ✅ Faithful runtime_test.go (implicit)
Evaluate service.go Service.Evaluate ✅ Faithful — evaluates in a lexical scope (context_symbol_id) and, with subject_symbol_id, against an instantiated object, the way %eval does after %instantiate: a feature then reads that object's feature value rather than the declared default, and the subject's own scope resolves inherited features when no context is named. A subject that is not a symbol, or that cannot be instantiated, is reported in-band rather than evaluated as something else; no subject leaves the existing behaviour unchanged. Naming a subject requires evaluate_subject; a service without it refuses that request with UNIMPLEMENTED runtime_test.go:TestEvaluate_*, TestEvaluateWithSubject*, capability_test.go, conformance evaluate_arithmetic, evaluate_subject_slot, evaluate_no_subject_default, evaluate_subject_not_found, client/python/tests/test_model_surface_integration.py
Instantiate service.go (feature values read through Instance.GetFeatureValue, so a derived default is evaluated against the instance; InstanceGraphToProto in convert.go returns every instance reachable from the root in InstantiateResponse.instances) ✅ Faithful — a composite feature value still marshals as the child's id, and that child is carried in the same response, so a nested object is reachable over gRPC without a follow-up RPC; expansion is bounded at depth 8 and stops at a type already on the path, as %features bounds it, so a self-referential part cannot instantiate forever. The object is created in the model's one persistent runtime and held, under the qualified name it was instantiated as, for as long as the model stays cached — ids count on across calls, and instantiating a name again makes it denote the new object while the earlier one stays held by id — so RunDocumentQuery, RenderDocument and ValidateInstance reach it afterwards runtime_test.go:TestInstantiate_*, objects_test.go:TestInstantiateAgainKeepsTheEarlierObject, :TestObjectBindingsRunConcurrently, instance_graph_test.go:TestInstantiate_ReturnsNestedInstances, _ReturnsDeepNestedInstances, _CollectionOfInstances, _FeatureValueErrorReported, _SelfReferentialPartTerminates, _MutuallyRecursivePartsTerminate, conformance instantiate_part, instantiate_derived_slot
ExecuteAction service.go:265-312 ✅ Faithful runtime_test.go:TestExecuteAction_*, conformance execute_action_inputs, execute_action_no_initial
RunAnalysis analysis.go Service.RunAnalysis (runtime.RunAnalysis over the model's verification context, the subject instantiated as VerifyRequirement instantiates one) ✅ Faithful — runs an analysis definition or usage named by symbol_id, with subject_symbol_id as its subject where the usage binds none, arguments bound to its in parameters positionally and named_arguments by name; answers the case's out/return values as CalcOutputs with their units, each objective and assertion as a Verdict (holds, or error carrying why it is undecided), the subject's instance graph, and on refusal an error with failure_reason — WRONG_KIND for a symbol that is not an analysis, AMBIGUOUS_SUBJECT, EVALUATION for an unbound subject or parameter, a failing step, a deadlocked body or an exhausted budget. Requires verification; an unknown model is NOT_FOUND. Exposed in the Connect adapter, the Go client (Client.RunAnalysis with Subject/Arguments/Argument options) and the Python client (Model.run_analysis answering an AnalysisResult) analysis_test.go:TestRunAnalysis*, capability_test.go, conformance 13-run-analysis.json (8 scenarios), client/python/tests/test_analysis_integration.py
ExecuteState service.go:315-355 ✅ Faithful runtime_test.go:TestExecuteState_*, conformance execute_state_transitions
ValidateInstance validate.go Service.ValidateInstance (runtime.Context.ValidateObject over an object of symbol_id, instantiated as VerifyConstraint instantiates one) ✅ Faithful — answers one Verdict per assertion about the object and the objects it holds, each labelled with instance_id, instance_type_id and the root-relative instance_path (wheels[2]), then a summary verdict of kind object that holds only when every assertion holds and the walk was complete — bounded reports a walk cut short, and an undecided summary carries error with FAILURE_REASON_EVALUATION; the object's instance graph in instances, verification-case verdicts in verification_verdicts, and on refusal an in-band error (an empty or unknown symbol, or an object that could not be built). Requires verification; an unknown model is NOT_FOUND. Exposed in the Connect adapter, the Go client (Client.ValidateInstance answering a Validation with Valid()/Violated()) and the Python client (Model.validate_instance answering a Validation) validate_test.go:TestValidateInstance, capability_test.go, engines_test.go, conformance 09-verify.json, client/opensysml/surface_test.go:TestValidateInstance, client/python/tests/test_verification.py, test_model_surface_integration.py
ListEngines internal/frontend/grpc/engines.go Service.ListEngines, engineSelection, CapabilityEngines (over analysis.Default().Listings()) ✅ Faithful — lists the analysis engines of the build in name order with the authority, questions, bounds and process status of each; the engine field of the verification, calculation, analysis and sweep requests selects the engine (unset is auto, a set value requires engines, an unknown name is INVALID_ARGUMENT), and their responses carry engine, strength and bounds. See Analysis Engines below engines_test.go:TestListEnginesNamesEveryEngine, :TestEngineFieldNeedsTheEnginesCapability, :TestAnUnknownEngineIsInvalidArgument, :TestVerdictsCarryEngineStrengthAndBounds, :TestNamedEngineRefusalIsFinalOverTheWire, :TestEngineExploreIsScheduleExplore, client/python/tests/test_engines.py
ApplyEdits verification and metadata authoring internal/frontend/grpc/edit.go requestsVerificationObjectiveAuthoring, requestsMetadataAuthoring, requestsMetadataPrefixAuthoring, editOperations; internal/check/edit/verify.go Model.addVerifySplice, metadata.go Model.addMetadataSplice, prefix.go Model.addMetadataPrefixSplice, add.go Model.addMemberSplice/writeMember ✅ Faithful — add_verify writes into or creates a verification objective; add_metadata writes long or shorthand metadata usages with values; add_member.metadata_prefixes and add_metadata_prefix write prefixes in the grammar-admitted position, the latter on an existing declaration and resolving metadata types in that declaration's scope. Verification and unnamed objective edits require verification_objective_authoring; metadata usages and new-member prefixes require metadata_authoring; existing-declaration prefixes require metadata_prefix_authoring, alongside authoring internal/frontend/grpc/authoring_test.go, internal/frontend/grpc/capability_test.go, internal/check/edit/authoring_test.go, internal/check/edit/prefix_test.go, client/python/tests/test_edit.py, client/python/tests/test_wire_compat.py, client/opensysml/authoring_internal_test.go, Java PublicTypesTest/EditProtosTest, tests/grpc/conformance_test.go, Java conformance Api.java
ApplyEdits internal/frontend/grpc/edit.go Service.ApplyEdits, engine in internal/check/edit ✅ Faithful for its operations — sets feature values and renames declarations; adds members with constraint bodies, asserted constraints and anonymous reference-form assert/assert not, calc/case/analysis/verification/use-case result expressions, documentation, exhibits and state subactions; adds connection-like usages, satisfy usages, requirement constraints, state transitions, first/then action sequencing members, imports, comments, line notes, and metadata prefixes on existing declarations; deletes declarations and moves them into namespaces. Edits splice the reached source spans and leave every other byte identical. Reference-form assertions validate their feature reference through post-edit analysis, which requires a constraint target; they do not acquire a member name. This matches pilot ConstraintUsage_namingFeature: absent a RequirementConstraintMembership, the first owned redefinition supplies a name, which assert <ref> does not have. Requirement-body kinds refuse result expressions because their grammar has no result-expression member. The edited source is re-parsed and re-analysed before it is returned, so a refusal carries diagnostics and no content; a model evicted from the cache is NOT_FOUND. A rename rewrites references as well as its declaration, and a non-cascade deletion of a referenced element is refused — see Source-Preserving Model Editing below. Every call requires apply_edits; authoring edits additionally require authoring. Connection, satisfy, requirement-constraint, transition, sequence, modified-member, implicit-directed-usage, constraint-body/assertion, state-action, import, documentation, comment, and metadata-prefix edits additionally require connection_authoring, satisfy_authoring, requirement_constraint_authoring, transition_authoring, sequence_authoring, implicit_parameters, member_modifiers, constraint_body_authoring, state_action_authoring, import_authoring, documentation_authoring, comment_authoring, and metadata_prefix_authoring, respectively. An AddMember carrying both doc and a body expression requires both capabilities; missing requirements are UNIMPLEMENTED internal/frontend/grpc/edit.go, internal/frontend/grpc/edit_test.go, internal/check/edit/edit_test.go, capability_test.go, client/python/tests/test_edit.py, client/python/tests/test_wire_compat.py:test_apply_edits_is_an_added_rpc

Server Process Lifecycle (cmd/sysml-grpc)

Rule Implementation (file:function) Tests Status
The published binary starts on the port it is given (-port 0 taking an ephemeral one), logs the address it listens on, serves RPCs, and stops on SIGINT/SIGTERM with a graceful stop and exit status 0 cmd/sysml-grpc/main.go main (net.Listen, then grpc.Server.GracefulStop on the signal, bounded by a 30s forced Stop) cmd/sysml-grpc/lifecycle_test.go:TestServiceServesRPCsAndShutsDownCleanly (built binary as a process, readiness from its own listening line, GetServerInfo → ParseFile → Instantiate), :TestShutdownWithAnOpenConnection, :TestHealthEndpointReportsTheBuild ✅ Faithful — the process ends on its own; a client connection left open does not hold it
A start that cannot proceed is a reported error and a non-zero exit, never a hang: an unknown flag, a port already in use, a cache size that is not a positive count cmd/sysml-grpc/main.go main (flag.Parse, sysmlgrpc.NewService, net.Listen) cmd/sysml-grpc/lifecycle_test.go:TestUnknownFlagExitsNonzero, :TestOccupiedPortExitsNonzero, :TestInvalidCacheSizeExitsNonzero ✅ Faithful — each names what failed on stderr and exits non-zero
A request naming something the service does not have — a file that does not exist, a model hash it never issued — is NotFound on that call, and the service keeps serving internal/frontend/grpc/service.go Service.ParseFile, Service.GetSymbol cmd/sysml-grpc/lifecycle_test.go:TestMissingModelIsATypedError (a later GetServerInfo is still answered and the process still exits 0) ✅ Faithful
/health answers on the main port and, while -health-port is still supported, on that port too; it reports the build, and no other path is served. The second listener logs a deprecation warning, and -health-port 0 does not bind it cmd/sysml-grpc/main.go healthHandler, main; connect.go connectHandler cmd/sysml-grpc/server_test.go:TestHealthHandlerAnswersOnlyHealth, lifecycle_test.go:TestHealthEndpointReportsTheBuild, :TestDefaultTransportServesConnectAndHealthOnMainPort, :TestSecondaryHealthPortLogsDeprecation, :TestHealthPortZeroServesHealthOnMainPortOnly ✅ Faithful
The default port serves gRPC, gRPC-Web and the Connect protocol together, so an existing gRPC client — a generated stub, grpcurl, the Python client — reaches it unchanged and a curl reaches it at all; -transport grpc still serves grpc-go alone cmd/sysml-grpc/main.go main, connect.go serveConnect cmd/sysml-grpc/connect_test.go:TestConnectServesEveryProtocol, :TestConnectServesGRPCClientsUnchanged, :TestConnectAnswersAPlainPOST, lifecycle_test.go:TestDefaultTransportServesConnectAndHealthOnMainPort, conformance/ under -protocols grpc,connect,connect-json ✅ Faithful — every scenario runs once per protocol, asserting identical results and identical status codes
A browser reaches the service only where it is configured to: -cors-allowed-origins names exact origins, * is refused at startup, and a listed origin's preflight is answered with the gRPC-Web trailer headers exposed cmd/sysml-grpc/cors.go parseCORSOrigins, corsMiddleware cmd/sysml-grpc/cors_test.go:TestCORSAllowsConfiguredPreflight, :TestCORSRefusesUnlistedPreflight, :TestCORSRejectsWildcard ✅ Faithful — CORS is a browser control, not authentication, and this service still has none
-tls-cert/-tls-key serve every protocol over TLS 1.2-or-later on the same port, negotiating h2 and http/1.1; without them the port is cleartext h2c. application/grpc-web-text is not implemented and answers 415 cmd/sysml-grpc/connect.go serveConnect cmd/sysml-grpc/connect_test.go:TestServeConnectTLS, :TestConnectRejectsGRPCWebText ⚠️ Approximate — grpc-web-text is unimplemented upstream in connect-go v1.20 and affects only a client that cannot read a binary body; a fetch client never asks for it
A JSON-encoded response large enough for protojson to dominate the call is warned about, naming the procedure, and is otherwise unaltered cmd/sysml-grpc/connect.go connectLoggingInterceptor cmd/sysml-grpc/connect_interceptor_test.go:TestConnectLoggingInterceptorWarnsForLargeJSONOnly ✅ Faithful — thresholds proto.Size rather than encoding a second time, so the check costs no marshal

SysML v2 API & Services Query Conformance

Standard: the Query, Constraint, PrimitiveConstraint and CompositeConstraint components of the SysML v2 API & Services OpenAPI schema (api/openapi.yaml in Systems-Modeling/SysML-v2-API-Java-Client). This is the only query surface the standard defines; the language has none.

Reference: docs/reference/api.md § "SysML v2 API & Services Query" documents the property table, the @type mapping and the comparison choices.

Construct Implementation (file:function) Tests Status
Query.scope — elements considered, empty being the whole model query.go:queryEval.candidates, elementWalk TestQueryScopeRestrictsToAnElementAndItsNested, TestQueryWithoutWhereSelectsWholeScope, TestQueryScopeMayNameALibraryElement ✅ Faithful — a scope entry is a qualified name (or the standard's {"@id": …} reference, translated client-side) and covers that element and everything nested in it, parents first in declaration order. A library element may be named, so the loaded stdlib is queryable
Query.select — properties projected query.go:projectedProperties, queryEval.project TestQuerySelectProjectsOnlyThoseProperties, TestQuerySelectReportsEveryPropertyByDefault, TestQueryOmitsPropertiesAnElementDoesNotHave ✅ Faithful — an empty selection reports every queryable property; a property an element does not have is absent rather than empty
Query.where absent query.go:queryEval.matches TestQueryWithoutWhereSelectsWholeScope ✅ Faithful — selects the whole scope
PrimitiveConstraint with = query.go:queryEval.matchesPrimitive, equalsAny TestQueryByTypeSelectsThatMetamodelType, TestQueryEqualMatchesAnyOfAListedValue, TestQueryIsAbstractSelectsAbstractDefinitions, TestQueryTypePropertyReportsResolvedType ✅ Faithful — textual equality, matching any listed value, which is how the standard's clients write a @type filter
PrimitiveConstraint with > / < query.go:validateOrdered, compareOrdered, parseOrdered TestQueryOrderedComparisonOnMultiplicity, TestQueryOrderedComparisonOnUnorderedPropertyFails, TestQueryOrderedComparisonAgainstNonNumberFails, TestQueryOrderedComparisonNeedsOneOperand ✅ Faithful, with documented choices — numeric, one operand, ordered properties only (multiplicityLower/Upper); * is infinity. A non-ordered property, an unparsable operand or more than one operand is INVALID_ARGUMENT, never a false verdict
PrimitiveConstraint.inverse query.go:queryEval.matchesPrimitive TestQueryInverseNegatesTheVerdict ✅ Faithful — negates its own constraint's verdict, so a constraint and its inverse partition the scope
CompositeConstraint with and / or, nested query.go:queryEval.matchesComposite, validateComposite TestQueryCompositeNesting, TestQueryFaultUnderADecisiveConstraintIsReported ✅ Faithful — nests arbitrarily and short-circuits when evaluating, but every nested constraint is validated first, so a malformed one under an already-decisive sibling is still reported
Property names — @id, @type, name, declaredName, shortName, declaredShortName, documentation, qualifiedName, owner, isAbstract, type, multiplicityLower, multiplicityUpper internal/semantic/query/query.go:propertyNames (single source of truth), PropertyNames; properties.go:PropertyReader.Values TestQuerySelectReportsEveryPropertyByDefault, TestQueryUnknownPropertyFailsRatherThanMatchingNothing, TestQuerySelectUnknownPropertyFails ✅ Faithful for the set implemented — the set is closed and an unknown property is a typed QueryError (INVALID_ARGUMENT) listing the ones that exist, never an empty answer. Other metamodel properties (isComposite, …) are not queryable: known limitation
Element::shortName, Element::declaredShortName (KerML 1.1 §8.2.4) semantics/masking.go:EffectiveShortNameOf, namingFeature, inheritedIdentifier (shared with EffectiveNameOf); semantics/annotations.go:ReflectiveFeatureValues; query/properties.go semantics/documentation_test.go TestEffectiveShortNameIsTheDeclaredOne, TestEffectiveShortNameFollowsTheRedefinedFeature, TestEffectiveShortNameFollowsTheReferencedFeature, TestEffectiveShortNameStopsAtADeclaredName, TestEffectiveShortNameOfAReferenceOutranksItsRedefinition; queryexec/documentation_test.go TestExecuteProjectsShortNameAndDocumentation, TestExecuteOrdersByShortName, TestExecuteFiltersOnShortNameAndDocumentation, TestExecuteComputesElementShortNameAndDocumentation, TestExecuteShortNameFollowsAReferenceSubsetting, TestExecuteShortNameIsNotDerivedForANamedFeature ✅ Faithful — the declared short name governs, and a feature that declares a name of its own derives no short name (shortName is derived only where declaredName and declaredShortName are both absent); a feature declaring neither takes the short name of the feature it reference-subsets or, failing that, the one feature it redefines, the same naming feature effectiveName follows. An element without one projects as absent, never as an empty string
Element::documentation (KerML 1.1 §8.2.4), body text of the element's owned Documentation semantics/documentation.go:DocumentationOf, SetSourceText; lexer/comment.go:CommentBody (shared with LSP hover); query/properties.go; queryexec/computed.go Element::documentation semantics/documentation_test.go, lexer/comment_test.go, queryexec/documentation_test.go, grpc/query_test.go TestQuerySelectReportsEveryPropertyByDefault ✅ Faithful — every owned doc body, in declaration order, with the /* */ delimiters, indentation and the * margin (a * opening every continuation line) removed and line breaks kept, so an authored * — emphasis, a bullet — survives; an element with none projects as absent. The single-valued Query RPC and OSLC surfaces report the first body; document queries carry every body as a multi-valued cell
@type — element → metamodel type name query.go:metamodelTypeNames (per kind), MetamodelTypeNameOf (per element: connectorEndTypeName, kermlTypeName) TestMetamodelTypeNameCoversEveryKind, TestQueryByTypeSelectsThatMetamodelType, w6g4_query_type_test.go:TestQueryTypeOfConnectorEnds, :TestQueryTypeOfKerMLTypes, :TestQueryTypeOfSatisfyRequirement, :TestMetamodelTypeNameCoversEveryKindDeclared ✅ Faithful — every name is the metaclass the pinned grammar's production returns. The three previously called approximate are exact: IndividualDefinition returns SysML::OccurrenceDefinition (an individual is an occurrence with isIndividual set, and the metamodel has no individual class), AliasMember returns SysML::Membership, and a connector end is ConnectorEnd returns SysML::ReferenceUsage — refined to PortUsage on an interface, whose ends are InterfaceEnd returns SysML::PortUsage. kerMLType is refined from the declaration's keyword (Class, Structure, Association, Behavior, Predicate, Interaction); a library kerMLType reports the same @type cold and warm because every load path parses the library document, so a restored symbol carries its declaration
Malformed query — no constraint form, no operator, no operand, empty composite query.go:QueryError, validateConstraint, Service.Query TestQueryMalformedConstraintsFail, TestQueryUnsetQueryFails, TestQueryUnknownScopeFails, TestQueryFaultIsReportedWithNoElementsToConsider ✅ Faithful — the where tree is validated before any element is read, so every malformed shape fails with INVALID_ARGUMENT naming what is wrong however few elements the scope holds; an unknown scope is an error, not an empty answer
Empty result query.go:Service.Query TestQueryMatchingNothingIsEmptyNotAnError, client/python/tests/test_query.py ✅ Faithful — a well-formed query selecting nothing answers with no elements
Capability negotiation service.go:CapabilityQuery, CapabilityOSLCQuery, client/python/opensysml/capabilities.py:CAPABILITY_QUERY capability_test.go, conformance query/an_oslc_text_query_is_parsed, client/python/tests/test_query.py:test_query_requires_the_capability ✅ Faithful — every Query requires query; a request setting oslc_query additionally requires oslc_query. Either missing requirement is UNIMPLEMENTED, while a structured query does not require OSLC support
Standard JSON payload accepted verbatim client/python/opensysml/query.py:build_query, Model.query client/python/tests/test_query.py (test_cookbook_payload_translates_verbatim and the malformed-payload table) ✅ Faithful — a cookbook payload is sent unchanged; @type tags, symbolic operators and scalar-or-list value are translated to the RPC's oneof and enums, and a payload the standard does not describe raises QueryError before anything is sent

Known limitations (query):

  • No graph traversal and no transitive closure, by design of the standard: there are no path expressions, no joins, no "all elements under X" and no "everything specializing Y" constraint — containment is expressible only as a scope, and specialization not at all, even though semantics.Model can answer it. The Query RPC is an interop surface for the standard's clients, not OpenSysML's expressive query story.
  • Query.owningProject and Query.@id are accepted in a payload and ignored: this service holds parsed models, not projects or commits.
  • Results are unordered by the standard and are returned in declaration order here; there is no paging.
  • Only the properties tabulated above are queryable; metadata annotations and derived metamodel properties other than the names, short names and documentation are not.
  • An element carries the value of a property it declares; an inherited value is not reported (type reports the resolved type of the feature itself).
  • An element with no qualified identity — an unnamed doc, an anonymous usage or connect — is not answered at all: its qualified name has an empty segment, so it is neither a unique @id nor a name a scope could use (TestQueryOmitsElementsWithNoQualifiedIdentity). Nor is one declared inside an action body — an if branch, a loop body — whose owner-less scope names it only locally, so the name resolves to no element (TestQueryOmitsBodyLocalDeclarations).

Test Coverage (AGENTS.md §5.2 Four-Layer Contract)

Current: - ✅ Layer 1 (Golden AST): Covered via parser tests (fixtures in tests/parser/testdata/) - ✅ Layer 2 (Execution conformance): tests/grpc/conformance_test.go drives Evaluate, Instantiate, ExecuteAction, ExecuteState and GetSymbol from .sysml + .expected.json pairs in tests/grpc/testdata/conformance/ (count in the Test Coverage list near the top; three of them failure modes), each parsed through the ParseFile RPC so the whole wrapper is exercised. Schema: that directory's README.md. - ✅ Layer 3 (Golden traces): N/A — the wrapper adds no ordering behavior of its own; traces are pinned at the runtime tier. - ✅ Layer 4 (Robustness): internal/frontend/grpc/robustness_test.go covers the wrapper's failure modes (unknown model hash, unknown symbol, malformed expression, a standard library that did not load); execution-level failure modes stay pinned in internal/exec/runtime/robustness_test.go.

Rationale: the gRPC layer is a protocol wrapper over internal/exec/runtime, which carries full §5.2 compliance for execution semantics. Its own conformance cases assert what the wrapper is responsible for: symbol lookup by FQN, input binding, value marshalling in both directions (including which Value oneof arm is set), the state-visit trace, and in-band error reporting.

Known Limitations (Non-blocking)

Runtime: - entering a composite state runs its own entry body before the region's initial transition reaches the substate, so a parent's do body can run before a substate's entry body (state_anonymous_action_body.trace.golden). Pre-existing region-entry scheduling, not specific to inline bodies - an entry/do/exit behavior that both performs an action and states a body of its own is reported at execution rather than at parse time: which of the two SysML means is unadjudicated, so neither is chosen - a calc output only a branch that did not run would assign is unassigned for that activation; the body is not statically required to bind every output it declares - a calc whose only computation is rebinding an inout in its body, with no out and no return, is still reported as having no result expression: an inout is bound by the invocation, so it does not count as an output the body computes - an object carried over an unrelated declaration keeps its identity but not its execution: an execution belongs to the analysis it started in, so its behaviors are started again from their initial states in the rebuilt analysis, what the discarded run wrote is dropped, and the restart is reported. Re-declaring what the object runs drops the object itself with a reported reason instead (runtime/adopt.go Adopt/restartBehaviors, writeBoundBehaviors; repl/session.go rebindRestartedMachine; runtime/adopt_test.go:TestAdoptRestartsACarriedObjectsBehavior, :TestAdoptRefusesAnObjectWhoseBehaviorCannotRestart, repl/classifier_behavior_test.go:TestObjectMachineRestartsOverAnUnrelatedDeclaration, :TestRestartedMachineRunsInTheNewContext, :TestRewritingTheExhibitedMachineDropsTheObject). Tool-defined: the spec has no notion of re-analysing an edited model - a carried object is bound to the symbols the prompt resolves in, not the index's own for the same declaration: the runtime keys its occurrences by symbol, and the prompt resolves Demo::holder through the buffer's scope tree, so the carried object is what a feature chain through the part reads on every surface — %eval Demo::holder.n answers the 5 an action wrote and %features lists, holder.cells.rank reads the array behind the carried value, a debugger still stepping writes to it, and Demo::holder === Demo::holder holds — while a resubmission that changes the holder's declaration drops the object and every surface reports the loss (runtime/model.go RegisterScope/declaredSymbol, runtime/adopt.go adoption.rebind; repl/session.go getOrCreateRuntime; runtime/adopt_test.go:TestAdoptRebindsIntoTheScopeTreeTheCallerResolvesIn, repl/carryover_eval_test.go, cmd/sysml/carryover_test.go). Before this the object was rebound to the index's symbol, so %eval materialized a fresh, unwritten occurrence beside it - the Kernel frame roots a Systems library member may restate — Anything::self, Occurrence::timeSlices, incomingTransfers, outgoingTransfers — are named in runtime/library_frame.go frameRoots, since the library marks them no differently from Item::voids; a member of a Systems, Domain or OpenSysML library that redefines or subsets one of them is frame too, every other one is a feature of the object. So matingOccurrences and spaceBoundary are no features of a ShapeItems shape (ErrNoSuchFeature), and the assert constraint bodies of Path/Polygon that reach p1.matingOccurrences are not evaluated when a shape materializes - a ShapeItems feature derived from a partial binding — binding [1] bind [0..1] tf.edges = [0..1] tfe links one unspecified edge of tf to tfe — is ErrBindingEnd naming the binding, since neither the spec, the library nor the pinned pilot artifact (which answers the unevaluated usage node there) determines which edge; box.tfe…box.urre, box.tflv…box.brrv and so box.vertices are reported this way under %features box and %eval, while box.faces, box.edges (twenty-four), the per-face length/width, and every Rectangle/Triangle edge and vertex answer. The curved shapes bind cf : Surface, a Kernel struct whose edges are frame, so Cylinder::be/ae are ErrBindingEnd and edges/vertices ErrNoSuchFeature, while base.edges/af.edges answer each Disc's own edge: binding [1] declares one link, which relates that edge to be without binding be [2] whole - a nested body over a value the redefined declaration wrote governs over that value, but supersedes it whole: a feature the body does not value takes its type's own default rather than the bound value's, since a FeatureValue binds a feature as a whole

Stochastic execution (OpenSysML extension): - the random functions are scalar: a result takes a unit (uniform(1, 80) [s]), but quantity-valued bounds (uniform(1 [s], 80 [s])) are refused as type mismatches - a random accept after duration is drawn once, when the wait is established; a wait that is re-established (a state re-entered, a loop body run again) draws again - a weighted decision whose holding branches' weights total zero is refused rather than taken uniformly

Python bindings: - generated typed classes (opensysml.generate) cover structural usages only: behavioral and connector usages are not instance feature values, so no property is emitted for them. specializes, subsets and redefines all become Python base classes, in declaration order, and a redefining feature takes the type and multiplicity it does not restate from what it redefines; a base another declared base already specializes is left implicit, and a base order that linearizes no way at all keeps the bases it can and records what it left out as a comment, rather than emitting a module that fails to import. Redefinition narrowing is still not checked - TypedObject.from_instance rejects an instance whose type another generated class describes, and accepts a generated subclass of the expected one; it accepts a type no generated class describes, because Instantiate on a usage reports the usage's own FQN (Demo::myCar), which the client cannot relate to the definition typing it. A wrong-typed instance is therefore caught only when its type has a generated class of its own; unchecked(instance) bypasses the check deliberately - a service opensysml did not spawn is never stopped by it: attaching takes no ownership reference and writes no state, so only the spawning process stops the service it recorded, when its last connection is released. The record authenticates the pid it names by the process start time written with it, so a reused pid is cleaned up rather than signalled (connection.py:_write_ownership_record, _authenticate_record, client/python/tests/test_lifecycle.py, test_stale_service.py) - an instance_id outside an Instantiate response (an Evaluate result, say) is still a bare int64: those responses carry no instance graph to resolve it - init.py:11-16 - Shadows builtins (RuntimeError, eval) - a downloaded binary is verified against the digest the shipped release-digests.json (binary.py:PINNED_SHA256) pins for its release, independent of the origin that served it; a version with no pin fails rather than falling back to the served .sha256, unless $OPENSYSML_ALLOW_UNPINNED_DOWNLOAD names that repository (or is 1) and accepts same-origin trust. A opensysml release pins only service releases published before it, so a newer service needs a newer opensysml or that opt-in (scripts/pin_release_checksums.py, client/python/tests/test_binary.py)

Standard behavioral notation: - a succession written at namespace level (first part1::action1 then requirement1;) is parsed and carried with both ends, but there is no enclosing behavior to lower it into, so it does not execute - a succession end with no name is carried by identity, so a model whose succession has a positional end is reported as unsupported by the RDF export rather than written back - a state machine's change conditions are re-tested once per micro-step and again at quiescence, and fire on the condition rising, one rise being consumed by the poll that observed it. KerML gives no real time, so the cadence is a tool-defined choice, not a spec requirement: the coarser per-RTC-step alternative was considered and may be revisited. A machine that cannot progress reports which condition it waits on (StateExecutor.SuspendReason) rather than completing silently - a flow whose ends name no feature to carry (flow a to b; between two action nodes) and a flow whose end names something that is not a node of the action are reported when the graph is built: the notation needs a payload or a pin at each end - the Open-MBEE corpus models still report the two OMG-side notations adjudicated above (end ; outside an interface body, 'SysML Standard Diagrams'::gv) and unresolved library references in the notebook models (Scalarattributes::String, start, envelopingShapes, mRefs). The conjugated end and timeslice item item1 are legal and are accepted - what DesertKite.sysml (branch InitialDesign) and OOSEM.sysml report, and nothing else: the 'SysML Standard Diagrams'::gv sites above; attribute 'Animal Capture Rate' :>> OOSEM::MOE;, where MOE is the short name of a member of OOSEM::'OOSEM Measures' and so is not a member of OOSEM itself; and 3 references to the decision node __unnamed1 of 'Move with Herd', which resolve to nothing because a control node's name is not registered as a symbol (symbols/builder.go buildDecl)

Go gRPC layer: - SymbolInfo.attributes reports only defaults that fold to a model-level constant; one written as a feature reference or a call is reported absent rather than guessed (internal/frontend/grpc/attributes.go) - metadata["type"]/metadata["specializes"] still report only the first edge, kept for compatibility; specializations is the complete list - runtime instances are request-local, so an id is resolvable only against the response that carried it; there is no RPC that fetches an instance by id later - a quantity crosses in both directions: every outbound path reads one, and an ExecuteAction input or EvaluateCalc argument carrying one is decoded against the model's index (ProtoToValueIn), so a unit that does not resolve — or resolves to something that is not a measurement unit — is reported rather than bound as an unusable value. The Python client has no quantity encoder yet, so a caller cannot send one until that lands with the rest of the Python API surface

These are documented for transparency; none block production use.


Language Server (internal/frontend/lsp, cmd/sysml-lsp)

Standard: LSP 3.17 § Lifecycle Messages. Reference: docs/reference/api.md § internal/frontend/lsp.

Measured coverage: 424 top-level Test functions in internal/frontend/lsp, plus the built-binary lifecycle tests in cmd/sysml-lsp.

Rule Implementation (file:function) Tests Status
The server is started by an editor over stdin/stdout, and a client that names the transport on the command line (--stdio, as TransportKind.stdio sends) is served rather than rejected cmd/sysml-lsp/main.go run (explicit stdio flag; Go's flag accepts -stdio and --stdio) cmd/sysml-lsp/lifecycle_test.go:TestStdioTransportServesTheLifecycle (both spellings, built binary over pipes), cmd/sysml-lsp/main_test.go:TestCommandLine ✅ Faithful — the flag is a documented no-op because stdio is the only transport; every other unknown flag still exits 2 with usage, so a typo is not swallowed
shutdown is answered, and afterwards every request but exit is answered InvalidRequest (-32600); a non-exit notification is dropped internal/frontend/lsp/lifecycle.go Server.Shutdown, Server.lifecycleHandler (wraps the handler chain on the read loop, ahead of async dispatch) internal/frontend/lsp/lifecycle_test.go:TestAfterShutdownOnlyExitIsServed, :TestNotificationAfterShutdownIsDropped, cmd/sysml-lsp/lifecycle_test.go:TestRequestAfterShutdownIsInvalidRequest ✅ Faithful
Keyword completion offers the reserved words and the contextual ones — the words the parser reads as syntax positionally that Keywords() deliberately omits (point, chain, defer, …, plus var in .kerml) — per document language, and the VS Code grammars highlight the same set internal/syntax/lexer/contextual.go ContextualWords(source.Kind) (the one source of truth, disjoint from Keywords()); internal/frontend/lsp/completion.go Server.Completion; editors/vscode/tools/gengrammar/grammar.go repository, checkUnreserved (keywords-contextual rule; generation fails if a word is reserved as well) internal/frontend/lsp/f9_contextual_completion_test.go:TestCompletionOffersContextualWords, :TestCompletionOffersVarInKerMLOnly, :TestContextualWordsRemainUsableAsNames, internal/syntax/lexer/f9_contextual_test.go, editors/vscode/tools/gengrammar/grammar_test.go:TestContextualWordsAreHighlighted, :TestContextualWordsAreLanguageSpecific, :TestRenderRejectsAReservedContextualWord ✅ Faithful (nothing is reserved by being listed: the lexer is untouched, attribute point : Real; still parses, and on is offered by neither language, being a literal of no pinned grammar and syntax in no position of ours)
A location in the bundled standard library — a definition, a reference's declaration, a rendering origin — is reported under the sysml-stdlib: scheme with its path within the library and a position computed from the bundled text in UTF-16 code units; opensysml/stdlibContent (advertised as openSysmlStdlibContent) serves that text; hover, definition, references, document symbols and semantic tokens answer against such a URI; didOpen/didClose of one change nothing and didChange is refused as InvalidRequest, never applied internal/frontend/lsp/uri.go libraryURI, libraryURIName, Server.document, Server.documentURI; internal/frontend/lsp/stdlib.go Server.StdlibContent, Server.stdlibHandler, Server.refuseLibraryChange; internal/workspace/model/workspace.go Workspace.LibraryDocument, WithLibrarySource; internal/semantic/symbols/index.go Index.IsLibraryDocument internal/frontend/lsp/stdlib_test.go:TestLibraryURIRoundTrip, :TestDefinitionOfLibrarySymbolOpensVirtualDocument, :TestDefinitionWithinLibraryDocuments, :TestRequestsAgainstLibraryURI, :TestReferencesLocateLibraryDeclaration, :TestStdlibContentServesBundledText, :TestLibraryDocumentIsReadOnly, :TestLibraryLocationsUseUTF16Positions, :TestRunServesLibraryDocumentsOverStream ✅ Implemented (workspace/symbol lists the workspace's own declarations, as before, so it never emits a library location)
exit ends the process: status 0 after a preceding shutdown, 1 otherwise internal/frontend/lsp/lifecycle.go Server.Exit, Server.ExitCode; internal/frontend/lsp/server.go Server.Run (returns on the exit signal and closes the connection itself); cmd/sysml-lsp/main.go serve internal/frontend/lsp/lifecycle_test.go:TestExitEndsTheSessionWithTheStatusLSPRequires, cmd/sysml-lsp/lifecycle_test.go:TestExitAfterShutdownEndsTheProcess, :TestExitWithoutShutdownIsNonzero, :TestClosedStreamEndsTheProcess ✅ Faithful — Run returns rather than being killed from a handler, so the process leaves no server behind per editor window
A rename (textDocument/rename) that would collide, capture or shadow is refused with an error the editor shows, naming the element the new name would mean: the new long or short name already means something where the element is declared (a sibling's long or short name, or an outer, imported or inherited name it would shadow), or a reference the rename rewrites — in any open workspace document, each segment read the way that reference is resolved: in its own scope, as a member of the preceding segment, as a feature chain step in the operand's type, as a redefinition target among the generals — would read another element afterwards, a qualifier respelled onto an element that lacks the rest of the name included (the reference would be left unresolved), a segment that would name several members at once (the reference would be left ambiguous), and an alias for the element itself (the rename would leave alias New for New). A name taken only in an unrelated scope, the target itself at a shorthand redefinition's shared span, or a rename to the name already borne, is not a conflict; textDocument/prepareRename still offers the name, since it does not know the new one internal/frontend/lsp/rename.go Server.Rename → model/refindex.go Workspace.RenameConflict (the reverse reference index keeps the resolve.Reference each segment belongs to) → rename/rename.go Check, the one implementation the edit API's Model.renameSplices uses too internal/frontend/lsp/rename_conflict_test.go (TestRenameRefusesSiblingLongName, :TestRenameRefusesSiblingShortName, :TestRenameRefusesShortNameOntoTakenName, :TestRenameRefusesCaptureByInterveningDeclaration, :TestRenameRefusesQualifiedSegmentCollision, :TestRenameRefusesQualifiedCaptureThroughImport, :TestRenameRefusesQualifierCaptureWhereTheSuffixIsMissing, :TestRenameRefusesQualifierCaptureInFeatureChainMember, :TestRenameRefusesSegmentLeftAmbiguous, :TestRenameRefusesCaptureInAnotherDocument, :TestRenameRefusesTakingAnAliasForItself, :TestRenameRefusesCaptureByAnAliasForItself, :TestRenameRefusesShadowingAnOuterName, :TestRenameRefusesCaptureThroughFeatureChain, :TestRenameSucceedsThroughFeatureChainWhenSubtypeLacksName, :TestRenameToTheSameNameIsNotAConflict, :TestRenameSucceedsWhenNameTakenOnlyInUnrelatedScope, :TestRenameSucceedsWhenOnlySameNameIsTheTargetItself, :TestPrepareRenameOffersNameWhoseRenameMayCollide) ✅ Faithful — the refusal is the edit API's (rows under Source-Preserving Model Editing), conservative in the same way: any name visible at the declaration or at a rewritten reference is refused, even where shadowing was intended

Native Document-Query Planning (internal/ir/queryplan) — OpenSysML extension

SysML v2 defines no reusable query-definition language. This extension uses ordinary SysML calculation definitions plus the bundled, non-normative DocumentQueries library and does not alter the grammar.

Construct Implementation (file:function) Tests Status
A calculation definition specializing DocumentQueries::Query is recognized as a document query; its effective inherited and declared inputs, result type and multiplicity are retained in an immutable plan queryplan/compiler.go IsQueryDefinition, Compile, compiler.signature; semantics/redefinition.go Model.BehaviorParametersOf; queryplan/plan.go queryplan/compiler_test.go:TestCompileQueryCompositionDependencyOrder, :TestCompiledProgramIsImmutableToCallers, :TestDocumentQueryVocabularyIsBundled ✅ Implemented
An input parameter's default is retained in the plan as a compiled expression together with the query that declared it: a default naming a model element binds that element (the %run-query <p>=<expr> convention), any other default compiles as an expression in the declaring query's scope, the nearest default along the parameter's redefinition lineage wins over an inherited one, the queries a default invokes are plan dependencies, a default the expression language cannot represent is a typed planning failure naming the parameter, and a default whose statically known type (a literal, a named element, an enumeration literal of another enumeration) or multiplicity (an element list, a builtin or named-query invocation) does not fit the parameter is refused at planning with the checks an explicit argument gets — only what the values alone decide is left to execution queryplan/compiler.go compiler.signature, compiler.compileDefault, compiler.compileDefaultExpression, compiler.validateDefault, compiler.valueTypeConforms, compiler.parameterLineage; queryplan/plan.go Parameter.Default, Parameter.DefaultQuery; queryplan/errors.go ErrorUnsupportedDefault, ErrorDefaultType, ErrorDefaultMultiplicity queryplan/compiler_test.go:TestCompileRetainsParameterDefaults, :TestCompileResolvesInheritedAndRedefinedDefaults, :TestCompileRejectsUnrepresentableDefaults, :TestCompileValidatesParameterDefaults; queryexec/execute_test.go:TestExecuteDefaultMismatchFailsAtPlanning ✅ Implemented
A name in a query expression — a default, a list member or an operation or named-query argument — reads the query's input parameter of that name (a parameter is recognized through its redefinition and subsetting lineage only, so naming the parameter's type binds that type) or else binds the model element it denotes; the element is checked against the receiving parameter's type and multiplicity at planning (every element is an Element; none is a data value — not a String, not an abstract ScalarValue or DataValue, not a user attribute def — except an enumeration literal, which is a value of the enumeration that declares it and so binds a parameter typed by that enumeration or a general one; otherwise conformance to the declared type), and a name denoting neither a parameter nor an element, or the result parameter, is a typed planning failure queryplan/compiler.go compiler.compileReference, compiler.parameterIncludes, compiler.parameterLineage, compiler.validateArgument, compiler.elementConforms; semantics/model.go Model.IsDataType, Model.LiteralConforms; semantics/enumeration.go EnumerationOwning; queryplan/plan.go OperationElement, Expression.Element; queryexec/execute.go executor.evaluateElement, executor.valueConforms queryplan/compiler_test.go:TestCompileBindsElementsNamedInQueryBodies, :TestCompileValidatesElementsNamedInArguments, :TestCompileBindsEnumerationLiterals; queryexec/execute_test.go:TestExecuteBindsElementsNamedInQueryBodies, :TestExecuteEvaluatesParameterDefaults, :TestExecuteBindsEnumerationLiterals ✅ Implemented
A result expression is compiled into a closed set of parameter, literal, sequence, ownership/relationship traversal, filtering, ordering and projection operations; unknown or unsupported expressions fail with a typed planning error queryplan/compiler.go compiler.compileExpression, compiler.compileInvocation, resultExpression; queryplan/errors.go queryplan/compiler_test.go:TestCompileReportsDistinctDefinitionFaults, :TestCompileRetainsPositionalBuiltinInvocation ✅ Implemented
A named query may invoke another named query only with explicit named bindings; bindings are normalized in parameter order and duplicate, unknown, missing, positional and non-input parameter forms are distinct typed failures queryplan/compiler.go compiler.compileInvocation, compiler.compileNamedArguments, compiler.signature queryplan/compiler_test.go:TestCompileRejectsPositionalQueryInvocation, :TestCompileValidatesNamedQueryBindings, :TestCompileRejectsNonInputQueryParameter ✅ Implemented
Dependencies are compiled once in deterministic dependency-first order; direct and indirect cycles are rejected with the complete cycle path queryplan/compiler.go compiler.compileDefinition, compiler.cycleError queryplan/compiler_test.go:TestCompileMemoizesRepeatedDependency, :TestCompileRejectsDirectAndIndirectCompositionCycles ✅ Implemented
A projection may declare computed columns with exactly one of Column(name, expression), Column(name, cell) or Column(name, path): expressions compile in query scope, cells bind a typed row parameter and compile direct features as row-property operations or deeper chains as row-member operations, and paths use the same parsed member segments as Project and OrderBy; expression arithmetic (+, -, *, /), string concatenation, unary +/- and ?? defaults remain supported. Invalid or multiple sources, a non-literal name, an unknown feature reference, an unsupported operator, a statically detectable operand type mismatch, a duplicate column name and an empty projection are distinct typed planning failures with source spans queryplan/columns.go compiler.compileColumns, compiler.compileColumn, compiler.compileColumnExpression, compiler.compileColumnOperator, compiler.validateColumnOperator, compiler.validateProject; queryplan/errors.go queryplan/columns_test.go:TestCompileCellAndPathColumns, :TestCompileComputedColumns, :TestCompileComputedColumnDiagnostics ✅ Implemented
Definition and expression source provenance survives compilation, and planning failures are emitted by the constraint validation tier as source-located document-query-* diagnostics provenance/origin.go; passes/document_query.go DocumentQueryPass, documentQueryDiagnostic; passes/analyze.go queryplan/compiler_test.go:TestCompileQueryCompositionDependencyOrder, passes/document_query_test.go ✅ Implemented
A projection may declare relationship-derived columns: RelatedColumn(name, relationshipKind, direction, maxDepth, aggregate = "list") names an output column that traverses one relationship kind from each row, positional or named as the vocabulary signature declares, compiled into the plan's closed related-column operation carrying the column name as its target; a non-literal name, a missing, duplicate, unknown or mistyped argument, a surplus positional argument, a statically unsupported aggregate (other than list, count, any) and a name colliding with another column are distinct typed planning failures naming the column queryplan/plan.go OperationRelatedColumn, RelatedAggregateList, RelatedAggregateCount, RelatedAggregateAny, RelatedAggregateSupported; queryplan/columns.go compiler.compileColumns, compiler.compileRelatedColumn, columnName, staticString; queryplan/errors.go ErrorColumnAggregate; libs/stdlib/OpenSysML Libraries/DocumentQueries.sysml RelatedColumn queryplan/columns_test.go:TestCompileRelatedColumns, :TestCompileRelatedColumnDiagnostics ✅ Implemented

Native Document-Query Execution (internal/doc/queryexec) — OpenSysML extension

The execution layer consumes the immutable plan against the shared symbol index and semantic model. It does not mutate the workspace or re-derive a parallel semantic representation.

Construct Implementation (file:function) Tests Status
Entry bindings are checked against effective parameter types and multiplicities; missing, unknown and nonconforming bindings are distinct typed failures queryexec/execute.go Execute, executor.bind, executor.bindValues, executor.valueConforms; queryexec/errors.go queryexec/execute_test.go:TestExecuteRejectsInvalidBindingsAndRelationshipTraversal ✅ Implemented
An unbound parameter with a default takes it: the compiled default is evaluated once per query execution, before any row is produced, in the declaring query's scope — it may read the other parameters and invoke named queries within the shared visit, invocation-depth and invocation-count budgets — and its value passes the same type and multiplicity checks as an explicit binding; an explicit binding overrides the default, and a callee invoked by another query fills its own omitted defaults the same way. Every surface goes through this executor: %run-query, -run-query, RunDocumentQuery, and a document content block that leaves the parameter unbound queryexec/execute.go executor.bind, executor.bindValues, executor.evaluateElement; docplan/compiler.go compiler.compileBindings (an unbound defaulted parameter is left to the executor) queryexec/execute_test.go:TestExecuteEvaluatesParameterDefaults, :TestExecuteNamedQueryInvocationUsesCalleeDefaults, :TestExecuteDefaultsRespectBudgetsAndRunOncePerExecution; repl/docquery_test.go:TestRunQueryUsesParameterDefaults; cmd/sysml/run_query_test.go:TestRunQueryFlag; grpc/docquery_test.go:TestRunDocumentQueryUsesParameterDefaults, :TestRenderDocumentUsesParameterDefaults; docplan/compiler_test.go:TestCompileLeavesDefaultedParametersToTheExecutor; docrender/markdown_test.go:TestMarkdownDefaultedQueryParameters ✅ Implemented
Direct ownership, bounded breadth-first descendants and bounded breadth-first ancestors preserve declaration order and deduplicate by semantic element identity queryexec/operations.go executor.evaluateOwned, executor.evaluateDescendants, executor.evaluateAncestors; symbols/identity.go KeyOf queryexec/execute_test.go:TestExecuteDescendantsBreadthFirstAndBounded, :TestExecuteAncestorsBreadthFirstAndDeduplicated ✅ Implemented
Type, metadata, name and constant-feature filters use shared metamodel, annotation, conformance and feature-value semantics; unknown and unevaluable features are typed failures rather than false predicates queryexec/operations.go executor.evaluateWhereType, executor.evaluateWhereMetadata, executor.evaluateWhereName, executor.evaluateWhereFeature; semantics/annotations.go Model.ConstantFeatureValues queryexec/execute_test.go:TestExecuteTraversalFilteringOrderingAndProjection, :TestExecuteMetadataAndNameFilters, :TestExecuteReportsUnknownAndUnevaluableFeatures ✅ Implemented
Projection produces ordered typed cells, and stable ordering applies explicit ascending/descending, missing-value and multiple-value policies without losing row/cell alignment queryexec/value.go; queryexec/operations.go executor.evaluateProject, executor.evaluateOrderBy queryexec/execute_test.go:TestExecuteTraversalFilteringOrderingAndProjection, :TestExecuteOrderPoliciesAndProjectedCellAlignment ✅ Implemented
Computed columns evaluate their expression once per result row against the row element's constant feature values, appended after declared properties in declaration order; integer arithmetic stays integer, mixed integer/real widens to real, + concatenates strings, ?? supplies defaults for absent values, and a failing expression — a multi-valued operand, an operand type mismatch, division by zero — fails the query with a typed error naming the query, column and row rather than yielding an empty cell, so ?? is the one deliberate absent-value mechanism; computed names are visible to OrderBy (ordering by the projected cells) and downstream grouping queryexec/computed.go executor.computedColumns, executor.evaluateColumnCell, executor.applyColumnOperator; queryexec/operations.go executor.evaluateProject, executor.evaluateOrderBy; queryexec/errors.go ErrorColumnOperand, ErrorColumnOperandType, ErrorColumnDivisionByZero queryexec/computed_test.go:TestExecuteComputedArithmeticAndConcatenation, :TestExecuteOrdersAndGroupsByComputedColumns, :TestExecuteComputedColumnsAreDeterministic, :TestExecuteComputedResultsAreImmutableToCallers, :TestExecuteComputedColumnFailuresAreTyped ✅ Implemented
Traversal consumes an explicit visit budget, and result rows, cells, columns and execution failures retain query or model provenance queryexec/execute.go Options; queryexec/operations.go executor.consumeVisit; queryexec/value.go; queryexec/errors.go queryexec/execute_test.go:TestExecuteDescendantsBreadthFirstAndBounded, :TestExecuteTraversalFilteringOrderingAndProjection, :TestExecuteReportsUnknownAndUnevaluableFeatures ✅ Implemented
Named-relationship traversal (RelatedElements) follows specialization, subsetting, redefinition, typing, connection (connection/connector/interface usages), allocation, satisfaction and verification edges, outgoing or incoming, breadth-first to a bounded depth; edges are resolved through the semantic model and resolver into per-kind edge tables built once per model (memoized in Context.Related across a document's queries, rebuilt after the index takes an edit, not charged to the visit budget), results keep declaration order, deduplicate by semantic identity, charge the shared visit budget per element reached and retain provenance, and an unknown relationship kind or direction is a typed failure queryexec/related.go executor.evaluateRelated, executor.relatedNeighbors, executor.relationshipEdges; symbols/index.go Index.WorkspaceDocuments queryexec/related_test.go:TestExecuteRelatedLineageBothDirectionsAndDepth, :TestExecuteRelatedConnectionsAllocationsAndAssertions, :TestExecuteRelatedSeedsAreDeduplicatedBySemanticIdentity, :TestExecuteRelatedConsumesTheVisitBudget, :TestExecuteRelatedLeavesTableConstructionUncharged, :TestExecuteSharesRelationshipTablesThroughTheContext, :TestExecuteRebuildsRelationshipTablesAfterAnIndexEdit, :TestExecuteRelatedComposesWithFiltersProjectionAndInvocation, queryexec/execute_test.go:TestExecuteRejectsInvalidBindingsAndRelationshipTraversal ✅ Implemented
A named query invokes another compiled definition through the plan's dependency-ordered definitions; invoked bindings and declared results are re-validated at the invoked query, and rows, projected columns/cells, declaration order, semantic identity and provenance survive the invocation boundary, including nested and empty results queryexec/execute.go executor.evaluateInvoke, executor.bind, executor.validateResult queryexec/execute_test.go:TestExecuteInvokesNamedQueriesPreservingOrderAndIdentity, :TestExecuteNestedInvocationPreservesProjectedColumnsAndCells, :TestExecuteInvocationPropagatesEmptyResults, :TestExecuteInvocationBindsProjectedArgumentRowElements, :TestExecuteValidatesInvokedResultsAtTheirDeclaration, :TestExecuteInvocationBindingMismatchFailsAtPlanning ✅ Implemented
Invocation is bounded even for malformed or externally constructed plans: a target missing from the plan, a re-entered query, an exhausted invocation depth, and an exhausted total invocation count are distinct typed failures, and invoked traversal shares the caller's visit budget queryexec/execute.go executor.evaluateInvoke, Options.InvocationDepth, Options.InvocationBudget; queryexec/errors.go ErrorUnknownInvocation, ErrorInvocationCycle, ErrorInvocationDepth, ErrorInvocationBudget queryexec/execute_test.go:TestExecuteInvocationDepthIsBounded, :TestExecuteInvocationCountIsBounded, :TestExecuteInvocationSharesTheVisitBudget ✅ Implemented
%run-query <name> [<p>=<expr>...] compiles the named query, binds its entry parameters from prompt expressions or element names, executes it and prints its ordered rows and projected cells; typed execution failures are reported as errors repl/docquery.go Session.RunDocumentQuery, Session.runDocumentQuery, queryValues, renderRowSet; repl/meta.go (command table, dispatch) repl/docquery_test.go:TestRunQueryProjectsOrderedRows, :TestRunQueryBindingExpressions, :TestRunQuerySurfacesTypedExecutionFailures, :TestRunQueryRejectsNonQueryDefinition ✅ Implemented
-run-query "<name> [<p>=<expr>...]" runs a document query from a script, repeatably like -calc; rows are reported on stdout, as JSON with -json, and a query that could not be run leaves the unresolved exit status cmd/sysml/main.go (flag), cmd/sysml/check.go checks, runChecks cmd/sysml/run_query_test.go:TestRunQueryFlag, :TestRunQueryJSON ✅ Implemented
A value is a runtime object beside a model element or a scalar: ObjectValue wraps a *runtime.Instance under the label the session reaches it by (car, car.wheels[2], #7), its provenance the usage its owner holds it as (else the definition it was materialized from), read back by the typed Value.Object and Value.Declaration accessors; a Row selects an element or an object. The executor takes an optional *runtime.Context and the session's held roots (Context.Runtime, Context.Roots); a context with roots and no runtime is refused, and without a runtime execution is exactly the model-only execution it was queryexec/value.go ValueObject, ObjectValue, Value.Object, Value.Declaration; queryexec/execute.go Context, Root, Context.HeldRoot, executor.rowArgument, executor.valueConforms; runtime/held.go Instance.HeldUnder queryexec/objects_test.go:TestObjectValueCarriesInstanceLabelAndDeclaration, queryexec/execute_test.go (unchanged model-only behavior) ✅ Implemented
Every operation that takes an element row takes an object row and reads what the object holds: OwnedElements and Descendants are the objects it holds as its parts (each element of a collection its own object under wheels[1], wheels[2]), Ancestors the objects holding it, WhereType tests its declaration, its type and the classifiers a behavior gave it through Model.Conforms, WhereName its path, and WhereFeature, Project, OrderBy and Column read its current feature values — a scalar as the query's scalar kinds, an object-valued feature as object values; traversal deduplicates by object identity and consumes the visit budget as element traversal does queryexec/objects.go objectDeclaration, objectTypes, executor.heldObjects, executor.objectOwner, executor.objectIsA, executor.objectConforms, executor.objectPropertyValues, executor.objectFeatureValues, executor.objectCellValues; queryexec/operations.go (row dispatch); queryexec/computed.go, queryexec/derived.go (object rows) queryexec/objects_test.go:TestExecuteTraversesObjectsUnderTheirPaths, :TestExecuteReadsCurrentObjectValues ✅ Implemented
Objects(type = T) enumerates the objects the session holds that are of T — the held roots and, breadth-first, every object they hold, each once under its path — in root then declaration order; it is a typed ErrorNoRuntime outside a session, zero rows in a session holding nothing, and ErrorUnknownClassification for a type the model does not declare queryplan/plan.go OperationObjects; queryplan/compiler.go (DocumentQueries::Objects); queryexec/objects.go executor.evaluateObjects; libs/stdlib/OpenSysML Libraries/DocumentQueries.sysml Objects queryexec/objects_test.go:TestExecuteObjectsEnumeratesTheSession ✅ Implemented
WhereMetadata keeps an object whose declaration — the usage it stands for — is annotated; RelatedElements reads the model's relationships and refuses an object row with a typed ErrorObjectRow naming the object, rather than answering for the usage it stands for queryexec/execute.go executor.elementArgument; queryexec/errors.go ErrorObjectRow queryexec/objects_test.go:TestExecuteRefusesObjectRowsWhereTheModelIsRead, repl/docquery_test.go:TestRunQueryRefusesObjectRowsInElementOperations ✅ Implemented
%run-query and -run-query bind a parameter to an object the session holds: a name binds the object held under it while one is held and the element otherwise, #<id> an object by id, car.wheels[2] a nested object by path — through the one object resolver %features, %invoke and %validate use — and the query runs in the session's runtime context with its held roots; rows over objects print the object's path and id repl/docquery.go Session.queryContext, Session.boundObject, queryValues; repl/lookup.go Session.resolveObject, Session.rootCarriers; cmd/sysml/check.go (-instantiate beside -run-query) repl/docquery_test.go:TestRunQueryBindsHeldObjectOverElement, :TestRunQueryBindsObjectByIDAndPath, cmd/sysml/run_query_test.go:TestRunQueryOverObjects, cmd/sysml/manual_examples_test.go:TestManualCookbookModelAnalysesCleanly ✅ Implemented
RunDocumentQuery binds a parameter to an object the service holds for the model — the object arm of DocumentValue, a DocumentObject with instance_id (the id Instantiate answered), path (the usage name car/Garage::car, the id #2, or a walk through feature values from either, car.wheels[2], indexes from 1 — what %run-query accepts, parsed and walked by the one objref package the REPL uses) or both, the path then having to reach the object with that id — and the query runs in the model's runtime with its held roots. A row that is an object, and an object-valued cell, is answered as the object arm with the id, the path it is reached under and the usage it stands for; Objects(type = T) enumerates the held population (named objects by qualified name, then displaced ones by id) and answers no rows before the first Instantiate; Verdicts over a bound object checks its current values. A binding while nothing is held, or naming an id or a usage no Instantiate created, is NOT_FOUND naming the parameter and the RPC that creates one; a malformed reference, a path through a namespace, a scalar feature or an out-of-range index, an object bound to a non-Element parameter, an id the path disagrees with, or a DocumentObject naming neither is INVALID_ARGUMENT, with the REPL's wording. The Go client binds with ObjectByID/ObjectByPath and decodes Object (ID, Path, Element) as a cell and as Row.Object; the Python client binds with ObjectRef(id=…)/ObjectRef(path=…) and decodes ObjectRef as a cell and as DocumentRow.object; Node, Java and Rust carry the regenerated stubs grpc/objects.go heldObjects (hold, roots, queryContext, resolve, byID, resolvePath, namedRoot, lookup); grpc/docquery.go documentBindings, boundValue, documentObject, rowSetResponse; grpc/service.go Service.Instantiate; objref/objref.go Parse, objref/walk.go Walker.Walk; client/opensysml/documents.go Object, ObjectByID, ObjectByPath, Row.Object; client/python/opensysml/document.py ObjectRef, DocumentRow.object, _bound_value, _value_of; tools/cmd/conformance/pkgclient.go cellToProto/cellFromProto grpc/objects_test.go:TestInstantiateHoldsObjectsForQueries, :TestObjectsEnumeratesHeldObjects, :TestInstantiateAgainKeepsTheEarlierObject, :TestObjectBindingsRunConcurrently, :TestVerdictsOverHeldObject, :TestRenderDocumentReadsHeldObjects; grpc/robustness_test.go:TestGRPCObjectBindingRobustness; gRPC conformance document_query_object_by_id, document_query_object_by_path, document_query_object_row, document_query_objects, document_query_verdicts, document_query_object_not_held; conformance document/an_object_binding_needs_an_object_the_model_holds; objref/objref_test.go; client/opensysml/objects_test.go:TestRunDocumentQueryBindsAHeldObjectByID, :TestRunDocumentQueryBindsAHeldObjectByPath, :TestRunDocumentQueryEnumeratesHeldObjectsAndTheirVerdicts, :TestAnObjectBindingNamesWhatItCannotReach; client/python/tests/test_document.py::test_an_object_binds_by_id_by_path_or_both, ::test_an_object_row_decodes_to_the_object_and_its_usage, ::TestDocumentsAgainstRealService::test_a_query_binds_the_object_instantiate_built_by_id, ::test_a_query_binds_the_object_instantiate_built_by_path, ::test_objects_enumerates_what_the_model_holds, ::test_verdicts_over_the_object_instantiate_built, ::test_an_object_binding_the_model_cannot_reach_is_refused ✅ Implemented
Verdicts(source, kind = "all") checks the object behind each row as a whole — the held object when the row is one, the row element's declared object otherwise (definition defaults and redefinitions through the same DeclaredReader the derived-value recipes read) — with the one runtime.ValidateObject sweep %validate/-validate=<object> use, and answers one verdict row per assertion about it and the objects it holds: each assert constraint, each requirement carried, each satisfy whose subject it is, and each verification case verifying such a requirement, in the sweep's order (the root's assertions, then each held object's in declaration order, a collection's members under wheels[1], wheels[2]). A verdict value keeps the assertion as its element, so name, qualifiedName, documentation, WhereName and WhereType read the constraint or requirement, and adds kind (constraint, requirement, satisfaction, verification), carrier (the object or the usage checked), path (root-relative), verdict (holds, violated, undecided), condition (the expression found false), reason (nonempty on every undecided row) and verification (the VerdictKinds of the cases verifying a requirement); a verification row's own outcome is its verdict: pass → holds, fail → violated, inconclusive/error → undecided with the detail as reason. kind keeps one kind of assertion; an unknown kind is ErrorInvalidArgument queryplan/plan.go OperationVerdicts; queryplan/compiler.go (DocumentQueries::Verdicts); queryexec/value.go ValueVerdict; queryexec/verdict.go Verdict, VerdictValue, Value.Verdict, assertionVerdict, verificationVerdict, carrierPath; queryexec/verdicts.go executor.evaluateVerdicts, executor.verdictsOf, executor.reportVerdicts, executor.verdictPropertyValues, verificationKinds; queryexec/derived.go derivedValues.verified; runtime/declared.go DeclaredReader.Validate; libs/stdlib/OpenSysML Libraries/DocumentQueries.sysml Verdicts, Verdict queryexec/verdicts_test.go:TestExecuteVerdictsAboutASessionObject, :TestExecuteVerdictsSelectsAKind, :TestExecuteVerdictsReadCurrentValues, :TestExecuteVerdictsAboutADeclaredElement ✅ Implemented
A verdict row flows through the row operations: WhereFeature, OrderBy and Project read the verdict properties before the assertion's own, Column expressions read them by name, and WhereName/WhereType/WhereMetadata read the assertion. Two things are refused rather than approximated: Verdicts over a row that is no object — a package, an attribute usage — is ErrorNotAnObject naming the element, and a walk the runtime could not complete (an unbounded recursive part, an exhausted materialization budget) is ErrorIncompleteValidation carrying the cause instead of a table missing rows; OwnedElements, Descendants, Ancestors, RelatedElements and Verdicts itself refuse a verdict row (ErrorVerdictRow), as does a binding carrying one queryexec/operations.go (verdict dispatch); queryexec/computed.go (DocumentQueries::Verdict columns); queryexec/execute.go executor.rowArgument, executor.elementArgument; queryexec/errors.go ErrorVerdictRow, ErrorNotAnObject, ErrorIncompleteValidation; queryexec/verdicts.go executor.incompleteValidation queryexec/verdicts_test.go:TestExecuteVerdictRowsThroughRowOperations, :TestExecuteVerdictsRefuseAnIncompleteWalk, repl/docquery_test.go:TestRunQueryReportsVerdicts ✅ Implemented
A verdict renders everywhere a query result does: %run-query/-run-query print each row as <assertion> on <path>: <verdict> and its projected cells (-json carries the same text); a document table or list cell is that text in Markdown and PDF, and in HTML a span.sysml-verdict with data-verdict, data-path and, over a held object, data-object, beside the data-element of the assertion; RunDocumentQuery answers the row as the verdict arm of DocumentValue (DocumentVerdict: assertion, kind, text, path, verdict, condition, reason, verification) — checked as declared for an element bound by element_id, as it is for an object bound by object — and refuses a verdict bound as a parameter with INVALID_ARGUMENT; the Go and Python clients decode it as DocumentVerdict on the row and refuse to bind one repl/docquery.go queryValues; docir/evaluate.go; docrender/markdown.go, docrender/html.go; grpc/docquery.go documentVerdict, boundValue; client/opensysml/documents.go DocumentVerdict, Row.Verdict; client/python/opensysml/document.py DocumentVerdict, DocumentRow.verdict; tools/cmd/conformance/pkgclient.go cellToProto/cellFromProto docrender/verdicts_test.go:TestMarkdownVerdictReportGolden, :TestMarkdownVerdictReportDeclared, :TestHTMLVerdictReport; repl/docquery_test.go:TestRunDocumentQueryVerdict; cmd/sysml/run_query_test.go:TestRunQueryReportsVerdicts; grpc/docquery_test.go:TestRunDocumentQueryAnswersVerdicts; client/opensysml/surface_test.go:TestRunDocumentQueryAnswersVerdictRows, :TestADocumentQueryBindingRefusesAVerdict; client/python/tests/test_document.py; conformance document/a_verdicts_query_answers_verdict_rows ✅ Implemented
WhereRelated(source, relationshipKind, direction, maxDepth, exists = true) keeps each source row by whether at least one element is reachable from it over the named relationship, through the same edge tables, kind and direction validation and visit budget as RelatedElements (executor.traverseRelated is the one breadth-first walk both share); exists = false keeps the rows with none, an omitted exists defaults to true, and an object or verdict row is refused as RelatedElements refuses it queryexec/where_related.go executor.evaluateWhereRelated, executor.hasRelated; queryexec/related.go executor.relationshipArguments, executor.traverseRelated; queryexec/execute.go executor.booleanArgument queryexec/where_related_test.go:TestExecuteWhereRelatedKeepsRowsByRelationshipExistence, :TestExecuteWhereRelatedHonoursDepthAndEveryKind, :TestExecuteWhereRelatedKeepsProjectedColumns, :TestExecuteWhereRelatedReportsTheErrorsOfRelatedElements, :TestExecuteWhereRelatedChargesTheVisitBudget; queryplan/compiler_setops_test.go:TestCompileCoverageAndSetOperations, :TestCompileValidatesCoverageAndSetOperationArguments ✅ Implemented
Except(source, exclude) and Union(source, other) are ordered set operations over rows: Except keeps the source rows absent from exclude once each in source order, Union emits the source rows then the rows of other not yet present, each row once; identity is the semantic identity traversal deduplicates by — symbols.KeyOf for a model element, the instance for a held object, for a verdict its assertion with the object it was checked on (its path when no object is held), for a state its object, machine and state path, and for an event its record's place in the trace; Union refuses inputs whose projected columns differ (ErrorInvalidArgument) queryexec/setops.go executor.evaluateExcept, executor.evaluateUnion, setKeyOf, sameColumns queryexec/setops_test.go:TestExecuteSetOperationsOverElements, :TestExecuteSetOperationsComposeCoverage, :TestExecuteSetOperationsKeepProjectedColumns, :TestExecuteSetOperationsOverSessionObjects, :TestExecuteSetOperationsOverVerdicts, :TestExecuteSetOperationsOverStatesAndEvents ✅ Implemented
States(source) answers the state each session object's machine is in now, as state rows: one per active leaf across every orthogonal region, the object as object/path (the ValueObject the source row was), machine the exhibited usage, name the leaf, statePath its dotted path under the machine (on.run; a synthesized region owner of a parallel state is left out), region the orthogonal region the leaf runs in and enclosing the active composite states above it, the row standing for the state declaration so its own properties read too. InState(name) is the inverse: the held objects, each once, whose machine is in the named leaf or composite state, by simple name or dotted path, as object rows. Both read the session's held roots as Objects does (ErrorNoRuntime without a session), refuse an object exhibiting no state machine (ErrorNoStateMachine), a source row that is no object (ErrorNotAnObject) and a state no held machine declares (ErrorUnknownState); they read StateExecutor.ActiveStates and move no clock queryplan/plan.go OperationStates, OperationInState; queryplan/compiler.go (DocumentQueries::States, InState); queryexec/value.go ValueState; queryexec/state.go State, StateValue, Value.State; queryexec/states.go executor.evaluateStates, executor.evaluateInState, executor.objectArgument, stateMachinesOf, activeState, regionName, machineName, stateSymbol, stateNamed, machineDeclaresState, executor.statePropertyValues; queryexec/errors.go ErrorNoStateMachine, ErrorUnknownState, ErrorStateRow; runtime/state_executor.go StateExecutor.ActiveStates, EnclosingStates, StatePath; libs/stdlib/OpenSysML Libraries/DocumentQueries.sysml States, InState, State queryexec/states_test.go:TestExecuteStatesListsEveryActiveLeaf, :TestExecuteStateRowsThroughRowOperations, :TestExecuteInStateFindsObjectsByLeafOrEnclosingState, :TestExecuteStatesRefusals; repl/docquery_states_test.go:TestRunQueryStatesOverSession; repl/cookbook_states_test.go:TestCookbookStateAndEventRecipes ✅ Implemented
A terminated state machine holds no active configuration, so States answers no row for its object and InState matches nothing, while a machine that reached done reports it as the final state; a States or Events source bound to an object the run destroyed is refused with ErrorObjectDestroyed naming the object and the activation mark of its destruction, rather than answering a stale row or surfacing an unevaluable-feature failure; a destroyed object leaves the population — Objects, InState and element-derived sources skip it and its label still resolves for Events rows, while a source naming only destroyed objects is the same refusal runtime/lifetimes.go Context.Destroyed; queryexec/objects.go executor.eachSessionObject, executor.evaluateObjects; queryexec/states.go executor.evaluateInState, executor.objectArgument, executor.objectsDeclaredBy, executor.objectDestroyedError; queryexec/errors.go ErrorObjectDestroyed queryexec/robustness_state_event_queries_test.go:TestQueryRobustnessStateEventQueries; runtime/robustness_state_event_queries_test.go:TestRuntimeRobustnessStateEventQueries; grpc/robustness_docquery_states_events_test.go:TestGRPCRobustnessDocumentQueryStatesEvents ✅ Faithful
The trace is a typed relation the printer writes from: TraceRecorder keeps a TraceRecord per accept, send, transition, state entry, exit and do step, choice draw (the alternatives and the one taken, due order and region order included) and unevaluable guard, each with its TraceOrigin — the clock's instant, the object and the behavior — beside the free-text lines the other tracers write, and Entries() prints every record through TraceRecord.Line, so -trace/%trace output is byte-for-byte what it was and cannot drift from the record; %trace off and Clear discard it. Events(source = null, kind = "all", since = null, before = null) reads the records in the order the run made them as event rows: kind, time (the origin's instant as a quantity in the clock's unit), object/path/machine, state/from/to, target (a send's addressee), event, payload (name = value per field), alternatives/taken (a choice) and text (the printed line); source keeps the records of the objects behind its rows (every object's when left out), kind one or several comma-separated kinds, and [since, before) — inclusive start, exclusive end — a bare number in the clock's unit or a duration converted through Context.ClockMagnitude. Refused: a session recording no trace (ErrorNoTrace), a kind the record has not (ErrorInvalidArgument), a bound that is no instant — a non-duration quantity, a clock carrying no unit — or a before at or before since (ErrorInvalidInterval), and OwnedElements, Descendants, Ancestors, RelatedElements, States, Verdicts and Events themselves over a state or event row (ErrorStateRow, ErrorEventRow); WhereFeature, OrderBy, Project and Column read the state and event properties before the element's own, and WhereName/WhereType the state declaration or the object's type runtime/trace.go TraceKind, TraceOrigin, TraceRecord, TraceRecord.Line, TraceRecorder.Records, TraceRecorder.Entries, RecordAccept, RecordSend, RecordStateTransition, RecordStateEntry, RecordStateExit, RecordDoStep, RecordNote; runtime/context.go Context.ClockMagnitude; queryplan/plan.go OperationEvents; queryexec/value.go ValueEvent; queryexec/event.go Event, EventValue, Value.Event; queryexec/events.go executor.evaluateEvents, executor.eventKindArgument, executor.instantArgument, eventKinds; queryexec/errors.go ErrorNoTrace, ErrorInvalidInterval, ErrorEventRow; repl/trace.go (the session's recorder); cmd/sysml/check.go checks.runQueries (queries run after -state/-action and -advance); libs/stdlib/OpenSysML Libraries/DocumentQueries.sysml Events, Event runtime/trace_test.go:TestExecutionTrace (goldens unchanged); queryexec/events_test.go:TestExecuteEventsReadsTheTraceInOrder, :TestExecuteEventsByKind, :TestExecuteEventsIntervalIsClosedOpen, :TestExecuteEventRowsThroughRowOperations, :TestExecuteEventsRefusals; repl/docquery_states_test.go:TestRunQueryEventsOverSession; cmd/sysml/run_query_test.go:TestRunQueryOverStatesAndTrace ✅ Implemented
A state or event row renders everywhere a query result does: %run-query/-run-query print a state row as <object>.<machine> in <statePath> and an event row as t=<instant> <object>.<machine>: <text>; a document cell is that text in Markdown and PDF, and in HTML a span.sysml-state with data-machine, data-state, data-region or a span.sysml-event with data-event-kind, data-time, each with the object's data-object; RunDocumentQuery answers the state and event arms of DocumentValue (DocumentState: object, machine, name, path, region, enclosing, state, text; DocumentEvent: kind, time, object, machine, state, from, to, target, event, payload, alternatives, taken, text) over the held population, whose run is traced from the first Instantiate into a recorder keeping the most recent OPENSYSML_GRPC_MAX_HELD_EVENTS records (default 100,000; an Events interval reaching a dropped record is a trace-truncated error, FAILED_PRECONDITION), and refuses either bound as a parameter with INVALID_ARGUMENT; the Go and Python clients decode them as DocumentState/DocumentEvent on the row and in cells and refuse to bind one, Node, Java and Rust carry the regenerated stubs repl/docquery.go formatQueryValue; docir/evaluate.go; docrender/markdown.go, docrender/html.go (the PDF backend hands the HTML-input engines the HTML backend's page); grpc/docquery.go documentState, documentEvent, boundValue; grpc/objects.go Service.objects (the traced population); api/proto/sysml.proto DocumentState, DocumentEvent; client/opensysml/documents.go DocumentState, DocumentEvent, Row.State, Row.Event; client/python/opensysml/document.py DocumentState, DocumentEvent, DocumentRow.state, DocumentRow.event; tools/cmd/conformance/pkgclient.go cellToProto/cellFromProto docrender/states_test.go:TestMarkdownStateReportGolden (testdata/state_report.golden.md), :TestHTMLStateReport; docpdf/docpdf_test.go:TestRenderStateReportPage; docpdf/integration_test.go:TestRenderStateReportWithInstalledEngines (real engines, skipped when absent); repl/docquery_states_test.go; cmd/sysml/run_query_test.go:TestRunQueryOverStatesAndTrace; gRPC conformance document_query_states, document_query_in_state, document_query_events; client/opensysml/states_test.go:TestRunDocumentQueryAnswersStateAndEventRows, :TestStateAndEventRowsAreNotBound; client/python/tests/test_document.py::test_a_state_row_decodes_to_the_object_and_its_state, ::test_an_event_row_decodes_to_the_trace_record, ::test_a_state_or_event_binding_is_refused ✅ Implemented
A relationship-derived column traverses from each row's element — an object row's declaration, the assertion of a verdict row, the state or behavior a state or event row is of — with the breadth-first traversal RelatedElements uses (every relationship kind and direction it accepts, bounded by maxDepth, deduplicated by semantic identity, ordered as reached, charged to the shared visit budget); list yields the related elements as one multi-valued cell (an empty cell when none), count an integer and any a boolean that stops at the first element reached, and the cells are read downstream by name — WhereFeature and OrderBy over a projected column read its cells (an element compares and orders as its qualified name, under the text operators), a document table groups by it, and %run-query, Markdown, HTML and RunDocumentQuery carry every value as they carry a multi-valued documentation cell. An unknown relationship kind, an invalid direction and an exhausted budget are the typed failures RelatedElements raises, naming the column; a row no element declares (an event posted from outside the run) is a typed undeclared-row failure naming the column and the row queryexec/related_column.go relatedColumn, relatedColumnOf, executor.evaluateRelatedCell, columnScoped; queryexec/related.go executor.validateRelationship, executor.traverseRelated; queryexec/where_related.go executor.hasRelated; queryexec/computed.go computedColumns, executor.evaluateColumnCell; queryexec/operations.go projectedColumn, executor.featureValues, compareValue, executor.compareOrdered; queryexec/errors.go; docplan/compiler.go columnNames queryexec/related_column_test.go:TestExecuteRelatedColumnsBuildATraceabilityMatrix, :TestExecuteRelatedColumnFollowsDepthAndDirection, :TestExecuteRelatedColumnReportsItsColumnInErrors, :TestExecuteRelatedColumnLeavesTableConstructionUncharged, :TestExecuteRelatedColumnAnyStopsAtTheFirstElement, :TestExecuteRelatedColumnsFilterAndOrderDownstream, :TestExecuteRelatedColumnElementsCompareByQualifiedName, :TestExecuteRelatedColumnTraversesFromAnObjectsDeclaration, :TestExecuteRelatedColumnRefusesARowNoElementDeclares; grpc/related_column_test.go:TestRunDocumentQueryCarriesRelatedColumns; docplan/runs_test.go:TestCompileGroupedTableSeesRelatedColumns ✅ Implemented

Known limitations: relationship traversal covers the kinds above only; refinement and derivation links have no dedicated semantic representation yet and are unknown kinds.


Native Document Planning and IR (internal/ir/docplan, internal/doc/docir) — OpenSysML extension

Standard: none. This is the document layer of the OpenSysML document-query extension: a document authored natively in SysML v2 as a part definition specializing the bundled, non-normative DocumentQueries::Document, compiled into an immutable document plan, and evaluated into an immutable, backend-agnostic document tree by executing its queries through the execution engine above. It does not alter SysML v2 language semantics.

Rule Implementation (file:function) Tests Status
A part definition specializing DocumentQueries::Document is recognized as a document; its required title, nested sections and content blocks (paragraphs, tables, lists, diagrams) compile in declaration order into an immutable plan, and nested documents, untitled sections, non-literal attributes and unsupported members are distinct typed failures docplan/compiler.go IsDocumentDefinition, Compile, compiler.compileMembers, compiler.compileContent; docplan/plan.go; docplan/errors.go docplan/compiler_test.go:TestCompileTelescopeDocument, :TestCompiledPlanIsImmutable, :TestCompileReportsMissingSectionTitle, :TestCompileReportsNestedDocument, :TestCompileReportsInvalidContent ✅ Implemented
A paragraph carries static text, one query, or inline runs; a table requires a query and may carry a caption, and a list requires a query and a bullet/number style; a missing, conflicting or unknown query, an invalid style, and combining runs with text or a query are distinct typed failures docplan/compiler.go compiler.compileParagraph, compiler.compileTable, compiler.compileList, compiler.compileQueryRef docplan/compiler_test.go:TestCompileReportsParagraphWithoutContent, :TestCompileReportsParagraphWithTextAndQuery, :TestCompileReportsTableWithoutQuery, :TestCompileReportsUnknownQuery, :TestCompileReportsInvalidListStyle; docplan/runs_test.go:TestCompileReportsRunAndGroupErrors ✅ Implemented
A paragraph composes inline runs in declaration order: a Span carries required text and an optional plain/emphasis/strong/code style, a Link carries required text and a required URL target, and a Ref names a content block of this or another document, or another document's root, with the target's title, caption or name as its default label; a missing text, an invalid style, a missing link or reference target, an unknown target, a target that is neither content nor a document or without a stable name, a target usage typed by more than one document, an ambiguous run kind, and a query or nested content inside a run are distinct typed failures docplan/compiler.go compiler.compileRuns, compiler.compileRun, compiler.compileSpanRun, compiler.compileLinkRun, compiler.compileRefRun, compiler.resolveRefs, compiler.crossDocumentTarget; docplan/plan.go Run docplan/runs_test.go:TestCompileParagraphRuns, :TestCompiledRunsAreImmutable, :TestCompileReportsRunAndGroupErrors; docplan/crossdoc_test.go ✅ Implemented
A table with a groupBy attribute names a projected column to group its rows by; the name is validated at planning time against the query's statically-known projection — declared properties, computed and relationship-derived column names alike, carried through the filters, ordering, WhereRelated, Except and a Union of matching inputs — and an empty or unprojected name is a typed failure docplan/compiler.go compiler.compileTable, staticColumns, expressionColumns, argumentColumns, columnNames docplan/runs_test.go:TestCompileGroupedTable, :TestCompileGroupedTableSeesComputedColumns, :TestCompileGroupedTableSeesRelatedColumns, :TestCompileReportsRunAndGroupErrors, docplan/setops_test.go:TestCompileGroupedTableThroughSetOperations, :TestCompileSetOperationsRejectUnprojectedGroupColumn, :TestCompileUnionOfDifferingColumnsIsLeftToExecution ✅ Implemented
A diagram names its source with ref source = <name>: a declared view carries its stated rendering kind, a plain element requires a kind attribute naming one the view engine draws (tree, interconnection, state, action, table, sequence), and an optional direction (TB/LR/RL/BT) is accepted only by kinds drawn as directed graphs, an optional palette only by kinds with a DOT or PlantUML form (a sequence included, PlantUML filling its participants; a table excluded); a missing, non-referential or unknown source, a missing, conflicting or unsupported kind, an invalid or unsupported direction and an invalid or unsupported palette are distinct typed failures at planning time docplan/compiler.go compiler.compileDiagram, compiler.diagramSource, compiler.sourceTarget; view/direction.go ParseDirection, Kind.SupportsDirection; view/palette.go ParsePalette, Kind.SupportsPalette docplan/diagram_test.go:TestCompileDiagramWithDeclaredView, :TestCompileDiagramWithElementAndKind, :TestCompileDiagramWithoutSource, :TestCompileDiagramWithUnknownSource, :TestCompileDiagramRejectsKindOnAView, :TestCompileDiagramRequiresKindForAnElement, :TestCompileDiagramRejectsUnsupportedKind, :TestCompileDiagramRejectsAViewOfUnsupportedKind, :TestCompileDiagramRejectsInvalidDirection, :TestCompileDiagramRejectsDirectionOnASequence, :TestCompileDiagramWithPalette, :TestCompileDiagramRejectsInvalidPalette, :TestCompileDiagramRejectsPaletteOnATable, :TestCompileDiagramAcceptsPaletteOnASequence ✅ Implemented
A content block's query reference is compiled with the query planner, and its in bindings are validated against the compiled signature: unknown, duplicate and missing parameters, and type or multiplicity mismatches are distinct typed failures at planning time, while a parameter left unbound that declares a default is left to the executor docplan/compiler.go compiler.compileQueryRef, compiler.compileBindings, compiler.validateBinding; queryplan/compiler.go Compile docplan/compiler_test.go:TestCompileReportsUnknownParameter, :TestCompileReportsMissingBinding, :TestCompileReportsBindingTypeMismatch, :TestCompileReportsBindingMultiplicityMismatch, :TestCompileWrapsQueryPlanningFailure ✅ Implemented
Document, content, query-reference and binding provenance survives planning, and document planning failures are emitted by the constraint validation tier as source-located document-plan-* diagnostics docplan/plan.go; passes/document_plan.go DocumentPlanPass, documentPlanDiagnostic; passes/analyze.go docplan/compiler_test.go:TestCompileTelescopeDocument, passes/document_plan_test.go ✅ Implemented
Evaluating a plan executes each referenced query through the execution engine with the planned bindings and produces an immutable document tree — sections, paragraphs of text runs, tables of typed rows and cells, lists of items — in declaration and query-result order, with provenance on every node tracing to the model declaration, query row or projected value behind it docir/evaluate.go Evaluate, evaluator.evaluateNode, evaluator.executeQuery, executionValue; docir/ir.go docir/evaluate_test.go:TestEvaluateTelescopeDocument, :TestEvaluatedDocumentIsImmutable ✅ Implemented
A cross-document reference carries its target document's identity and named path in the backend-neutral IR; evaluating a set of plans together forces each externally-referenced content block to emit its stable anchor, and each backend maps the identity to a deterministic file name (:: → -, other bytes hex-escaped, .md or .html) joined with the anchor as a relative link, so -render-documents writes a linked set that resolves on disk with byte-identical repeated output docir/ir.go TextRun.TargetDocument, AnchorFor; docir/evaluate.go EvaluateSet; docrender/markdown.go refDestination, DocumentFileName; docrender/html.go htmlRefDestination, DocumentHTMLFileName; repl/docrender.go Session.RenderDocumentSetMarkdown; cmd/sysml/render_document.go runRenderDocuments docir/crossdoc_test.go, docrender/crossdoc_test.go, cmd/sysml/render_documents_test.go ✅ Implemented
A diagram evaluates by rendering its view — a declared view in the kind it states, a plain element in the planned kind — through the view engine into a backend-neutral rendering carried immutably in the tree with the kind, caption, direction and provenance; no Markdown or Mermaid is stored in the IR, and a rendering failure is a typed evaluation failure wrapping the view error docir/evaluate.go evaluator.evaluateDiagram; docir/ir.go Content.Rendering, Content.Direction; view/clone.go Rendering.Clone docir/diagram_test.go:TestEvaluateDiagramFromDeclaredView, :TestEvaluateDiagramFromElementAndKind, :TestDiagramRenderingIsDefensivelyCopied ✅ Implemented
An empty query result evaluates to a valid empty table or list preserving the projected column schema, and query execution failures — an invalid context or plan, an unsupported operation, an exhausted budget — surface as typed document-evaluation failures wrapping the execution error docir/evaluate.go evaluator.evaluateTable, evaluator.evaluateList, evaluator.executeQuery; docir/errors.go docir/evaluate_test.go:TestEvaluateTelescopeDocument, :TestEvaluateRequiresPlanAndContext, :TestEvaluateWrapsQueryExecutionFailure, :TestEvaluateHonorsExecutionBudget ✅ Implemented
Planned inline runs evaluate into typed, backend-neutral IR runs — plain, emphasis, strong, code, link, reference — each keeping its provenance; a referenced content node carries a stable anchor derived from its named path, and a reference run's target is that anchor, so backends need no name resolution docir/evaluate.go evaluatedRun, styledKind, anchorFor, referencedAnchors; docir/ir.go TextRun, RunKind docir/runs_test.go:TestEvaluateInlineRuns, :TestAnchorForEncoding ✅ Implemented
A grouped table's rows partition immutably by the group column's cell text in order of first appearance, keeping row order inside each group and the full ungrouped row set beside them; a group column missing from the executed result is a typed failure docir/evaluate.go evaluator.groupRows, evaluator.cellText; docir/ir.go TableGroup docir/runs_test.go:TestEvaluateGroupedTable, :TestEvaluatedGroupsAreImmutable ✅ Implemented
A query-backed paragraph or list nests column runs — a SpanColumn maps a projected column to spans with a fixed style or a per-row style column, a LinkColumn maps text and target columns to links — compiled immutably in declaration order and validated at planning time against the query's statically-known projection: a missing column name, an unprojected name, an invalid fixed style, combining style with styleColumn, column runs without a query or alongside text or inline runs, and a column run outside a paragraph or list are distinct typed failures docplan/compiler.go compiler.compileColumnRuns, compiler.compileColumnRun, compiler.requiredColumn, compiler.validateRunColumns; docplan/plan.go ColumnRun docplan/columnruns_test.go:TestCompileColumnRuns, :TestCompiledColumnRunsAreImmutable, :TestCompileColumnRunsSkipsUnknownableProjections, :TestCompileReportsColumnRunErrors ✅ Implemented
Column runs evaluate each result row into typed IR runs in template order — emphasis, strong, code, plain, or links — with each run keeping its projected value's provenance; a projection only known at evaluation is checked then, and an unprojected column, a row whose style column does not supply exactly one of the four style strings, and a row whose target column does not supply one non-empty destination are typed failures naming the query, column and row docir/evaluate.go evaluator.templateIndexes, evaluator.templateRuns, evaluator.rowStyle, evaluator.rowTarget; docir/errors.go docir/columnruns_test.go:TestEvaluateColumnRuns, :TestEvaluateColumnRunErrors; docrender/markdown_test.go:TestMarkdownTelescopeReportGolden ✅ Implemented
A document evaluates over the objects a session holds: a query parameter the document binds to a usage's name binds the object held under it while one is held (Context.HeldRoot) and the declared element otherwise, so one document renders the declared model in a session holding nothing and the objects in one that does; an object cell or list item carries the object's path as its text, its provenance the usage it stands for docir/evaluate.go evaluator.executionValue; queryexec/execute.go Context.HeldRoot docir/evaluate_test.go:TestEvaluateBindsHeldObject, repl/docquery_test.go:TestRenderDocumentOverHeldObjects (a value read after %invoke changed it) ✅ Implemented

Known limitations: Column runs style query-produced text and link to external URLs; query-produced Ref-style cross-references to other content blocks are not modeled. Computed column expressions cover feature references, literals, arithmetic, string concatenation and ?? defaults; feature chains, invocations and comparison operators inside a column expression are typed planning failures.


Native Document Rendering (internal/doc/docrender) — OpenSysML extension

Standard: none. This is the rendering layer of the OpenSysML document-query extension: an evaluated document tree written out as a backend-specific artifact. The renderer consumes only the document IR — never plans, symbols or ASTs — so a rendering cannot drift from what evaluation produced. Markdown and HTML are backends of equal standing, each reading the same tree; PDF output (internal/doc/docpdf) converts the Markdown through external converter subprocesses, so the binary links no PDF renderer and the IR stays presentation-neutral.

Rule Implementation (file:function) Tests Status
An evaluated document renders to deterministic CommonMark-compatible Markdown: the title as a level-1 ATX heading, each section one level deeper saturating at 6, paragraphs from space-joined text runs, and bullet and numbered lists, all in the IR's declaration order docrender/markdown.go Markdown, renderContent, heading, renderList docrender/markdown_test.go:TestMarkdownTelescopeReportGolden, :TestMarkdownGoldenStructure ✅ Implemented
A table renders as a GitHub-flavored pipe table under its caption, an emphasized paragraph, headed by the projected column names; a query without projected columns gets a single element column, and an empty result still writes its header and delimiter while an empty list renders as nothing docrender/markdown.go renderTable, tableCells, writeTableRow docrender/markdown_test.go:TestMarkdownTelescopeReportGolden, :TestMarkdownGoldenStructure ✅ Implemented
Typed values render faithfully as plain text: strings unquoted, integers in base 10, reals in shortest notation, booleans as true/false, unbounded multiplicity as *, and elements by qualified name with the declared name as fallback docrender/markdown.go valueText, cellText docrender/markdown_test.go:TestMarkdownTelescopeReportGolden, :TestMarkdownGoldenStructure ✅ Implemented
Content cannot corrupt document structure: \|, *, _, #, backticks, backslashes, brackets and HTML-sensitive characters are backslash-escaped, newlines fold to spaces in prose and <br> in table cells, and a leading quote, bullet or ordered-list marker in a paragraph is escaped docrender/markdown.go inline, tableCell, blockStart, inlineEscaper docrender/markdown_test.go:TestMarkdownEscaping, :TestMarkdownGoldenStructure ✅ Implemented
Typed runs render by kind with content still escaped: emphasis and strong in flanking */** delimiters with whitespace kept outside, code in a backtick fence longer than any inner backtick run, links as inline links with escaped text and a pointy-bracket destination escaping backslashes and angle brackets and percent-encoding newlines, and references as links to in-document anchors docrender/markdown.go runText, delimited, codeSpan, destination docrender/runs_test.go:TestMarkdownEmphasisDelimiters, :TestMarkdownCodeSpans, :TestMarkdownLinkDestinations, :TestMarkdownGoldenInlineRuns ✅ Implemented
A referenced content node is preceded by an HTML anchor carrying its stable identifier, and a grouped table renders one pipe subtable per group under the group key in strong emphasis, in the IR's first-appearance order; an empty grouped result still writes one header and delimiter docrender/markdown.go renderContent, renderTable, pipeTable docrender/runs_test.go:TestMarkdownGoldenInlineRuns; docrender/markdown_test.go:TestMarkdownTelescopeReportGolden ✅ Implemented
A diagram node renders as a fenced ```mermaid block through the view engine's Mermaid writer, its direction applied as the flowchart's flow or a stateDiagram-v2 direction statement, under its caption in emphasis; a table-kind view renders as a pipe table, and a missing rendering or a kind the engine cannot draw is a typed rendering failure docrender/markdown.go renderDiagram, diagramBlocks, diagramSource, viewTable; view/mermaid.go Rendering.MermaidWith docrender/diagram_test.go:TestDiagramMermaidKinds, :TestDiagramDirection, :TestDiagramCaption, :TestDiagramTableKind, :TestDiagramMissingRendering, :TestDiagramUnrenderableKind, :TestDiagramDeterminism; docrender/markdown_test.go:TestMarkdownTelescopeReportGolden ✅ Implemented
The form a document's graph-shaped diagrams are written in is chosen when the document is rendered, not stated in the model: MarkdownOptions.DiagramForm/HTMLOptions.DiagramForm take mermaid (the default when empty), dot or plantuml, resolved once and applied to every diagram of the document; with dot the Markdown backend writes a fenced ```dot block and the HTML backend embeds the source in <pre class="dot">, both with the direction as rankdir; with plantuml a fenced ```plantuml block and <pre class="plantuml">, the direction as PlantUML's direction statement; a table-kind view is written as a table whichever form is chosen; a form outside the three is a typed ErrorUnknownForm naming all three, and a diagram kind the form cannot write (a sequence as DOT) is a typed ErrorUnrenderableForm naming the block; the CLI exposes the choice as -diagram-form on -render-document and -render-documents in every -doc-form, the REPL as %render-document <name> [mermaid\|dot\|plantuml], the LSP as diagramForm on opensysml/renderDocument, and RenderDocument over gRPC keeps the Mermaid default docrender/markdown.go MarkdownOptions, diagramForm, diagramSource; docrender/html.go HTMLOptions, writeFigure; docrender/errors.go ErrorUnknownForm, ErrorUnrenderableForm; cmd/sysml/render_document.go markdownOptions, checkDiagramForm; repl/docrender.go RenderDocumentMarkdown; lsp/document.go renderDocumentParams.DiagramForm; model/docquery.go RenderDocumentMarkdown docrender/diagram_test.go:TestDiagramDotForm, :TestDiagramPlantUMLForm, :TestDiagramFormResolution, :TestDiagramFormErrors; docrender/markdown_test.go:TestMarkdownDiagramFormIsChecked; docrender/html_diagram_test.go:TestHTMLDiagramDotForm, :TestHTMLDiagramPlantUMLForm; docrender/markdown_test.go:TestMarkdownTelescopeReportPlantUMLGolden (testdata/telescope_report.plantuml.golden.md); docrender/html_test.go:TestHTMLDiagramForm; cmd/sysml/render_document_test.go:TestRenderDocumentDiagramForm, render_documents_test.go:TestRenderDocumentsDiagramForm, render_document_html_test.go:TestRenderDocumentHTMLDiagramForm, render_document_pdf_test.go:TestRenderDocumentPDFDiagramFormDot, :TestRenderDocumentPDFDiagramFormPlantUML; repl/docrender_test.go:TestRenderDocumentDiagramForm; lsp/document_test.go:TestRenderDocumentDiagramForm ✅ Implemented
A whole document renders end to end from native SysML v2 source — parse, resolve, semantics, document planning, evaluation, rendering — locked by a committed golden Markdown file with an -update flag; a nil document is a typed rendering failure docrender/markdown.go Markdown; docrender/errors.go docrender/markdown_test.go:TestMarkdownTelescopeReportGolden, :TestMarkdownNilDocument ✅ Implemented
The same document tree renders as semantic HTML straight from the IR: an <article class="sysml-document"> holding nested <section> elements whose heading level saturates at 6, paragraphs, <ul>/<ol> lists, real <table>s with a <caption> and <th scope="col"> header, one <tbody class="sysml-group"> per group of a grouped table, and <figure> diagrams with a <figcaption>, carrying Mermaid source in a <pre class="mermaid"> or a table-kind view's cells; a nil document, a content kind the backend does not know, a missing rendering and an undrawable kind are typed rendering failures docrender/html.go HTML, writeDocument, writeSection, writeTable, writeList, writeFigure; docrender/errors.go docrender/html_test.go:TestHTMLTelescopeReportGolden, :TestHTMLSemanticStructure, :TestHTMLNilDocument; docrender/html_diagram_test.go:TestHTMLDiagramMermaidKinds, :TestHTMLDiagramTableKind, :TestHTMLDiagramErrors ✅ Implemented
Every HTML node keeps the model facts the Markdown text loses, as data- attributes a stylesheet or downstream processor can select on: the content kind and declared name of each node, the query behind a table, list or query-generated paragraph, the group column and group key, each row's selected element with its qualified name and symbol kind, each cell's projected column and value kind, each diagram's view, kind and direction, and a cross-document reference's target document docrender/html.go writeContent, writeTable, writeRows, writeCell, writeValue, elementAttrs, runHTML docrender/html_test.go:TestHTMLSemanticStructure, :TestHTMLTelescopeReportGolden; docrender/html_crossdoc_test.go:TestHTMLCrossDocumentLinksResolve ✅ Implemented
Content cannot corrupt the markup or escape its element: text, attribute values, Mermaid source and comment text are HTML-escaped, a link destination is emitted as href only for a scheme a document may navigate to and kept as data-href otherwise, an inline stylesheet that would close its <style> element is refused, and rendering the same document twice is byte-identical docrender/html.go htmlText, attr, htmlComment, navigableURL, Stylesheet.check docrender/html_test.go:TestHTMLEscaping, :TestHTMLLinkSchemes, :TestHTMLStylesheetErrors, :TestHTMLDeterministic ✅ Implemented
A reader can restyle a document without patching it: the default stylesheet is one @layer opensysml whose every value comes from a --sysml-* token on .sysml-document, so unlayered reader CSS wins on cascade origin rather than specificity; the markup carries no style attribute, no styling-only wrapper and no styling on an id, which is an anchor only; -html-css sheets are attached after the default one in the order given, a file inlined and a URL linked; -html-no-default-css leaves the default out and -html-fragment writes the <article> alone docrender/document.css; docrender/html.go DefaultStylesheet, HTMLOptions, writeShellStart, InlineStylesheet, LinkedStylesheet docrender/html_test.go:TestHTMLDefaultStylesheetIsOverridable, :TestHTMLNoInlineStylesOrUnknownClasses, :TestHTMLIdentifiersAreAnchorsOnly, :TestHTMLSuppliedStylesheets, :TestHTMLEmptyStylesheet, :TestHTMLTelescopeReportFragmentGolden ✅ Implemented
A section without an authored anchor is addressed by an identifier derived from its named path, distinct per occurrence and never taking an identifier a reference resolves to; a cross-document reference links to the target document's deterministic .html file name, escaped as anchors are docrender/html.go contentIDs, reservedIDs, derivedID, uniqueID, htmlRefDestination, DocumentHTMLFileName docrender/html_test.go:TestHTMLAnonymousSections, :TestHTMLAnonymousSectionLeavesReservedAnchor; docrender/html_crossdoc_test.go:TestHTMLLinkedDocumentsGolden, :TestDocumentHTMLFileNameEncoding ✅ Implemented
-doc-form html writes the HTML to -o or stdout and -render-documents -doc-form html writes the linked set as .html pages whose cross-document links resolve on disk; a set writes its stylesheets as files beside the pages — the default sheet and each local -html-css file, under an escaped, length-bounded, collision-free name — and links them from every page, while a -html-css URL stays a link; -html-default-css writes the default sheet and nothing else, and an HTML-only or document option asked for with a form that cannot honor it is refused cmd/sysml/render_document.go runRenderDocument, setStylesheets, setStylesheetName, escapeStylesheetName, shortenStylesheetName; cmd/sysml/main.go (flags, exclusions) cmd/sysml/render_document_html_test.go:TestRenderDocumentHTMLFlag, :TestRenderDocumentHTMLStylesheets, :TestRenderDocumentHTMLDocumentOptions, :TestRenderDocumentHTMLFlagConflicts, :TestSetStylesheetNameLength; cmd/sysml/render_documents_test.go:TestRenderDocumentsHTML, :TestRenderDocumentsHTMLStylesheets, :TestRenderDocumentsHTMLStylesheetNames, :TestRenderDocumentsHTMLFlagConflicts ✅ Implemented
%render-document <name> compiles the named document, runs its queries and prints the Markdown; an unknown name, a non-document and extra arguments are reported as errors, since a document binds its queries' parameters in the model repl/docrender.go Session.RenderDocumentMarkdown, Session.doRenderDocument; repl/meta.go (command table, dispatch) repl/docrender_test.go:TestRenderDocumentPrintsMarkdown, :TestRenderDocumentUsageAndErrors, :TestRenderDocumentListedInHelpAndCompletion ✅ Implemented
-render-document <name> renders a document from a script, writing the Markdown to -o or stdout with notices on stderr; the named files are loaded as one model, so a document may query elements its siblings declare; a document that could not be rendered leaves the unresolved exit status, and combining it with checks, -json, or another writing mode is refused cmd/sysml/main.go (flag, mode exclusions); cmd/sysml/render_document.go runRenderDocument cmd/sysml/render_document_test.go:TestRenderDocumentFlag, :TestRenderDocumentOutputFile, :TestRenderDocumentSeveralFiles, :TestRenderDocumentFlagConflicts ✅ Implemented
-doc-form pdf converts the evaluated document to PDF through a swappable external converter — WeasyPrint (default), pandoc or Prince, selected with -pdf-engine — run as a subprocess with SOURCE_DATE_EPOCH pinned; an engine that reads HTML (WeasyPrint, Prince) is handed the HTML backend's standalone page and pandoc the Markdown backend's text, so docpdf parses neither; the binary links no PDF renderer, Markdown and HTML output need none of the tools, a nil document is a typed rendering error before any tool runs, and a missing tool is a typed error naming it, its override variable and the other engines docpdf/converter.go Converter, Input, EngineNamed, tool.locate, runTool; docpdf/docpdf.go Render, htmlOptions; cmd/sysml/render_document.go documentForm docpdf/docpdf_test.go:TestEngineNamed, :TestConverterCapabilities, :TestRenderToolMissing, :TestRenderWithFakeConverter, :TestRenderToolFailed, :TestRenderNoPDF, :TestRenderNilDocument, :TestRenderHTMLIsTheBackendsPage, :TestRenderForPandoc; cmd/sysml/render_document_pdf_test.go:TestRenderDocumentPDF, :TestRenderDocumentPDFEngineMissing, :TestRenderDocumentPDFFlagConflicts; docpdf/integration_test.go (real tools, skipped when absent) ✅ Implemented
Deliverable options — a title page, a table of contents, hierarchical section numbering — are flags of the output step (-doc-title-page, -doc-toc, -doc-number-sections, with the original -pdf-* spellings kept as aliases), never document-model attributes, applied by the HTML backend for the engines that read its page and by pandoc's equivalent flags for pandoc; the HTML backend's presentation options reach PDF the same way — -html-theme, -html-no-default-css and -html-css are honored by the HTML-input engines, with the user's sheets unlayered after the default and print layers, and refused with a typed error for pandoc, whose page is not the backend's; -html-fragment, -html-mermaid and -html-math are HTML-only and refused for every engine docpdf/docpdf.go Options, checkOptions, htmlOptions; docpdf/converter.go Converter.Supports, pandocConverter.Convert; docpdf/print.css; cmd/sysml/main.go (flags) docpdf/docpdf_test.go:TestRenderHTMLIsTheBackendsPage, :TestRenderReaderStylesheets, :TestPrintStylesheetContract, :TestConverterCapabilities; cmd/sysml/render_document_pdf_test.go:TestRenderDocumentPDF, :TestRenderDocumentPDFStylesheets, :TestRenderDocumentPDFPandocStylesheets, :TestRenderDocumentPDFFlagConflicts ✅ Implemented
A document's graph-shaped diagrams are listed from the IR in document order (docrender.Diagrams, a table-kind view excluded, since it is a table in every form), drawn to SVG by the tool for the selected form as a subprocess and handed back to the backends — HTMLOptions.DiagramImages puts each image in its figure in place of the source, and pandoc's Lua filter swaps the fence for the image. Mermaid is drawn by mermaid-cli (mmdc, OPENSYSML_MMDC, required as soon as a diagram is present); -diagram-form dot by Graphviz (OPENSYSML_DOT, else dot on PATH; -Tsvg under the engine the block's // layout: header names — dot, neato, neato -n, neato -n2 — so a positioned view is drawn where the model put it); -diagram-form plantuml by the PlantUML jar (java -jar $OPENSYSML_PLANTUML_JAR -tsvg -pipe, java from OPENSYSML_JAVA or PATH, GRAPHVIZ_DOT pointed at the Graphviz found), each configured path made absolute since the tools run in the render directory. Graphviz and the jar are optional: absent, every diagram stays as the HTML backend's <pre class="dot">/<pre class="plantuml"> under a notice naming the variable to set — the print stylesheet sets it, and pandoc's filter writes the same notice ahead of the fence — and the render succeeds; present and failing, or writing anything but one well-formed SVG document, it is the typed tool-failed error carrying its stderr, as mmdc is. A document without diagrams needs no diagram tool docrender/artwork.go Diagrams, Diagram; docrender/html.go HTMLOptions.DiagramImages, writeFigure; docpdf/diagrams.go drawDiagrams, diagramToolFor, rasterizer, checkSVG; docpdf/mermaid.go mermaidRasterizer; docpdf/graphviz.go graphvizRasterizer, layoutArgs; docpdf/plantuml.go plantumlRasterizer, locatePlantUMLJar; docpdf/converter.go DotEnv, JavaEnv, PlantUMLJarEnv, absolute, toolRun; docpdf/docpdf.go Render; docpdf/pandoc.go writeArtworkFilter, dotNotice, plantumlNotice; docpdf/print.css; docpdf/errors.go; scripts/download-doc-pdf-toolchain.sh (pinned Graphviz and PlantUML jar beside WeasyPrint, mmdc and KaTeX; the pdf-toolchain CI job runs the installed-tool tests with OPENSYSML_REQUIRE_PDF_TOOLCHAIN=1) docrender/artwork_test.go:TestDiagramsListGraphShapedViews, :TestDiagramsErrors, :TestHTMLDiagramImages; docpdf/docpdf_test.go:TestRenderDiagramFormKeepsSourceForOtherForms, :TestRenderDiagramToolMissing, :TestRenderDiagramToolWritesNothing, :TestRenderForPandoc, :TestRenderForPandocKeepsOtherFormsUnderNotice; docpdf/diagrams_test.go:TestSourceNoticesNameTheVariables, :TestRenderDOTWithFakeGraphviz, :TestDrawDOTWritesTheDiagramSource, :TestGraphvizLayoutArgs, :TestDrawDOTRunsTheHeaderEngine, :TestRenderPlantUMLWithFakeJava, :TestDrawPlantUMLKeepsStdoutAsTheImage, :TestRenderPlantUMLWithRelativeJarAndJava, :TestRenderPlantUMLWithoutJavaKeepsSource, :TestRenderDiagramToolFailed, :TestDrawDiagramToolWroteNoSVG, :TestDrawDiagramToolWroteAPrefacedSVG, :TestDrawDiagramsWithoutDiagrams, :TestMermaidStaysRequiredBesideOptionalTools, :TestRenderForPandocDrawsDOTAndPlantUML; cmd/sysml/render_document_pdf_test.go:TestRenderDocumentPDFDiagramFormDot, :TestRenderDocumentPDFDiagramFormPlantUML; docpdf/integration_test.go:TestRenderDiagramsWithInstalledMermaid, :TestRenderDiagramsWithInstalledGraphviz (an ordinary graph and a neato -n layout whose nodes keep the model's coordinates), :TestRenderDiagramsWithInstalledPlantUML (a drawn diagram and a malformed one refused with Syntax Error) ✅ Implemented (the figure is embedded as SVG; WeasyPrint does not embed Graphviz's own -Tpdf output, so it is not offered. Java is the one tool the toolchain script does not provision; CI uses the runner's JDK)
Inline runs, links, in-document references and anchors reach the PDF as the HTML backend writes them — emphasis, strong and code spans as their elements, a link as <a href>, a Ref as a fragment link to the target's id, so it is a working internal link in every engine — and pandoc reads the Markdown backend's dialect itself; a caption is a native <caption> or <figcaption> in the HTML, and for pandoc the emphasized paragraph the Markdown backend writes ahead of a table, diagram or formula, which the filter marks by matching it, in document order, against the captions the IR lists (docrender.Captions), so an emphasized paragraph elsewhere stays prose and the Markdown output carries no caption marker docrender/html.go writeRuns, writeTable, writeFigure; docrender/artwork.go Captions; docrender/markdown.go renderTable (caption paragraph); docpdf/pandoc.go writeArtworkFilter, artworkFilterBody; docpdf/pandoc.css docrender/artwork_test.go:TestCaptionsFollowDocumentOrder; docrender/runs_test.go:TestMarkdownGoldenInlineRuns, :TestMarkdownGroupedTableBlankKey; docpdf/docpdf_test.go:TestRenderHTMLIsTheBackendsPage, :TestRenderForPandoc, :TestLuaString; docpdf/integration_test.go:TestRenderInlineRunsWithInstalledEngines ✅ Implemented
Mathematics is an explicit part of the document model, never inferred from prose: a Span/SpanColumn with style = "math" plans as a StyleMath run and evaluates to a RunMath text run holding raw LaTeX, and the Formula content block (required source, optional caption, a Ref target by name) plans as ContentFormula carrying its source and provenance; a blank source, a blank math span, an unknown style or a query row supplying no LaTeX to a math column is a typed error. Markdown writes $…$ and $$…$$ with the LaTeX unescaped while a $ in ordinary prose is escaped and one inside a formula cannot close it; HTML writes sysml-math spans and sysml-formula figures between MathJax delimiters and -html-math cdn\|<url> loads a pinned MathJax confined to those elements; PDF lists each distinct formula from the IR (docrender.Formulas, inline and display, in document order) and typesets it once through the katex CLI (OPENSYSML_KATEX, stylesheet beside it or OPENSYSML_KATEX_CSS) as HTML without MathML, copies its stylesheet and fonts beside the page, and lays out the result under WeasyPrint and Prince from the HTML backend's page with the typeset HTML in place of the sysml-math source (HTMLOptions.Math) and under pandoc through the Lua filter, which swaps each math element for the typeset HTML; a document without formulas needs no KaTeX, and an unclosed $$ block, a missing tool or stylesheet and a KaTeX parse error are typed errors libs/stdlib/OpenSysML Libraries/DocumentQueries.sysml Formula; docplan/plan.go StyleMath, ContentFormula, Content.Source; docplan/compiler.go compileFormula; docir/ir.go RunMath, ContentFormula; docir/evaluate.go (ErrorBlankMath); docrender/markdown.go renderFormula, mathSpan, displayMath; docrender/html.go MathScript, MathScriptURL, inlineMathHTML, displayMathHTML; docrender/artwork.go Formulas, Formula.TeX; docrender/html.go HTMLOptions.Math; docpdf/katex.go renderFormulas, katexStylesheet; docpdf/pandoc.go mathKey; cmd/sysml/render_document.go mathScriptURL, checkMathScript docplan/math_test.go:TestCompileMathSpan, :TestCompileMathSpanRejectsBlankSource, :TestInvalidRunStyleNamesMath, :TestCompileMathSpanColumn, :TestCompileFormula, :TestCompileFormulaIsCloned, :TestCompileFormulaErrors; docir/math_test.go:TestEvaluateMath, :TestEvaluateMathColumnRuns, :TestEvaluatedFormulaIsCloned; docrender/math_test.go:TestMarkdownMathReportGolden (testdata/math_report.golden.md), :TestMarkdownMathRuns, :TestMarkdownMathSpan, :TestMarkdownDisplayMath, :TestHTMLMathReportGolden, :TestHTMLMathMarkup, :TestHTMLMathScript; docrender/artwork_test.go:TestFormulasListDistinctMath; docrender/artwork_test.go:TestHTMLMathTypeset; docpdf/math_test.go:TestRenderFormulasWithFakeTools, :TestRenderFormulasForPandoc, :TestRenderWithoutFormulasNeedsNoKatex, :TestRenderFormulasKatexMissing, :TestRenderFormulasStylesheetOverride, :TestRenderFormulasKatexFails; docpdf/integration_test.go:TestRenderFormulasWithInstalledKatex (real KaTeX and engines, skipped when absent); cmd/sysml/render_document_html_test.go:TestRenderDocumentHTMLMath ✅ Implemented (known limitation: the formula's LaTeX is authored text or a query value, not derived from a constraint or calc expression; HTML typesetting needs the page to load MathJax, so without -html-math the LaTeX source is shown)
opensysml/documents lists the workspace's document definitions in qualified-name order and opensysml/renderDocument renders one to Markdown through the same planning/evaluation/rendering pipeline, advertised as the openSysmlRenderDocument experimental capability; an unknown name, an ambiguous name, a non-document, and a plan or execution failure fail the request with the typed error's message internal/frontend/lsp/document.go Server.Documents, Server.RenderDocument; internal/frontend/lsp/render.go renderHandler; internal/workspace/model/docquery.go Workspace.DocumentDefinitions, Workspace.RenderDocumentMarkdown internal/frontend/lsp/document_test.go:TestDocumentsListsWorkspaceDocuments, :TestRenderDocumentMarkdown, :TestRenderDocumentTypedErrors, :TestRenderDocumentAmbiguousName ✅ Implemented
Authoring a document in the editor is served by the standard LSP methods: document-query/document-plan diagnostics publish live with spans and codes, definition jumps from a query invocation to its calc def, from a binding name to the query's parameter, and from a bound value to the model element, hover on a reference shows the target's signature and doc comment, and completion offers the visible query definitions — imports included, qualified names filtered the same way — in a calc usage's type position and exactly the query's in parameters in binding position internal/frontend/lsp/diagnostics.go; internal/frontend/lsp/definition.go Server.Definition; internal/frontend/lsp/hover.go Server.Hover; internal/frontend/lsp/completion.go Server.Completion, calcTypingPositionAt; internal/workspace/model/docquery.go Workspace.QueryBindingParameter, Workspace.QueryUsageParameters, Workspace.QueryTypeCandidates; internal/ir/docplan/compiler.go QueryTarget internal/frontend/lsp/document_test.go (definition, hover, completion, live document-plan diagnostics), internal/frontend/lsp/diagnostics_test.go:TestPublishDiagnosticsKeepsQueryDependencyErrorsInTheirDocument ✅ Implemented

Known limitations: PDF output is CLI-only: the REPL and LSP surfaces render Markdown, and gRPC Markdown or HTML, since the PDF path needs the CLI's converter toolchain. Prince is recognized as an engine but not provisioned by scripts/download-doc-pdf-toolchain.sh (it is commercial), so its converter is verified by the HTML and stylesheets it is handed. PDF output is byte-reproducible against one pinned toolchain (SOURCE_DATE_EPOCH is pinned); different converter versions or system fonts produce different bytes. Pandoc's page is its own, not the HTML backend's, so -html-theme, -html-no-default-css and -html-css are refused for that engine, and its captions are recognized by matching the Markdown's emphasized paragraphs against the document's captions in order rather than by markup. A PDF is one document, never a linked set, so a Ref into another document links to that document's file name under every engine, which no PDF beside it carries; an in-document Ref is a working internal link. HTML presentation is configurable by stylesheet (-html-css, -html-no-default-css, -html-default-css, -html-fragment), and the same sheets reach a WeasyPrint or Prince PDF; a Mermaid theme is not, so a diagram's model-level attributes (caption, direction) remain its whole presentation surface beyond the stylesheet. The document IR is not reported as JSON, so -json does not combine with -render-document. Inline formatting applies to statically-authored runs (Span, Link, Ref); text produced by a query renders as escaped plain runs. A reference targets a content block of this or another document, or another document's root, through a usage typed by the target document definition; -render-documents <dir> writes the linked set with deterministic file names so the relative links resolve on disk, and a single-document render links to the target's expected file name whether or not it was rendered. The editor's Markdown rendering is on demand (the Render Document command), not a live preview that re-renders as the model is typed. Hover on the bundled DocumentQueries library types shows their signatures but no prose documentation, since the library declares none.


Analysis Engines (internal/exec/analysis) — OpenSysML extension, not a conformance claim

Standard: none. SysML v2 defines what an execution of a model is and what a condition evaluates to; it defines no notion of how strong the evidence behind a verdict is, nor which of several ways of asking about a model's executions a tool should use. The analysis framework (docs/internals/design/analysis-framework.md) is therefore an extension over the runtime: the interpreter stays normative, an engine is a way of asking about the executions it defines, and a disagreement between engines is resolved in the interpreter's favor.

Rule Implementation (file:function) Tests Status
Every verdict states its standing — the claim, the strength of the evidence (not covered < observed < witnessed as a confirmed existential, bounded < proved) and what earned it (holds (observed: 1 run under reverse)); a reached bound is named and lowers the strength; no path promotes observed to bounded or bounded to proved; a sat witness that fails replay is not covered with the reason, never violated analysis/result.go Strength, Consistent; analysis/standing.go Result.Standing, Plan.Standing, Step.Standing; repl/engines.go standingPrefix (the standing: line ending every REPL and CLI verdict, -json lines) analysis/scale_test.go:TestConsistentAdmitsTheScale, :TestEveryEngineClaimStrengthPair (every (Claim, Strength) pair each of run, explore, sweep, solve may produce), :TestABudgetReachedLowersTheStrengthAndPrintsTheBound, :TestAWitnessThatFailsReplayIsNotCovered, :TestNoPathPromotesTheStrength; repl/engines_test.go:TestVerdictsCarryTheirPlanAndStanding; cmd/sysml/engines_test.go:TestVerdictsCarryTheirStanding ✅ As designed
Selection: auto (the default) puts a question to the engines covering it strongest first, as before; a named engine is asked alone and its refusal or not covered answer is the verdict; all puts it to every covering engine at once, on min(Jobs, engines) goroutines with the jobs divided among them, and composes their results in name order; an unknown name is a typed UnknownEngineError before anything runs, on every surface (sysml -engine, %engine, the wire's INVALID_ARGUMENT); explore as an engine is the exploring schedule analysis/selection.go Selection, ParseSelection, Registry.Select, UnknownEngineError, Explores; analysis/dispatch.go Registry.AnswerWith, Plan.Selection, Plan.Refused; repl/engines.go Session.Engine, Session.SetEngine; repl/meta.go %engines, %engine; cmd/sysml/main.go engineSelection, -engines, -engine; grpc/engines.go engineSelection analysis/selection_test.go:TestParseSelectionReadsTheFlag, :TestSelectRefusesAnUnknownEngine, :TestNamedEngineRefusalIsFinal, :TestNamedEngineNotCoveredIsFinal, :TestAllRunsEveryCoveringEngineInNameOrder, :TestAnswerIsAnswerWithUnderAuto; analysis/all_test.go:TestAllRunsTheCoveringEnginesConcurrently, :TestAllWithFewerJobsThanEnginesRunsThemInTurn, :TestAllConcurrentFaultStopsThePlanAtTheFaultInNameOrder, :TestAllConcurrentDeadlineKeepsWhatFinishedAndMarksTheRest, :TestAllDoesNotCancelAUniversalRunWhenAWitnessArrives; repl/engines_test.go:TestEngineShowsAndSetsTheSelection, :TestNamedEngineRefusalIsFinal, :TestAllRunsEveryCoveringEngine, :TestEngineExploreAtThePromptIsRefused, :TestEngineSelectionIsIndependentOfTheSchedule, :TestEngineSelectionKeepsTheDebuggerSession; cmd/sysml/engines_test.go:TestEngineRefusesAnUnknownName, :TestEngineNamedIsFinal, :TestEngineAllListsEveryCoveringEngine, :TestEngineExploreIsScheduleExplore; grpc/engines_test.go:TestAnUnknownEngineIsInvalidArgument, :TestNamedEngineRefusalIsFinalOverTheWire, :TestEngineExploreIsScheduleExplore ✅ As designed (the composition does not depend on the order the engines finish)
Composition under all: a witnessed violation stands over any universal claim; a universal claim an execution refutes is a disagreement, resolved in the interpreter's favor, the refuted result demoted to not covered with the reason and both named in the plan; agreeing universal claims stand at the strongest earned, never promoted; differing observed values are a witnessed sensitivity; an engine the plan's deadline cancels is kept in the plan, marked cancelled with the bound it reached, and the composed result is at the strength the finished engines earned analysis/compose.go Compose, Disagreement; analysis/dispatch.go Step.Cancelled, Step.Bounds, Plan.Disagreements, Plan.Results analysis/compose_test.go:TestComposeResolvesAContradictionInTheInterpretersFavor (a proof-claiming engine in the shape the SMT engine will fill against explore's witness), :TestAllDemotesTheContradictedProofInThePlan, :TestComposeTakesTheStrongestEarnedWithoutPromotion, :TestComposeIsIndependentOfFinishOrder, :TestComposeSatisfiableWitnessStandsOverUnsat, :TestComposeDifferingValuesAreASensitivity; analysis/selection_test.go:TestAllKeepsFinishedResultsAndMarksTheCancelled, :TestAllWithNothingFinishedFailsWithTheDeadline, :TestAllWithACallerGoneKeepsWhatFinished, :TestAllStopsOnARunError, :TestAllRefusedByEveryEngineNamesEachRefusal ✅ As designed
The engines of the build are listed — name, kind (built-in or the manifest entry kind), protocol (-, object, stdio/1), authority, the questions each answers, the bounds it declares and whether its process was found (solve names the solver it discovered), and for a manifest entry its file, command and version — in name order, on every surface; listing starts no process analysis/listing.go Registry.Listings, Listing, Lines; cmd/sysml/main.go -engines; repl/meta.go %engines; grpc/engines.go Service.ListEngines analysis/registry_test.go:TestDefaultHoldsTheFrameworksEngines, :TestAbsentProcessListsAndRefuses; engines/engines_test.go:TestDefaultHoldsTheFrameworksEnginesAndSMT, :TestSMTStatusIsTheSolvers, :TestPresentProcessIsListed; cmd/sysml/engines_test.go:TestEnginesListsTheBuild, :TestEnginesExitsWithoutAModel; repl/engines_test.go:TestEnginesListsEveryRegisteredEngine; grpc/engines_test.go:TestListEnginesNamesEveryEngine; client/python/tests/test_engines.py ✅
Machine-readable standing: -json checks carry plan (selection, composed standing, each step's engine and status, the disagreements) and results[] (per answering engine: engine, claim, strength, bounds with reached, witness, standing) beside the keys they always carried; the wire carries engine, strength and bounds on Verdict, RunAnalysisResponse, RunSweepResponse and the verification responses, engine on their requests (unset is auto; a set field requires the engines capability), and ListEngines; the Python client takes engine= and reads Verdict.engine, .strength, .bounds; no existing key, field or flag changed cmd/sysml/report.go checkPlan, checkResultOf, checkBound, checkWitness; api/proto/sysml.proto Bound, EngineInfo, ListEngines; grpc/engines.go CapabilityEngines; grpc/analysis.go, grpc/sweep.go, grpc/verify.go; client/python/opensysml/engines.py Bound, Standing, EngineInfo, connection.py Connection.list_engines cmd/sysml/engines_test.go:TestJSONReportsThePlan, :TestJSONReportsTheExploredPlan; grpc/engines_test.go:TestEngineFieldNeedsTheEnginesCapability, :TestVerdictsCarryEngineStrengthAndBounds; client/python/tests/test_engines.py; make proto-breaking, make man-check ✅ (whether the -json keys are a patch or a minor change is an item of the release checklist in CONTRIBUTING.md)
External tools: a performance of an action carrying AnalysisTooling::ToolExecution is put to the tool:<name> engine of its toolName, never to the action's body — one process per performance, one JSON object each way over its standard input and output, toolName and uri passed through uninterpreted, inputs/outputs keyed by ToolVariable.name with value and unit, outputs converted to the coherent unit of the parameter's declared quantity kind; each *.json entry of the directory OPENSYSML_TOOLS names (toolName, version, executable, variables) registers one engine answering compute at authority observed, listed with its process status like solve; a tool with no entry is refused with tool 'ModelCenter' is not registered; set OPENSYSML_TOOLS; a non-zero exit, malformed or missing output, an output no parameter receives, a unit the model does not declare and the timeout OPENSYSML_TOOL_TIMEOUT (default 10s) are typed errors that fail the performance — no default value is invented; equal inputs answered differently are a tool-divergence note of the run. ToolExecution on a calc def or calc usage is the same contract on every calc surface (sysml -calc, %calc, EvaluateCalc, derived attributes, document formulas): the in/inout parameters carrying ToolVariable are the inputs, the out/inout parameters carrying it plus the result parameter — under its ToolVariable name, else its declared name, else result — bind from the reply, and the body never evaluates analysis/manifest.go LoadManifest, ExternalsFromEnv, ToolEntry, ManifestError, ToolAbsentError; analysis/registry.go DefaultFromEnv; analysis/tool.go NewTool, toolEngine.Covers, toolEngine.Run, ToolRequestOf, ToolReplyOf, WrongToolError, ToolVariableError; analysis/tool_runner.go toolRunner.RunTool; analysis/dispatch.go Registry.AnswerWith (the plan-scoped runner); runtime/tool.go ActionExecutor.performByTool, ToolCall, ToolRunner, ToolNotRegisteredError, ToolError, ToolDivergence; runtime/tool_calc.go calcToolCall, computeCalcByTool with the hooks in runtime/invoke_calc.go and runtime/calc_usage.go; runtime/declared.go NewDeclaredReaderIn (derived features read through the held runner); semantics/coherent_unit.go Model.CoherentUnitFor; cmd/sysml/main.go, grpc/service.go (DefaultFromEnv at startup) analysis/tool_fixture_test.go:TestPilotFixtureRunsAgainstTheStandIn (the pilot AnalysisAnnotation against the Go stand-in testdata/toolstandin), :TestPilotFixtureFailsWithTheToolsFault, :TestPilotFixtureTimesOut, :TestPilotFixtureRefusesAnUnregisteredTool, :TestPilotFixtureRefusesAnAbsentExecutable, :TestPilotFixtureReportsANonDeterministicTool, :TestToolEngineDispatch; analysis/tool_test.go:TestManifestReadsOneEntryPerJSONFile, :TestManifestFaultsAreTyped, :TestManifestRefusesTwoEntriesForOneTool, :TestExternalsFromEnvUnsetIsNoTool, :TestExternalsFromEnvRegisterEachEntry, :TestToolRegistrationIsIsolatedAndUnique, :TestToolEngineCoversItsOwnComputationsOnly, :TestToolTimeoutFromEnv, :TestToolRequestCarriesTheCallUninterpreted, :TestToolReplyIsOneObjectOfOutputsOrAnError; runtime/tool_test.go:TestToolExecutionPerformsThroughTheRunner, :TestToolExecutionWithoutRunnerIsNotRegistered, :TestToolExecutionRefusesBadAnswers, :TestToolExecutionNotesDivergence; runtime/tool_calc_test.go (TestToolCalc* — binding, conversion, derived attributes, a body never run, every typed failure), runtime/robustness_toolcalc_test.go:TestRuntimeRobustnessToolCalc; analysis/tool_calc_test.go (TestToolCalc* against the testdata/toolcalc stand-in — answer, refusal, exit, timeout, unregistered); repl/tool_calc_test.go, cmd/sysml/tool_calc_test.go (%calc, -calc, -render-document); tests/grpc/testdata/conformance/evaluate_calc_tool* ✅ As designed (under smt, a tool output is a free input — the contract that engine will meet; it is not on this branch)
External engines: each kind: engine entry of the directory OPENSYSML_ENGINES names (read beside OPENSYSML_TOOLS under one rule set — outside every workspace, owner-writable only, command confined to the manifest directory and never looked up on PATH, duplicate names refused across both directories, policy and sampler entries parsed and listed as not served) registers an engine under its own name, spoken to over its standard input by JSON-RPC-shaped lines — describe checked against the entry field by field, covers, run, cancel, progress coalesced to a quarter second per open run, errors unsupported/budget/internal — one process per plan, several open requests on it when concurrent, one line and the captured standard error each bounded by OPENSYSML_TOOL_MAX_OUTPUT (default 64 MiB); the model is handed as sources or as graphs:1, the versioned byte-stable export of the lowered ActionGraph/StateGraph, rdf refused as a later stage; nothing the engine claims stands unchecked: violated is witnessed when its schedule replays and the property is false at the move named, sensitive when two schedules replay and end the feature differently, satisfiable when the assignment is confirmed, a universal claim observed over executions that replay and otherwise not covered with the claim kept, admit refused naming the referee-record stage; auto reaches an external engine after every built-in engine refused, all composes it with them and a stood claim beside a built-in witness is a disagreement; -engines, %engines and ListEngines list it (-engines -probe and %engines probe start each engine once), the service lists but does not serve it until -serve-external-engines analysis/manifest.go ManifestsFromEnv, ExternalsFromEnv, EntryKind; analysis/engine_entry.go EngineEntry, confinedPath, NotServedError; analysis/process.go outputLimitFromEnv, boundedBuffer; analysis/session.go session, enginePool, Reporter, ProgressInterval; analysis/enginewire/wire.go; analysis/external.go externalEngine, SubjectError; analysis/external_question.go; analysis/external_standing.go; analysis/withheld.go; analysis/listing.go Listing.Origin, Registry.Probed; analysis/modelform/graphs.go GraphsOf, GraphsVersion; analysis/modelform/sources.go SourcesOf; cmd/sysml/main.go -engines -probe; repl/engines.go %engines probe; grpc/engines.go ListEngines; cmd/sysml-grpc/main.go -serve-external-engines; docs/reference/engine-protocol.schema.json analysis/engine_entry_test.go:TestManifestReadsEveryKind, :TestEngineEntryFaultsAreTyped, :TestManifestRefusesTwoEntriesForOneEngineName, :TestEngineCommandIsConfinedToTheManifestDirectory, :TestToolExecutableIsConfinedToTheManifestDirectory, :TestEngineEntryPresentChecksTheProgramWithoutRunning, :TestManifestUnderAWorkspaceIsNotRead, :TestManifestRefusesWritableByOthers; analysis/process_test.go:TestOutputLimitFromEnv, :TestBoundedBufferKeepsThePrefixAndStops, :TestToolOverflowNamesTheBound; analysis/session_test.go:TestSessionHandshakeCoversAndRun, :TestSessionHandshakeMismatchIsTypedPerField, :TestSessionStartupFailuresAreTyped, :TestSessionErrorCodesAreTyped, :TestSessionProtocolBreaksEndTheSession, :TestSessionLineOverTheBoundEndsTheSession, :TestSessionExitDuringRunIsTyped, :TestSessionCancelIsAnswered, :TestSessionIgnoredCancelEndsTheProcess, :TestSessionProgressIsCoalesced, :TestSessionMatchesConcurrentAnswersByID, :TestEnginePoolHonorsConcurrent, :TestEnginePoolEndsTheEngineForThePlanAtTheFirstFault; analysis/external_standing_test.go:TestExternalViolationStandsAfterReplay, :TestExternalViolationRefusedWhenThePropertyHolds, :TestExternalViolationIsJudgedAtTheMoveNamed, :TestExternalWitnessThatDoesNotReplay, :TestExternalWitnessShapeIsChecked, :TestExternalWitnessInputsAreRefused, :TestExternalEntryWithoutWitnessesEarnsNoExistential, :TestExternalSensitivityNeedsTwoDivergingSchedules, :TestExternalExecutionsAreObservedAfterReplay, :TestExternalExecutionsFailingTheClaimEarnNothing, :TestExternalUniversalClaimsWithoutExecutionsAreNotCovered, :TestExternalNoneIsTheEnginesOwnRefusal, :TestExternalAssignmentIsConfirmed, :TestExternalEngineInAutoAndAll, :TestExternalStoodClaimDisagreesWithBuiltIn, :TestExternalPlanIsTheSameUnderEitherConcurrency, :TestExternalSubjectsAreDeclarationKinds; analysis/schema_test.go:TestSchemaValidatesEveryStandinMessage, :TestSchemaRefusesWhatTheSessionRefuses, :TestSchemaMatchesTheWireTypes; analysis/modelform/graphs_test.go:TestGraphsActionCarriesTheLoweredGraph, :TestGraphsStateCarriesTheLoweredGraph, :TestGraphsAreByteStable, :TestSourcesOfListsEveryDocumentInOrder, :TestRefuseRDFFormIsTyped, :TestGraphsMatchTheGoldens; cmd/sysml/external_engines_test.go:TestEnginesSpawnsNothingAndProbeSpawnsEachEntryOnce, :TestEnginesUnderTheWorkspaceIsNotRead, :TestProgressGoesToStandardError; repl/engines_test.go:TestEnginesListsManifestEnginesAndProbesOnRequest, :TestProgressIsPrintedWhereTheSessionSays; grpc/engines_test.go:TestManifestEnginesAreListedButNotServedByDefault, :TestServeExternalEnginesRunsTheNamedManifestEngines ✅ As designed (stage 1 of the bring-your-own-engine design; referee records and admit, policy/sampler strategies, the rdf form, in-process Go, WebAssembly and the grpc transport are later stages)
Parallel runs: Budget.Jobs (-jobs <n>, %jobs <n>, OPENSYSML_JOBS; default one per CPU, fewer where the memory available leaves less than 512 MiB per worker; a count below one or no integer is the typed JobsError before anything runs; the flag overrides the environment; the gRPC service takes the serving binary's count, no request field) is how many runs of one plan go at once, each on a worker of its own — a resolver and semantic model per job over the shared frozen index, built lazily per plan, Model.NewContext being job 0 — so no run sees another's memo; the result of a plan does not depend on Jobs: explore runs its prefixes on a work queue ordered as the sequential exploration takes them (Runs a cut in that order, committed and speculative runs, at most Jobs speculative runs discarded in a plan's lifetime, never more than Runs + Jobs executions, the table merged by outcome identity with the least witness; a violating run is an outcome of the table as under one job, explore answering the universal outcomes alone, so the witness cut awaits an existential question on the queue), all composes in name order whatever the order of finishing, and sweep runs its rows on the Jobs workers, each row in a fresh run-owned context over the plan's worker (SweepRun takes the row's context; SweepRow.Context keeps it for the row's readers, so nothing a row produced is read through another context) and assembles its table in plan order whatever order the rows finish in, Runs remaining the row limit and one job the former loop; the REPL, CLI and gRPC paths instantiate a row's subject and self in the row's context and carry the arguments' values into it (Context.Carry: evaluated once at the prompt, so a held feature a run wrote reads as written; every object a value names — alone, in a collection, as a function's self — the one the row makes for it, the row's subject when they coincide; a deferred expression or a function closing over a run's bindings the typed NotPortableError under SweptArgumentError); the gRPC response renumbers each row's objects so the request-wide instances table names every row's own; %sweep on a held object runs each row on an object of its own that is the held object as the sweep found it: the held object's declaration materialized afresh when the held closure is pristine — reached from a declaration (one nested in another's feature on the like of its root, walked along the same path), the root not destroyed, no feature written since materialization, no signal posted to it awaiting dispatch, every behavior its type exhibits or performs still as its start left it (the executors record whether they have moved at their own step points — a step that fails after moving a token, writing a performance or leaving a state included — ActionExecutor.moved/StateExecutor.moved, carried through Snapshot/Restore), none waiting on a clock that has left zero, no signal open to any taker in flight while it runs a behavior — and otherwise a copy from one image of the held graph (Context.Image, HeldImage.Materialize: the roots' closure by value — identities, lives, owners, feature values, the messages bound for it and, where it runs a behavior, those open to any taker, occurrences and the executors' captured state — taken once while the session's state is held and made in every row's context under the same identities, with fresh executors on the row's clock; #id-named objects admitted when the image holds the identity, feature paths when the copy resolves them, which it does whenever the held graph does), refusing with the typed SweptObjectError naming the reason what the image cannot carry (ErrSnapshotMidRun, ErrSnapshotPausedBody, NotPortableError, HeldImageError), never sharing the session's context, whose state lock is released while the rows run; a deadline met mid-sweep starts no further row, discards the rows in flight and reports the caller's error and no table; -json plan carries workers and warming (ms), the human-readable report neither analysis/jobs.go ParseJobs, JobsFromEnv, DefaultJobs, JobsError; analysis/result.go Budget.Jobs, BudgetOf; analysis/worker.go Model.WorkerAt, Model.NewContextOn, warmed; analysis/dispatch.go answerAll, allCoordinator, Plan.Workers, Plan.Warming; runtime/explore.go ExploreWith (Explore is its one-job form); runtime/explore_queue.go exploreQueue (work, next, startable, finish, fold); runtime/sweep.go SweepRun, SweepRow.Context, runSweepRow; runtime/sweep_queue.go RunSweepWith, sweepQueue (work, take); runtime/pristine.go Context.Pristine, HeldStateError; runtime/action_executor.go ActionExecutor.moved, newActionExecutorOn; runtime/state_executor.go StateExecutor.moved, newStateExecutorOn; runtime/snapshot.go actionCapture.moved, stateCapture.moved; runtime/held_image.go Context.Image, HeldImage.Materialize, HeldImage.Holds, HeldImageError, ErrImageIdentityTaken, ErrImageBindingTaken, ErrImageClock, ErrImageBound, ErrImageRoot (a destroyed root refused as ErrOccurrenceDestroyed; a failed materialization leaving the destination as it found it); runtime/held_image_behavior.go (executor state by value and its translation); runtime/classifier_behavior.go bindClassifierBehavior; runtime/carry.go Context.Carry, Bring, NotPortableError; analysis/sweep.go sweepEngine.Run (Model.NewContextOn per job); repl/sweep.go Session.runSweep, sweptArgs, sweptValue, sweptHeld, sweptImage, rowObjects, sweptObject, sweptOwner, sweptRef, SweptObjectError, SweptArgumentError, sweepTableLines; repl/lookup.go Session.heldRoot, heldLabel; grpc/sweep.go Service.RunSweep, sweepResponse, rowIDs; grpc/verify.go verifyContext.on; grpc/convert.go ValueToProtoIn, InstanceToProto; cmd/sysml/main.go -jobs; cmd/sysml/report.go checkPlan.Workers/Warming; repl/jobs.go Session.Jobs, Session.SetJobs; repl/meta.go %jobs; grpc/service.go Service.jobs (from JobsFromEnv at construction); usage/environment.go OPENSYSML_JOBS runtime/explore_queue_test.go:TestExploreWithIsExploreOverTheConformanceCorpus (one job against eight over every case with an admissible set), :TestExploreWithKeepsTheLeastWitnessOfALaterFasterViolation, :TestExploreWithCutsRunsJustAboveTheWitness (testdata/later_prefix_violates_faster.sysml), :TestExploreWithNeverRunsMoreThanRunsPlusJobs (conformance action_explore_slow_first_writer.sysml, a bounded recursion for the slow prefix), :TestExploreWithBuildsEachRunOnItsJob, :TestExploreWithStopsWhenTheCallerGoesAway, :TestExploreWithFailsWhenFreshFails; analysis/jobs_test.go:TestJobsFromEnv, :TestParseJobsNamesItsSource; analysis/isolation_test.go:TestPlansOnOneModelHaveWorkersOfTheirOwn (Jobs workers per plan, two plans on two goroutines under -race); analysis/all_test.go:TestAllPlanCountsTheWorkersOfEveryEngine; cmd/sysml/jobs_test.go:TestJobsFlagAndEnvironment, :TestJSONIsTheSameOnOneJobAsOnEight (the three determinism fixtures and a sweep through -json), :TestSweepReportsAreTheSameOnOneJobAsOnEight (every CLI sweep golden and the trade-study sweep, text and -json), :TestJSONReportsThePlanWorkers; repl/jobs_test.go:TestJobsShowsAndSetsTheCount, :TestSetJobsReachesTheBudgetAndKeepsTheSession; runtime/sweep_queue_test.go:TestRunSweepWithTablesRowsInPlanOrderWhateverTheirArrival (a context per row among its checks), :TestRunSweepWithKeepsEachRowsWritesToItself, :TestRunSweepWithStopsAtTheDeadline, :TestRunSweepWithFailsWhenAJobHasNoContext; runtime/carry_test.go:TestCarryTakesAValueIntoAnotherContext, :TestCarryRefusesWhatNoOtherContextHolds; runtime/value_kinds_test.go:TestEveryValueKindIsDispatched (carrying among the surfaces walked); analysis/isolation_test.go:TestSweepsOnOneModelHaveWorkersOfTheirOwn (two sweeps on one model on two goroutines under -race); repl/sweep_rows_test.go:TestSweepRowsReadAlikeOnOneJobAndOnEight (every REPL sweep golden, the trade-study sweeps and a descending bounded recursion whose rows arrive out of plan order), :TestSweepRowsWritingTheSubjectKeepTheWritesToThemselves, :TestSweepOverAnObjectNotAsItsDeclarationMadeItRunsOnItsImage, :TestSweepOverAMovedStateMachineRunsOnItsImage, :TestSweepOverAnObjectNoImageCarriesIsRefused, :TestSweepOverADestroyedObjectIsRefused; repl/sweep_held_test.go:TestSweepOverAFreshObjectRunningABehaviourReadsAsTheSequentialFormDid (the sequential form's table pinned on one job and on eight), :TestSweepOverAMovedPerformedActionRunsOnItsImage; runtime/pristine_test.go:TestPristineFollowsAStateMachinesMoves, :TestPristineFollowsAPerformedActionsMoves (the record through the moves and through Snapshot/Restore; a signal posted refused), :TestPristineRefusesATimedBehaviorOnceTheClockHasMoved, :TestPristineRefusesABehavingObjectWhileAnOpenMessageIsInFlight, :TestActionStepFailingAfterAMoveRecordsIt, :TestActionBodyFailingAfterAWriteRecordsTheMove, :TestStateChangeTransitionFailingRecordsTheMove; runtime/held_image_test.go:TestHeldImageCarriesAMovedStateMachine, :TestHeldImageOfAFreshObjectIsPristine, :TestHeldImageCarriesAParkedAction, :TestHeldImageCarriesTheRunsSchedulePolicy, :TestHeldImageRefusesWhatItCannotCarry (a destroyed root among them), :TestHeldImageRefusesAMessageNamingAnObjectNotHeld, :TestHeldImageOfABehaviorlessClosureLeavesTheBusBe, :TestHeldImageRefusesADestinationWithAWaitDueBeforeItsInstant (one already due at the destination's instant among them), :TestHeldImageRefusesAPausedBody, :TestHeldImageMaterializeFailsWhole (a destination on a shared sequence among them), :TestHeldImageKeepsTheIdentitiesSetAsideForConnectors, :TestHeldImageRefusesAnIdentitySetAsideByTheDestination, :TestHeldImageRefusesAUsageDenotingAnotherObjectOfTheDestination, :TestHeldImageRoundTrip (every conformance case the image admits, captured in one context, made in another and run beside the source), :TestHeldImageServesConcurrentSweeps (one image, two sweeps of eight rows on two goroutines under -race); cmd/sysml/sweep_test.go:TestSweepOverAnInstantiatedObjectRunningABehaviourThroughCLI; grpc/sweep_test.go:TestRunSweepOverASubjectRunningABehaviour, :TestRunSweepOverSubjectsRunningABehaviourConcurrently, :TestSweepOverAnObjectReachedThroughAnotherRunsOnItsLike, :TestSweepArgumentsReadAsThePromptReadsThem, :TestSweepArgumentsNamingAnObjectBindTheRowsOwn, :TestSweepLeavesADebuggerStepping; grpc/sweep_test.go:TestRunSweepAnswersAlikeOnOneJobAndOnEight, :TestRunSweepRowsNameTheirOwnObjects; make man-check ✅ Faithful (what the image does not carry is refused, typed: a body paused mid-statement, a session inside a step, a value closed over its run)
The smt engine (internal/exec/smt, the SMT model checking design, stages 1 to 3): a holds question about an action with the schedule free, the inputs free or both is put to an SMT solver over the lowered ActionGraph — straight-line bodies, fork, join, merge, decisions (two holding guards are the executor's decision branch choice, ranged over, not asserted impossible), body loops unrolled Budget.Unroll times (analysis.DefaultUnroll, 4, when zero; the -check-unroll flag and %check-bounds unroll= set it; reported as the unroll bound), pins and object flows; clock, messages, nested flows, object-valued assignments, calc invocation and nonlinear arithmetic refuse the whole behavior before any query, naming the node and construct. In s_0 a feature the model binds — a default, a value the performing object holds, a value the caller fixed through Start — is asserted equal to it and a feature it leaves unbound is a free variable whose domain is the declared type's: Boolean, Integer within the runtime's signed 64-bit range, Natural as an integer >= 0, Real, Rational and a quantity type over one as the solver's reals, an enumeration or variation point as its constructors; a HoldsAsk.Inputs name (-check-input, %check-input) drops a bound feature's binding so it ranges over that domain, and a name that is no feature the action reads (nothing, an output, a result, a node) is the typed InputError before any query; a declared type the encoding cannot narrow to a sort (String, a collection, an object-valued feature, a type with no translation) is the typed DomainError naming the feature and type, not covered before any query, never a silent unconstrained variable; a pinned value is checked against its domain the same way. A HoldsAsk.Assume constraint or requirement (-check-assume, %check-assume) is translated as any condition is and asserted over s_0, one the translator refuses being refused naming the construct before any query; the assumptions are asked for consistency first and a set no initial state satisfies is not covered: assumptions admit no initial state, never proved. The requirement or constraint property and the deadlock property are asked as ∃ i ≤ k. ¬R(s_i) and ∃ i. stutter ∧ ¬complete over k = Budget.Depth moves (smt.DefaultMoves, 40, when the engine is asked alone without a depth) under Budget.Solver; unsat is proved only when no schedule reaches the move, unroll or slot bound at k and bounded otherwise, the proof meaning for every value of the free inputs in their domains and its Result.Inputs and Result.Assumptions listing each input with its type, domain, value or freedom and each assumption; every sat model is decoded to the free inputs' values (InputTaken, a rational spelled exactly, a constructor by its qualified name) and ChoiceTaken lines, replayed through the interpreter under replay: with the inputs fixed before the defaults and the moves followed after, and is violated (witnessed) only when the replay reaches the state the solver described — a replay that diverges or cannot set an input, unknown, a timeout and a refusal are not covered with the reason. A sensitive question — asked when -check-diverge/%check-diverge names a feature, whichever engine answers, so check and smt read one flag and -engine all puts one question to both — is decided per feature by a two-copy query: copy B of the whole encoded relation shares s_0 and the free inputs with copy A and renames every other variable, both copies complete within k moves and the feature's final values differ; a sat decodes to two schedules, both replayed through runtime.Replay before the verdict is sensitive (witnessed) with the two final values, the first move the schedules part at and the move each took, Result.Witness and Result.Contrast the pair and -check-witness writing -A and -B files either of which replay:/%replay follows, a replay that does not reproduce its value not covered with the disagreement; on unsat the deadlock and typed-error properties are asked next, a sat there that violated finding, then ∃ schedule. cut_k ∨ loopflag, whose sat is no sensitivity found within k moves as (holds, bounded) — the pair check's clean exhaustive search of the same question answers — and whose unsat alone is (holds, proved); the three are separate queries, so no CapIncremental is needed; a feature of the performing object (this.level) and a body stage 4 encodes (the clock, a paused nested flow) are per-feature and whole-behavior typed refusals, never a silently narrowed answer, and absent a named feature the default set is check's — the action's attributes, answered, and the performer's, refused each by name. A fixed schedule, outcomes and every other question kind are refused with a typed reason; explore and check keep refusing FreeInputs with theirs, so -engine all with -check-input shows check refused in the plan beside smt's answer. The engine is analysis.External over OPENSYSML_SMT, z3, cvc5, registered at authority proved in the build's registry engines.Default() (internal/exec/engines, which composes analysis.Default() — the framework's own engines, which smt imports and so cannot be constructed from — with smt; the CLI, REPL and gRPC service build it), so -engine smt and %engine smt reach it and -engines, %engines and ListEngines list it with its solver as its status; no surface asks holds under auto, so its registration moved no verdict or plan line, and a tool-computed output is not yet a free input because a body performing a tool:<name> engine is refused as stage 1 refuses it smt/support.go Analyze, Flow, BodyLoop, DefaultUnroll; smt/state.go Sorts, State, Move; smt/encode.go Encode, Encoding, Encoding.initial, Encoding.domain; smt/input.go Encoding.frees, Encoding.checkReleases, Encoding.domainText, noDomain; smt/property.go Encoding.Conditions, Encoding.Assume, Encoding.Deadlock, Encoding.Violation, Encoding.Failure, Encoding.Uncertainty, Encoding.Cuts, Encoding.Completion, Encoding.Outputs; smt/witness.go Encoding.Decode, Encoding.decodeRun, Encoding.decodeInputs, Encoding.DecodeOutputs, Encoding.Fix; smt/twocopy.go CopyPrefix, CopyNames, Pair, Encoding.Pair, Encoding.Sensitivity, Encoding.DecodePair, Diverging; smt/sensitive.go run.decideSensitive, run.diverging, run.sensitive, run.replayRun, NoSensitivityWithin; analysis/ask.go CheckKind; analysis/check.go SensitivityFile, divergenceWitness; analysis/compose.go (a stated ClaimSensitive stands over ClaimHolds about the same feature); runtime/check.go ResolveCheckFeatures, FeatureOwner, ActionExecutor.PerformerAttributes; smt/engine.go New, Engine.Unrolling, Engine.Covers, Engine.Run, Engine.Process, DefaultMoves, NoInitialState, run.decide, run.inputs, run.replay, run.write, ScheduleError; smt/errors.go ErrNotEncoded, UnsupportedError, FlowError, ErrSlotOverflow; analysis/engine.go InputError, ErrInput, DomainError, ErrDomain, FreedomError; analysis/question.go HoldsAsk (Conditions, Inputs, Assume, Diverge), Question.Holds, FreeInputs; analysis/result.go Result.Contrast; analysis/result.go Input, Result.Inputs, Result.Assumptions, Budget.Unroll, DefaultUnroll, Witness.Inputs; analysis/standing.go Result.inputsEvidence; engines/engines.go Default, DefaultFromEnv; runtime/replay.go InputTaken, ParseInput, ParseWitness, Witness.Inputs, WitnessInputError; runtime/action_executor.go ActionExecutor.fixWitnessInputs; cmd/sysml/usage.go (-check-input, -check-assume, -check-unroll); cmd/sysml/report.go checkInput; repl/meta.go (%check-input, %check-assume, %check-bounds unroll=) smt/referee_test.go:TestRefereeCorpus (checks 1–3 over every corpus action case with outcomes whose body encodes: outcome sets equal explore's, every witness replays, the verdict agrees with exhaustive exploration; check 5: every feature the case's action writes is asked as a sensitive question, a sensitive verdict has both witnesses replayed to two values among the case's outcomes and a not sensitive feature has one value across them; the refused cases are counted and named), :TestRefereeInputs (check 4: every violated witness with inputs, replayed under explore with those inputs pinned, reproduces the violation), smt/input_test.go:TestUnboundInputsAreFreeAndBoundOnesPinned, :TestFreeInputRangesOverItsDomain, :TestReleasingABoundInputDropsItsBinding, :TestFreeInputWithoutADomainIsRefused; smt/input_engine_test.go:TestEngineRangesOverUnboundInputs, :TestEngineNarrowsTheDomainToTheDeclaredType (proved under Natural, violated under Integer), :TestEngineWitnessNamesAnEnumerationConstructor, :TestEngineAssumesOverTheInitialState (an assumption turns violated into proved; a contradictory set is not covered, never proved), :TestEngineReleasesABoundInput, :TestEngineRefusesInputsItCannotFree, :TestEngineRefusesAnAssumptionItCannotTranslate, :TestEngineWitnessWithInputsReplaysThroughTheStart, :TestEngineWitnessWithoutInputsReplaysAsBefore; smt/sensitivity_test.go:TestSensitivityOfForkBranchesWritingOneFeature (x sensitive with both witnesses replayed to the oracle's two values, leftRan and rightRan proved not sensitive), :TestSensitivityWritesBothWitnesses, :TestSensitivityShortOfCompletionIsBounded (bounded, never proved, below the completing depth), :TestSensitivityOfJoinWaitingForSlowestBranch (arrived not sensitive), :TestSensitivityUnderDeadlockIsTheDeadlock, :TestSensitivityRefusesTheClockAndPausedFlows (the stage-4 row, refused naming the construct), :TestSensitivityRefusesAPairTheInterpreterRefutes, :TestPairIsTheRelationTwiceOver (copy B keeps every sort, declaration, assertion, objective, pin and flag of the query, renamed); analysis/check_test.go:TestCheckKindAsksSensitiveForANamedFeature, :TestCheckAnswersASensitiveQuestionWithAPair; analysis/compose_test.go:TestComposeSensitivityWitnessStandsOverNotSensitive; cmd/sysml/smt_engine_test.go:TestEngineSMTDecidesSensitivity, :TestEngineAllComposesSensitivity; repl/smt_test.go:TestEngineSMTDecidesSensitivity; smt/discipline_test.go:TestMergeLoopBeyondTheBoundIsBounded, :TestBodyWhileBeyondUnrollingIsBounded, :TestDivisionByZeroIsReportedNotBounded, :TestPinnedMergeOutcomes, :TestEngineWithoutASolverIsAbsent, :TestEncodingEmitsOnlyPortableFeatures (the two-copy query of every output among the queries checked); smt/engine_test.go:TestEngineDescribesItself, :TestEngineRefusesWhatItDoesNotAnswer, :TestEngineDecidesConditions, :TestEngineDecidesDeadlock, :TestEngineRefusesWhatItDoesNotEncode, :TestEngineReportsUnknownAsNotCovered, :TestEngineWitnessesIntegerOverflow; smt/property_test.go:TestConditionPropertiesFollowTheRun, :TestDeadlockProperty, :TestConditionReadingNoValueIsUndefined, :TestConditionOverAnObjectIsRefused; smt/encode_test.go:TestEncodeForkJoinCompletes, :TestEncodePinsAndObjectFlows, :TestSortsNameEveryNodeEdgeAndSlot; smt/support_test.go:TestAnalyzeNumbersForkJoinFlow, :TestAnalyzeRecordsBodyLoops, :TestAnalyzeRefusesMessages, :TestAnalyzeRefusesNoInitial; solve/portability_test.go:TestPortability (a datatype the query declares for the nodes of a flow; an incremental dialogue over named variables; a free input over an enumeration's datatype; a real-sorted input read back exactly); runtime/replay_input_test.go:TestParseInputReadsEverySpelling, :TestParseWitnessReadsInputsBeforeChoices, :TestReplayFixesWitnessInputs (a witness naming a feature the model lacks is refused naming it); engines/engines_test.go:TestDefaultHoldsTheFrameworksEnginesAndSMT, :TestSMTStatusIsTheSolvers, :TestHoldsRanksSMTOverCheck; analysis/registry_test.go:TestDefaultHoldsTheFrameworksEngines, :TestDefaultPutsHoldsToCheckAlone; repl/smt_test.go:TestEngineSMTRangesOverAReleasedInput, :TestEngineSMTAssumesOverTheInitialState, :TestEngineAllShowsCheckRefusingFreeInputs, :TestEngineSMTRefusesAnUnknownInput; cmd/sysml/report_test.go:TestCheckResultsReportInputsAndWitnessValues; cmd/sysml/engines_test.go:TestEnginesListsTheBuild; grpc/engines_test.go:TestListEnginesNamesEveryEngine ✅ As designed for stages 1 to 3 (every solver test skips with a named reason without a solver and fails under OPENSYSML_REQUIRE_SMT=1; a not sensitive verdict is a fact about the model asked, not about the runtime's scheduling, so no ordering row moves on its account); clock, messages, nested flows, k-induction, heap and calc inlining, state machines and the engine option on the wire are later stages and refuse with the reason
Schedule checking: check is the explicit-state model checker as an engine — outcomes and holds over the schedules of an invocation with its inputs as written: the -actions and -states started on one clock, run to the -advance horizon, and the machines of the objects they materialize — authority bounded, Replays: true, bounds depth, states, deadline and every executor budget — selected by -engine check/%engine check for the invocation's -action/-state/%action/%state (RunFor under the engine searching the behaviors named together, %advance the behavior last checked up to that horizon), with -check-property, -check-diverge, -check-witness, -check-depth (Budget.Depth), -check-states (Budget.Runs, the checker's unit; under all one figure serves explore as linearizations) and -check-timeout (Budget.Deadline, so a search the clock stops is the plan's cancellation naming time, not a verdict), and %check-property, %check-diverge, %check-witness, %check-bounds, %replay; a -check-* flag without the engine, the engine without a behavior, a check-only flag under -engine smt and a bound that is not a positive integer or duration are refused before anything runs; an exhaustive clean search is (holds/outcomes, bounded), never proved; a violation or divergent value is witnessed only once runtime.Replay replayed it on the plan's workers, a disagreement not covered with the reason; evaluate, sweep and satisfiable questions, a fixed schedule and free inputs are typed refusals; registered in Default(), and auto never picks it over explore, so no existing golden moves; the CLI exits 1 on a violation or divergence, 0 on an exhaustive clean search, 2 on a bounded, cancelled or refused one, and -json carries a check object (verdict, states, moves, depth, boundsHit, violations[], divergent[], outcomes[], witness paths) on the engine's results[] entry; a check ends no debugging session and starts none analysis/check.go checkEngine, NewCheck, CheckEngineName, Describe, Covers, Run, checkBounds, replayWitness, DefaultCheckDepth, DefaultCheckStates; analysis/question.go CheckAsk; analysis/check.go Checked; analysis/registry.go Default; cmd/sysml/check.go checkerOptions, checkTimeout, checks.checkerMisuse; cmd/sysml/report.go checkSearch, checkViolation, checkDivergent; cmd/sysml/usage.go; repl/checker.go Session.checkInvocation, checkInvocations, checking, doCheckBounds, doReplay; repl/invocation.go; repl/meta.go; examples/self-model/behavior.sysml, pipeline.sysml (the engine in the self-model) analysis/check_test.go:TestCheckDescribesItself, :TestCheckBoundsNameEachBudgetHit, :TestCheckCoversOutcomesAndHoldsOverAnActionsSchedules, :TestCheckBoundsAnOutcomeSet, :TestCheckWitnessesADivergence, :TestCheckHoldsOrWitnessesAProperty, :TestCheckWritesEveryWitness, :TestCheckWitnessThatFailsReplayIsNotCovered, :TestExploreRefereesCheck, :TestCheckTakesTheBudgetsDepthAndRuns, :TestCheckStopsAtThePlansDeadline, :TestChecksOnOneModelHaveWorkersOfTheirOwn (two checks on two goroutines under -race, each on workers of its own); analysis/check_clock_test.go:TestExploreRefereesCheckOverBehaviorsOnOneClock (an action's wait and a machine's timer due together: explore's table and the checker's outcome set agree, the tie's divergence witnessed), :TestChecksOfBehaviorsOnOneClockHaveWorkersOfTheirOwn; analysis/scale_test.go:TestEveryEngineClaimStrengthPair (check's pairs), :TestNoPathPromotesTheStrength; analysis/registry_test.go; cmd/sysml/check_engine_test.go:TestEngineCheckWitnessesADivergence, :TestEngineCheckJudgesAPropertyOfThePerformer, :TestEngineCheckNamesTheBoundsItHits, :TestEngineCheckRefusesMisuse, :TestEngineCheckSearchesBehaviorsOnOneClock (-state alone, with -advance, and beside an -action on one clock; the joint outcomes in -json), :TestEngineCheckSearchesAPausedBody, :TestJSONReportsTheCheckedPlan; cmd/sysml/engines_test.go; repl/checker_test.go:TestCheckSettingsShowSetAndClear, :TestEngineCheckSearchesTheActionsSchedules, :TestEngineCheckJudgesPropertiesOfThePerformer, :TestEngineCheckNamesTheBoundsItHits, :TestReplayStepsTheRunAWitnessRecords, :TestReplayRefusesAWitnessOfAnotherRun; repl/checker_state_test.go:TestEngineCheckSearchesStateMachines, :TestEngineCheckAdvanceNeedsACheckedInvocation, :TestReplayStepsBehaviorsOnOneClock; examples/self_model_test.go:TestSelfModelAnalysisFrameworkMatchesImplementation; every conformance, trace, sweep, REPL/CLI/gRPC golden unchanged; make man-check ✅ Faithful (single-threaded search: Jobs divides the replay of witnesses, not the search; ExecuteActionRequest carries no engine field, so the checker has no gRPC surface until the wire gains one)

Constraint Solving (internal/exec/solve) — advertised extension, not a conformance claim

Standard: none. SysML v2 defines evaluation of Invariant, RequirementUsage and assert satisfy against concrete values; it defines no solving semantics. This package is therefore an optional, additive extension that OpenSysML advertises, not a compliance item.

The runtime evaluator (internal/exec/runtime) remains the normative semantics: nothing here changes a verdict it reaches, and no verdict is ever derived from a translation. The package answers the questions the evaluator cannot — satisfiability, conflicting subsets, value synthesis — by translating conditions into a solver-independent term IR, writing that IR as an SMT-LIB2 script, and optionally running an external solver over it. No solver is bundled and no module dependency or cgo is added: a solver is an external process, so an absent one is a typed error and the feature is opt-in. The capability set — satisfiability, unsat-core conflict explanation, value synthesis, objective optimization — follows the design of the ConstraintSolverService in OpenMBEE's HMF (Apache 2.0), which binds Z3 in-process; the implementation here is independent (see Acknowledgements).

Conditions come from the evaluator's own collection (runtime.Context.ConditionsOf, the accessor over conditionsOf/appendConditions), so a translation encodes exactly what the evaluator checks, in the same order, with the same distinctions: require versus assume, negation, and a body meaning the conjunction of its conditions (a negated body denies that conjunction).

Rule Implementation (file:function) Tests Status
The conditions translated are the conditions the evaluator checks — inherited first, require versus assume kept, negation kept, a negated body denied as one conjunction internal/exec/runtime/condition.go Context.ConditionsOf, conditionsOf, appendConditions (one collection, shared) internal/exec/runtime/conditions_of_test.go, internal/exec/solve/translate_test.go ✅ Faithful to the evaluator
A constraint, requirement or assert satisfy translates to a Query: declared variables, finite datatype sorts, asserted terms, and the provenance (condition text, element, declaring symbol, file and span) of each assertion internal/exec/solve/translate.go Constraint, Requirement, Satisfaction, Translate; query.go internal/exec/solve/translate_test.go, satisfy_test.go ✅
Sorts come from the semantic type facts, never from the literals written: Boolean→Bool, Natural/Integer→Int (a Natural also declared non-negative), Rational/Real/Number→Real, String→String, an enumeration definition or a variation point→a finite datatype sort internal/exec/solve/reference.go sortOf, datatype internal/exec/solve/translate_test.go, goldens ring_variants.smt2 ✅
A quantity is normalized to the base units its unit reduces to, through the existing unit model, as an exact rational — so no rounding enters a script — and an incommensurable comparison or sum refuses at translation time internal/exec/solve/translate.go (quantity handling over semantics units/dimensions) internal/exec/solve/translate_test.go, golden touchdown.smt2 ✅
Anything outside the subset refuses with a typed ErrNotTranslatable naming the construct and where it was written; one refused conjunct fails the whole query, so no partial script exists to answer sat/unsat about conditions it does not contain internal/exec/solve/errors.go NotTranslatableError; translate.go internal/exec/solve/translate_test.go (one case per unsupported construct, plus all-or-nothing) ✅
The script is deterministic byte for byte: declarations ordered by name, assertions in evaluation order, generated names stable, set-logic chosen from the sorts and operators actually used internal/exec/solve/smtlib.go Script; logic.go Query.Logic internal/exec/solve/golden_test.go (.smt2 goldens, -update, translate-twice), smtlib_unit_test.go ✅
An object renders by its path: Markdown writes car.wheels[2] (brackets escaped) as any cell text; HTML marks the value span.sysml-object with data-object="#<id>" beside the data-element and data-element-kind of the usage it stands for, so a stylesheet or script can tell an object from the element it was declared by; PDF converts the same Markdown. -instantiate <name> is the one check flag -render-document/-render-documents take: the run creates the objects first, reports their materialization on stderr and refuses to render one that did not materialize cleanly docrender/markdown.go valueText; docrender/html.go htmlWriter.writeValue, elementAttrs; cmd/sysml/render.go loadRenderingModel; cmd/sysml/check.go checks.instantiatesOnly; cmd/sysml/main.go docrender/objects_test.go:TestMarkdownObjectReportGolden (testdata/object_report.golden.md), :TestMarkdownObjectReportDeclared, :TestHTMLObjectReport; cmd/sysml/render_document_test.go:TestRenderDocumentOverObjects ✅ Implemented

Translatable subset: not, and/&, or/|, xor, implies, if c ? a else b; ==, !=; <, <=, >, >=; +, -, *, unary -/+, division / (a Real quotient whatever the operand sorts, as the evaluator answers, with a non-zero divisor asserted) and integer remainder % (truncating toward zero as the evaluator does, with a non-zero divisor asserted); boolean, integer, real and string literals; quantity expressions (450.0 [km/h]); references to scalar-valued features and feature chains that ground in one; enumeration literals and variation-point variants.

Deliberately out of subset (each refuses, none is silently dropped): - collections and quantifiers — sequences, sets, ->select, ->collect, ->forAll, ->exists, ->size, indexing #(i), ranges, collection-valued features (bounded expansion is not implemented) - invocations of any kind, calc included: a body may iterate or read state, and folding one is the evaluator's job - SMT-LIB's own Euclidean div/mod, and real %, which the evaluator answers by floating-point remainder - exponentiation (**, ^) - classification and metadata operators (hastype, istype, @, @@, as, meta, all, ===, !==, ??, ~, null), complex numbers, and string operations other than equality - comparing or adding magnitudes of different dimensions, features whose type determines no scalar sort, unresolved names, and feature chains that ground in nothing

Known limitation: a variable stands for the values a feature may take, constrained only by its sort; a query with no partial assignment asserts no value a model declares, and an assert satisfy … by x translates the requirement's conditions read through the requirement's own parameters rather than substituting x. Such a query asks what the conditions permit, not what one object holds; a query with a partial assignment fixes the values an object holds or the model declares (see the synthesis rows below). Optimization is a later step.

Solving a query — experimental, opt-in

A solver is run as a process speaking SMT-LIB2 on standard input: OPENSYSML_SMT names one explicitly, else z3 and then cvc5 are looked for on PATH. OPENSYSML_SMT_TIMEOUT overrides the 10s budget one query is given.

Rule Implementation (file:function) Tests Status
A solver is discovered, never assumed: the override first, then z3 and cvc5 on PATH; an absent solver is a typed NoSolverError naming what to install, never a silent skip and never a fabricated verdict internal/exec/solve/solver.go Discover, newSolver; errors.go NoSolverError solve/solver_test.go:TestDiscovery, repl/check_test.go:TestCheckReportsAnAbsentSolver ✅
The three verdicts stay distinct to the user: sat, unsat, unknown. A timeout, or arithmetic the solver gave up on, is unknown with the reason it gave — never reported as either of the others solve/solver.go Solver.Solve, session.verdict, session.reasonUnknown; repl/check.go SolveStatus solve/solver_test.go:TestSolverVerdicts, :TestSolverTimeout, repl/check_test.go:TestCheckReportsAnUndecidedAnswer ✅
A solver process failure — crash, non-zero exit, malformed or missing reply, a model naming an undeclared variable — is a typed SolverProcessError, distinguished from unknown solve/solver.go session.read, session.finish, Solver.processError; errors.go SolverProcessError solve/solver_test.go:TestSolverProcessFailures, :TestSolverBadModel, repl/check_test.go:TestCheckReportsASolverProcessFailure ✅
On sat the model is read back and rendered in OpenSysML's own terms: the feature's qualified name, a quantity's magnitude in the base unit the query normalized to, an enumeration literal or variant by name — never raw SMT-LIB. A value the notation has no literal for is marked as the solver's own rather than mistaken for one solve/model.go assign, renderValue; solve/sort.go smtName solve/model_test.go, solve/solver_test.go:TestSolverVerdicts ✅
Integer / and % mean what the evaluator means: a whole-number quotient is a Real (7 / 2 is 3.5), the remainder truncates toward zero, and for a spread of sign combinations the solved quotient and remainder equal what internal/exec/runtime computes for the same expression. The encoding divides the exact Int terms and the evaluator divides as an exact rational rounded once to float64 (semantics.IntQuotient), so the two agree even for operands beyond 2^53, where rounding each operand first would move the quotient. A quotient float64 cannot represent — (2^53 + 1) / 2 — is where the exact ratio and the evaluator's rounded answer differ in the last bit; the witness replay and the rounded-query marking below keep that difference out of the verdicts solve/term.go TruncRem, RatioDiv; translate.go multiplicative, remainder solve/agreement_test.go:TestSolvedIntegerDivisionAgreesWithEvaluator, :TestSolvedDivisionRejectsEuclideanAnswer, :TestSolvedQuotientAgreesBeyondFloatExactRange, solve/replay_test.go:TestSolvedHalfUlpQuotientAgreesWithEvaluator ✅
A sat witness is confirmed against the evaluator's own arithmetic before it is reported: every assignment is replayed through float64 (semantics.RealArith, IntArith, IntQuotient), and a witness the replay rejects — one satisfying the exact rationals but not the rounding evaluator — is reported unknown with the reason, never sat solve/replay.go replayWitness, replayTerm, replayRatio; solver.go Solver.Solve solve/replay_test.go:TestSolvedWitnessRejectedByEvaluatorIsUndecided, :TestSolvedWitnessConfirmedByEvaluatorStaysSat ✅
A query whose conditions the evaluator computes in float64 is marked rounded (Query.Rounded): an exact-real unsat about it does not rule out values the evaluator's rounding would accept, so %check and %solve report it undecided rather than unsatisfiable, and %configure all and %optimize decline the completeness claim outright solve/replay.go Query.Rounded, roundedTerm; repl/check.go roundedUnsatReport; repl/synth.go roundedNoValuesReport, Session.enumerateConfigurations; repl/optimize.go optimizeQuery solve/replay_test.go:TestRoundedMarksFloatComputingQueries, repl/check_test.go:TestCheckLeavesRoundedUnsatUndecided ✅
Division by zero, which the evaluator refuses and SMT-LIB leaves total: a literal zero divisor refuses translation, and any other divisor — integer or real — carries a non-zero side condition, so no model is found by choosing zero solve/translate.go divisor, guard solve/translate_test.go:TestRefusals, solve/agreement_test.go:TestDivisorGuardRulesOutDivisionByZero ✅
%check <name> reports the verdict for a constraint, requirement or satisfaction assertion, and on sat the assignment. It is a read: nothing is materialized, and an action or state debugging session keeps running repl/check.go CheckSolve, doCheck; repl/meta.go (command table, dispatch) repl/check_test.go ✅ Faithful — an experimental surface, kept apart from %constraint/%satisfy, whose VerdictStatus it never collapses into
An unsat verdict is explained by an unsat core: the script names each assertion, cores are turned on, and the core is asked for only once the verdict is unsat. Labels are assertion positions, so each core member is the Assertion — and Provenance — it came from, rather than a table beside the query solve/smtlib.go CoreScript, CoreLabel, coreLabelIndex; solve/core.go session.explain, session.unsatCore solve/core_test.go:TestCoreScriptShape, :TestCoreLabelsRoundTrip, :TestCoreGolden, :TestExplainedTwoConditionConflict ✅
A reported core is minimal, or says it is not: reduction drops one member at a time, each round a fresh solver process, and Core.Minimal means dropping any one member left the rest satisfiable. A core past DefaultMaxCoreMembers, out of the OPENSYSML_SMT_CORE_BUDGET budget, or a round the solver did not decide, is reported as it stands with Core.Note saying why solve/core.go Solver.Explain, Solver.reduce, coreBudgetFromEnv solve/core_test.go:TestExplainReducesANonMinimalCore, :TestExplainReportsAnUnreducedCoreHonestly, :TestCoreBudgetFromEnv ✅
A solver that refuses cores, names an assertion the query did not assert, repeats one, answers unreadably or reports an empty core is a typed CoreError (both an ErrNoCore and an ErrSolverProcess), and one that fails mid-reduction stays the SolverProcessError it is — never an empty or invented core solve/core.go session.unsatCore, Solver.coreError, Solver.reduce; errors.go CoreError solve/core_test.go:TestExplainCoreFailures, :TestExplainReportsAFailureWhileShrinking ✅
%explain <name> prints the conflicting conditions of an unsatisfiable constraint, requirement or satisfaction assertion — role, condition as written, declaring element, file:line:col — in the query's assertion order, including RoleDomain bounds and RoleDefined guards, and names the supertype an inherited condition was declared by. sat points at %check, unknown explains nothing, and it is a read that leaves a debugging session running repl/explain.go ExplainSolve, explainQuery, conflictLines, doExplain; repl/meta.go (command table, dispatch) repl/explain_test.go ✅ Faithful — the same experimental surface as %check, and no verdict is fabricated when no solver is installed

Not a conformance claim: satisfiability is not evaluation. %check answers sat about conditions %constraint cannot evaluate at all (an unbound parameter has no value), and sat never means a condition holds of any object. A core says which conditions cannot hold together, not that any object violates them. Solving and conflict explanation are an experimental OpenSysML extension: SysML v2 defines no solving semantics, and verdicts about a model remain the evaluator's.

Known limitations: a variable divisor sets Query.Nonlinear, so unknown is an expected verdict there, and OPENSYSML_SMT names an executable, not a command line with arguments.

Exact reals against a rounding evaluator — what agreement is claimed

The evaluator computes Real arithmetic in IEEE 754 binary64; the translation reasons over SMT-LIB's exact Real (rational) sort. Those are different arithmetics, and the difference is reachable, not theoretical — checked against z3 4.8.12 and cvc5 1.3.4:

  • 0.1 + 0.2 == 0.30000000000000004 — the evaluator holds it; the exact encoding is unsat.
  • 0.1 + 0.2 == 0.3, 0.1 + 0.2 <= 0.3 — the evaluator rejects them; the exact encoding is sat.
  • x * 3.0 == 0.3 — exactly x = 1/10 satisfies it, a value the evaluator's 0.1 * 3.0 does not confirm.
  • (2^53 + 1) / 2 == 4503599627370496.0 — the evaluator's rounded quotient; the exact encoding is unsat.
  • (2^53 + 1) / 2 == 4503599627370496.5 — the exact ratio; the evaluator rounds to the same float64, so both accept it.

So an unqualified exact-real verdict can be wrong in both directions about the normative evaluator: a sat whose witness the evaluator rejects, and an unsat about conditions the evaluator's rounding would accept.

Why the encoding is not IEEE 754 (FloatingPoint/Float64): prototyped and measured under both backends rather than assumed. Formulas over pinned float64 constants are fast in both (milliseconds). Formulas with free FP variables are materially slower under z3 (~1s against cvc5's ~0.15s for one equality), and the mixed fragment this project's subset needs — Int division and truncating remainder joined to FP arithmetic by to_fp/fp.to_real — is not a fragment both backends decide: z3 answers unknown for a free Int quotient converted to FP (with or without (set-logic ALL)), and cvc5 ran past a 20s budget on division-plus-remainder cases z3 also gave up on. A fix that holds under only one backend is not acceptable here, so the FP encoding is recorded as not viable for the translatable subset on the supported solvers, not merely unattempted.

What is done instead — the verdict is narrowed to what both arithmetics support:

  • The exact encoding stays: integer bounds, remainders, enumerations, quantities and unit conversions keep exact reasoning, and every existing verdict about them is unchanged.
  • A sat witness is replayed through the evaluator's arithmetic (solve/replay.go) — float64 operations, IntQuotient for whole-number division marked by RatioDiv — and reported sat only if the replay confirms every assertion; a witness the evaluator would reject is reported unknown with the reason.
  • A query whose conditions the evaluator rounds (Query.Rounded: any float64-computing Real arithmetic, or a Real literal float64 cannot hold exactly) does not report exact-real unsat as an evaluator verdict: %check and %solve say undecided and why, %configure all and %optimize decline rather than claim exact-real completeness for a rounding arithmetic.

The agreement claimed after this: a sat reported to the user carries a witness the evaluator itself confirms, and an unsat is reported only where evaluator and exact arithmetic coincide (no rounded Real computation in the query). What is not claimed: completeness — a rounded query whose exact encoding answers unsat, or whose witness fails replay, is reported undecided even where a float64-aware solver might have decided it. Narrower, and sound; the alternatives — an exact-rational evaluator value representation (a contract change across semantics, runtime, formatting and serialization, not a modelling fix) and refusing to translate rounded queries at all (losing the confirmed-witness answers the replay preserves) — are recorded here so they are not re-derived. The exact-rational alternative has since been adjudicated against the pinned pilot and the specification text and declined; the evidence and the option comparison are in exact-rational evaluation.

SMT-LIB portability — which backends the extension supports

OPENSYSML_SMT may name any executable speaking SMT-LIB2 on standard input, so what a backend must support is stated, probed and reported rather than assumed of z3.

Rule Implementation (file:function) Tests Status
The logic set is the narrowest logic of the SMT-LIB 2.6 logic list that covers the sorts and operators the query actually uses: QF_UF, QF_LIA/QF_NIA, QF_LRA/QF_NRA, and AUFLIRA/AUFNIRA for a query over both Int and Real, the list defining no quantifier-free mixed logic. A quotient of Int-sorted operands is a Real term (to_real over both), so such a division selects a mixed logic; truncating remainder by a literal divisor keeps the linear integer logic (div/mod are the Ints theory's), a variable divisor selects the nonlinear one, and no logic is widened to avoid a hard case internal/exec/solve/logic.go Query.Features, Query.LogicChoice, Query.Logic solve/smtlib_unit_test.go:TestLogicSelection (a case per feature), :TestNonStandardLogicIsExplained, goldens mission_budget.smt2, ring_variants.smt2, touchdown.smt2 ✅
A non-standard logic is emitted only where the list defines none for the feature — datatypes and strings — and says so: LogicChoice.Standard is false, LogicChoice.Why names the features, and the script carries a comment above (set-logic ALL) solve/logic.go NonStandardLogic, unstandardisedWhy; smtlib.go Script, CoreScript solve/smtlib_unit_test.go:TestNonStandardLogicIsExplained, golden ring_variants.smt2 ✅ Non-standard by necessity, declared as such
What the layer requires of a backend is enumerated as capabilities — model output, unsat cores, incremental checks, declare-datatypes, strings, div/mod, nonlinear and mixed arithmetic, the non-standard logic, (maximize)/(minimize) and :opt.priority — and a query says which of them it needs solve/capability.go Capability, AllCapabilities, Capability.Feature; logic.go Query.Requires solve/capability_test.go:TestQueryRequires, :TestCapabilitiesProbeCapableBackend ✅
A backend is probed rather than believed: one small script per capability, run at most once per executable per process (cached), and only for what the query and operation need — never a subprocess per query. A caller who knows its backend declares them instead (DeclaredCapabilities) and nothing is probed solve/capability.go Solver.Capabilities, Solver.require, runProbe, capabilityCache, DeclaredCapabilities solve/capability_test.go:TestCapabilitiesProbeCapableBackend (probe count), :TestDeclaredCapabilitiesSkipProbing ✅
A capability the backend rejects makes the request a typed UnsupportedCapabilityError (an ErrUnsupportedCapability) naming the backend, the missing feature, the operation and what the backend said, refused before the query runs — never a silent degrade, a widened logic or a fabricated verdict solve/capability.go Solver.require; errors.go UnsupportedCapabilityError, Unsupported; solver.go Solver.Solve; core.go Solver.Explain; configure.go Solver.Configurations solve/capability_test.go:TestCapabilitiesProbeIncapableBackend, :TestUnsupportedCapabilityIsRefused ✅
Not supporting a feature, not being runnable, and not deciding stay three different reports: a probe the backend neither answered nor rejected settles nothing, so the query runs and its own unknown verdict or SolverProcessError is what is reported; an absent executable is a SolverProcessError rather than a claim about features; and a probe reply SMT-LIB does not define at all (maybe) is a SolverProcessError too, refusal being kept for (error …), unsupported or a defined reply that contradicts the check solve/capability.go capState, smtlibResponse, Capabilities.Undetermined, Capabilities.Missing solve/capability_test.go:TestUndeterminedCapabilityDoesNotRefuse, :TestCapabilitiesMissingBackend, :TestUnreadableReplyIsAProcessFailure, :TestUnsupportedReplyIsARefusal, solver_test.go:TestSolveReportsAnUndecidedAnswer ✅
Portability is measured, not asserted: a harness runs one query per feature against whatever OPENSYSML_SMT names and reports each as pass, refuse (the backend rejected a capability it needs) or fail (anything else, including a script the backend would not parse — ours to fix in the writer) solve/portability_test.go portabilityCases, TestPortability, runPortabilityCase solve/portability_test.go:TestPortability (run with z3 4.8.12 and cvc5 1.3.4), :TestPortabilityGateIsRequired; CI pr.yml / config.yml portability jobs ✅

Verified backends (probed on the machine this was written on, not read off documentation): z3 4.8.12 supports every capability above. cvc5 1.3.4 supports all but the two z3 optimization extensions — it rejects (maximize …) as a parse error and answers unsupported to :opt.priority — so every command works on it except %optimize, which refuses on it by naming the missing extension. Per-platform install instructions and the same matrix for users are 1. Install: solver compatibility.

Differential agreement with the evaluator — the evidence for the translation

The translation is checked against the normative evaluator rather than asserted to be faithful. For an element whose conditions translate, and a concrete assignment of the features they read, the property gated is: the query conjoined with that assignment is sat exactly when the evaluator says the conditions hold, and unsat exactly when it says they do not.

Rule Implementation (file:function) Tests Status
An assignment is pinned by equality assertions on a derived query — the AST and the translated query are never mutated — and every variable the query declares is pinned, so the solver decides the question the evaluator answered and not a weaker one internal/exec/solve/differential_test.go pinnedQuery, pinsOf, pinTerm solve/differential_corpus_test.go, differential_random_test.go ✅
A disagreement is reported with what debugging it needs: element, condition text, per-variable values, the evaluator's verdict or typed error, the solver status, the exact SMT-LIB script, and locations solve/differential_test.go diffGate.check, diffGate.compare, diffSummary.report as above ✅
A solver unknown is recorded, not counted as disagreement; an evaluator typed error is no verdict; and ErrDivisionByZero is required to correspond to the guarded query being unsat for that assignment rather than being skipped solve/differential_test.go compare, status solve/differential_corpus_test.go, differential_random_test.go ✅
Coverage is counted, never invisible: translated, skipped-by-refusal, agreed, disagreed, unknown, evaluator-refused, assumption-unmet and without-values are summarized per corpus, with each refusal's reason solve/differential_test.go diffSummary TestDifferentialConformanceCorpus, TestDifferentialStandardLibrary, TestDifferentialTrainingCorpus ✅
The gate runs over the runtime conformance corpus on the values those fixtures declare, the bundled standard library, and the OMG training corpus solve/differential_corpus_test.go conditionElements, hostOf, hostsOf (an element is checked on the object carrying the values it reads: the usage stating it, or the usages specializing an abstract family) the three tests above ✅
Randomized assignments over the translatable subset — boundaries, negatives, zero divisors, mixed signs, enumerations, variants and quantities in non-base units — are deterministic: a fixed default seed, OPENSYSML_DIFF_SEED to reproduce one, OPENSYSML_DIFF_SWEEP for a longer run solve/differential_random_test.go TestDifferentialRandomizedAssignments as named ✅
No solver installed means the gate skips loudly and checks nothing, and CI sets OPENSYSML_REQUIRE_SMT=1 so that skipping is a failure there solve/agreement_test.go requireSolver; solve/differential_test.go newGate .github/workflows/pr.yml, .circleci/config.yml (differential agreement gate steps) ✅

Two real disagreements the gate found, both fixed at their root:

  • A real quotient by zero answered an infinity. internal/exec/runtime/eval.go returned +Inf for a / 0.0, so a / b > 0.000001 "held" with b = 0.0, while integer division, real remainder, quantity division and the constant folder all report ErrDivisionByZero. Real division now reports it too (runtime/eval_operator_test.go:TestRealDivisionByZeroIsReported).
  • A variation point had two sorts. A usage redefining a variation attribute was given a finite sort of its own, distinct from the one the variation declaring the variants was given, so nesting == nesting::nestingTrue refused as mismatched operands. Both now read the variation that declares the variants (solve/reference.go variationDeclaring, solve/translate_test.go:TestVariationSortIsShared).

What agreement proves, and what it does not: the gate is evidence that the translation is faithful to the evaluator for the cases it covered — the concrete assignments of the corpora and of the generated models, within the translatable subset. It is not a conformance claim, it says nothing about the elements that refuse translation, and where the two ever differ the evaluator is right by definition.

Value synthesis and variant configuration — experimental, opt-in

Rule Implementation (file:function) Tests Status
A query takes a partial assignment: pins fix some features to the values the model already fixes and leave the rest free, so the solver synthesises them. Pinning nothing translates exactly the script it always did solve/pin.go Pin, PinSource, translator.fix; solve/translate.go TranslateWith, ConstraintWith, RequirementWith, SatisfactionWith, translator.pinnedAssertions solve/pin_test.go:TestNoPinsTranslateAsBefore, :TestPinnedValueIsAssertedAndReported, :TestGoldenWithFixedValues, :TestFixedValuesAreOrderedByTheirVariable ✅
A pinned value is read where the evaluator reads it — the object's feature values, else the declared default, through the runtime — and carries its provenance: held by an object, declared by the model, or chosen by the user solve/pin.go Fixed, FixedFor, fixedValue; repl/synth.go Session.declaredPins, owningElement solve/pin_test.go:TestFixedReadsTheValuesTheModelDeclares, repl/synth_test.go:TestSolveKeepsWhatAnObjectHolds, :TestSolveSynthesisesWhatIsFree ✅
A pinned quantity is normalized through the same unit machinery the translator uses, exactly: 5.4 [km/h] is fixed as the rational 1.5 in base units, and a value whose dimension does not match its feature is refused solve/pin.go translator.pinQuantity, translator.commensurable, ratOfConst, ratOfFloat; semantics/dimension.go Model.DimensionOfFeature, Model.DimensionOfUnit solve/pin_test.go:TestPinnedQuantityIsScaledExactly, :TestPinRefusesAnIncommensurableQuantity, :TestPinnedBareNumberRefusesAMeasuredFeature ✅
A pinned enumeration literal or variant is fixed as the datatype constructor the writer declares, and a value the subset cannot represent — or one of the wrong type — is a typed PinError wrapping ErrNotPinnable, never a silent drop solve/pin.go translator.pinTerm, translator.pinDatatype, translator.pinRefusal, PinError, ErrNotPinnable; solve/reference.go (datatype construction, Sort.Variation) solve/pin_test.go:TestPinnedEnumerationNamesItsConstructor, :TestPinRefusesAValueWithNoLiteral, :TestPinRefusesAValueOfTheWrongType, :TestPinnedStringIsAsserted ✅
unsat under a partial assignment means no values exist consistent with what is already fixed, reported as exactly that and distinct from the unpinned unsat; pinned assertions carry roles and indices, so an unsat core names the fixed values that conflict solve/query.go RolePinned, Query.Fixes, Query.Free; repl/synth.go Session.noValuesLines, Session.conflictingFixed solve/synthesis_test.go:TestSynthesisIsUnsatWhenTheFixedValuesForbidIt, :TestFixedValuesInAConflictAreNamed, repl/synth_test.go:TestSolveReportsNoValuesConsistentWithWhatIsFixed, :TestSolveReportsConditionsThatConflictOnTheirOwn ✅
A synthesised model is one witness, not a canonical answer, and is reported as such; values are rendered in OpenSysML's terms through the same model rendering %check uses solve/solver.go Result.Model (documented non-canonical); repl/synth.go Session.synthesise, fixedLines, synthesisedLines solve/synthesis_test.go:TestSynthesisFillsWhatIsNotFixed, :TestWitnessIsReportedInOpenSysMLTerms, repl/synth_test.go:TestSolveReportsAQuantityAsDeclared ✅
Variant configuration: a chosen selection is checked, a consistent one synthesised, and consistent selections enumerated — one fresh check-sat per solution, each excluding the whole previous assignment, built from the solver's own model solve/configure.go Solver.Configurations, session.enumerate, session.deny, session.blocking, Query.Variations, Query.FixValue; repl/synth.go Session.checkSelection, Session.synthesiseConfiguration solve/configure_test.go:TestConfigurationsEnumeratesEveryConsistentSelection, :TestVariationsAreTheVariationPointsRead, :TestFixValueChecksWhatItIsGiven, :TestChosenSelectionCanConflict ✅
The enumeration is bounded by DefaultMaxConfigurations (OPENSYSML_SMT_MAX_CONFIGURATIONS overrides it): reaching the bound sets Result.Truncated with Result.AtBound, a solver that stops deciding or runs out of time sets Result.Undecided (with Result.TimedOut for a deadline) and keeps the selections already found rather than discarding them, and results are called exhaustive only after a final check-sat answered unsat solve/configure.go MaxConfigurationsFromEnv, session.enumerate, partialResult, foundBeforeDeadline; solve/solver.go Solver.solve, Result.AtBound, Result.Undecided; repl/synth.go Session.enumerateConfigurations, truncation solve/configure_test.go:TestMaxConfigurationsFromEnv, :TestConfigurationsStopAtTheirBound, :TestConfigurationsKeepWhatTheyFoundWhenTimeRunsOut, repl/synth_test.go:TestConfigureStopsAtItsBound, :TestConfigureEnumeratesEverySelection ✅
Nested variation points, variants under constraints of their own, assume versus require roles and a denied (assert not) element are configured as the variables and assertions they already are; a query reading no variation point is a typed NoVariationsError, not an empty enumeration solve/configure.go Query.Variations, ErrNoVariations, NoVariationsError; solve/translate.go (roles, negation) solve/configure_test.go:TestConfigurationsOfAConstrainedVariant, :TestConfiguringWithoutVariationsIsTyped, :TestFixValueRefusesAFeatureWithNoValuesToName, solve/synthesis_test.go:TestSynthesisRespectsAssumedConditions, :TestSynthesisForADeniedElement ✅
%solve <name> synthesises values for a constraint, requirement or satisfaction assertion, printing what was already fixed and what the solver chose, and saying the answer is one witness. It is a read: nothing is materialized and a debugging session keeps running repl/synth.go Session.SolveValues, Session.doSolve; repl/check.go pinner; repl/meta.go (command table, dispatch) repl/synth_test.go:TestSolveSynthesisesWhatIsFree, :TestSolveAndConfigureKeepADebuggingSession, :TestSolveUnderANaturalDomainAndADivisorGuard, solve/synthesis_test.go:TestSynthesisUnderANaturalDomainAndADivisorGuard ✅ Faithful — the same experimental surface as %check, reusing SolveStatus/SolveReport and never collapsing into VerdictStatus
%configure <name> [<variation>=<variant>…] [all [<count>]] checks a chosen selection, synthesises one, or enumerates them up to a bound. An unknown variation point, a name that is not a variant of it, a variation chosen twice, a malformed count and a mixed request are each a message saying what to write instead repl/synth.go Session.ConfigureVariants, parseConfigure, Session.configureQuery, chooseVariants, matchVariation, matchVariant, Session.doConfigure; repl/meta.go (command table, dispatch) repl/synth_test.go:TestConfigureSynthesisesASelection, :TestConfigureChecksAChosenSelection, :TestConfigureRejectsWhatItCannotAnswer, :TestConfigureOnAnElementReadingNoVariation, :TestConfigureWithoutAName ✅ Faithful — distinct messages for no solver, an untranslatable element, a solver failure and unknown, never a silent skip

Known limitations of synthesis and configuration: a synthesised model is one of possibly many, and its choice is the solver's, as is the order selections are enumerated in; the enumeration is bounded and says when it was truncated rather than implying exhaustiveness; a variant is configured as the value of a variation point, not as an object, so nothing is materialized and features a variant would only have once bound are not constrained; a feature the conditions read whose value cannot be read as a scalar stays free and is reported as such rather than fixed; and a variation point outside the translatable subset refuses with ErrNotTranslatable like any other untranslatable condition.

Objective optimization — experimental, opt-in

SysML v2 states no direction, no value and no solving semantics for objective, so the contract this layer reads is OpenSysML's own, stated here and in solve/doc.go.

Which to use. Running the case (-analysis, %analysis, RunAnalysis, and -sweep over them) is the normative answer: it executes what the model says over the alternatives the model lists, with no solver, and a TradeStudies::TradeStudy over a finite listed subject is answered there (see Trade studies). %optimize answers a different question — the best values a case's conditions admit over a continuous domain — for an objective whose eval states an expression over the case's parameters. The two are kept apart rather than one delegating to the other: an objective whose eval is bound to the case's own calc (in calc :>> eval = evaluationFunction;, what TradeStudy writes) is refused by %optimize with a message pointing at the analysis run.

Rule Implementation (file:function) Tests Status
An objective's direction is the trade-study definition typing it — TradeStudies::MinimizeObjective or MaximizeObjective, specializations included, matched by symbol identity so a type merely named alike is not one. An objective typed by neither is a typed ObjectiveError wrapping ErrNotOptimizable, never a guessed direction runtime/analysis.go Context.objectiveDirection, Context.specializesLibraryType; solve/objective.go translator.direction runtime/analysis_test.go:TestObjectivesOfDirectionValueAndOrder, :TestObjectivesOfDirectionThroughSpecialization, :TestObjectivesOfWithoutDirection, solve/objective_test.go:TestObjectiveRefusals, repl/optimize_test.go:TestOptimizeRefusesAnObjectiveWithoutADirection ✅
An objective whose eval is bound to a calc held as a value — a TradeStudy's in calc :>> eval = evaluationFunction;, applying the case's calc to each alternative the subject lists — is a choice among listed alternatives, not an optimum over a continuous domain: a typed ObjectiveError wrapping ErrNotOptimizable names the calc and says to run the trade study as an analysis (-analysis, %analysis, RunAnalysis), which evaluates every alternative and reports the one selected runtime/analysis.go Objective.Evaluates, Context.calcHeldBy; solve/objective.go translator.objective, translator.refuseObjective solve/objective_test.go:TestObjectiveRefusals (ListedAlternatives), repl/optimize_test.go:TestOptimizeRefusesATradeStudyOverListedAlternatives ✅ Faithful — refuses rather than delegates, so %optimize never runs a case
The value improved is the expression the objective's redefinition of the library's eval calculation returns (objective o : MinimizeObjective { subject :>> selectedAlternative; in calc :>> eval { expression } }, or { return :>> result = expression; }), read from the lowered calculation body: TradeStudyObjective declares in calc eval : EvaluationFunction as its extension point and derives best from it (§7.22, §8.3.22.4), an ObjectiveMembership owning a requirement usage that carries no scalar value of its own; a value bound directly is read too, where a model can write one. An objective stating no value, or an eval computing in steps rather than stating one expression, is a typed refusal. An objective giving the library's bound best a value of its own (attribute :>> best = expression;) is a validateFeatureValueOverriding error and is refused with a message pointing at the eval spelling, never read as the value and never reported twice runtime/analysis.go Context.objectiveOf, Context.readEvalValue, Context.reboundBestOf, Context.objectiveMember; solve/objective.go translator.objective runtime/analysis_test.go:TestObjectivesOfValueScope, :TestObjectivesOfExplicitResult, :TestObjectivesOfStepwiseEval, :TestObjectivesOfReboundBest, :TestObjectivesOfWithoutValue, :TestObjectivesOfRedeclared, solve/objective_test.go:TestObjectiveDirectionAndValue, :TestObjectiveRefusals (ValuelessGoal, ReboundBest, StepwiseGoal), repl/optimize_test.go ✅
What is feasible is the case's own conditions — require/assume/assert/inv, inherited first, in the evaluator's order — together with the conditions each objective states in its own body; the trade-study conditions an objective inherits are about choosing among alternatives, not about feasible values, so they are left out runtime/analysis.go Context.CaseConditionsOf, Context.appendCheckedConstraint, Context.ownConditionsOf; solve/objective.go AnalysisWith runtime/analysis_test.go:TestCaseConditionsOf, :TestCaseConditionsOfInherited, :TestObjectivesOfOwnConditions, solve/objective_test.go:TestObjectiveOwnConditions, repl/optimize_test.go:TestOptimizeReportsTheGreatestValue ✅
Objectives, values and conditions are read through the runtime's own surfaces, so what is optimized is what the evaluator would evaluate: no declaration is re-parsed in the solver layer and no AST is mutated runtime/analysis.go Context.ObjectivesOf, RequireAnalysis; solve/objective.go Analysis runtime/analysis_test.go:TestObjectivesOfAnalysisUsage, :TestRequireAnalysis, solve/objective_test.go:TestObjectivesInDeclarationOrder, :TestAnalysisRefusesANonAnalysis ✅
An objective term joins the query as a first-class part of it: it declares variables, decides the logic, contributes datatype sorts, declared domains and divisor guards, and normalizes quantities exactly as an asserted condition does. A query with no objective writes the byte-identical script it always did solve/query.go Objective, Direction, Query.Objectives, Query.Optimizes, Query.Logic; solve/objective.go translator.optimize solve/objective_test.go:TestObjectiveVariablesAreDeclared, :TestObjectiveDecidesLogic, :TestQuantityObjectiveIsNormalized, :TestObjectiveOverVariantSelection, :TestObjectiveWithGuardedDivision, :TestObjectiveFreeScriptsAreUnchanged ✅
An objective outside the translatable subset refuses with a typed ObjectiveError naming the objective, why it was refused, what to write instead and where it was written — a nonlinear term (an optimizer improves a linear objective) included — never a silent skip solve/errors.go ObjectiveError, NoObjectiveError, ErrNotOptimizable, ErrNoObjective; solve/objective.go translator.refuseObjective solve/objective_test.go:TestObjectiveRefusals, solve/optimize_test.go:TestObjectiveErrorMessage, :TestNoObjectiveErrorMessage, repl/optimize_test.go:TestOptimizeRefusesANonlinearObjective, :TestOptimizeReportsAnAnalysisStatingNoObjective ✅
Several objectives are optimized lexicographically in declaration order, and the script says so itself with (set-option :opt.priority lex) rather than relying on a backend default — z3's box mode returns a model attaining only one of the optima it reports, which would make "the assignment attaining the optimum" untrue solve/smtlib.go writeScript, objectiveComment (priority, ordered (minimize e)/(maximize e), provenance comments) solve/objective_test.go:TestObjectiveScriptIsExplicitlyLexicographic, :TestSingleObjectiveScriptStatesPriority, goldens objective_lexicographic.smt2, objective_mass.smt2, objective_variants.smt2, objective_guarded.smt2, solve/optimum_test.go:TestLexicographicOptima ✅
(minimize …)/(maximize …), (get-objectives) and :opt.priority are solver extensions, not SMT-LIB2, and cvc5 implements none of them: the backend's capabilities are settled by the shared capability model before a query is sent (probed once and cached, or declared by the caller), and a backend without them is a typed NoOptimizationError wrapping both ErrNoOptimization and the UnsupportedCapabilityError that settled it. Nothing is degraded to a plain check-sat and presented as an optimum solve/optimize.go Solver.requireOptimization; solve/capability.go CapOptimization, CapOptimizationPriority, Solver.require solve/optimize_test.go:TestOptimizeRefusesABackendWithoutOptimization, :TestOptimizeRefusesABackendWithoutTheOptimizationCapability, :TestOptimumClassification, repl/optimize_test.go:TestOptimizeReportsABackendWithoutOptimization ✅
Every optimum is verified rather than trusted: the objective's value is read back from the reported model, and a further check asks whether any assignment does lexicographically better — unsat is what makes the answer an optimum. z3 4.8.12 (what apt and CI install) reports 9.5 as the maximum of x under x < 10.5, which this refutes instead of reporting solve/optimize.go session.optimize, session.attained, session.verifyOptimal, better, classifyOptima solve/optimum_test.go:TestOptimumIsVerifiedIndependently, :TestBoundThatIsNotAttained, solve/optimize_test.go:TestOptimumClassification (refuted by verification, verification undecided) ✅
The answers optimization adds stay apart, and none of them fabricates a number: OptimumAttained (verified), OptimumUnbounded (oo), OptimumBounded (an infinitesimal or interval bound, reported as a bound), OptimumUnverified, OptimumUndecided. unsat and unknown remain the verdicts they are, with no optima invented, and a solver answering unreadably is an OptimumError wrapping ErrNoOptimum and ErrSolverProcess solve/optimize.go OptimumStatus, Optimum, parseOptimum, classifyOptimum; solve/solver.go Result.Optima solve/optimize_test.go:TestOptimumClassification, :TestOptimizeRejectsUnreadableOptima, :TestOptimizeKeepsVerdictsApart, :TestOptimizeProcessFailures, :TestOptimizeRefusesAQueryWithoutObjectives, :TestOptimizeCarriesTheObjectiveThrough, solve/optimum_test.go:TestUnboundedOptimum, :TestUnsatisfiableAnalysis ✅
%optimize <name> reports each objective's optimum for an analysis definition or usage, with its declared unit and the assignment attaining it, through the same model rendering %check uses. It is a read: nothing is materialized and a debugging session keeps running repl/optimize.go Session.OptimizeSolve, optimizeQuery, optimumLines, doOptimize; repl/check.go SolveUnbounded, SolveNoOptimum; repl/meta.go (command table, dispatch) repl/optimize_test.go:TestOptimizeReportsTheLeastValue, :TestOptimizeReportsObjectivesInDeclarationOrder, :TestOptimizeKeepsADebuggingSessionAndMaterializesNothing, :TestOptimizeIsListedInHelpAndCompletion ✅ Faithful — the same experimental surface as %check, reusing SolveStatus/SolveReport and never collapsing into VerdictStatus

objective execution status changes with this PR: an objective was parsed, typechecked and otherwise inert; it is now executed as an optimization query by %optimize. This is not SysML v2 conformance — the spec defines no solving — and the runtime evaluator remains normative for every verdict about a model.

Known limitations of optimization: it needs z3, since optimization is a z3 extension; the objective term must be numeric and linear, so a nonlinear objective refuses (a computed divisor makes a term nonlinear, which is why divisor guards are exercised through the case's conditions rather than the objective's); an old z3 can report an optimum verification refutes, and that is reported as no optimum rather than as a value; boxed and Pareto multi-objective semantics are not offered, only lexicographic declaration order; and an analysis case whose conditions bound nothing is legitimately unbounded rather than an error.

Holds and satisfiable questions over verification — experimental, opt-in

SysML v2 defines evaluation of a constraint, requirement or assert satisfy against concrete values; it defines no claim over every assignment the free features can take. The question field of the verification RPCs (advertised as verification_questions) asks the two questions beyond evaluation that a solver can decide: holds — the claim holds for every assignment — and satisfiable — some assignment satisfies it. holds means no admissible assignment evaluates the claim false; an assignment whose evaluation fails — an arithmetic overflow, a zero divisor — is an evaluation error yielding no verdict, neither a counterexample nor a confirmation. The answers are the library's own semantics: a requirement's claim is the implication allTrue(assumptions) implies allTrue(constraints) — its assume constraints as hypotheses of its require ones — which is what RequirementConstraintCheck::result computes, so a holds verdict asserts the requirement's constraints exactly as the library means them. The model's other asserted constraints are not assumed as hypotheses: the question is about the element alone, as the evaluator reads it for an evaluation. The element is checked on the object that carries it, resolved as evaluation resolves it, and a value the model fixes — at any depth of a chain a condition names — is pinned to the value the evaluator reads rather than left free. A violated holds question and a satisfiable satisfiable question report the witnessing assignment — the free query variables' values as the evaluator replayed them, in base units — and a solver unsat on a violation query is reported as a proof only when the encoding is sound under the evaluator's float64 arithmetic (Query.RoundingSound): a rounded unsat stays undecided rather than claiming a proof.

Rule Implementation (file:function) Tests Status
A holds question asks a violation query — the domains, pinned and assumed conditions and the negation of the claim — so unsat is the claim holding: ConstraintViolation/RequirementViolation/SatisfactionViolation translate it with RoleViolated marking the negated claim; a requirement's claim is its assumptions implying its required conditions (the RequirementConstraintCheck::result implication), a computed divisor refuses rather than being hoisted past the negation, and a site shared by conditions on the open evaluation path is guarded by no condition at all solve/translate.go Subject.Violation, ConstraintViolation, RequirementViolation, SatisfactionViolation, translator.hoistable; solve/query.go RoleViolated, Query.Violation; solve/rounding.go roundingSite.guard solve/violation_test.go, solve/rounding_test.go:TestRoundingSoundSharedSiteOnTheOpenPath, :TestRoundingSoundSharedTautologyProves, analysis/holds_test.go ✅
A rounded unsat is a proved verdict only when every arithmetic site the claim's evaluation reaches is replayed soundly: Query.RoundingSound rewrites each float64-computing site as a fresh variable bounded by the doubles its exact value lies between — the bounding doubles being variables, 0, literals and other sites, each guarded by the evaluation path that would reach it — so a tautology over the shared site still proves while a divergence the evaluator would reach does not solve/rounding.go Query.RoundingSound; solve/solver.go Result.RoundingProved solve/rounding_test.go, :TestRoundingSoundDivergenceNotProved ✅
The solve engine answers holds/satisfiable over the violation/claim queries with the per-query claims aggregated — every query proved holding is ClaimHolds, any witnessed violation is ClaimViolated, an unproved rounded unsat is a refusal, not a verdict — and Registry.Prove puts the question to the engines as Registry.Solve does analysis/solve.go judgeOne, judgeHeld; analysis/ask.go Registry.Prove analysis/holds_test.go, analysis/solve_test.go, analysis/registry_test.go ✅
The verdict maps the plan's claim: ClaimHolds/ClaimSatisfiable→holds=true (status holds/satisfiable), ClaimViolated→violated, ClaimUnsatisfiable→unsatisfiable, nothing claimed→undecided with FAILURE_REASON_UNDECIDED and the reason; a violated or satisfiable answer carries witness — the free query variables in query order — and question/status are stamped on every verdict, evaluations included frontend/grpc/verify_questions.go symbolicVerdict, askSolvers, proveConstraint, proveRequirement, symbolicSatisfy, witnessOf; verify.go frontend/grpc/verify_questions_test.go, conformance 09-verify.json ✅
Values fixed on a redefinition fix the feature they redefine — both names read the one value — so a pin on a usage's :>> feature fixes the variable standing for the def-scope feature the conditions read, under its own name only: a chain-named feature of another object is not pinned solve/pin.go translator.pinnedVar frontend/grpc/verify_questions_test.go:TestVerifyQuestionsSatisfaction, :TestVerifyQuestionsRequirement, :TestVerifyQuestionsSubjectPins ✅
A declared value that does not evaluate (a default dividing by zero, or reading a feature with no value) is never left free: the translator tracks the evaluator's left-to-right, short-circuit evaluation path (and/&/implies read their right operand where the left holds, or/| where it fails, xor and not always, a conditional the selected branch, an element's conditions in order up to the first failing required one) as Var.Reached; a feature read on every path refuses the question (bad > 0.0 or true stays undecided, as evaluate fails), one reached only under a condition is guarded out of the query's models and listed in Query.Unreadable, so a witness or counterexample is one the evaluator confirms, while a proof (holds, unsatisfiable) is claimed only when Query.Reached — the assignments reaching the feature, constrained by the definedness guards the evaluator checks before the read and never by one it would check only after, such as a later required condition's divisor — is unsat; otherwise the verdict is undecided naming the feature (reads bad under some assignment of its free features). true or bad > 0.0 holds; free > 0.0 or bad > 0.0 is satisfiable at free = 0.5 and not proved to hold. A feature a chain reaches (inner.dependent) is read the same way: ChainPins pins the value it holds, leaves a feature holding nothing free, and reports a declared value that does not evaluate (ErrFeatureValueMaterialization) as an Unfixed naming the chain variable for UnfixedRead, so false or inner.dependent > 0.0 is undecided rather than satisfied by a chosen value solve/translate.go translator.path, translator.under, reaches, translator.read; solve/pin.go UnfixedRead, ChainPins, Query.Reached, Query.ReachedError, Var.Preceding; frontend/grpc/verify_questions.go translatedQuestion, symbolicVerdict, unreached solve/reach_test.go (incl. TestReachedKeepsOnlyTheGuardsCheckedBeforeTheRead), solve/chain_read_test.go, solve/pin_test.go:TestUnfixedReadRefusesOnlyReadFailures, solve/differential_random_test.go (unreadable defaults, direct and through a chain), frontend/grpc/verify_questions_test.go:TestVerifyQuestionsFollowTheEvaluatorsShortCircuitOrder, TestVerifyQuestionsGuardNestedReads, TestVerifyQuestionsAGuardAfterTheReadCertifiesNoProof ✅
A question other than evaluate/holds/satisfiable is INVALID_ARGUMENT; a symbolic question requires verification_questions; an engine that does not cover the question refuses by name (run does not answer holds), which is undecided, never a false verdict frontend/grpc/verify_questions.go verificationQuestion, askSolvers; service.go CapabilityVerificationQuestions frontend/grpc/verify_questions_test.go:TestVerifyQuestionsBogusIsInvalid, :TestVerifyQuestionsCapabilityWithheld, :TestVerifyQuestionsEngineRefusalIsUndecided ✅

Decided fragment and undecided reasons. The questions decide what the translatable subset decides: linear and nonlinear real/integer arithmetic within the solver's decision power (a nonlinear case the backend cannot close comes back unknown → undecided), quantities normalized to base units, booleans and enumerations. Outside it — exponentiation and computed divisors in a violation query, collections, quantifiers, invocations and the rest the subset refuses — the verdict is undecided with the refusal named, as is an absent solver or a rounded unsat without a proof. One divergence is recorded rather than fixed: an evaluate verdict reads assume conditions as trusted facts about the point evaluated, so a point violating an assumption may evaluate violated while holds — the library's assumption-means-entailment semantics — is proved. The two answer different questions and the verdict's question says which one it answered.

Clients and conformance: the question is question on the three verify requests; Verdict carries question, status and witness (WitnessAssignment of feature, replayed value, unit, exact); the clients take it as their engines option's sibling — Python question=, Go opensysml.Asking(...), Java VerifyOptions.asking(...), Node's options object — each refusing a symbolic question against a service predating verification_questions before anything is sent.