Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
b27f289
Merge pull request #746 from someshSandbox/feat/731-library-names
HuiJun Sep 30, 2026
c9ba99a
Merge pull request #749 from someshSandbox/feat/732-convert-id-form
HuiJun Sep 30, 2026
fd1080a
Merge pull request #751 from Open-MBEE/fix/grpc-parser-warnings
HuiJun Sep 30, 2026
4281e7d
Merge pull request #747 from someshSandbox/feat/733-query-element-id
devin-ai-integration[bot] Oct 1, 2026
a4357ba
Merge pull request #761 from someshSandbox/fix/760-succession-ends
HuiJun Oct 1, 2026
4139f86
Merge pull request #789 from someshSandbox/fix/760-requirement-constr…
HuiJun Oct 1, 2026
60b1e4f
Merge pull request #790 from someshSandbox/fix/760-variant-reference
HuiJun Oct 1, 2026
ef5e0c5
Merge pull request #748 from someshSandbox/fix/726-implicit-subsettin…
HuiJun Oct 1, 2026
1655b21
Merge pull request #794 from Open-MBEE/fix/733-element-id-by-declaration
HuiJun Oct 1, 2026
d203c58
Merge pull request #805 from someshSandbox/fix/transition-source-member
HuiJun Oct 2, 2026
d40d37b
Merge pull request #846 from Open-MBEE/fix/default-built-against-release
HuiJun Oct 3, 2026
a6c447d
Merge pull request #849 from Open-MBEE/feature/rust-release-digest-stamp
HuiJun Oct 3, 2026
0801ac0
Merge pull request #860 from Open-MBEE/feature/python-release-digest-…
HuiJun Oct 3, 2026
fc651bc
Merge pull request #873 from Open-MBEE/feature/python-metamodel-reader
HuiJun Oct 4, 2026
1942d25
docs(python): publish a short README to PyPI and keep the developer g…
devin-ai-integration[bot] Oct 5, 2026
59ba394
fix(python): import read_json where __all__ exports it
devin-ai-integration[bot] Oct 5, 2026
87fd124
chore(release): 0.9.2
devin-ai-integration[bot] Oct 5, 2026
d6b99c6
chore(release): scrub Sonar findings carried by 0.9.2 and restore met…
devin-ai-integration[bot] Oct 5, 2026
2c96c7d
fix(export,python): refuse an untargeted first-then end and a referen…
devin-ai-integration[bot] Oct 5, 2026
3b97f32
docs(project): performance record for 0.9.2 against 0.9.1
devin-ai-integration[bot] Oct 5, 2026
42c14dc
Merge pull request #756 from Open-MBEE/fix/dimension-one-identity
HuiJun Oct 1, 2026
152ad90
Merge pull request #763 from Open-MBEE/fix/orthogonal-initial-entry
HuiJun Oct 1, 2026
a63452c
Merge pull request #795 from Open-MBEE/fix/758-nonunique-composites
HuiJun Oct 1, 2026
4999161
Merge pull request #802 from Open-MBEE/fix/repl-ctrl-c-exits
HuiJun Oct 2, 2026
faac52a
fix(export): load sysml-toolkit api-json exports
devin-ai-integration[bot] Oct 3, 2026
d5b767b
fix(export): preserve toolkit API JSON round trips
devin-ai-integration[bot] Oct 4, 2026
d0be16a
fix(export): refuse named flow ends in a flow head
devin-ai-integration[bot] Oct 4, 2026
5394841
fix(export): preserve kindless API JSON returns
devin-ai-integration[bot] Oct 4, 2026
3f438a3
chore(release): fold the 0.9.2 changelog fragments for the added fixes
devin-ai-integration[bot] Oct 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 28 additions & 2 deletions .agents/skills/testing-sysml-repl/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -5043,8 +5043,9 @@ for reservation. Two consequences worth knowing before writing assertions:

Incomplete input (`part def U { ref redefines x[4;`) puts the REPL into a `...>` continuation
prompt, and *anything* typed there — including `%eval 1+1` — is swallowed as more model text.
Press **Ctrl-C** to abandon the continuation: the buffered text is then parsed, its diagnostics
print, and the `sysml>` prompt returns usable. Always follow a malformed submission with
Press **Ctrl-C** to abandon the continuation: the buffered text is discarded unparsed and the
`sysml>` prompt returns usable (a second Ctrl-C there exits the REPL). A blank line instead
submits the buffer, so its diagnostics print. Always follow a malformed submission with
`%eval 1 + 1` (expect `= 2`) to prove the session survived. Never type shell words like `clear`
at the prompt; it parses as a model line and produces `expected a namespace member`.

