Skip to content

Sync from development - #48

Merged
HuiJun merged 167 commits into
Open-MBEE:mainfrom
JPL-Devin:main
Aug 13, 2026
Merged

Sync from development#48
HuiJun merged 167 commits into
Open-MBEE:mainfrom
JPL-Devin:main

Conversation

@HuiJun

@HuiJun HuiJun commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator

No description provided.

devin-ai-integration Bot and others added 30 commits August 11, 2026 06:13
The Python client starts the service itself, but the wheel carries no Go
binary and the release published none, so a user without a Go toolchain
could not use the default path. Releases now publish raw
sysml-grpc-<os>-<arch> files with .sha256 sidecars, which is what
download_binary already expected, and its 'latest' path resolves the tag
instead of raising NotImplementedError.

Instantiate over gRPC also read raw slots, so a derived attribute came
back unmaterialized where the REPL evaluates it; it now reads through
Instance.GetSlot.

Co-Authored-By: jason.han <jason.han@jpl.nasa.gov>
…nd-next-steps

The Python client starts the service itself, but the wheel carries no Go
binary and the release published none, so a user without a Go toolchain
could not use the default path. Releases now publish raw
sysml-grpc-<os>-<arch> files with .sha256 sidecars, which is what
download_binary already expected, and its 'latest' path resolves the tag
instead of raising NotImplementedError.

Instantiate over gRPC also read raw slots, so a derived attribute came
back unmaterialized where the REPL evaluates it; it now reads through
Instance.GetSlot.

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: jason.han <jason.han@jpl.nasa.gov>
Co-authored-by: jason.han <jason.han@jpl.nasa.gov>
Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: jason.han <jason.han@jpl.nasa.gov>
The roadmap now lives with the rest of the docs and is linked from docs/README.md; its contents are also captured as repo-pinned knowledge notes.

Co-Authored-By: jason.han <jason.han@jpl.nasa.gov>
The roadmap now lives with the rest of the docs and is linked from docs/README.md; its contents are also captured as repo-pinned knowledge notes.

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: jason.han <jason.han@jpl.nasa.gov>
The graph carries the SysML vocabulary and element IRIs the Flexo MMS
SysML v2 service uses, so a converted model loads into that triplestore.
Element IRIs are the qualified name percent-escaped, which makes them
deterministic across conversions and reversible.

The Turtle layer is hand-written rather than a new dependency: the
subset needed here is small, and the parser rejects blank nodes,
collections and literal shorthands with a line number instead of
accepting them and dropping data.

Co-Authored-By: jason.han <jason.han@jpl.nasa.gov>
`ref part a : V`, `composite part b : V` and `derived attribute c`
parsed with their modifiers consumed but never transferred, so the
declaration came back with isReference, isComposite and isDerived all
false. parseBodyMember had no branch for a modifier followed by a kind
keyword, so those forms fell through to the anonymous-feature path.

Parse the declaration and apply the modifiers already consumed, without
clearing a flag the declaration parsed for itself.

The flow_payload_declaration golden had locked in the wrong behavior:
its two `ref part` members recorded ref=false, and now record ref=true.

Co-Authored-By: jason.han <jason.han@jpl.nasa.gov>
Each element carries its metaclass as rdf:type and its declaration as
SysML metamodel properties. The kind tables are declared once and the
reverse maps derived from them, so the two directions cannot drift.

Three properties live in a separate urn:systemica:sysml: namespace
because the metamodel does not define them and RDF cannot express them:
memberIndex (declaration order, which the notation is sensitive to and a
triple set is not), hasBody, and sourceText.

Saving to notation writes the original source through the formatter
rather than re-printing the graph, so comments and notes survive; only
the Turtle direction goes through the mapping.

Unsupported input is an error naming the element or line rather than a
partial conversion: a missing rdf:type, an owner absent from the graph, a
metaclass outside the mapping, or a syntax error on either side.

Co-Authored-By: jason.han <jason.han@jpl.nasa.gov>
The format follows the file extension. An unrecognized extension is an
error rather than a guess, and an empty session writes no file.

Co-Authored-By: jason.han <jason.han@jpl.nasa.gov>
-convert takes the input path as its flag value rather than a positional
argument, because Go's flag package stops parsing at the first
positional and `-convert in.sysml -o out.ttl` would otherwise leave the
output flags unparsed.

Formats come from the file extensions, with -from/-to to name them when
an extension is missing or unrecognized. The command writes nothing and
exits non-zero on any input it cannot convert faithfully.

convert_test.go drives the built binary as a process, which cmd/ had no
coverage for previously.

