Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
14d9267
feat(view): add case and mixed rendering kinds
devin-ai-integration[bot] Oct 3, 2026
637ff64
fix(view): preserve legacy rendering behavior
devin-ai-integration[bot] Oct 3, 2026
1a7b48b
Merge remote-tracking branch 'origin/develop' into feature/view-case-…
devin-ai-integration[bot] Oct 3, 2026
17a4596
test(view): make case fixture validate
devin-ai-integration[bot] Oct 3, 2026
19f78c5
fix(view): nest mixed containers in PlantUML
devin-ai-integration[bot] Oct 3, 2026
3f0bee3
fix(view): keep inherited roles and containment in case and mixed views
devin-ai-integration[bot] Oct 4, 2026
530ad60
fix(view): skip library-inherited case roles
devin-ai-integration[bot] Oct 4, 2026
7cce895
fix(view): keep nested mixed content
devin-ai-integration[bot] Oct 4, 2026
18cd61e
fix(view): build mixed structures in one pass
devin-ai-integration[bot] Oct 4, 2026
0a4a800
docs: note OpenSysMLRenderings dependency for other tools
devin-ai-integration[bot] Oct 4, 2026
15e1099
fix(view): skip structure inside behavior when discovering mixed members
devin-ai-integration[bot] Oct 4, 2026
cc00e34
Merge origin/develop into feature/view-case-mixed
devin-ai-integration[bot] Oct 4, 2026
ecd2ded
Merge origin/develop into feature/view-case-mixed
devin-ai-integration[bot] Oct 4, 2026
e4b10a8
feat(view): link case and mixed diagram elements to source
devin-ai-integration[bot] Oct 4, 2026
3ae4d4e
fix(view): make mixed circles and objective notes clickable in PlantUML
devin-ai-integration[bot] Oct 4, 2026
f6b7363
fix(view): keep paragraph breaks in linked PlantUML objective notes
devin-ai-integration[bot] Oct 4, 2026
2130b91
fix(view): keep linked PlantUML objective titles bold
devin-ai-integration[bot] Oct 4, 2026
d0b37e8
Merge remote-tracking branch 'origin/develop' into feature/view-case-…
devin-ai-integration[bot] Oct 4, 2026
8a00f0f
fix(view): expose ports in mixed diagrams
devin-ai-integration[bot] Oct 4, 2026
d5b1c3a
docs(view): describe mixed port text rendering
devin-ai-integration[bot] Oct 4, 2026
2d53a9e
fix(view): keep action parameter pins in minimal mixed views
devin-ai-integration[bot] Oct 4, 2026
6a02af9
fix(view): end mixed PlantUML connectors at their ports
devin-ai-integration[bot] Oct 4, 2026
199402b
fix(view): link every inherited role occurrence in mixed views
devin-ai-integration[bot] Oct 4, 2026
4e6d50f
fix(view): keep inherited role edges within their case in mixed views
devin-ai-integration[bot] Oct 4, 2026
e59f371
Merge remote-tracking branch 'origin/develop' into feature/view-case-…
devin-ai-integration[bot] Oct 4, 2026
2d2d25b
Merge remote-tracking branch 'origin/develop' into feature/view-case-…
devin-ai-integration[bot] Oct 4, 2026
096a0ed
Merge remote-tracking branch 'origin/develop' into feature/view-case-…
devin-ai-integration[bot] Oct 5, 2026
5ab287d
Merge remote-tracking branch 'origin/develop' into feature/view-case-…
devin-ai-integration[bot] Oct 5, 2026
657f9e9
test(wasm): check sysml-engine dependencies and bound its size growth
devin-ai-integration[bot] Oct 5, 2026
5a45a8f
Merge remote-tracking branch 'origin/develop' into feature/view-case-…
devin-ai-integration[bot] Oct 5, 2026
e4f5741
test(wasm): allow the engine's queryexec trace dependency
devin-ai-integration[bot] Oct 5, 2026
bc5192e
Merge remote-tracking branch 'origin/develop' into feature/view-case-…
devin-ai-integration[bot] Oct 5, 2026
9226756
docs: restore the grammar reference link in the README
devin-ai-integration[bot] Oct 5, 2026
3f1bc42
Merge remote-tracking branch 'origin/develop' into feature/view-case-…
devin-ai-integration[bot] Oct 5, 2026
2c270fd
docs: count OpenSysMLRenderings among the supplied pilot libraries
devin-ai-integration[bot] Oct 5, 2026
551e783
Merge remote-tracking branch 'origin/develop' into feature/view-case-…
devin-ai-integration[bot] Oct 6, 2026
5bb03cc
test(grpc): cover case and mixed views in RenderView
devin-ai-integration[bot] Oct 6, 2026
00dd4eb
Merge remote-tracking branch 'origin/develop' into feature/view-case-…
devin-ai-integration[bot] Oct 6, 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
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -321,7 +321,7 @@ The project is under active development, with the core infrastructure operationa
| Model save to notation (`%save model.sysml`, `sysml -convert sysml`) | ✅ Complete — writes the source through the formatter, so comments and spacing survive |
| SysML ↔ RDF Turtle conversion (`%save model.ttl`, `sysml -convert ttl`) | 🧪 **Experimental** — packages, definitions, usages, ports, connections, values, documentation, and the nodes an action or state body states (every one of the 346 models under `examples/` converts and round-trips; what is not mapped is refused with the construct named), but the vocabulary may change without a compatibility path. Every run says so; see [the RDF mapping's status](docs/reference/rdf-mapping.md#status-experimental) and [worked example](examples/rdf-interop-demo.sysml) |
| SysML v1 → v2 migration (`sysml Model.xmi -migrate sysml`, `.uml` and `.mdzip` too; migrated, not converted — `-convert` refuses a v1 model) | 🧪 **Experimental** — OMG UML 2.5 XMI with the SysML 1.x profile is read and written as v2 notation or RDF: packages, blocks, value types, properties, ports and connectors, requirements with satisfy/verify/derive, constraint blocks, instances and allocations, with a per-element migration report (mapped, approximated, unmapped, skipped). Behaviors, operations and units are not migrated yet, and the mapping may change without a compatibility path. Every run says so; see [the migration's status](docs/reference/sysml-v1-migration.md#status-experimental) |
| View rendering (`%render <view>`, `sysml -render`) | ✅ Complete for the kinds produced — containment tree, interconnection diagram, state machine, action flow, sequence diagram and table, as indented text or in the kind's machine-readable form (Mermaid, Markdown, Graphviz DOT with `-render-form dot`, PlantUML in the Pilot visualizer's B&W style with `-render-form plantuml`, [D2](https://d2lang.com) with `-render-form d2`). State and action renderings read the graph the runtime executes; the notation itself is tool-defined ([SysML v2 §10.2](docs/project/spec-compliance.md)) |
| View rendering (`%render <view>`, `sysml -render`) | ✅ Complete for the eight produced kinds — containment tree, interconnection diagram, state machine, action flow, case diagram, mixed canvas, sequence diagram and table — as indented text or in each kind's supported machine-readable form (Mermaid, Markdown, Graphviz DOT with `-render-form dot`, PlantUML in the Pilot visualizer's B&W style with `-render-form plantuml`, [D2](https://d2lang.com) with `-render-form d2`; D2 refuses case and mixed with a typed error). State and action renderings read the graph the runtime executes; the notation itself is tool-defined ([SysML v2 §10.2](docs/project/spec-compliance.md)) |
| Constraint solving (`%check`, `%explain`, `%solve`, `%configure`, `%optimize`) | 🧪 **Experimental** — an external SMT-LIB 2 solver decides whether conditions *can* be satisfied, explains an `unsat` with a minimal unsat core, synthesises satisfying values, enumerates the variant selections a model permits and optimizes an `analysis def`'s objectives (optimization needs z3, which implements it). The solver is optional and discovered on `PATH` or through `OPENSYSML_SMT`; a build with none reports that rather than a verdict — see [installing a solver](docs/guide/01-install.md#installing-a-solver-optional) |
| Source-preserving model edits (`ApplyEdits`, `model.edit()`) | ✅ Complete for four operations — set a feature's value, rename a declaration, add a member, and delete a declaration — rewriting the bytes of the model's own source so every untouched byte is identical. A rename rewrites the references to the renamed element too, and a non-cascade deletion of a referenced element is refused rather than approximated |
| Standard library bundling | ✅ Complete |
Expand Down Expand Up @@ -352,7 +352,7 @@ What these numbers cannot show: the OMG corpora are demonstrations rather than a

**Current commit:** All tests pass (`go test -race ./...`), builds clean (`go build ./...`).
**Test coverage:** top-level `Test` functions (counted from the `_test.go` files, as `go test ./...` runs them) covering parsers, semantics, runtime (actions, states, instances, operators, validation), behind golden ASTs, negatives, execution conformance cases, golden traces, runtime robustness cases and gRPC conformance and robustness cases. The figures are counted from the tree when the documentation site is built into the test inventory of [spec compliance](docs/project/spec-compliance.md), never committed, so a branch adding a test does not rewrite this page. A test skips only for want of something the run did not provide, and says what: the held-image round trip declines a conformance case that creates no instance, a few gate on a PDF or Mermaid toolchain, a pinned pilot artifact, the PSSM suite, a locale, a case-insensitive filesystem or a live Flexo stack, and the OMG corpus gates skip until the corpora are downloaded unless asked to fail.
**Parser coverage:** 105/105 bundled library files parse cleanly — the 94 official SysML v2 standard library files and the non-normative `OpenSysML Libraries/OpenSysMLMathFunctions.kerml`, `OpenSysML Libraries/DocumentQueries.sysml`, `OpenSysML Libraries/IdentityMetadata.sysml`, `OpenSysML Libraries/DiagramLayout.sysml`, `OpenSysML Libraries/OOSEM.sysml`, `OpenSysML Libraries/MOSA.sysml`, `OpenSysML Libraries/StateSpaceIntegration.sysml`, `OpenSysML Libraries/Stochastic.sysml`, `OpenSysML Libraries/RandomFunctions.kerml`, `OpenSysML Libraries/Simulation.sysml` and `OpenSysML Libraries/MigrationMetadata.sysml` extensions. Conformance verified by [stdlib_conformance_test.go](internal/workspace/libs/stdlib_conformance_test.go). Grammar reference: [OMG Xtext grammar](https://github.com/Systems-Modeling/SysML-v2-Pilot-Implementation/tree/master/org.omg.kerml.xtext/src/org/omg/kerml/xtext).
**Parser coverage:** 109/109 bundled library files parse cleanly — the 94 official SysML v2 standard library files and the 15 non-normative OpenSysML extensions: `OpenSysML Libraries/AnalysisRecords.sysml`, `DiagramLayout.sysml`, `DocumentQueries.sysml`, `IdentityMetadata.sysml`, `MOSA.sysml`, `MigrationMetadata.sysml`, `OOSEM.sysml`, `OpenSysMLMathFunctions.kerml`, `OpenSysMLRenderings.sysml`, `RandomFunctions.kerml`, `Simulation.sysml`, `StateMachines.sysml`, `StateSpaceIntegration.sysml`, `Stochastic.sysml` and `SysMLValidation.sysml`. Conformance verified by [stdlib_conformance_test.go](internal/workspace/libs/stdlib_conformance_test.go). Grammar reference: [OMG Xtext grammar](https://github.com/Systems-Modeling/SysML-v2-Pilot-Implementation/tree/master/org.omg.kerml.xtext/src/org/omg/kerml/xtext).
**Behavioral execution:** Calc/constraint/requirement/satisfy functional. Action/state executors handle nested invocation, control flow keywords, loop and conditional statements and the send statement (<!-- doc-counts:begin conformance-passing -->every conformance case passing<!-- doc-counts:end conformance-passing -->). Coverage is self-assessed against the specification text and the normative library: the pinned OMG pilot implementation evaluates expressions but does not execute actions or state machines headlessly, so no external implementation currently adjudicates these rows. See [spec compliance](docs/project/spec-compliance.md).
**Reference differential:** 386 files compared diagnostic-by-diagnostic against the pinned OMG pilot implementation (`2026-08`), 357 in full agreement; every divergence is enumerated and adjudicated in [the differential](docs/project/pilot-differential.md), reproducible with `go run -C tools ./cmd/pilot-diff`.
**Rejection oracle:** the reverse direction — do we reject what the reference rejects? 312 hand-written invalid models validated by both implementations, 303 rejected by both, 0 the pinned pilot rejects and we accept; the remainder only we reject — the control-node succession rules the pinned pilot leaves unimplemented and a non-Boolean succession guard it accepts once the standard library types it — and every permissiveness gap is enumerated with a reproducer and likely root cause in [the rejection oracle](docs/project/pilot-rejection.md), reproducible with `go run -C tools ./cmd/pilot-reject`. We wrote every case, so the count measures our coverage of the rejection surface, not our conformance — a sample, not a proof.
Expand Down
1 change: 1 addition & 0 deletions changes/unreleased/case-mixed-rendering.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- **Add case and mixed diagrams.** Case views show use, analysis and verification cases with their actors, subjects, objectives and inclusion relationships; mixed views combine structure, state, action and case content on one canvas.
1 change: 1 addition & 0 deletions changes/unreleased/case-mixed-rendering.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- **Keep case-specific rendering behavior isolated.** Case and mixed views use the `OpenSysMLRenderings` library package, read objective documentation without mutating shared semantic models, and limit their specialized shapes and labels to the new kinds.
1 change: 1 addition & 0 deletions changes/unreleased/plantuml-case-mixed-links.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- **Make case and mixed PlantUML links clickable in SVG.** Mixed control circles and each non-empty objective-note line retain source hyperlinks.
1 change: 1 addition & 0 deletions changes/unreleased/wasm-engine-dependency-check.changed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- **The WebAssembly gate checks `sysml-engine`'s dependencies directly.** Both the `js` and `wasip1` builds fail on protobuf, Connect, the gRPC and stdio transports, the analysis framework and its engines, the document backends, the workspace pipeline and the converters. The gzipped size budget rises to 7,700,000 bytes and now bounds growth only, so view-rendering growth no longer reads as a forbidden dependency.
2 changes: 1 addition & 1 deletion cmd/sysml/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -406,7 +406,7 @@ func runCLI() int {
return 2
}
if renderPorts != "" && renderView == "" && renderAllDir == "" {
fmt.Fprintln(os.Stderr, "sysml: -render-ports is how much of a part's ports -render or -render-all draws on an interconnection; name the view to render with -render or a directory with -render-all")
fmt.Fprintln(os.Stderr, "sysml: -render-ports is how much of a part's ports -render or -render-all draws on an interconnection or mixed rendering; name the view to render with -render or a directory with -render-all")
return 2
}
if renderUnplaced != "" && renderView == "" && renderAllDir == "" && renderDoc == "" && renderDocsDir == "" {
Expand Down
67 changes: 66 additions & 1 deletion cmd/sysml/render_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,14 @@ import (
// renderModel declares a view stating no rendering, one exposing nothing, and a
// part def to ask for by mistake.
const renderModel = `package Demo {
private import OpenSysMLRenderings::*;
part def Vehicle { part wheel : Wheel; }
part def Wheel;
part def Person;
use case def Mission { actor pilot : Person; }
view overview { expose Demo::Vehicle; }
view cases { expose Demo::Mission; render asCaseDiagram; }
view mixed { expose Demo; render asMixedDiagram; }
view parts { expose Demo::Vehicle; render Views::asElementTable; }
view empty;
}
Expand Down Expand Up @@ -46,6 +51,29 @@ func TestRenderWritesTheArtifactOnStdout(t *testing.T) {
}
}

func TestRenderCaseAndMixedViewsAndPseudoViews(t *testing.T) {
binary := buildCLI(t)
cases := []struct {
view string
flow string
}{
{"Demo::cases", "flowchart LR"},
{"#case", "flowchart LR"},
{"#case:Demo::Mission", "flowchart LR"},
{"#mixed", "flowchart TD"},
{"#mixed:Demo::Mission", "flowchart TD"},
}
for _, tc := range cases {
got := runStreams(t, binary, renderModel, "-render", tc.view)
if got.status != exitHolds {
t.Fatalf("-render %s: exit status = %d\n%s", tc.view, got.status, got.output())
}
if !strings.Contains(got.stdout, tc.flow) {
t.Errorf("-render %s lacks %q:\n%s", tc.view, tc.flow, got.stdout)
}
}
}

func TestRenderTextFormAndOutputFile(t *testing.T) {
binary := buildCLI(t)

Expand Down Expand Up @@ -312,6 +340,43 @@ func TestRenderD2Form(t *testing.T) {
if table.status != exitUnevaluable || !strings.Contains(table.stderr, "table rendering is not written as d2; ask for text, markdown, csv or tsv") {
t.Errorf("D2 of a table = %d\n%s", table.status, table.output())
}
for _, tc := range []struct {
name, message string
}{
{"Demo::cases", "case rendering is not written as d2; ask for text, mermaid, dot or plantuml"},
{"Demo::mixed", "mixed rendering is not written as d2; ask for text, mermaid, dot or plantuml"},
} {
got := runStreams(t, binary, renderModel, "-render", tc.name, "-render-form", "d2")
if got.status != exitUnevaluable || got.stdout != "" || !strings.Contains(got.stderr, tc.message) {
t.Errorf("D2 of %s = %d\n%s", tc.name, got.status, got.output())
}
}

dir = filepath.Join(t.TempDir(), "case-mixed")
got = runStreams(t, binary, renderModel, "-render-all", dir, "-render-form", "d2")
if got.status != exitHolds {
t.Fatalf("-render-all as d2: exit status = %d\n%s", got.status, got.output())
}
files, err := os.ReadDir(dir)
if err != nil {
t.Fatal(err)
}
wantFiles := []string{"Demo.empty.d2", "Demo.overview.d2"}
if len(files) != len(wantFiles) {
t.Errorf("-render-all as d2 wrote %v, want %v", files, wantFiles)
} else {
for i, name := range wantFiles {
if files[i].Name() != name {
t.Errorf("-render-all as d2 wrote %v, want %v", files, wantFiles)
break
}
}
}
for _, name := range []string{"Demo::cases", "Demo::mixed"} {
if !strings.Contains(got.stderr, name+": skipped:") || !strings.Contains(got.stderr, "not written as d2") {
t.Errorf("-render-all did not refuse %s as d2:\n%s", name, got.stderr)
}
}
}

// -render-palette fills the DOT and Mermaid forms' nodes from a named palette,
Expand Down Expand Up @@ -1049,7 +1114,7 @@ const portedModel = `package Demo {
}
`

// -render-ports chooses how much of a part's ports an interconnection draws:
// -render-ports chooses how much of a part's ports an interconnection or mixed rendering draws:
// minimal, the default, the ports a connector ends at, named alone; full, every
// port, typed. A name that is neither is refused with the two there are, and
// the flag without something to render likewise.
Expand Down
Loading
Loading