Expand Down Expand Up @@ -6151,6 +6152,31 @@ gRPC `choice-point` diagnostic encoding on the CLI surface.
exits 0; `-action Timed::pinger -advance 2` exits 2 and names the wait at t=5.0; `-action` alone
runs to completion (exit 0); `-advance` with no behavior, or a negative one, exits 2.

## Dimension-one identity: `MeasurementReferences::one` in a unit product (PR #756)

`one` is the identity of the unit product — `0.7 [one] * 800 [W]` is `560.0 [W]`, `2 [one] * 3 [one]`
is `6 [one]`, and `800 [W] * 120 [s] * 0.7 [one]` in a calc returning `EnergyValue` is
`67200.0 [SI::J]`, identical to the bare `0.7` spelling. `one*…` in the unit, or
`SI::'kg⋅m²⋅s⁻²'*one`, is the pre-fix signature. Traps when asserting this:

- **Compare an untyped product against the no-`one` control, not against a named unit.** A bare
`800 [SI::W] * 120 [SI::s]` prints the coherent base spelling `SI::'kg⋅m²⋅s⁻²'` on `develop`
too (energy and torque share dimensions; only a declared quantity kind such as `EnergyValue`
selects `SI::J`). So `1 [one] * 800 [W] * 120 [s]`, `… * 1 [one]` and `(…) / 1 [one]` are correct
when they equal that control, and would be a spurious failure against `SI::J`.
- **A real reference, not display text.** The Python `Unit` equality includes spelling (`J` vs
`SI::J`), so evaluate `<expr>.mRef == SI::J` (must be `true`) and, on the wire, compare the
`unit_id` of `<expr>.mRef` with that of a directly evaluated `SI::J` — both are the declaration
ID `SI::joule`. `unit.same_reduction(target.unit)` proves scale/dimension only, not identity.
- **Meaningful dimension-one units stay.** `3 [rad] * 1 [one]` is `3 [rad]`, likewise `sr`, and a
model's percent (`attribute def PercentUnit :> DimensionOneUnit { attribute :>> unitConversion :
ConversionByConvention { :>> referenceUnit = one; :>> conversionFactor = 0.01; } }`) survives as a
factor: `50 [percent] * 800 [W]` spells `percent*W` while `== 400 [W]` is `true` and
`.mRef == W` is `false`. A fix that drops any dimension-one factor passes the `one` cases and
fails these.
- CLI `-e` result lines are indented before `=`; strip the whitespace before parsing the magnitude in
a throwaway harness.

### Devin Secrets Needed

None for these local REPL/gRPC checks.
430 changes: 375 additions & 55 deletions .circleci/config.yml

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1136,7 +1136,7 @@ jobs:
make python-install
# Pinned and wheel-only: an unpinned resolve runs whatever was
# published today, and a source distribution runs its own build code.
pip install --only-binary :all: pytest==9.0.3 pytest-mock==3.15.1 psutil==7.2.2 fmpy==0.3.32
pip install --only-binary :all: pytest==9.0.3 pytest-mock==3.15.1 psutil==7.2.2 fmpy==0.3.32 mypy==2.4.0 types-protobuf==7.35.1.20260906 'pyright[nodejs]==1.1.414'

# The FMI runner test's FMUs, same pin as the Go gate's.
- name: Cache the Reference-FMUs
Expand Down Expand Up @@ -1175,6 +1175,7 @@ jobs:
- name: Run Python client tests
env:
OPENSYSML_REQUIRE_SERVICE: 1
OPENSYSML_REQUIRE_TYPECHECKERS: 1
run: make python-test

- name: Verify Python client import
Expand Down
69 changes: 69 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,75 @@ release is described in [docs/project/releasing.md](docs/project/releasing.md).

## Unreleased

## 0.9.2 — 2026-10-05

### Added

- **The service's `Convert` takes the id form.** `ConvertRequest.id_form` spells derived element ids as `sysml -id` does when notation is written as a graph (`ttl`, `api-json`), from inline content, a file or a parsed model of several documents: `qualified` (the default) or `uuid`. A service client got only the qualified form before. Any other value, or one given for another direction, is `INVALID_ARGUMENT` (#732).

- **A converted model names the standard library elements it references.** A reference to a library element is its normative id, a hash no reader can turn back into a name. Turtle, the API's JSON and `Convert` now state each referenced library element under that id, with its metaclass, `qualifiedName` and `declaredName`, marked `isLibraryElement` (KerML `Element::isLibraryElement`). Every `@id` an API JSON output references is then an element of it. The library itself is still not exported, and reading a graph back checks each name against the bundled library (#731).

- **Add generated SysML metaclasses and a standalone JSON element reader to the Python client.** Read API and toolkit exports as a lazy typed graph without loading JSON into the engine.

- **A query can report an element's `elementId`.** `select: ["elementId"]` (and `where` on it, and OSLC) reports the `elementId` `Convert` writes for the element: a declared id, a standard-library element's normative id, or the encoding of its qualified or positional name, from the writer's own identity tables. A query result then joins a converted graph, which the qualified name did not do for a library element or a declared id (#733).

### Changed

- **The metamodel table is generated from the OMG SysML v2 metamodel version 20250201, read from the pinned pilot implementation's `SysML.ecore`, and records each property's ordering, derivation, redefinitions, subsettings and opposite.** It was pinned to the 202407 rendering of `Open-MBEE/sysmlv2-rdf-ontology`, which has not moved since. The table now declares `FlowUsage`, `PayloadFeature`, `FlowEnd`, `TerminateActionUsage` and the other metaclasses added since, so a type owns them through a `sysml:FeatureMembership` and lists them in `sysml:ownedFeature`, as it owns any feature; `nonunique` is written `sysml:isUnique false`, since the metamodel no longer declares `isNonunique`. Graphs written by earlier releases still read. `ontology.Property` gains `Ordered`, `Derived`, `Redefines`, `Subsets` and `Opposite`, `ontology.Class` gains `Abstract`, and the ontology gate reports a subject typed by an abstract metaclass. CI fails when the table drifts from the pinned metamodel (`make ontology-table-check`).

- **Stamp Rust release digests into each published crate.** The publish job adds the release tag's
service-asset digests from its checksum manifest, so a crates.io installation can verify and
download the binary it was built against by default. A Git-checkout build or a request for
another release still needs a matching pin or `$OPENSYSML_ALLOW_UNPINNED_DOWNLOAD`; Rust does not
verify the manifest's Sigstore signature itself.

- **A download refused because `sigstore` is not installed says so.** When no pin covers a release and the `sigstore` package the signed manifest is verified with cannot be imported, `opensysml` raises `SigstoreUnavailableError` (an `UnpinnedReleaseError`) naming the package, the install (`python -m pip install 'sigstore>=4.5.0,<5'`), and that a release of `opensysml` pins its own core release, so this arises only for another release or an older client. The binary is still not downloaded unverified.

### Fixed

- **Clients use their built-against service release by default.** Python and Java download and
verify that release. A Rust crate published from a release tag also verifies its built-against
release: the publish job stamps its digests from the release checksum manifest. A Git-checkout
build or a request for another release still needs a pin or
`$OPENSYSML_ALLOW_UNPINNED_DOWNLOAD`; Rust does not verify the manifest's Sigstore signature.

- A bare `assume c;`, `require c;` or `assert c;`, and one naming a feature chain (`require q.k;`), is now exported in the reference form the grammar gives it: the constraint usage owns a `ReferenceSubsetting` to the feature (or to the chain feature of `q.k`), as `require P::c;` already did, rather than an inline `sysx:condition` expression. The notation, and graphs that state a condition inline, read back as before.

- **`first a then b;` owns its connector ends.** It was exported as a `SuccessionAsUsage` with only the derived `sourceFeature` and `targetFeature`, so a reader of the API's JSON found no ends. It now owns two `ConnectorEnd`s under `EndFeatureMembership`s, each with a `ReferenceSubsetting` to the feature it names, as the grammar's `SuccessionAsUsage` does; the derived properties are still written, and the notation reads back unchanged. A graph whose ends and derived properties name different features, or whose end declares a name or bounds, is refused rather than written without them (#760).

- **The gRPC service reports the parser's warnings, and what the notation passes make of them, as the command line does.** `ParseFile` and `ParseSources` handed the analysis an empty parse-diagnostic list, so a reserved keyword written as a name (`part filter : X;`) loaded with no diagnostic where `sysml -validate` reports the `reserved-keyword-name` error, and strict conformance never escalated a parser `nonstandard-notation` warning. The service now hands the analysis the parse's errors and warnings the way the workspace does and reports each diagnostic once; the Python, Java, Julia and other clients see the same diagnostics for a document as the CLI. Edits validate their result under the same parse diagnostics.

- **Uniqueness and constancy conformance hold of an implicit subsetting.** `action def A { action b[*] nonunique; }` was accepted, though `b` implicitly subsets the unique `Actions::Action::subactions`; only a written `:>`/`:>>` was checked. The feature the usage implicitly subsets is now checked as a written one is, and the finding is reported at the declaration, once (#726).

- **The metamodel JSON reader unwraps standalone DataVersion envelopes, excludes namespaces with owner metadata from roots, rejects references to the wrong metaclass by default, and rejects non-finite real values.** This reports malformed exported values when read, lets callers opt out of range checking with `check_ranges=False`, and resolves custom metaclasses through `supertypes=`.

- **Ecore upper bounds above one are multi-valued in the ontology table.** `MultiplicityRange::bound` and `Flow::flowEnd` (upper bound 2) were marked single-valued; they are now `Many`, so api-json writes them as arrays.

- **A released `opensysml` wheel verifies the service it downloads against a digest it ships.** The release pipeline now builds the `sysml-grpc` binaries first, hashes them and stamps their digests under the release tag into the `release-digests.json` the wheel and sdist package (`pin_release_checksums.py --from-binaries`), and fails the release unless the built wheel and sdist pin all five service assets and the signed `SHA256SUMS.txt` lists the same digests. `pip install opensysml==X.Y.Z` followed by `opensysml.connect()` therefore installs its own core release with no environment variable and no `sigstore` at run time; the signed manifest is what the client verifies for another release, or one newer than itself.

- **A query reports each element's own `elementId` where two identity scopes declare one qualified name.** The ids a conversion writes were recorded by qualified name, so of two elements named alike in two scopes the query reported the id written last for both. They are now recorded by declaration as well, so each reports the id `Convert` writes for it.

- A transition now owns what the grammar gives it ahead of its trigger: the `FeatureChainMember` naming the source of a `first` transition (a `Membership` whose member is the state, or for `first a.b` an `OwningMembership` owning the chain), then an `EmptyParameterMember`, and a second one ahead of a trigger. A chained target (`then b.c`) is the feature chain its end's reference subsetting owns, rather than the feature it reaches. `first b.c` and `then b.c` read back as written, where they came back as `first c` and `then c`; a source member or chained end that disagrees with the transition's `sysml:source`/`sysml:target` is refused. A graph in the API element form that states the structure alone, without the collapsed source and target, or a chain only by its derived `chainingFeature` list, reads back the same (#803).

- `variant x;` is now exported as the `VariantReference` the grammar gives it: a `ReferenceUsage` whose owned `ReferenceSubsetting` references the `x` visible outside the variation, rather than a new `PartUsage` declaring `x`. `variant P::x;` and `variant a.b;`, whose reference is a qualified name or a feature chain, now parse, and are exported the same way, with the chain feature owned for `a.b`. Reading back writes `variant x;` only when `x` reaches the referenced feature, and refuses it otherwise. An unresolved `variant x;` keeps `x` as a literal reference, an unrestricted name such as `'a::b'` staying one name. `variant ref x;` declares `x`, as the grammar reads it, and is written back as `variant ref x;` rather than `ref variant x;`.

- **The Windows MSI jobs no longer check out the repository, so a tag whose tree Windows cannot check out can still get its installer.** The `msi` and `msi-signed` jobs consume `packaging/msi`, `scripts/build-msi.sh` and `LICENSE`, which the Linux build job uploads as a workflow artifact from the tagged commit; `git checkout` on Windows — which fails on any tracked path it forbids — is no longer on the installer's critical path.

- **The Windows release workflow can be re-run against an existing tag, and the tree checks out on Windows again.** A document-renderer fixture whose name held `<`/`>` broke `git checkout` on the Windows runners, so no MSI was built; the fixture is renamed, a hygiene test now rejects tracked paths Windows cannot hold, and `release-windows.yml` accepts a `tag` dispatch input (`gh workflow run release-windows.yml --ref main -f tag=vX.Y.Z`) that builds the tagged commit and publishes only the MSI assets.

- A factor written in `MeasurementReferences::one` is absorbed as the identity of the unit product when quantities multiply or divide (`0.7 [one] * 800 [W]` is `560.0 [W]`, `800 [W] / 0.5 [one]` is `1600.0 [W]`, `2 [one] * 3 [one]` is `6 [one]`), so `power * duration * efficiency` with `efficiency = 0.7 [one]` folds to `67200.0 [SI::J]` — a real reference to `SI::J`, admitted to an `EnergyValue` and comparable with `[SI::J]` literals — instead of `[SI::'kg⋅m²⋅s⁻²'*one]`; `sqrt(9.0 [one])` is `3.0 [one]`. Once every other unit cancels the identity is the unit again, however the operations were grouped (`(2 [one] * 3 [m]) / 3 [m]` and `2 [one] * (3 [m] / 3 [m])` are both `2.0 [one]`). The identity is recognised structurally (a plain `DimensionOneUnit` reducing to scale one), so `rad`, `sr` and a unit scaling `one` by convention (a percent) stay in the product.

- **The messages-and-events demo validates clean.** `examples/parser_features_demo_messages_events.sysml` declared its two channel messages `nonunique`, which a message in a part cannot be (it implicitly subsets the unique `Parts::Part::ownedActions`); the modifier is dropped and the example is no longer a known failure (#758).

- **The SysML v1 migrator no longer writes `nonunique` where v2 forbids it.** A v1 property with `isUnique="false"` (or a MagicDraw `[]`/`[n]` type modifier) that becomes a usage implicitly subsetting a unique library feature — a composite `part` or `item` in a part (`Items::Item::subparts`/`subitems`), an `action` in an action or a part (`subactions`/`ownedActions`), and the rest the implicit-subsetting rules name — or that redefines or subsets a feature written unique, is written without the modifier, and the report marks the entry approximated with the feature that forbade it. Attributes, references and parameters keep `nonunique`. The uniqueness is read from the bundled library through the same rules the checker applies, so the migrated notation validates clean. A slot repeating an instance on such a feature is unmapped as a slot conflict, as on any feature written unique.

- **A SysML v1 initial pseudostate whose transition enters a sibling orthogonal region is written as that region's entry.** The migrator refused such a transition as lying in an orthogonal region and wrote its own region with no entry at all, so the runtime stopped the whole machine at initialization with `region … has no initial state`. The entry `entry; then s;` is now written in the region that owns the target — the sub-state of the `parallel` state or the body of a nested composite state — and the pseudostate and transition are reported as approximated, naming that region; a region owning several initials takes as its own the one entering it, so the stray one no longer displaces it. One coinciding with the target region's own entry into the same vertex is written once; one conflicting with an entry into another vertex is refused with both targets named, and the region's own entry is kept. An initial pseudostate no transition leaves is no entry either, so a donated one may enter its region; one entering a region of another, non-orthogonal state is refused. The owner's `entry; then regions;` default entry appears once every region has an entry.

- **Ctrl-C at an empty REPL prompt exits the REPL.** Ctrl-C used to only clear the line, so it never ended a session. It still discards a partly typed line, and at a `...>` continuation prompt it now discards the buffered text instead of submitting it; a second Ctrl-C at the empty `sysml>` prompt then exits with the status `%quit` and Ctrl-D leave.

- **Toolkit full-JSON imports preserve flows, transitions, and library references.** Flow ends owned through `EndFeatureMembership` are folded into the flow head; transitions read trigger, guard, and effect features by their membership kind rather than mistaking parameters for effects; and document-defined library packages remain declarations. Library stubs with stale ids can resolve by their stated qualified name or graph-stated name and owner chain, with a warning, while unmatched identities report both the id and name. Apollo API-JSON import avoids rescanning the whole graph for each owner.
- Reference-usage return parameters retain their metaclass on toolkit JSON round trips. Bulk API JSON decoding delays duplicate indexing until parsing is complete, and graph normalization reuses the imported graph's storage while releasing excess triple capacity.

## 0.9.1 — 2026-09-26

### Added
Expand Down
Loading
Loading