Co-Authored-By: jason.han <jason.han@jpl.nasa.gov>
RDF_INTEROP.md covers the vocabulary, the deterministic element IRIs, the
round-trip guarantees and every construct the mapping does not represent.
SPEC_COMPLIANCE.md gains the capability map with honest status flags, and
ROADMAP.md records the three items this work deliberately left open.

Co-Authored-By: jason.han <jason.han@jpl.nasa.gov>
Written files get 0600, matching the only other non-test WriteFile in the
repo (internal/core/libs/cache.go). Reading the input the user named on
the command line carries a documented #nosec G304, as the equivalent
reads in internal/grpc and internal/core/model already do.

Co-Authored-By: jason.han <jason.han@jpl.nasa.gov>
End-to-end testing found the one silent loss in the feature: `//` and
`/* */` trivia belongs to no element, so it does not survive
notation -> RDF -> notation, while a direct .sysml save keeps it. The
`doc` and `comment` keywords are declarations rather than trivia and do
convert both ways.

TestCommentsThroughRDF pins both halves of that, so the behavior cannot
change without the documentation changing with it.

Co-Authored-By: jason.han <jason.han@jpl.nasa.gov>
The synthetic parameter of `action X accept p : T` took its span from the
start of the enclosing member, so its source text was a copy of its own
parent declaration, minus the `;` that had not been consumed yet.

Nothing read that span before, which is why it went unnoticed; the RDF
conversion does, and re-emitted the parent nested inside itself. The span
now covers just the parameter.

No existing golden changed. accept_action_shorthand covers both the plain
and `via` forms.

Co-Authored-By: jason.han <jason.han@jpl.nasa.gov>
…strict

Homebrew scans the version from the release tag in the URL, so the explicit
version line made 'brew audit --strict --online' fail as redundant. Drop it from
the template and the render script, and document the tap repository naming rule
(brew tap x/y only resolves x/homebrew-y).

Co-Authored-By: jason.han <jason.han@jpl.nasa.gov>
Two message-clarity issues found in end-to-end testing of the refusal
paths, both misleading rather than wrong.

- `%save x.ttl` reported the position as `x.ttl:1:59` — the output file,
  which does not exist yet and is not where the position points. The
  session buffer has no path, so it is now named `<session>`.
- The `then` error read as though the keyword were at the position given,
  but the flag hangs off the member on one side of it. It now says which
  member it is beside, by name.

Co-Authored-By: jason.han <jason.han@jpl.nasa.gov>
…ew 6.x

Co-Authored-By: jason.han <jason.han@jpl.nasa.gov>
* fix(packaging): make the rendered Homebrew formula pass brew audit --strict

Homebrew scans the version from the release tag in the URL, so the explicit
version line made 'brew audit --strict --online' fail as redundant. Drop it from
the template and the render script, and document the tap repository naming rule
(brew tap x/y only resolves x/homebrew-y).

Co-Authored-By: jason.han <jason.han@jpl.nasa.gov>

* docs(packaging): note that hand-cloned taps need brew trust on Homebrew 6.x

Co-Authored-By: jason.han <jason.han@jpl.nasa.gov>

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: jason.han <jason.han@jpl.nasa.gov>
… tap trust

Co-Authored-By: jason.han <jason.han@jpl.nasa.gov>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: jason.han <jason.han@jpl.nasa.gov>
Seven bugs found in review of the save/convert feature:

- an unnamed `comment /* x */` lost its keyword and came back as trivia
- a long Turtle literal ending in `"` emitted four quotes in a row
- `derived var feature x : T;` produced an empty membership and dropped a
  leading visibility keyword, which the bundled KerML.kerml relies on
- graph reads rescanned every triple, making conversion quadratic
- prefix selection depended on Go map iteration order
- `chain` was dropped when writing a usage back from a graph
- the `then` refusal was only reached for usages, so a succession on a
  definition or package member converted anyway

Kind keyword synonyms are now carried as `sysx:declaredKeyword` rather than
normalized to the canonical keyword, and a synonym in a shape the graph cannot
rebuild is refused instead. The metaclass names this mapping invents move to the
`sysx:` namespace, and notation-to-notation conversion checks syntax like every
other direction.

Co-Authored-By: jason.han <jason.han@jpl.nasa.gov>
* feat(rdf): add a triple model with a Turtle writer and parser

The graph carries the SysML vocabulary and element IRIs the Flexo MMS
SysML v2 service uses, so a converted model loads into that triplestore.
Element IRIs are the qualified name percent-escaped, which makes them
deterministic across conversions and reversible.

The Turtle layer is hand-written rather than a new dependency: the
subset needed here is small, and the parser rejects blank nodes,
collections and literal shorthands with a line number instead of
accepting them and dropping data.

