v0.4.3: Identity Release
OpenSysML 0.4.3
Release 0.4.3 is where an element gets an identity the notation can carry. The SysML v2 textual
notation deliberately records no element identity, so a model saved as .sysml and re-parsed had
fresh ids everywhere and a rename was a delete plus a create. An element may now declare the
repository element it is — standard user-defined metadata (@ElementId, with a ProjectRef
binding a document to its repository once, at the root namespace), shipped as an IdentityMetadata
library extension that any conforming tool already parses and preserves. Identity is validated,
survives notation → RDF → notation, and sysml -sync-diff computes the change set between a
model and a repository graph keyed by that identity, so a rename or a retype is an update to the
same element.
Alongside it, a solver verdict becomes the evaluator's verdict, the four non-Go clients and the
public Go API were each exercised by worked examples that surfaced the fixes below, the
conformance suite and pilot differential render their runs as CI test results, and a profiling
pass across the toolchain removed the costs a performance census found rather than the costs
assumed — a multi-file -validate goes from 6.8 s to 0.30 s over a 100-file corpus.
No model that validated under 0.4.2 stops validating, and no import path moves. Upgrading is a
binary swap.
An element's identity, carried in the notation
-
An element declares its repository identity.
@ElementId { id = "…"; }annotates the
element it is written about, and@ProjectRef { projectId = "…"; }on a root namespace binds
the document to its repository, so element-level ids inherit their scope. Identity is opt-in per
element: an element without an annotation keeps today's derived, latest-wins identity. The two
metadata definitions ship as a non-normative library extension (IdentityMetadata, entering the
same gates as the vendored files — the bundled-library check now reports 97/97 clean), and a
constraint-tier pass validates id shape, scope binding and uniqueness across the workspace,
including anonymous about-form usages, annotations declared in libraries, and targets outside
the built roots. -
Identity survives the RDF round trip. The writer mints subject IRIs from the effective id —
the declared one where an annotation exists, the encoded qualified name where none does — marks
declared ids, writesProjectRefbindings as provenance triples, and refuses colliding ids
across a mixed-scope workspace rather than silently merging two elements. The reader keys
subjects on the element id, reports a dangling id as its own error, and re-materializes the
annotations on the way back to notation, sonotation → RDF → notationpreserves which
repository element each declaration is. -
A model diffs against a repository, keyed by identity.
sysml model.sysml -sync-diff repo.ttl
reports the change set — creates, updates, deletes, and renames seen as updates to the same
element — and never writes: applying is a separate step.-sync-basenames the graph at the
last-seen commit, so repository changes since then surface as conflicts rather than silent
overwrites; deletes are reported always and confirmed with-sync-confirm-deletes;
-sync-mint-idsmints a UUID for each unannotated element being created and-sync-annotate
writes the model back out with each minted id declared, preserving the source text. The
last-seen commit is tool state beside the model (<model>.sync.json), never written into the
notation. -
The design is a project record (
docs/project/element-identity-annotations.md), and the
notation has been submitted to OMG for standardization (docs/project/omg-issues.md).
Sound solver verdicts
The SMT translation reasons over exact rationals while the evaluator computes in IEEE 754
binary64, and the difference is reachable: the exact encoding holds 0.1 + 0.2 == 0.3 sat, which
the evaluator rejects.
-
A
satis a witness the evaluator confirms; anunsatis claimed only where the arithmetics
coincide. Every satisfying assignment is replayed through the evaluator's float64 arithmetic
and reportedunknownwith the reason when the replay rejects it; a query whose conditions the
evaluator rounds is marked,%checkand%solvereport its exact-realunsatas undecided,
and%configure alland%optimizedecline the completeness claim outright. Narrower, and
sound — what is given up is completeness on rounded queries, and a census over the repository's
solver-facing corpora found none of them recoverable. -
A whole-number quotient is a Rational.
5 / 2answers2.5forNaturalandInteger
operands alike, which is what the reference evaluator answers; the quotient is computed as an
exact ratio rounded once to float64, so it agrees with the exact SMT encoding even beyond 2^53. -
The remaining alternative, an exact-rational evaluator value representation, was adjudicated
against the pinned pilot and the specification text and declined
(docs/project/exact-rational-evaluation.md).
The clients, exercised
-
Worked examples for every client, run by the tests. Runnable examples drive the Node, Java,
Rust and Go clients over one capable model — quantities, enumerations, multiplicity, nesting,
unvalued features; parsing, diagnostics, symbol navigation, evaluation and instantiation — and
each client gains a reference page of its own (docs/reference/java-api.md,
docs/reference/node-api.md,docs/reference/rust-api.md). The examples were written to find
defects and did; the fixes are below. -
The Java client's package is
org.openmbee.opensysml, the DNS-verified namespace every
future Java artifact belongs under, rather thanio.opensysml. The client has never been
published, so no released consumer moves with it. -
The Python client 0.4.0 was published to PyPI.
Reporting and examples
-
The conformance suite and the pilot differential render as CI test results. The conformance
runner emits JUnit XML (stored even when the gate fails), andpilot-diffwrites JUnit XML with
one suite per corpus root alongside SARIF 2.1.0 with one result per disagreeing diagnostic
group, located on the compared model file. -
The implementation models itself.
examples/self-modelis the analysis pipeline, surfaces,
invariants and views of this implementation written as a SysML v2 model across five files whose
packages import each other, with a make target rendering its diagrams and documents and a test
evaluating its invariants and checking its figures against the implementation they describe. -
-render-documenttakes a model of several files. A document may query elements its sibling
files declare:sysml model/*.sysml -render-document Reports::MassReport -o report.mdloads the
named files as one model. -
A pilot corpus records the pin it was fetched at. Each corpus directory carries a stamp
naming the repository and tag it came from: a stale stamp triggers a re-download, a current one
is left alone, and a directory without a stamp is left alone with a warning. -
The HTML document backend is recorded as an agreed design for rendering documents as
semantic, styleable HTML straight from the document IR (docs/project/html-document-backend.md)
— proposed, not implemented.
A faster toolchain, by measurement
A September performance census and an execution-performance record
(docs/project/performance-census-2026-09.md, docs/project/execution-performance-2026-09.md)
measured where the toolchain stands; the costs they flagged are removed, and none of these
changes any diagnostic, verdict or rendered output.
-
A multi-file
-validatebatch is indexed once. Validation loaded each file through a path
that reopened the session document, reindexed it and re-expanded every wildcard import, so a
batch of N files paid N full indexes over a growing buffer. The batch is now a single
submission, with each file's own syntax errors, load notices and summary still printed in file
order. Over the training corpus: 0.26 s → 0.12 s at 25 files, 0.59 s → 0.13 s at 50,
6.8 s → 0.30 s at 100 — a fixed floor plus a term that scales with the input, no quadratic
term. -
A calc invocation reuses a pooled frame. Each invocation allocated a fresh parameter map,
evaluation context, statement host and engine — ~1.7 KiB per call, and GC took half the CPU of
recursion-heavy evaluations. Returned frames go on a free list; a frame is only ever held by
one active invocation, so recursion never aliases. -
A run target resolves from a per-document name table. Starting a calc, state machine or
instantiation re-walked the whole document scope tree per run, so a run cost O(model). At
4,000 elements a state-machine start goes 204 µs → 6.4 µs, a calc 222 µs → 4.5 µs, an
instantiation 217 µs → 3.3 µs, and the figures no longer scale with model size. -
The parser's token buffer is a bounded window. The parser buffered every non-trivia token
of a file up front; consumed tokens are now dropped once no checkpoint can rewind to them.
With the REPL parsing each submitted file once rather than twice, a load allocates 30% fewer
objects and holds 16% less live heap. -
The
about-metadata index is cached at index freeze time, so a session no longer walks the
bundled standard library's scope trees to build it: the empty-session floor returns to
~0.19 ms and 117 KiB allocated (−80% wall, −83% allocation). -
A repeated REPL command does not re-parse its text. Argument lists and command name text
are memoized by exact text; evaluation still runs on every call against the current session
state.
Fixes
-
A wider-typed expression binds to a narrower feature.
return : Integer = 7 / 2;was
refused statically because the quotient's type isRational, yet an expression's static type
only bounds its values —4 / 2is whole. A binding, argument or index is now refused
statically only when the two types are disjoint or the value is a literal, whose type is
exact; everything else is deferred to evaluation, where the value it actually turns out to be
is still checked. This matches the pinned pilot, which accepts the declaration. -
Every exported
Sessionmethod holds the session lock. The run, check/solve, view,
document and diagnostics entry points did not take it, so a Tab completion racing one of them
touched the lazily built index, name table and runtime context unsynchronized — 144 races
under-race, now none. -
Node client: restarting the service waits for the previous process to exit before
reconnecting, and does not wait on one that already exited. -
Arithmetic outside a type's range is reported, not returned. A wrapped Integer sum, the
least Integer's negation and its remainder, an infinite Real from an out-of-range literal, a
folded infinity, and a quantity magnitude outside the Real range each answered as if computed;
every one is now a typed error naming the range. An ordinary negated literal evaluates rather
than being mistaken for a fold, seeded outputs are reported for what they are, and an escaped
attribute default is decoded before use. -
An unbound requirement subject is reported as unbound. A requirement checked with nothing
supplying its subject read as a modelling mistake in the condition; the diagnostic now names the
subject and the three ways to supply one — bind it, check it on an object, or assert
satisfaction by an element. -
A debounced call a later trigger superseded does not run. A timer firing as the next trigger
arrived ran the work its successor now owned and deleted the successor's entry; a callback now
confirms it is still the timer its key waits on. -
Node client: a short name is looked up on a model adopted by hash; a missing symbol's error
names what was looked for; an RPC failure surfaces as a typed client error rather than a raw
transport error; an impossible encoding or timeout is refused at construction; and a failed
handshake carries the status it failed with. -
Java client: a call that outlives its request timeout is reported as the timeout it is, not
as the service being unavailable, and a value kind the client cannot read is a refusal rather
than a value silently dropped from the sequence holding it. -
Rust client: an empty
$OPENSYSML_SERVICEno longer selects an unnamed binary, a response
above the transport's 10 MB default is read, a qualified value is read outside its declaring
scope, string escapes decode, Integer overflow and non-finite Real folding are reported, and an
instance graph iterates in declaration order rather than map order. -
The toolchain download paths close their quality-gate findings. The stall watchdog owns a
thread rather than an executor, the pandoc fetch refuses a plaintext redirect, and the mermaid
install runs no dependency's lifecycle script and finishes before calling itself present.
Verification
The pre-tag gate of docs/project/releasing.md on the tagged commit: gofmt -l .,
go build ./..., go vet ./..., make lint, go test -race -count=1 ./...,
TestStdlibConformance, the training and pilot corpus gates with
OPENSYSML_REQUIRE_TRAINING_CORPUS=1 OPENSYSML_REQUIRE_PILOT_CORPORA=1, the cmd/pilot-diff and
cmd/pilot-xpect provenance baselines, the documentation link/label/figure checkers, and
doc-counts -check.