Co-Authored-By: jason.han <jason.han@jpl.nasa.gov>

* fix(parser): keep feature modifiers written before a kind keyword

`ref part a : V`, `composite part b : V` and `derived attribute c`
parsed with their modifiers consumed but never transferred, so the
declaration came back with isReference, isComposite and isDerived all
false. parseBodyMember had no branch for a modifier followed by a kind
keyword, so those forms fell through to the anonymous-feature path.

Parse the declaration and apply the modifiers already consumed, without
clearing a flag the declaration parsed for itself.

The flow_payload_declaration golden had locked in the wrong behavior:
its two `ref part` members recorded ref=false, and now record ref=true.

Co-Authored-By: jason.han <jason.han@jpl.nasa.gov>

* feat(export): convert between SysML notation and RDF Turtle

Each element carries its metaclass as rdf:type and its declaration as
SysML metamodel properties. The kind tables are declared once and the
reverse maps derived from them, so the two directions cannot drift.

Three properties live in a separate urn:systemica:sysml: namespace
because the metamodel does not define them and RDF cannot express them:
memberIndex (declaration order, which the notation is sensitive to and a
triple set is not), hasBody, and sourceText.

Saving to notation writes the original source through the formatter
rather than re-printing the graph, so comments and notes survive; only
the Turtle direction goes through the mapping.

Unsupported input is an error naming the element or line rather than a
partial conversion: a missing rdf:type, an owner absent from the graph, a
metaclass outside the mapping, or a syntax error on either side.

Co-Authored-By: jason.han <jason.han@jpl.nasa.gov>

* feat(repl): add %save to write the session as notation or RDF

The format follows the file extension. An unrecognized extension is an
error rather than a guess, and an empty session writes no file.

Co-Authored-By: jason.han <jason.han@jpl.nasa.gov>

* feat(cli): add -convert for SysML/RDF conversion

-convert takes the input path as its flag value rather than a positional
argument, because Go's flag package stops parsing at the first
positional and `-convert in.sysml -o out.ttl` would otherwise leave the
output flags unparsed.

Formats come from the file extensions, with -from/-to to name them when
an extension is missing or unrecognized. The command writes nothing and
exits non-zero on any input it cannot convert faithfully.

convert_test.go drives the built binary as a process, which cmd/ had no
coverage for previously.

Co-Authored-By: jason.han <jason.han@jpl.nasa.gov>

* docs: document saving, the RDF mapping and its limitations

RDF_INTEROP.md covers the vocabulary, the deterministic element IRIs, the
round-trip guarantees and every construct the mapping does not represent.
SPEC_COMPLIANCE.md gains the capability map with honest status flags, and
ROADMAP.md records the three items this work deliberately left open.

Co-Authored-By: jason.han <jason.han@jpl.nasa.gov>

* fix(export): satisfy gosec on the new save and convert paths

Written files get 0600, matching the only other non-test WriteFile in the
repo (internal/core/libs/cache.go). Reading the input the user named on
the command line carries a documented #nosec G304, as the equivalent
reads in internal/grpc and internal/core/model already do.

Co-Authored-By: jason.han <jason.han@jpl.nasa.gov>

* docs(export): note that the RDF hop drops lexical comments

End-to-end testing found the one silent loss in the feature: `//` and
`/* */` trivia belongs to no element, so it does not survive
notation -> RDF -> notation, while a direct .sysml save keeps it. The
`doc` and `comment` keywords are declarations rather than trivia and do
convert both ways.

TestCommentsThroughRDF pins both halves of that, so the behavior cannot
change without the documentation changing with it.

Co-Authored-By: jason.han <jason.han@jpl.nasa.gov>

* fix(parser): scope the accept parameter's span to the parameter

The synthetic parameter of `action X accept p : T` took its span from the
start of the enclosing member, so its source text was a copy of its own
parent declaration, minus the `;` that had not been consumed yet.

Nothing read that span before, which is why it went unnoticed; the RDF
conversion does, and re-emitted the parent nested inside itself. The span
now covers just the parameter.

No existing golden changed. accept_action_shorthand covers both the plain
and `via` forms.

Co-Authored-By: jason.han <jason.han@jpl.nasa.gov>

* fix(repl,export): make the refusal messages name the right place

Two message-clarity issues found in end-to-end testing of the refusal
paths, both misleading rather than wrong.

- `%save x.ttl` reported the position as `x.ttl:1:59` — the output file,
  which does not exist yet and is not where the position points. The
  session buffer has no path, so it is now named `<session>`.
- The `then` error read as though the keyword were at the position given,
  but the flag hangs off the member on one side of it. It now says which
  member it is beside, by name.

Co-Authored-By: jason.han <jason.han@jpl.nasa.gov>

* fix(export): address review findings on the RDF conversion

Seven bugs found in review of the save/convert feature:

- an unnamed `comment /* x */` lost its keyword and came back as trivia
- a long Turtle literal ending in `"` emitted four quotes in a row
- `derived var feature x : T;` produced an empty membership and dropped a
  leading visibility keyword, which the bundled KerML.kerml relies on
- graph reads rescanned every triple, making conversion quadratic
- prefix selection depended on Go map iteration order
- `chain` was dropped when writing a usage back from a graph
- the `then` refusal was only reached for usages, so a succession on a
  definition or package member converted anyway

Kind keyword synonyms are now carried as `sysx:declaredKeyword` rather than
normalized to the canonical keyword, and a synonym in a shape the graph cannot
rebuild is refused instead. The metaclass names this mapping invents move to the
`sysx:` namespace, and notation-to-notation conversion checks syntax like every
other direction.

Co-Authored-By: jason.han <jason.han@jpl.nasa.gov>

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: jason.han <jason.han@jpl.nasa.gov>
…rce text

A comment inside a declaration head is part of the span the head was read
from, so scanning that text for the kind keyword let a word inside the
comment win: `attribute // the flow rate` recorded `flow` and came back
from RDF as a FlowUsage. The keyword is now threaded from the token that
was consumed.

Also: a usage whose head is kept verbatim no longer has its synonym
keyword refused (`verify R;` converted with an error before), and Turtle
`PREFIX`/`BASE` only count as keywords when a whole word, so a prefixed
name such as `base:Thing` is read as a term rather than rejected.

Co-Authored-By: jason.han <jason.han@jpl.nasa.gov>
- %save to notation warns and writes when the session does not fully parse
- indent continuation lines one level deeper than their statement
- fold a leading comment into the declaration it documents
- expand ~, name a missing parent directory, advise per surface
- state an overwrite, and write atomically at 0644
…e name

A SatisfyRequirementUsage's `by` operand names the subject of the
satisfaction. Taking it as the usage's own name declared a member that
shadowed the real subject in the enclosing scope, so sibling references
to that subject reported bogus subsets/redefines type errors.

Co-Authored-By: jason.han <jason.han@jpl.nasa.gov>
…embers

The inline (unbraced) action body loop kept consuming behavioral
statements, so members following the node - the enclosing body's final
node and its succession edges - became members of that node and the
enclosing action lost its successors. The body is now one statement plus
its 'then'-chained continuations only.

Co-Authored-By: jason.han <jason.han@jpl.nasa.gov>
while/loop-until/for and if/else parsed, resolved and typechecked, then were
dropped at lowering, so a model that read correctly produced a wrong answer with
no diagnostic. Lower them as body statements and execute them recursively, with
a frame per body-local block and a step per iteration.
…ation

The function library declares its numeric functions abstractly, with a
signature and no body, so a call had nothing to invoke and `**` reached
the runtime as an unsupported operator. A model could not compute a
square root or a sine.

Dispatch is by the declaration's qualified name, in the one calc
invocation path, so tracing and diagnostics are unchanged and a model's
own `calc sqrt` — which has a body — is evaluated from it. An unqualified
call that resolves to nothing dispatches by local name, which is what
makes `sysml -e "sqrt(2.0)"` work in a model that imports no part of the
library.

One shared `semantics.Pow` serves the constant folder and the runtime, so
a folded and an evaluated exponentiation agree. Integer operands with a
non-negative exponent give an Integer; anything else gives a Real. A
domain failure, an overflow or a non-finite result is declined by the
folder and reported by the runtime rather than becoming a NaN, an
infinity or a wrapped integer.

Co-Authored-By: jason.han <jason.han@jpl.nasa.gov>
…longs to the enclosing body

Continuing the unbraced body on any 'then' still stole succession edges
written directly after the node: 'then start bump;' names members of the
enclosing body, so only the inline statement forms parseSuccessionEdge
recognises continue the body. The enclosing mixed body also only consumes
a 'then' as a chain marker when a keyword-introduced declaration or
statement follows it, not a named edge.

Co-Authored-By: jason.han <jason.han@jpl.nasa.gov>
HuiJun and others added 29 commits August 12, 2026 14:11
The section this PR added duplicated the parser A/B section already on main,
and its copy of the corpus sweep reintroduced the word-splitting loop that
compared 27 of 127 models and reported a false zero. Keep main's safe sweep and
add only what is new: LSP hover/definition and short-name probes as sweeps 4-5,
and the lowering/runtime naming probes as a subsection.

Co-Authored-By: jason.han <jason.han@jpl.nasa.gov>
…name

Deriving the name from the naming feature moved the single key a node is
filed under rather than adding one, so a succession naming a short-labelled
step by its label (first d then s;) stopped resolving while the derived name
started to. The symbol layer registers both keys; node lookup and breakpoint
matching now do too.

Co-Authored-By: jason.han <jason.han@jpl.nasa.gov>
* Testing parser and naming changes end-to-end in the sysml REPL

Co-authored-by: jason.han <jason.han@jpl.nasa.gov>

* docs(skill): build baseline binaries with go build -C instead of cd

* docs: correct claim that argv positional handles paths with spaces

* docs(skill): fold the naming probes into the existing A/B section

The section this PR added duplicated the parser A/B section already on main,
and its copy of the corpus sweep reintroduced the word-splitting loop that
compared 27 of 127 models and reported a false zero. Keep main's safe sweep and
add only what is new: LSP hover/definition and short-name probes as sweeps 4-5,
and the lowering/runtime naming probes as a subsection.

Co-Authored-By: jason.han <jason.han@jpl.nasa.gov>

---------

Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: jason.han <jason.han@jpl.nasa.gov>
…d negatives

AGENTS.md 5.1 asks for a golden fixture and negative cases with any change to
the accepted grammar. The golden shows each keyword member dumping as its
symbol twin, differing only in the short name.

Co-Authored-By: jason.han <jason.han@jpl.nasa.gov>
A member redefining more than one feature derives no name (KerML 7.3.4.5), so a value on it reaches none of them and binds at best to a declared short name. It stays well-formed, so the constraint tier reports it as a warning next to the other redefinition rules.

Co-Authored-By: jason.han <jason.han@jpl.nasa.gov>
…lationship-parity

* fix(parser): read a relationship keyword after a short name as a relationship

A declared short name is no declared name: KerML derives effectiveName from
declaredName alone, so a usage stating only a short name still answers to the
feature it references or redefines, and a relationship keyword after that short
name states a relationship rather than spelling the name.

Co-Authored-By: jason.han <jason.han@jpl.nasa.gov>

* fix(lower): let a step answer to both its short name and its derived name

Deriving the name from the naming feature moved the single key a node is
filed under rather than adding one, so a succession naming a short-labelled
step by its label (first d then s;) stopped resolving while the derived name
started to. The symbol layer registers both keys; node lookup and breakpoint
matching now do too.

Co-Authored-By: jason.han <jason.han@jpl.nasa.gov>

* test(parser): lock the short-name relationship forms with a golden and negatives

AGENTS.md 5.1 asks for a golden fixture and negative cases with any change to
the accepted grammar. The golden shows each keyword member dumping as its
symbol twin, differing only in the short name.

Co-Authored-By: jason.han <jason.han@jpl.nasa.gov>

* feat(passes): warn when a value lands on a member no redefinition names

A member redefining more than one feature derives no name (KerML 7.3.4.5), so a value on it reaches none of them and binds at best to a declared short name. It stays well-formed, so the constraint tier reports it as a warning next to the other redefinition rules.

Co-Authored-By: jason.han <jason.han@jpl.nasa.gov>

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: jason.han <jason.han@jpl.nasa.gov>
The negative fixture list gained cases on both sides; both are kept.

Co-Authored-By: jason.han <jason.han@jpl.nasa.gov>
…reference

A `then` before a keyword the body already declares a member with is an edge
end (`then end;`, `then third end;`), not the kind of a member being
declared, so the modifier and namespace cases no longer claim it first. A
usage names a succession end by its effective name, the name lowering
resolves it by, so `perform a; then action b;` sequences rather than warns.

Co-Authored-By: jason.han <jason.han@jpl.nasa.gov>
… printer

Co-authored-by: jason.han <jason.han@jpl.nasa.gov>
`perform b;` declares an occurrence usage, so a `then` between it and the
named member before it is a succession rather than a statement chained into
one node's body, where the keyword was dropped without a word. With no named
member before it — a block's `assign …; then perform body;` — it still reads
as that chain, which the lowered block already runs in order.

Co-Authored-By: jason.han <jason.han@jpl.nasa.gov>
…half-named one

`then b if x;` is a control flow edge, whose empty source the builder skipped,
so lowering could not resolve it while the unguarded `then b;` worked; both
node kinds now take the member before them. Reading a graph, a succession
naming one end is reported the way the encoder refuses to write one, rather
than printed as a bare `succession;` that declares no order.

Co-Authored-By: jason.han <jason.han@jpl.nasa.gov>
* feat(parser): desugar a member-attached `then` into a succession edge

A `then` prefixing a body member sequences the members either side of it,
so it is desugared at parse time into the *ast.SuccessionEdge the `then a b;`
notation already built, and ast.Membership's HasSuccession, SuccessionTarget
and SuccessionGuard fields are gone with every site that set them.

Lowering and the RDF mapping already honour that node, so execution now
follows a member-attached `then` rather than member order, and the mapping
emits sysml:SuccessionAsUsage with source/target ends instead of refusing
the conversion.

Co-Authored-By: jason.han <jason.han@jpl.nasa.gov>

* fix(parser): take a `then` before an anonymous member rather than reading the kind keyword as an edge end

`then part;` and `then action { }` declare a member with no name, which an
edge end cannot reference. The keyword was handed to the edge parser, which
read the kind keyword as the target's name; it is now taken as the prefix it
is and warned about like any other unnamed end.

Co-Authored-By: jason.han <jason.han@jpl.nasa.gov>

* fix(parser): sequence from the member before the `then`, and name a short-form state

An unnamed member now clears the succession source, so a `then` after one warns
instead of sequencing from an older named member. A substate declared in the
short form (`state a;`) names its end, so `then state b;` builds a real edge the
state lowering turns into a completion transition.

Co-Authored-By: jason.han <jason.han@jpl.nasa.gov>

* fix(parser): read the succession edge form in every body that can hold one

A calc or requirement body reported the `then a b;` the RDF mapping writes back
as a syntax error, so a converted model no longer parsed; a state body read it as
an unnamed succession usage, which cleared the member a following `then`
sequences from and produced a different graph on a second conversion. A state
body's `then` now builds the same *ast.SuccessionEdge as every other body, a
region carries a member-attached `then` too, and a usage of an edge kind is not
taken as a succession's source.

Co-Authored-By: jason.han <jason.han@jpl.nasa.gov>

* fix(parser): read a keyword an edge end names, and a member named by reference

A `then` before a keyword the body already declares a member with is an edge
end (`then end;`, `then third end;`), not the kind of a member being
declared, so the modifier and namespace cases no longer claim it first. A
usage names a succession end by its effective name, the name lowering
resolves it by, so `perform a; then action b;` sequences rather than warns.

Co-Authored-By: jason.han <jason.han@jpl.nasa.gov>

* fix(parser): sequence a performed step after a named member

`perform b;` declares an occurrence usage, so a `then` between it and the
named member before it is a succession rather than a statement chained into
one node's body, where the keyword was dropped without a word. With no named
member before it — a block's `assign …; then perform body;` — it still reads
as that chain, which the lowered block already runs in order.

Co-Authored-By: jason.han <jason.han@jpl.nasa.gov>

* fix(parser,export): complete a guarded one-name succession, report a half-named one

`then b if x;` is a control flow edge, whose empty source the builder skipped,
so lowering could not resolve it while the unguarded `then b;` worked; both
node kinds now take the member before them. Reading a graph, a succession
naming one end is reported the way the encoder refuses to write one, rather
than printed as a bare `succession;` that declares no order.

Co-Authored-By: jason.han <jason.han@jpl.nasa.gov>

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: jason.han <jason.han@jpl.nasa.gov>
Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: jason.han <jason.han@jpl.nasa.gov>
…laring scope

An expression written inside an action or state machine body was evaluated with
no scope at all, so it could see neither the imports nor the enclosing members
of the document it was written in: a quantity attribute default reported
"unresolved unit m" and an assignment naming a package member reported
"unresolved feature".

Lowering now carries the declaring scope on what it emits — the graph, each
statement and block, each state and each transition — and the executors read it
instead of re-deriving one from symbol.Decl. Frames are still pushed over that
scope, so a token value or block-local binding keeps shadowing a same-named
declaration. Unqualified evaluation resolves as a written reference does, and
the declaration it finds is evaluated in its own declaring scope.

Closes ROADMAP A3a: the residual failure there was the same defect in the
REPL's %constraint/%requirement, which evaluated against the document root.

Co-Authored-By: jason.han <jason.han@jpl.nasa.gov>
A state written directly among a machine's members took the machine's scope, so
a member the state declares was invisible to its own entry, do and exit
behaviors, and every substate and region below it lost its scope too.

Co-Authored-By: jason.han <jason.han@jpl.nasa.gov>
The test compared two indexes built from the shared user cache, so a parallel
test package populating it between the two builds made one index carry qualified
names and the other local ones, failing over provenance rather than removal.

Co-Authored-By: jason.han <jason.han@jpl.nasa.gov>
Co-authored-by: jason.han <jason.han@jpl.nasa.gov>
…aluation

* fix(runtime): evaluate action and state body expressions in their declaring scope

An expression written inside an action or state machine body was evaluated with
no scope at all, so it could see neither the imports nor the enclosing members
of the document it was written in: a quantity attribute default reported
"unresolved unit m" and an assignment naming a package member reported
"unresolved feature".

Lowering now carries the declaring scope on what it emits — the graph, each
statement and block, each state and each transition — and the executors read it
instead of re-deriving one from symbol.Decl. Frames are still pushed over that
scope, so a token value or block-local binding keeps shadowing a same-named
declaration. Unqualified evaluation resolves as a written reference does, and
the declaration it finds is evaluated in its own declaring scope.

Closes ROADMAP A3a: the residual failure there was the same defect in the
REPL's %constraint/%requirement, which evaluated against the document root.

Co-Authored-By: jason.han <jason.han@jpl.nasa.gov>

* fix(lower): give a state declared in a machine body its own scope

A state written directly among a machine's members took the machine's scope, so
a member the state declares was invisible to its own entry, do and exit
behaviors, and every substate and region below it lost its scope too.

Co-Authored-By: jason.han <jason.han@jpl.nasa.gov>

* test(libs): give the removal test its own library cache

The test compared two indexes built from the shared user cache, so a parallel
test package populating it between the two builds made one index carry qualified
names and the other local ones, failing over provenance rather than removal.

Co-Authored-By: jason.han <jason.han@jpl.nasa.gov>

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: jason.han <jason.han@jpl.nasa.gov>
Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: jason.han <jason.han@jpl.nasa.gov>
…ery path

A name in the unit position of a quantity expression resolves to the nearest
declaration (KerML 8.2.3.5.3/8.2.3.5.4) and the position only checks that it
conforms (8.2.3.5.1), so a sibling named like an imported unit shadows it with a
diagnostic naming the declaration and the unit it hid. Conditions used to reach
past the nearer declaration; they now evaluate in their own body scope, so all
four evaluator paths answer one routine.

The prompt evaluates in the namespace the session works in, so imported units
resolve unqualified; %calc parses its arguments as expressions, so a quantity
survives; and a quantity renders as written in a violation and by the Real
convention in a result table.

Co-Authored-By: jason.han <jason.han@jpl.nasa.gov>
…e it

A whitespace-separated argument list is cut into arguments before the
expression parser sees it, so `5 -3` is two arguments while `5 - 3` — an
expression left unfinished across the space — stays one.

Also name the shadowed unit when the shadowing declaration sits in the
namespace that declares the import, by resolving the name again with that
declaration hidden instead of searching its parent scope.

Co-Authored-By: jason.han <jason.han@jpl.nasa.gov>
An `=` nested in a call, a bracket or a string belongs to that expression,
so it no longer refuses `add(x = 1, y = 2)` as an argument; only a bare
identifier bound at the argument's top level is a named argument.

A qualified name in unit position resolves to what it names, so it is
reported without the shadowing explanation \u2014 whose suggestion would
otherwise paste the whole written name onto the unit's namespace.

Co-Authored-By: jason.han <jason.han@jpl.nasa.gov>
…g it

%eval retained a lookup failure so an imported name could still be
evaluated, which also swallowed the ambiguity error. A typed
AmbiguousNameError separates the two: a name found nowhere falls through
to the expression path, a name several declarations answer to is reported.

Co-Authored-By: jason.han <jason.han@jpl.nasa.gov>
… hidden unit

A unit owned by no namespace has the same qualified name as its simple one,
so the suggestion repeated the name that had just failed. The diagnostic now
names the shadowed unit without advising a spelling in that case.

Co-Authored-By: jason.han <jason.han@jpl.nasa.gov>
…-resolution

* fix(semantics): resolve a unit name by ordinary name resolution on every path

A name in the unit position of a quantity expression resolves to the nearest
declaration (KerML 8.2.3.5.3/8.2.3.5.4) and the position only checks that it
conforms (8.2.3.5.1), so a sibling named like an imported unit shadows it with a
diagnostic naming the declaration and the unit it hid. Conditions used to reach
past the nearer declaration; they now evaluate in their own body scope, so all
four evaluator paths answer one routine.

The prompt evaluates in the namespace the session works in, so imported units
resolve unqualified; %calc parses its arguments as expressions, so a quantity
survives; and a quantity renders as written in a violation and by the Real
convention in a result table.

Co-Authored-By: jason.han <jason.han@jpl.nasa.gov>

* fix(repl): separate a signed %calc argument from the expression before it

A whitespace-separated argument list is cut into arguments before the
expression parser sees it, so `5 -3` is two arguments while `5 - 3` — an
expression left unfinished across the space — stays one.

Also name the shadowed unit when the shadowing declaration sits in the
namespace that declares the import, by resolving the name again with that
declaration hidden instead of searching its parent scope.

Co-Authored-By: jason.han <jason.han@jpl.nasa.gov>

* fix(repl): scope the named-argument check to the argument's own binding

An `=` nested in a call, a bracket or a string belongs to that expression,
so it no longer refuses `add(x = 1, y = 2)` as an argument; only a bare
identifier bound at the argument's top level is a named argument.

A qualified name in unit position resolves to what it names, so it is
reported without the shadowing explanation \u2014 whose suggestion would
otherwise paste the whole written name onto the unit's namespace.

Co-Authored-By: jason.han <jason.han@jpl.nasa.gov>

* fix(repl): report an ambiguous name at the prompt instead of resolving it

%eval retained a lookup failure so an imported name could still be
evaluated, which also swallowed the ambiguity error. A typed
AmbiguousNameError separates the two: a name found nowhere falls through
to the expression path, a name several declarations answer to is reported.

Co-Authored-By: jason.han <jason.han@jpl.nasa.gov>

* fix(semantics): drop the remedy clause when no spelling qualifies the hidden unit

A unit owned by no namespace has the same qualified name as its simple one,
so the suggestion repeated the name that had just failed. The diagnostic now
names the shadowed unit without advising a spelling in that case.

Co-Authored-By: jason.han <jason.han@jpl.nasa.gov>

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: jason.han <jason.han@jpl.nasa.gov>
Adds a `publish-pypi` job in a `release-python` workflow filtered to
`pysysml-v*` tags, authenticating from the restricted `pypi-publish` context.
The package's version is declared once, in `python/pysysml/_version.py`, read by
the packaging metadata and by `pysysml.__version__` through the installed
distribution; `python/setup.py` is gone and a test fails if a second version
literal reappears. The job resolves the version from the tag, refuses a version
the index already has, builds a wheel and an sdist, checks them with
`twine check --strict`, installs the wheel into a clean virtualenv and imports
it, and only then uploads - pre-release versions to TestPyPI.

Also cuts the 0.0.5 changelog section (with the changes that landed after most
of it was written, and an honest current limitation set verified on the built
binary), corrects the README/QUICKSTART transcripts and the test counts repeated
across the docs against a real run, and documents the PyPI release path and the
upstream promotion the tag needs in docs/RELEASING.md.

Co-Authored-By: jason.han <jason.han@jpl.nasa.gov>
…n prompts

The interactive pass over the rewritten transcripts found three doc-side
divergences from the binary: the first %save block omits the "(replaced
the existing file)" suffix the REPL always prints there, the Runtime
Features Engine/Car snippet needs "import ScalarValues::*;" for Real to
resolve in a fresh session, and the behavioral blocks used an unindented
continuation prompt.

Co-Authored-By: jason.han <jason.han@jpl.nasa.gov>
…and-0.0.5-prep

* feat(release): publish pysysml to PyPI from CircleCI and prepare 0.0.5

Adds a `publish-pypi` job in a `release-python` workflow filtered to
`pysysml-v*` tags, authenticating from the restricted `pypi-publish` context.
The package's version is declared once, in `python/pysysml/_version.py`, read by
the packaging metadata and by `pysysml.__version__` through the installed
distribution; `python/setup.py` is gone and a test fails if a second version
literal reappears. The job resolves the version from the tag, refuses a version
the index already has, builds a wheel and an sdist, checks them with
`twine check --strict`, installs the wheel into a clean virtualenv and imports
it, and only then uploads - pre-release versions to TestPyPI.

Also cuts the 0.0.5 changelog section (with the changes that landed after most
of it was written, and an honest current limitation set verified on the built
binary), corrects the README/QUICKSTART transcripts and the test counts repeated
across the docs against a real run, and documents the PyPI release path and the
upstream promotion the tag needs in docs/RELEASING.md.

Co-Authored-By: jason.han <jason.han@jpl.nasa.gov>

* docs(quickstart): correct save suffix, missing import and continuation prompts

The interactive pass over the rewritten transcripts found three doc-side
divergences from the binary: the first %save block omits the "(replaced
the existing file)" suffix the REPL always prints there, the Runtime
Features Engine/Car snippet needs "import ScalarValues::*;" for Real to
resolve in a fresh session, and the behavioral blocks used an unindented
continuation prompt.

Co-Authored-By: jason.han <jason.han@jpl.nasa.gov>

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: jason.han <jason.han@jpl.nasa.gov>
@HuiJun
HuiJun merged commit b6c8bbc into Open-MBEE:main Aug 13, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant