diff --git a/.agents/skills/testing-pilot-corpora-gate/SKILL.md b/.agents/skills/testing-pilot-corpora-gate/SKILL.md index 4491ea0ead..f9ee0145ce 100644 --- a/.agents/skills/testing-pilot-corpora-gate/SKILL.md +++ b/.agents/skills/testing-pilot-corpora-gate/SKILL.md @@ -183,8 +183,8 @@ gate's own helpers are package-private but reusable (`pilotCorporaGate.files(t)` `actionlint`, `shellcheck`, `python3 scripts/check-doc-links.py`, `gofmt`, `go vet`, `go run -C tools ./cmd/pilot-diff` (validators pre-downloaded; ~4min, prints e.g. -the headline the committed baseline holds — `388 file(s), 359 fully agreeing; 45 agreed -diagnostic(s), 44 only ours, 85 only the pilot's` at the `2026-08` pin, so read it from +the headline the committed baseline holds — `389 file(s), 359 fully agreeing; 45 agreed +diagnostic(s), 44 only ours, 92 only the pilot's` at the `2026-08` pin, so read it from `docs/project/pilot-differential-baseline.json` rather than from this line) and `make lint` (staticcheck+gosec, ~2min) all work. There is **no** `yamllint` and **no** `circleci` CLI, so `.circleci/config.yml` can only be parsed as YAML, not schema-validated — say so diff --git a/.agents/skills/testing-pilot-differential/SKILL.md b/.agents/skills/testing-pilot-differential/SKILL.md index 13b930788a..673089e10e 100644 --- a/.agents/skills/testing-pilot-differential/SKILL.md +++ b/.agents/skills/testing-pilot-differential/SKILL.md @@ -24,8 +24,8 @@ GNU-format diagnostics **relative to `--root`**. Consequences for testing: - Measured at the `2026-08` pin after the view concern framing round retired the six view-body `frame` `syntax` rows (bare parameters already at their effective range `[0..*]`, so the adjudicated `Behaviors.kerml:14` multiplicity warning is gone while the `[1]` `RocketEquation` - inputs keep its warning at `delta-v-budget.sysml:93`): `388 file(s), 359 fully agreeing; 45 agreed, - 44 only ours, 85 only the pilot's`, JSON totals `openSysMLDiagnostics 90 / pilotDiagnostics 131 / + inputs keep its warning at `delta-v-budget.sysml:93`): `389 file(s), 359 fully agreeing; 45 agreed, + 44 only ours, 92 only the pilot's`, JSON totals `openSysMLDiagnostics 90 / pilotDiagnostics 138 / severityMismatch 1`; the two only-ours rows the multiplicity rule added are the expected `action-step-multiplicity-not-fixed` warnings on `training/18. Action Performance/Action Performance Example.sysml:10` and `pilot-examples/Camera Example/Camera.sysml:4`. ~2 min wall, byte-identical across runs *and* after a from-scratch rebuild of @@ -148,8 +148,8 @@ warning (the `[1]` `RocketEquation` inputs still produce the warning at `delta-v-budget.sysml:93`), is current: the action-step multiplicity rule adds the two expected `action-step-multiplicity-not-fixed` warnings on `takePhoto[*]` in the training corpus and `takePicture[*]` in `Camera Example/Camera.sysml`, and the view concern framing round retired the -six view-body `frame` `syntax` rows on `examples`; a live run gives `388 file(s), 359 fully -agreeing; 45 agreed, 44 only ours, 85 only the pilot's`, byte-identical to the committed baseline, and +six view-body `frame` `syntax` rows on `examples`; a live run gives `389 file(s), 359 fully +agreeing; 45 agreed, 44 only ours, 92 only the pilot's`, byte-identical to the committed baseline, and `docs/project/pilot-differential.md`'s "Results" table matches. The prior rebaseline, when the Legend of the Red Dragon example left for its own repository, gave `380 file(s), 344 fully agreeing; 38 agreed, 42 only ours, 1614 only the pilot's`. diff --git a/.agents/skills/testing-pilot-execution-referee/SKILL.md b/.agents/skills/testing-pilot-execution-referee/SKILL.md index 97288489a0..e0446dbd79 100644 --- a/.agents/skills/testing-pilot-execution-referee/SKILL.md +++ b/.agents/skills/testing-pilot-execution-referee/SKILL.md @@ -150,8 +150,8 @@ pilot answers the representation's own. See `pilot-exec-diff: :: model no/such/model.sysml: stat : no such file or directory`. - **Additivity.** `go run -C tools ./cmd/pilot-diff` must still print the headline the - committed baseline holds (`388 file(s), 359 fully agreeing; 45 agreed - diagnostic(s), 44 only ours, 85 only the pilot's` at the `2026-08` pin — read it from the baseline JSON, not from this line, since each + committed baseline holds (`389 file(s), 359 fully agreeing; 45 agreed + diagnostic(s), 44 only ours, 92 only the pilot's` at the `2026-08` pin — read it from the baseline JSON, not from this line, since each fix round moves it) and `jq -S` diff clean against `docs/project/pilot-differential-baseline.json`; `git status --porcelain` empty at the end. diff --git a/.agents/skills/testing-pilot-xpect/SKILL.md b/.agents/skills/testing-pilot-xpect/SKILL.md index 97b0630f18..60c0cd9db6 100644 --- a/.agents/skills/testing-pilot-xpect/SKILL.md +++ b/.agents/skills/testing-pilot-xpect/SKILL.md @@ -423,8 +423,8 @@ census in `w5c_census_test.go` is live two ways: perturb one pinned triple (e.g. ## Regression neighbour `go run -C tools ./cmd/pilot-diff` (~1m12s) must still print the headline the *committed* baseline holds — -at the `2026-08` pin that is `388 file(s), 359 fully agreeing; 45 agreed diagnostic(s), 44 -only ours, 85 only the pilot's`. Read the number out of +at the `2026-08` pin that is `389 file(s), 359 fully agreeing; 45 agreed diagnostic(s), 44 +only ours, 92 only the pilot's`. Read the number out of `docs/project/pilot-differential-baseline.json` rather than trusting this line, since a landing fix round moves it. When the baseline is itself stale (it was at `19a3ce03`, holding 273 / 281 / 317), a failing `cmp` against it is *not* evidence of an Xpect regression — compare the summary line, and see diff --git a/README.md b/README.md index 0a759c45b7..4e0c44b7cb 100644 --- a/README.md +++ b/README.md @@ -338,11 +338,11 @@ The project is under active development, with the core infrastructure operationa **Measured against the pinned reference** (`PILOT_TAG=2026-08`, artifact `0.62.0`). Every number below is generated by `make docs-counts` from the committed baselines and gated; none of them is typed in by hand. -- **Corpus agreement:** 359 of 388 files agree diagnostic-by-diagnostic; 44 diagnostics are ours alone and 85 the reference's alone, and the first number must be read by root: the aggregate includes candidate conformance differences, intentional execution-scope warnings on reference corpora and diagnostics from our own examples ([differential](docs/project/pilot-differential.md), `go run -C tools ./cmd/pilot-diff`). +- **Corpus agreement:** 359 of 389 files agree diagnostic-by-diagnostic; 44 diagnostics are ours alone and 92 the reference's alone, and the first number must be read by root: the aggregate includes candidate conformance differences, intentional execution-scope warnings on reference corpora and diagnostics from our own examples ([differential](docs/project/pilot-differential.md), `go run -C tools ./cmd/pilot-diff`). - **Declared-diagnostic silence:** of the 512 declared `errors` rows in the reference's own Xpect suites, we report nothing for 0. 245 we report word-for-word; 248 wording-only and 7 location-only differences are agreement in substance and are not counted as gaps; 0 more we report as a warning and 2 elsewhere in the file ([Xpect oracle](docs/project/pilot-xpect.md), `go run -C tools ./cmd/pilot-xpect`). - **Scope agreement:** 230 of 230 declared scope assertions match exactly (same source). - **Permissiveness gaps:** of 313 invalid models we wrote ourselves, the reference rejects 4 that we accept by default, and 300 both reject; 4 further cases agree only when we are asked strictly. We authored every one of these cases ourselves, so the denominator measures the reach of our own corpus and not our conformance; agreement reached only under an opt-in strict mode is weaker evidence than agreement by default ([rejection oracle](docs/project/pilot-rejection.md), `go run -C tools ./cmd/pilot-reject`). -- **Declared errata:** the registry declares 12 defect(s) in the published reference material — 4 with a specification-derived correction, 8 documented without one, since no intended reading can be inferred ([OMG issues](docs/project/omg-issues.md), `tools/oracle/errata`). Every figure above is as published and stays the conformance statement; running the same oracles over the corrected text instead reports 360 of 388 files agreeing, 43 diagnostics ours alone and 85 the reference's alone, 0 declared rows we are silent on, and 0 of 313 authored cases the reference alone rejects. The corrected figures are diagnostic only: an erratum never reclassifies a divergence category, and the published corpus is never edited. +- **Declared errata:** the registry declares 12 defect(s) in the published reference material — 4 with a specification-derived correction, 8 documented without one, since no intended reading can be inferred ([OMG issues](docs/project/omg-issues.md), `tools/oracle/errata`). Every figure above is as published and stays the conformance statement; running the same oracles over the corrected text instead reports 360 of 389 files agreeing, 43 diagnostics ours alone and 92 the reference's alone, 0 declared rows we are silent on, and 0 of 313 authored cases the reference alone rejects. The corrected figures are diagnostic only: an erratum never reclassifies a divergence category, and the published corpus is never edited. - **Self-assessed surface:** the action, state-machine and classifier-behavior rows have no external referee at all — the four refereed figures above cannot see them, because the pinned artifact evaluates expressions but executes neither actions nor state machines. [Spec compliance](docs/project/spec-compliance.md) counts them. What these numbers cannot show: the OMG corpora are demonstrations rather than an official conformance suite; the differential is one-directional, comparing the diagnostics the two implementations report on the same files; the Xpect suites are the pilot authors' test intent rather than a certification oracle; and none of these is a percentage of the specification — no global compliance figure is claimed anywhere. @@ -354,7 +354,7 @@ What these numbers cannot show: the OMG corpora are demonstrations rather than a **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:** 110/110 bundled library files parse cleanly — the 94 official SysML v2 standard library files and the 16 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`, `StateActivity.kerml`, `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 (every conformance case 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:** 388 files compared diagnostic-by-diagnostic against the pinned OMG pilot implementation (`2026-08`), 359 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`. +**Reference differential:** 389 files compared diagnostic-by-diagnostic against the pinned OMG pilot implementation (`2026-08`), 359 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? 313 hand-written invalid models validated by both implementations, 304 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. **Training examples:** 100/100 files report no semantic errors, gated by `tests/corpus/testdata/training_examples_expected.txt`; the gate does not count execution-scope warnings. Download with `./scripts/download-training-examples.sh` (from the [OMG training directory](https://github.com/Systems-Modeling/SysML-v2-Pilot-Implementation/tree/master/sysml/src/training)). See [training examples](docs/project/training-examples.md) for analysis. **Semantic layer:** a complete implementation of runtime operators, feature chains and validation rules. See [examples/semantic-layer/](examples/semantic-layer/) for a full demonstration. diff --git a/changes/unreleased/gridview-relationship-matrix.added.md b/changes/unreleased/gridview-relationship-matrix.added.md new file mode 100644 index 0000000000..220492a70a --- /dev/null +++ b/changes/unreleased/gridview-relationship-matrix.added.md @@ -0,0 +1 @@ +- **Render filtered `GridView`s as relationship matrices.** Matrix views show satisfy, verify, allocation, connection, derivation, refinement and dependency relationships in text, Markdown, CSV and TSV, including unnamed exposed members; Mermaid, DOT, PlantUML and D2 are refused as graph-only forms. Ordinary tables and other renderings keep their existing output. diff --git a/cmd/sysml/render_test.go b/cmd/sysml/render_test.go index 71290b573b..45cb8f3ca6 100644 --- a/cmd/sysml/render_test.go +++ b/cmd/sysml/render_test.go @@ -121,6 +121,67 @@ func TestRenderOfATabularView(t *testing.T) { } } +const matrixModel = `package Example { + private import StandardViewDefinitions::*; + requirement def Requirement; + requirement r : Requirement; + part def Vehicle; + part vehicle : Vehicle { + satisfy r; + } + view def RelationshipMatrixView :> GridView { + filter @SysML::SatisfyRequirementUsage; + } + view relationshipMatrix : RelationshipMatrixView { + expose vehicle::**; + } +} +` + +func TestRenderOfAGridViewRelationshipMatrix(t *testing.T) { + binary := buildCLI(t) + + for _, tc := range []struct { + form string + want string + }{ + {"text", "Example::relationshipMatrix - matrix rendering"}, + {"markdown", "| Source / Target | Example::r |"}, + {"csv", "Source / Target,Example::r\nExample::vehicle,satisfy\n"}, + {"tsv", "Source / Target\tExample::r\nExample::vehicle\tsatisfy\n"}, + } { + t.Run(tc.form, func(t *testing.T) { + got := runStreams(t, binary, matrixModel, "-render", "Example::relationshipMatrix", "-render-form", tc.form) + if got.status != exitHolds { + t.Fatalf("exit status = %d, want %d\n%s", got.status, exitHolds, got.output()) + } + if !strings.Contains(got.stdout, tc.want) { + t.Errorf("stdout is missing %q:\n%s", tc.want, got.stdout) + } + }) + } + + for _, form := range []string{"mermaid", "dot", "plantuml", "d2"} { + got := runStreams(t, binary, matrixModel, "-render", "Example::relationshipMatrix", "-render-form", form) + if got.status != exitUnevaluable || !strings.Contains(got.stderr, "matrix rendering is not written as "+form) { + t.Errorf("matrix as %s = %d\n%s", form, got.status, got.output()) + } + } + + dir := filepath.Join(t.TempDir(), "matrix") + all := runStreams(t, binary, matrixModel, "-render-all", dir) + if all.status != exitHolds { + t.Fatalf("-render-all exit status = %d, want %d\n%s", all.status, exitHolds, all.output()) + } + written, err := os.ReadFile(filepath.Join(dir, "Example.relationshipMatrix.md")) // #nosec G304 -- the test wrote this path. + if err != nil { + t.Fatalf("read -render-all matrix: %v", err) + } + if !strings.Contains(string(written), "| Source / Target | Example::r |") { + t.Errorf("-render-all matrix is missing its table:\n%s", written) + } +} + // A table is written as CSV or TSV when asked, on stdout or into a file, and a // graph-shaped view is refused either form. func TestRenderOfATableAsDelimitedValues(t *testing.T) { @@ -613,6 +674,8 @@ func TestDefaultRenderFormFollowsTheDestination(t *testing.T) { {"a table at a terminal", view.KindTable, "", true, view.FormText}, {"a table into a pipe", view.KindTable, "", false, view.FormMarkdown}, {"a table into a file", view.KindTable, "table.md", true, view.FormMarkdown}, + {"a matrix into a pipe", view.KindMatrix, "", false, view.FormMarkdown}, + {"a matrix into a file", view.KindMatrix, "matrix.md", true, view.FormMarkdown}, {"a tree at a terminal", view.KindTree, "", true, view.FormText}, {"a tree into a pipe", view.KindTree, "", false, view.FormMermaid}, } diff --git a/cmd/sysml/usage.go b/cmd/sysml/usage.go index b41d9b4733..36a9161aeb 100644 --- a/cmd/sysml/usage.go +++ b/cmd/sysml/usage.go @@ -433,11 +433,13 @@ func doc() usage.Doc { "(/, \\, :, ., %, control characters, what Windows reserves) as %XX; " + "a name past 255 bytes is cut and tagged ~ and a hash of the whole. " + "A graph-shaped rendering defaults to Mermaid. DOT writes tree, " + - "interconnection, state and action renderings, plus case and mixed; " + - "PlantUML writes those kinds and sequence. D2 writes tree, " + - "interconnection, state, action and sequence renderings, " + - "while case and mixed renderings refuse D2 with a typed error. Neither " + - "Graphviz, PlantUML nor D2 is needed to write them. A table is written as a " + + "interconnection, state, action, case, mixed, requirement, definition and " + + "package renderings; PlantUML writes those kinds and sequence. D2 writes " + + "tree, interconnection, state, action, sequence, requirement, definition " + + "and package renderings; case and mixed renderings refuse D2 with a typed " + + "error. Table and matrix renderings refuse graph forms with a typed error. Neither " + + "Graphviz, PlantUML nor D2 is needed to write graph forms. A table or matrix is " + + "written as a " + "Markdown table by default, and as comma- or tab-separated values with " + "-render-form csv or tsv: a header record of the columns, then a record " + "per row, quoted as RFC 4180 quotes a field. Graph forms are drawn in the " + @@ -493,10 +495,10 @@ func doc() usage.Doc { "is installed and as a dot fence otherwise, and every other graph-shaped " + "view is Mermaid source; with Graphviz absent a positioned view falls " + "back to Mermaid under a notice saying so. -diagram-form mermaid, dot, " + - "plantuml or d2 writes each graph-shaped diagram in that form where the " + - "kind supports it, in Markdown and HTML alike; D2 writes tree, " + - "interconnection, state, action and sequence, and case or mixed diagrams " + - "are refused. A table-kind view stays a table. Neither " + + "plantuml or d2 writes every graph-shaped one in that form instead, in " + + "Markdown and HTML alike, while a table or matrix view stays a table. D2 " + + "writes tree, interconnection, state, action, sequence, requirement, " + + "definition and package renderings, and refuses case and mixed diagrams. Neither " + "Graphviz, PlantUML nor D2 is needed to write a fence.", "-doc-form html writes semantic HTML instead, carrying each element's " + "identity and kind, styled by a stylesheet in a cascade layer your " + @@ -676,9 +678,9 @@ func registerFlags(fs *flag.FlagSet) { fs.StringVar(&outputPath, "o", "", outputUsage()) fs.StringVar(&modelChecks.compare, "compare-results", "", "Run every configuration this -migration-results file indexes — or those -action names — with its recorded runs and duration mode, or the -runs and -draws given, seeded from -seed, and table the tool's and OpenSysML's min, mean, p50, p90 and max of each observable with their relative difference") - fs.StringVar(&renderView, "render", "", "Render this view of the model instead of running it, in the form its render member states; # renders every file loaded and #: one element, kind being tree, interconnection, state, action, case, mixed, sequence or table, without a declared view") + fs.StringVar(&renderView, "render", "", "Render this view of the model instead of running it, in the form its render member states; # renders every file loaded and #: one element, kind being tree, interconnection, state, action, case, mixed, sequence, table or matrix, without a declared view") fs.StringVar(&renderAllDir, "render-all", "", "Render every declared view into this directory") - fs.StringVar(&renderForm, "render-form", "", "Form -render or -render-all writes: text, mermaid, markdown, dot, plantuml, d2, csv or tsv (csv and tsv for a table); D2 writes tree, interconnection, state, action and sequence renderings, not case or mixed; default from the destination for -render, each kind's machine form for -render-all") + fs.StringVar(&renderForm, "render-form", "", "Form -render or -render-all writes: text, mermaid, markdown, dot, plantuml, d2, csv or tsv (csv and tsv for a table or matrix); D2 writes tree, interconnection, state, action, sequence, requirement, definition and package renderings, not case or mixed; default from the destination for -render, each kind's machine form for -render-all") fs.StringVar(&renderPalette, "render-palette", "", "Palette the dot, mermaid, plantuml or d2 form fills nodes from, by keyword family: okabe-ito, tol-bright, tol-muted, tol-light, brewer-set2, brewer-dark2, viridis or cividis; default black and white") fs.StringVar(&renderLink, "render-link", "", "Link template for rendered elements: {file} is the path as loaded; use absolute paths for vscode:// or file:// links. Placeholders: {file}, {line}, {col}, {qname}, {id}") fs.StringVar(&renderStyle, "render-style", "", "Drawing style of the dot or mermaid form: pilot (default), the Pilot visualizer's black and white, or cameo, the look of Cameo Systems Modeler; applies to -render, -render-all and document diagrams") @@ -689,7 +691,7 @@ func registerFlags(fs *flag.FlagSet) { fs.StringVar(&renderDoc, "render-document", "", "Compile this document definition, run its queries and write the rendered document") fs.StringVar(&renderDocsDir, "render-documents", "", "Render every document definition, linked to one another, into this directory; a document that cannot be rendered gets a page stating why and the run exits 3") fs.StringVar(&docForm, "doc-form", "", docFormUsage()) - fs.StringVar(&diagramForm, "diagram-form", "", "Form the documents' graph-shaped diagrams are written in: mermaid, dot, plantuml or d2; D2 writes tree, interconnection, state, action and sequence renderings, not case or mixed; unset, a positioned view is dot and any other mermaid; a table-kind view is a table either way") + fs.StringVar(&diagramForm, "diagram-form", "", "Form the documents' graph-shaped diagrams are written in: mermaid, dot, plantuml or d2; D2 writes tree, interconnection, state, action, sequence, requirement, definition and package renderings, not case or mixed; unset, a positioned view is dot and any other mermaid; a table or matrix view is a table either way") fs.BoolVar(&docNumberFigures, "doc-number-figures", false, docNumberFiguresUsage()) fs.BoolVar(&debugMode, "debug", false, "Report every diagnostic over the whole session buffer, with the pass that produced it") diff --git a/docs/internals/architecture.md b/docs/internals/architecture.md index b80bb30763..449cdbc9d1 100644 --- a/docs/internals/architecture.md +++ b/docs/internals/architecture.md @@ -841,11 +841,11 @@ Every behavioral feature must have: **Measured against the pinned reference** (`PILOT_TAG=2026-08`, artifact `0.62.0`). Every number below is generated by `make docs-counts` from the committed baselines and gated; none of them is typed in by hand. -- **Corpus agreement:** 359 of 388 files agree diagnostic-by-diagnostic; 44 diagnostics are ours alone and 85 the reference's alone, and the first number must be read by root: the aggregate includes candidate conformance differences, intentional execution-scope warnings on reference corpora and diagnostics from our own examples ([differential](../project/pilot-differential.md), `go run -C tools ./cmd/pilot-diff`). +- **Corpus agreement:** 359 of 389 files agree diagnostic-by-diagnostic; 44 diagnostics are ours alone and 92 the reference's alone, and the first number must be read by root: the aggregate includes candidate conformance differences, intentional execution-scope warnings on reference corpora and diagnostics from our own examples ([differential](../project/pilot-differential.md), `go run -C tools ./cmd/pilot-diff`). - **Declared-diagnostic silence:** of the 512 declared `errors` rows in the reference's own Xpect suites, we report nothing for 0. 245 we report word-for-word; 248 wording-only and 7 location-only differences are agreement in substance and are not counted as gaps; 0 more we report as a warning and 2 elsewhere in the file ([Xpect oracle](../project/pilot-xpect.md), `go run -C tools ./cmd/pilot-xpect`). - **Scope agreement:** 230 of 230 declared scope assertions match exactly (same source). - **Permissiveness gaps:** of 313 invalid models we wrote ourselves, the reference rejects 4 that we accept by default, and 300 both reject; 4 further cases agree only when we are asked strictly. We authored every one of these cases ourselves, so the denominator measures the reach of our own corpus and not our conformance; agreement reached only under an opt-in strict mode is weaker evidence than agreement by default ([rejection oracle](../project/pilot-rejection.md), `go run -C tools ./cmd/pilot-reject`). -- **Declared errata:** the registry declares 12 defect(s) in the published reference material — 4 with a specification-derived correction, 8 documented without one, since no intended reading can be inferred ([OMG issues](../project/omg-issues.md), `tools/oracle/errata`). Every figure above is as published and stays the conformance statement; running the same oracles over the corrected text instead reports 360 of 388 files agreeing, 43 diagnostics ours alone and 85 the reference's alone, 0 declared rows we are silent on, and 0 of 313 authored cases the reference alone rejects. The corrected figures are diagnostic only: an erratum never reclassifies a divergence category, and the published corpus is never edited. +- **Declared errata:** the registry declares 12 defect(s) in the published reference material — 4 with a specification-derived correction, 8 documented without one, since no intended reading can be inferred ([OMG issues](../project/omg-issues.md), `tools/oracle/errata`). Every figure above is as published and stays the conformance statement; running the same oracles over the corrected text instead reports 360 of 389 files agreeing, 43 diagnostics ours alone and 92 the reference's alone, 0 declared rows we are silent on, and 0 of 313 authored cases the reference alone rejects. The corrected figures are diagnostic only: an erratum never reclassifies a divergence category, and the published corpus is never edited. - **Self-assessed surface:** the action, state-machine and classifier-behavior rows have no external referee at all — the four refereed figures above cannot see them, because the pinned artifact evaluates expressions but executes neither actions nor state machines. [Spec compliance](../project/spec-compliance.md) counts them. What these numbers cannot show: the OMG corpora are demonstrations rather than an official conformance suite; the differential is one-directional, comparing the diagnostics the two implementations report on the same files; the Xpect suites are the pilot authors' test intent rather than a certification oracle; and none of these is a percentage of the specification — no global compliance figure is claimed anywhere. diff --git a/docs/project/pilot-differential-baseline.json b/docs/project/pilot-differential-baseline.json index d68ca28db2..51abc63919 100644 --- a/docs/project/pilot-differential-baseline.json +++ b/docs/project/pilot-differential-baseline.json @@ -61,8 +61,8 @@ "name": "examples", "dir": "examples", "origin": "ours", - "files": 50, - "digest": "sha256:d9088e5424df569771dce01bae6dec149f7ffa6a8fc34dca2b098c15be43991c" + "files": 51, + "digest": "sha256:0f6842f811dfd7a78b6218b8183fb8f43410a45b909b10ef9a6c4db14d8d6831" }, { "name": "probes", @@ -82,14 +82,14 @@ "recorded": "2026-10-06" }, "totals": { - "files": 388, + "files": 389, "filesFullyAgreeing": 359, "agreement": 45, "severityMismatch": 1, "openSysMLOnly": 44, - "pilotOnly": 85, + "pilotOnly": 92, "openSysMLDiagnostics": 90, - "pilotDiagnostics": 131 + "pilotDiagnostics": 138 }, "roots": [ { @@ -931,14 +931,14 @@ "name": "examples", "dir": "examples", "totals": { - "files": 50, + "files": 51, "filesFullyAgreeing": 45, "agreement": 0, "severityMismatch": 0, "openSysMLOnly": 7, - "pilotOnly": 54, + "pilotOnly": 61, "openSysMLDiagnostics": 7, - "pilotDiagnostics": 54 + "pilotDiagnostics": 61 }, "files": [ { @@ -955,6 +955,26 @@ } ] }, + { + "path": "gridview-relationship-matrix.sysml", + "agreement": [], + "severityMismatch": [], + "openSysMLOnly": [], + "pilotOnly": [ + { + "line": 64, + "severity": "warning", + "category": "kind-mismatch", + "count": 1 + }, + { + "line": 64, + "severity": "warning", + "category": "unmapped", + "count": 6 + } + ] + }, { "path": "mosa-demo/mosa-demo.sysml", "agreement": [], @@ -1562,16 +1582,36 @@ "message": "Duplicate of inherited member name 'memoized' from Evaluator, Resolver", "count": 1 }, + { + "side": "pilot", + "message": "Duplicate of inherited member name 'performanceRequirement' from RelationshipMatrixExample, obj", + "count": 1 + }, { "side": "pilot", "message": "Duplicate of inherited member name 'registry' from AnalysisFramework, tiersAreGated", "count": 1 }, + { + "side": "pilot", + "message": "Duplicate of inherited member name 'result' from ,", + "count": 1 + }, { "side": "pilot", "message": "Duplicate of inherited member name 'runtime' from executionIsBounded, snapshotsAreRunState", "count": 1 }, + { + "side": "pilot", + "message": "Duplicate of inherited member name 'self' from Metaobject, View", + "count": 1 + }, + { + "side": "pilot", + "message": "Duplicate of inherited member name 'source' from , controllerAllocation, plantAllocation, powerLink", + "count": 1 + }, { "side": "pilot", "message": "Duplicate of inherited member name 'start' from Action", @@ -1597,6 +1637,16 @@ "message": "Duplicate of inherited member name 'stdlib' from libraryIsClean, snapshotIsDerived", "count": 1 }, + { + "side": "pilot", + "message": "Duplicate of inherited member name 'subj' from , vehicleBudget", + "count": 1 + }, + { + "side": "pilot", + "message": "Duplicate of inherited member name 'target' from , controllerAllocation, plantAllocation, powerLink", + "count": 1 + }, { "side": "pilot", "message": "Duplicate of inherited member name 'tokens' from LanguageServer, Parser", @@ -1727,14 +1777,14 @@ } ], "totals": { - "files": 388, + "files": 389, "filesFullyAgreeing": 360, "agreement": 45, "severityMismatch": 1, "openSysMLOnly": 43, - "pilotOnly": 85, + "pilotOnly": 92, "openSysMLDiagnostics": 89, - "pilotDiagnostics": 131 + "pilotDiagnostics": 138 }, "findings": [ { diff --git a/docs/project/pilot-differential.md b/docs/project/pilot-differential.md index 18a4cfc437..e7884bdaab 100644 --- a/docs/project/pilot-differential.md +++ b/docs/project/pilot-differential.md @@ -241,7 +241,7 @@ nor double-counted as two independent disagreements. --- -## Results (pilot `2026-08`, 388 files) +## Results (pilot `2026-08`, 389 files) | Root | Files | Fully agreeing | Ours | Pilot | Agreed | Severity-only | Only ours | Only pilot | |---|---:|---:|---:|---:|---:|---:|---:|---:| @@ -250,9 +250,9 @@ nor double-counted as two independent disagreements. | `examples/pilot-corpora/sysml-validation` | 56 | 56 | 0 | 0 | 0 | 0 | 0 | 0 | | `examples/pilot-corpora/kerml-examples` | 58 | 56 | 9 | 0 | 0 | 0 | 9 | 0 | | `tests/testdata` | 21 | 11 | 55 | 77 | 45 | 1 | 9 | 31 | -| `examples` | 50 | 45 | 7 | 54 | 0 | 0 | 7 | 54 | +| `examples` | 51 | 45 | 7 | 61 | 0 | 0 | 7 | 61 | | `tools/referee/diff/testdata` (probes) | 4 | 1 | 6 | 0 | 0 | 0 | 6 | 0 | -| **Total** | **388** | **359** | **90** | **131** | **45** | **1** | **44** | **85** | +| **Total** | **389** | **359** | **90** | **138** | **45** | **1** | **44** | **92** | **Read the `only ours` total by root, never as one number.** Step 2 removes nine resolver false positives from the reference's **own** corpora: `pilot-examples` 16 → **7** and @@ -294,7 +294,7 @@ Per category, the only-ours totals are: `training` 1 `multiplicity`; `pilot-exam `unmapped`, 2 `units`, 5 `kind-mismatch`, 1 `multiplicity`; `kerml-examples` 9 `unmapped`; `testdata` 8 `unmapped`, 1 `multiplicity`; `examples` 4 `unmapped`, 1 `kind-mismatch`, 2 `multiplicity`; `probes` 6 `unmapped`. Only-pilot: `testdata` 12 `kind-mismatch`, 14 `unmapped`, -3 `syntax`, 2 `unresolved-reference`; `examples` 17 `unmapped`, 37 `kind-mismatch`. +3 `syntax`, 2 `unresolved-reference`; `examples` 23 `unmapped`, 38 `kind-mismatch`. ### View concern framing round @@ -959,8 +959,8 @@ cascades through the rest of the file. The movement is entirely one file, | Count | Before the initializer rewrite | Now | |---|---:|---:| -| only pilot | 82 | **85** | -| pilot diagnostics | 123 | **131** | +| only pilot | 82 | **92** | +| pilot diagnostics | 123 | **138** | | severity-only | 9 | **1** | The rewrite itself took only-pilot to 61 and pilot diagnostics to 101; the `Now` column states @@ -1185,13 +1185,13 @@ page's history. | Count | Now | |---|---:| | overall: fully agreeing / only ours / our diagnostics | **359 / 44 / 90** | -| only pilot | **85** | -| pilot diagnostics | **131** | +| only pilot | **92** | +| pilot diagnostics | **138** | | severity-only | **1** | | unmapped, our side | **46** | | kerml-examples: only ours | **9** | | pilot-examples: only ours | **12** | -| examples: only pilot | **54** | +| examples: only pilot | **61** | The KerML root is now the *cleanest* of the three OMG roots in proportion: **9** only-ours against 6 only-pilot, with 56 of 58 files fully agreeing (439 / 6 and 10 / 58 when the root was added, and diff --git a/docs/project/rdf-corpus-roundtrip.md b/docs/project/rdf-corpus-roundtrip.md index b2d9bc9616..875426505f 100644 --- a/docs/project/rdf-corpus-roundtrip.md +++ b/docs/project/rdf-corpus-roundtrip.md @@ -9,7 +9,7 @@ pin in `scripts/pilot-pin.sh`. | Root | Files | |---|---| -| `committed` (everything under `examples/` outside the downloaded roots) | 41 | +| `committed` (everything under `examples/` outside the downloaded roots) | 47 | | `sysml-v2-training` | 100 | | `pilot-corpora/kerml-examples` | 58 | | `pilot-corpora/sysml-examples` | 99 | @@ -59,19 +59,19 @@ Recorded against the corpus above, reproduced byte-identically on a second run: | Verdict | Files | |---|---| -| `stable` | 354 | -| `whitespace-only` | 0 | +| `stable` | 353 | +| `whitespace-only` | 7 | | `graph-diff` | 0 | | `unwritable` | 0 | | `unparseable` | 0 | | `refused` | 0 | -| **total** | **354** | +| **total** | **360** | -So every one of the 354 files converts to Turtle, and every one comes back as the same Turtle byte -for byte. That is the source text at work: the decoder writes each file back from the -`sysx:sourceText` it carries (see [What the gate does not do](#what-the-gate-does-not-do)), so the -files that came back up to whitespace, as a different graph, or that could not be written back or -re-read from canonical notation all moved to `stable` when it landed. +So every one of the 360 files converts to Turtle: 353 come back byte-identical, and seven differ +only in normalized source-text whitespace. The decoder writes each file back from the +`sysx:sourceText` it carries (see [What the gate does not +do](#what-the-gate-does-not-do)). The per-file ratchet records the seven whitespace-only results +separately; no file was graph-different, unwritable, unparseable, or refused. The last 40 refusals were one family: a synonym, portion, event or assertion keyword on a declaration with no name of its own (`feature :>> x;`, `event m.start;`, `snapshot :>> start { … }`, @@ -158,10 +158,11 @@ to, under the same `sysx:sourceText` normalisation. | Verdict | Files | |---|---| -| `stable` | 354 | +| `stable` | 351 | +| `whitespace-only` | 7 | | `graph-diff` | 2 | | every other verdict | 0 | -| **total** | **356** | +| **total** | **360** | The two forms are one graph, so a file's two verdicts should agree, and they do for every file but two: `Vehicle Example/Annex_A_VehicleViews.sysml` and `Vehicle Example/SysML v2 Spec Annex A diff --git a/docs/project/spec-compliance.md b/docs/project/spec-compliance.md index c53c9364dc..2fb4a4434c 100644 --- a/docs/project/spec-compliance.md +++ b/docs/project/spec-compliance.md @@ -2147,7 +2147,8 @@ semantics layer over the conjugation parity of the typing/specialization chain. | A namespace's `filter` restricts the imported memberships it re-exports (`package P { public import Q::*; filter @Safety; }`, KerML 8.2.4, SysML v2 7.4.4) | `symbols/filter.go` `NamespaceFiltersIn` and `symbols/index.go` `reexportGated` (each way a name is re-exported records the conditions along it as one route; a name with no route is ungated) — the index records the gate and never evaluates it; `resolve/filter.go` `Resolver.admitsUnderName` evaluates a candidate against the routes through `semantics.Model.SatisfiesElementFilter`, and `resolve/unqualified.go` `matchImport`, `resolve/qualified.go` and `symbols/index.go` `LookupDirectChildrenFrom` share that one admission test; a `filter` member of a definition or usage body — a view narrowing what its `expose` lines surface — reaches the same test through `resolve/filter.go` `Resolver.importAdmits`, which composes `symbols.NamespaceFiltersIn(scope)` with the import's own clause; a `filter` at a document's root gates that document's root-level imports alone, since each document owns its root namespace (`symbols/filter.go` `namespaceFiltersGating`, keyed per document by `symbols/index.go` `gateKeyOf`) | `symbols/index_test.go` `TestExpandWildcardImportsGatesAFilteredReexport`, `TestExpandWildcardImportsKeepsAnUnfilteredRoute`, `TestExpandWildcardImportsRecordsAGateOnce`; `resolve/filter_test.go`; `model/element_filter_test.go` (including `TestFilterMemberOfADefinitionBodyRestrictsItsImports`, `TestFilterConditionResolvesThroughTheImportsItFilters`, `TestARootFilterRestrictsOnlyItsOwnDocument`), `symbols/index_test.go` `TestRootNamespaceFiltersGateOnlyTheirOwnDocument`; `libs/loader_cache_test.go` `TestFilteredImportsSurviveCacheRestore` | ✅ Faithful (a filter restricts only what the namespace re-exports, per maintainer decision: its own directly declared members are never hidden, only the condition's own names resolve unfiltered — otherwise a condition could not name a metadata type the namespace itself imports, since the condition's own names would be filtered by the condition (`resolve.Resolver.InCondition`) — and a name reached by an unfiltered route as well as a filtered one stays visible: the routes are alternatives, and the conditions along one route are a conjunction) | | An import or expose filter restricts what that import brings in (`import P::*[@Safety];`, `expose vehicle::**[@Safety];`, SysML v2 7.4.4, 8.3.26) | `ast.Import.FilterExpr` reaches the index through `symbols/index.go` `wildcardImport.filter`, gating the memberships that import surfaces; the same `admitsUnderName` decides them, so the qualified and the unqualified route agree, including the whole-index fallback for a top-level name (`resolve/qualified.go` `lookupGlobalTop`, which a root-level filtered import would otherwise bypass); the condition's own names are resolved as references like a `filter` member's, so a typo in the clause is an unresolved reference and editor navigation over it works (`resolve/document.go`, `resolve/references.go`) | `resolve/filter_test.go`, `model/element_filter_test.go` (a filtered `expose` in a view surfaces a strict subset, an element the condition rejects is unresolvable by either route, `TestAFileLevelFilteredImportHidesWhatItRejects`, `TestAnUnresolvedNameInAnImportFilterIsReported`), `parse/element_filters.golden` | ✅ Faithful (an import's condition and the filters of the namespace it imports compose: the intersection is what the importer sees) | | A view's exposed elements are queryable (SysML v2 7.24 Views and Viewpoints, 8.3.26 Expose) | `semantics/expose.go` `Model.ExposedElements` (a view's own `expose` relationships, then those of the views it specializes since an Expose is protected, in declaration order and once each) and `Model.NestedViews` (the views in its body, to walk a view tree), enumerated by `resolve/filter.go` `Resolver.ImportedElements` — the same admission, visibility and filter gating a lookup through that import makes, so the exposed set is what the view body actually resolves | `semantics/expose_test.go` (`TestExposedElementsNamespaceWildcard`, `TestExposedElementsRecursiveExpose`, `TestExposedElementsWithAnElementFilter`, `TestExposedElementsWithAViewBodyFilter`, `TestExposedElementsOfNestedViews`, `TestExposedElementsExposingAnotherView`, `TestExposedElementsInheritedFromAViewDefinition`, `TestExposedElementsOfAViewExposingNothing`, `TestExposedElementsOfANonView`) | ✅ Faithful (an empty exposed set is no error; asking a non-view is `semantics.ErrNotAView`. The REPL surface is `%view` — `repl/view.go` `doView`) | -| A view's exposed set is rendered as the rendering its `render` member states, and as a containment tree where it states none (SysML v2 7.24 Views and Viewpoints, §10.2 — the rendering is tool-defined) | `semantics/rendering.go` `Model.ViewRenderings` (the `render` members of the view and of the views it specializes) and `Model.RenderingTarget` (the rendering the member references or declares); `view/view.go` `Renderer.KindOf`, `Renderer.Render` (the kind, then the exposed set from `Model.ExposedElements`) and `Renderer.RenderExposed` (an arbitrary selected set); `view/pseudo.go` derives the `#` vocabulary from the kinds this build supports; `view/tree.go`, `view/interconnection.go`, `view/case.go`, `view/mixed.go` (the model's own connector and flow ends, not source text; a part's node carries the ports its definition gives it as `Node.Ports`, `featureWalk.pinPorts`/`Renderer.typedPorts`, and an end naming one is the port, `Edge.FromPort`/`Edge.ToPort`, through `featureWalk.endNode`/`memberEnd`), `view/behavior.go` (the lowered `lower.StateGraph`/`lower.ActionGraph`, never a re-parse of `symbol.Decl`), `view/table.go` (the exposed elements, the elements declared in them and the nested views, as rows), `view/sequence.go` `Renderer.renderSequence` (the occurrences an interaction declares as lifelines, the model's own flow ends as directed messages, ordered by the successions between the events those messages run between), `view/text.go`, `view/mermaid.go`, `view/dot.go`, `view/plantuml.go` and `view/markdown.go` (the forms, chosen per kind by `Kind.MachineForm` in `view/form.go`) | `view/render_test.go` `TestGoldenRenderings` (text and machine-readable goldens for tree, interconnection, state, action, case, mixed, a filtered view and a table, from `.sysml` fixtures), `TestTreeRenderingShowsNestedViewsAndDefaults`, `TestInterconnectionRenderingDrawsConnections`; `view/interconnection_ports_test.go` (a typed part's ports pinned, named and typed with `~`, in DOT, PlantUML and text, the connectors at the pins, a portless part plain, a part def's own ports nested, each usage's pins its own), `TestStateRenderingComesFromTheLoweredGraph`, `TestActionRenderingComesFromTheLoweredGraph`, `TestRenderingUsesFilteredAndInheritedExposure`, `TestTableRenderingRows`, `TestTableFormsAreMarkdownNotMermaid`, `TestMermaidLabelsAreEscaped`; `view/sequence_test.go` `TestSequenceRenderingDrawsLifelinesAndMessages`, `TestSequenceRenderingFromTheShortViewName`, `TestSequenceRenderingReportsWhatItCannotShow`, `TestSequenceRenderingHonoursStatedOrder`, `TestSequenceRenderingReportsASuccessionCycle`, `TestSequenceMermaidDeclaresParticipantsFirst`; `view/pseudo_test.go` | ✅ Faithful to the notation, tool-defined in output (the kinds produced are a tree, an interconnection diagram, a state machine, an action flow, case and mixed diagrams, a sequence diagram, a table and the GeneralView requirement, definition and package graphs; state and action renderings read the graphs the runtime executes, so a rendering cannot drift from what runs. Mermaid is the machine-readable form of the graph-shaped kinds and Markdown that of a table; SysML §10.2 specifies no artifact) | +| A view's exposed set is rendered as the rendering its `render` member states, and as a containment tree where it states none (SysML v2 7.24 Views and Viewpoints, §10.2 — the rendering is tool-defined) | `semantics/rendering.go` `Model.ViewRenderings` (the `render` members of the view and of the views it specializes) and `Model.RenderingTarget` (the rendering the member references or declares); `view/view.go` `Renderer.KindOf`, `Renderer.Render` (the kind, then the ordinary rendering's exposed set from `Model.ExposedElements`; a selected `GridView` matrix uses `Model.ExposedMembers`) and `Renderer.RenderExposed` (an arbitrary selected set); `view/pseudo.go` derives the `#` vocabulary from the kinds this build supports; `view/tree.go`, `view/interconnection.go` (the model's own connector and flow ends, not source text; a part's node carries the ports its definition gives it as `Node.Ports`, `featureWalk.pinPorts`/`Renderer.typedPorts`, and an end naming one is the port, `Edge.FromPort`/`Edge.ToPort`, through `featureWalk.endNode`/`memberEnd`), `view/behavior.go` (the lowered `lower.StateGraph`/`lower.ActionGraph`, never a re-parse of `symbol.Decl`), `view/table.go` (the ordinary exposed elements, the elements declared in them and the nested views, as rows), `view/sequence.go` `Renderer.renderSequence` (the occurrences an interaction declares as lifelines, the model's own flow ends as directed messages, ordered by the successions between the events those messages run between), `view/text.go`, `view/mermaid.go` and `view/markdown.go` (the forms, chosen per kind by `Kind.MachineForm` in `view/form.go`); relationship-matrix behavior is detailed in the [GridView Relationship Matrix](#gridview-relationship-matrix) row | `view/render_test.go` `TestGoldenRenderings` (text and machine-readable goldens for tree, interconnection, state, action, a filtered view and a table, from `.sysml` fixtures), `TestTreeRenderingShowsNestedViewsAndDefaults`, `TestInterconnectionRenderingDrawsConnections`; `view/interconnection_ports_test.go` (a typed part's ports pinned, named and typed with `~`, in DOT, PlantUML and text, the connectors at the pins, a portless part plain, a part def's own ports nested, each usage's pins its own), `TestStateRenderingComesFromTheLoweredGraph`, `TestActionRenderingComesFromTheLoweredGraph`, `TestRenderingUsesFilteredAndInheritedExposure`, `TestTableRenderingRows`, `TestTableFormsAreMarkdownNotMermaid`, `TestMermaidLabelsAreEscaped`; `view/sequence_test.go` `TestSequenceRenderingDrawsLifelinesAndMessages`, `TestSequenceRenderingFromTheShortViewName`, `TestSequenceRenderingReportsWhatItCannotShow`, `TestSequenceRenderingHonoursStatedOrder`, `TestSequenceRenderingReportsASuccessionCycle`, `TestSequenceMermaidDeclaresParticipantsFirst`; `view/pseudo_test.go` | ✅ Faithful to the notation, tool-defined in output (the kinds produced are a tree, an interconnection diagram, a state machine, an action flow, a sequence diagram, a table and a filtered GridView relationship matrix; state and action renderings read the graphs the runtime executes, so a rendering cannot drift from what runs. Mermaid is the machine-readable form of the graph-shaped kinds and Markdown that of a table or matrix; SysML §10.2 specifies no artifact) | +| A view's exposed set is rendered as the rendering its `render` member states, and as a containment tree where it states none (SysML v2 7.24 Views and Viewpoints, §10.2 — the rendering is tool-defined) | `semantics/rendering.go` `Model.ViewRenderings` (the `render` members of the view and of the views it specializes) and `Model.RenderingTarget` (the rendering the member references or declares); `view/view.go` `Renderer.KindOf`, `Renderer.Render` (the kind, then the exposed set from `Model.ExposedElements`) and `Renderer.RenderExposed` (an arbitrary selected set); `view/pseudo.go` derives the `#` vocabulary from the kinds this build supports; `view/tree.go`, `view/interconnection.go`, `view/case.go`, `view/mixed.go` (the model's own connector and flow ends, not source text; a part's node carries the ports its definition gives it as `Node.Ports`, `featureWalk.pinPorts`/`Renderer.typedPorts`, and an end naming one is the port, `Edge.FromPort`/`Edge.ToPort`, through `featureWalk.endNode`/`memberEnd`), `view/behavior.go` (the lowered `lower.StateGraph`/`lower.ActionGraph`, never a re-parse of `symbol.Decl`), `view/table.go` (the exposed elements, the elements declared in them and the nested views, as rows), `view/sequence.go` `Renderer.renderSequence` (the occurrences an interaction declares as lifelines, the model's own flow ends as directed messages, ordered by the successions between the events those messages run between), `view/text.go`, `view/mermaid.go`, `view/dot.go`, `view/plantuml.go` and `view/markdown.go` (the forms, chosen per kind by `Kind.MachineForm` in `view/form.go`) | `view/render_test.go` `TestGoldenRenderings` (text and machine-readable goldens for tree, interconnection, state, action, case, mixed, a filtered view and a table, from `.sysml` fixtures), `TestTreeRenderingShowsNestedViewsAndDefaults`, `TestInterconnectionRenderingDrawsConnections`; `view/interconnection_ports_test.go` (a typed part's ports pinned, named and typed with `~`, in DOT, PlantUML and text, the connectors at the pins, a portless part plain, a part def's own ports nested, each usage's pins its own), `TestStateRenderingComesFromTheLoweredGraph`, `TestActionRenderingComesFromTheLoweredGraph`, `TestRenderingUsesFilteredAndInheritedExposure`, `TestTableRenderingRows`, `TestTableFormsAreMarkdownNotMermaid`, `TestMermaidLabelsAreEscaped`; `view/sequence_test.go` `TestSequenceRenderingDrawsLifelinesAndMessages`, `TestSequenceRenderingFromTheShortViewName`, `TestSequenceRenderingReportsWhatItCannotShow`, `TestSequenceRenderingHonoursStatedOrder`, `TestSequenceRenderingReportsASuccessionCycle`, `TestSequenceMermaidDeclaresParticipantsFirst`; `view/pseudo_test.go` | ✅ Faithful to the notation, tool-defined in output (the kinds produced are a tree, an interconnection diagram, a state machine, an action flow, case and mixed diagrams, a sequence diagram and a table; state and action renderings read the graphs the runtime executes, so a rendering cannot drift from what runs. Mermaid is the machine-readable form of the graph-shaped kinds and Markdown that of a table; SysML §10.2 specifies no artifact) | | A `GeneralView` whose filters select a specialization the library documents for it (a requirement view, a definition and usage view, a package view), or a case metaclass, is drawn as that graph of nodes and edges, the case one by the case diagram's writer; any other filter, and none, keeps the containment tree | `view/general.go` `Renderer.generalSpecialization` (the filter compiled by `Model.CompileElementFilter`, a metaclass classification or an `or` of them, matched through the metaclass's supertypes), `renderGeneral` (satisfy, verify, derive, refine, allocate, specialization, typing, composition, reference, containment and import edges between drawn symbols), `view/verdict.go` and `runtime/verdict_overlay.go` `RequirementVerdicts` (the opt-in verdicts overlay) | `view/general_test.go` `TestGeneralViewSpecializationSelection`, `TestGeneralViewFilteredExposeSelects`, `view/general_case_test.go` `TestGeneralViewCaseRouteMatchesCaseView`, `TestGeneralViewUnresolvedRelationshipEnds`, `TestGoldenVerdictOverlay`, `TestRequirementRenderingWithoutOverlayHasNoVerdicts`; `view/render_test.go` `TestGoldenRenderings` (the `general-*` goldens); `repl/view_render_test.go` `TestRenderDrawsVerdictsWhenAskedFor`; `repl/view_render_test.go` `TestRenderDrawsAGeneralViewCaseRoute`; `cmd/sysml/render_test.go` `TestRenderOverlay`, `TestRenderGeneralViewCaseRoute`; `lsp/render_test.go` `TestRenderDrawsVerdictsWhenAsked`, `TestRenderGeneralViewCaseRoute`; `docplan/diagram_test.go` `TestCompileDiagramOfAGeneralViewCaseRoute`; `examples/general-views-demo` (accepted clean by the pinned pilot, `use-cases.sysml` with the standard library alone) | ✅ Faithful to the notation, tool-defined in output (§10.2 specifies no artifact; which filters select which graph is this tool's reading of `StandardViewDefinitions::GeneralView`'s documentation) | | A rendering this build does not produce, a name that is no view, a view exposing nothing, and an exposed element a rendering cannot represent are each explicit | `view/view.go` `UnsupportedKindError` (wrapping `view.ErrUnsupportedKind`, naming the kind, the view and the rendering it stated), `Renderer.Render` (`semantics.ErrNotAView` for a non-view, as `%view` answers), `Rendering.Empty` and `view/text.go` (an empty artifact saying whether the view exposes nothing or nothing exposed was representable), `Rendering.Notices` (what a kind could not draw); `cmd/sysml/render.go` reports and skips unsupported declared views and incompatible forced forms during `-render-all`, prefixing each notice with its source view | `view/render_test.go` `TestUnsupportedRenderingKinds`, `TestRenderingSomethingThatIsNoView`, `TestRenderingAViewExposingNothing`, `TestRenderingReportsWhatItCannotRepresent`; `repl/view_render_test.go` `TestRenderOfAnUnsupportedKindNamesIt`, `TestRenderOfANonViewIsTyped`, `TestRenderOfAViewExposingNothingSaysSo`; `cmd/sysml/render_test.go` `TestRenderReportsWhatItCouldNotDo`, `TestRenderAllSkipsUnsupportedKindsAndWrongForcedForms`, `TestRenderAllPrefixesRenderingNoticesWithTheirView` | ✅ Faithful (a stated kind that is not produced is a typed error naming it, never a substituted rendering; a form the kind is not written in is a `WrongFormError` naming the one it is; an element that cannot be drawn is reported, not dropped. A one-view render stops on either typed error; a render-all run skips only that view and renders the rest) | | A graph-shaped rendering (tree, interconnection, state, action, case, mixed) is also written as Graphviz DOT, an alternative to Mermaid for Graphviz toolchains and large-graph layouts, without a Graphviz installation: a `digraph` with `// view:`, `// kind:`, `// stated:`, `// not represented:`, `// canvas:` and `// layout:` header comments, `rankdir` from the direction, containment as `subgraph "cluster_"` (a tree as `arrowhead=none` edges, as its Mermaid form), an edge at a cluster drawn to a node inside it and clipped with `lhead`/`ltail`, state pseudo-states as `point`/`circle`/`doublecircle`, states as rounded boxes, regions as dashed clusters, cases as ellipses, actors and subjects as boxes, objectives as notes, transition labels as the state writer's trigger/guard/effect text, and edge kinds parallel to the Mermaid arrows (connection `arrowhead=none`, composition diamond tails, association and anchors without arrowheads, include dashed, typing and reference dashed with open heads, specialization with an empty head); every identifier and label is quoted through one helper; the DiagramLayout geometry is written as Graphviz reads it, one pixel to one point with y flipped from the library's y-down origin (`inputscale=72`, `dpi=72`; y measured from the canvas's bottom edge, negated with no canvas height): a positioned node pinned at the centre of its box with `pos="x,y!", pin=true`, its size as `width`/`height` in inches — a stated size with `fixedsize=true` and the label fitted to it (the head wrapped at the width and shrunk from 14 pt to 8 pt until it fits, the keyword and detail lines kept only while height remains, an overrunning head ellipsized), an unstated one fitted to the label so the box's corner stays where the Layout put it — `collapsed` as `comment="collapsed"`, a positioned cluster's `bb` stated (its stated box, or the one from its corner round its positioned members) and its anchor pinned at the centre, a `Route` as the edge's `pos` B-spline through its waypoints (a route of one waypoint drawn as no line and noticed as `// not represented:`), a sized `Canvas` as an invisible point pinned at each corner so the drawing's bounding box is the canvas, and the `// layout:` header naming `neato -n2` when every node is placed and any edge routed, `neato -n` when every node is placed and none routed, `neato` when some nodes are, `dot` when none; sequence and table have no DOT form and are the same `WrongFormError` the other forms raise | `view/dot.go` `Rendering.DOT`, `Rendering.DOTWith`, `dotWriter` (`engine`, `graphAttributes`, `dotNodeAttributes`, `dotPin`, `dotBox`, `dotClusterAttributes`, `dotAnchorAttributes`, `clusterBox`, `dotEdgeAttributes`, `dotSpline`, `flipY`, `dotInches`), `dotQuote`; `view/form.go` `FormDot`, `Forms`, `DiagramForms`, `Kind.SupportsForm`, `Options`, `Write`, `WriteWith`; `cmd/sysml/render.go` (`-render-form dot`, `.dot` under `-render-all`); `repl/meta.go` (`%render dot`); `lsp/render.go` `renderForm` (`form: "dot"`) | `view/dot_test.go` `TestGoldenDOT` (`testdata/*.dot.golden` beside the Mermaid goldens, each checked by an in-test DOT syntax walker: balanced braces, every edge endpoint declared as a node or cluster, quoted identifiers), `TestDOTFormSupport`, `TestDOTQuotesEveryIdentifierAndLabel`, `TestDOTNestedClusters`, `TestDOTDirections`, `TestDOTEdgeKinds`, `TestDOTStateShapesAndLabels`, `TestDOTEmptyAndNotices`, `TestDOTWritesTheGeometry` (`testdata/layout.dot.golden` beside the Mermaid and text goldens of the layout fixture; the flipped axis with and without a canvas height; states and transitions placed), `TestDOTPinsEveryNode` (`neato -n` and `neato -n2`, the one-waypoint notice, a stated, a member-fitted and a corner-only cluster's `bb` and anchor, a tree's positioned parent, pseudo-state centring, the zero-extent, unit-only and unpositioned canvas); the syntax walker parses every `pos` and `bb`; `cmd/sysml/render_test.go` `TestRenderDotForm`; `repl/view_render_test.go` `TestRenderWritesDotWhenAskedFor`; `lsp/render_test.go` `TestRenderWritesDotWhenAskedFor` | ✅ Faithful to the notation, tool-defined in output (Mermaid stays the machine-readable form `Kind.MachineForm` chooses; DOT is written on request. Producing the DOT needs no `dot` binary, and the goldens are validated by the in-test syntax walker rather than by `dot -Tsvg`; the one place Graphviz runs is the PDF backend, drawing the block on request when a Graphviz is installed (the document-rendering rows below). A `Route` is written as the polyline through its waypoints, not smoothed; a node with no stated size is given the writer's estimate of its label's extent (8.4 pt a glyph, 16.8 pt a line), not `fixedsize`, so Graphviz may grow the box for its own font and move the corner by the difference. The gRPC API has no view-render RPC — `RenderDocument` alone, to Markdown — so no wire contract carries a form) | @@ -2605,6 +2606,7 @@ boundaries; the landed Track E behavior is recorded in the execution rows and th **Intentionally Unspecified (No Normative Semantics):** - Verification verdict evaluation (VerdictKind/PassIf) - SysML v2 §9.3.2: "evaluation... intentionally not specified normatively". OpenSysML *does* report a verdict — it runs the case body as it runs an analysis case body and reports the `VerdictKind` that run produced, computed by the library's own `PassIf` calculation where the body calls it, `inconclusive` where the body produces no verdict value and `error` where the run failed — but that reading of the body is this tool's, not a normative one, and it is reported beside the requirement-satisfaction verdicts rather than replacing them; nested subcases are reported individually because the library states no roll-up. See the Verification Case map - Variability/variation selection - SysML v2 §9.4: "Selection of variants is not specified normatively" — OpenSysML selects the variant a variation usage is bound to (`attribute :>> cut = cut::cutIdeal;`) and errors on an unselected, unknown, or multiply-selected variation; see the Variation and Variant map +- View/viewpoint rendering - SysML v2 §10.2: "rendering semantics intentionally left to tools". OpenSysML *does* render a view — a tree, an interconnection diagram, a state machine, an action flow, a sequence diagram, a table or a `GridView` relationship matrix, as text, Mermaid, Markdown or delimited records (`%render`, `sysml -render`; see the rendering rows) — but the artifact itself is this tool's output, not a normative form, and the kinds it does not produce are a typed `view.UnsupportedKindError` rather than a substituted rendering. The matrix admits unnamed members through its opt-in exposure path; ordinary renderings continue to omit unnamed exposed members. The viewpoint conformance verdicts OpenSysML reports are likewise tool-defined (see the viewpoint conformance row) - View/viewpoint rendering - SysML v2 §10.2: "rendering semantics intentionally left to tools". OpenSysML *does* render a view — a tree, an interconnection diagram, a state machine, an action flow, case and mixed diagrams, a sequence diagram or a table, as text, Mermaid or a Markdown table (`%render`, `sysml -render`; see the rendering rows) — but the artifact itself is this tool's output, not a normative form, and the kinds it does not produce are a typed `view.UnsupportedKindError` rather than a substituted rendering. The viewpoint conformance verdicts OpenSysML reports are likewise tool-defined (see the viewpoint conformance row) - Case and mixed renderings extend the tool-defined view kinds with use, analysis and verification cases and a shared canvas for structural, state, action and case nodes; their text, Mermaid, DOT and PlantUML writers preserve the relationships represented by each form | `view/case.go` `renderCase`, `view/mixed.go` `renderMixed`; `view/text.go`, `view/mermaid.go`, `view/dot.go`, `view/plantuml.go` | `view/case_mixed_test.go`, `view/testdata/{case,mixed}.*.golden`; CLI, REPL, LSP and docplan rendering tests | ✅ Implemented as a tool-defined rendering extension - Allocation execution - SysML v2 §9.2.4: syntax defined, execution semantics not normative @@ -2631,6 +2633,12 @@ boundaries; the landed Track E behavior is recorded in the execution rows and th --- +### GridView Relationship Matrix + +| Rule | Implementation | Tests | Status | +| --- | --- | --- | --- | +| A standard `GridView` with a resolved positive relationship selector renders the selected relationships as a matrix. SysML metaclass selectors match by identity; refinement and derivation metadata selectors also match specializations. `render asElementTable`, unknown selectors, and selectors nested under `not` retain table rendering. Matrix exposure uses `Model.ExposedMembers` and admits unnamed members through the view's own and inherited filters; ordinary tables and renderings continue to use `Model.ExposedElements`. Relationship edges are extracted once in semantics and query execution delegates to that model API. Rows and columns follow first edge occurrence; cells deduplicate keywords in the fixed order `satisfy`, `verify`, `allocate`, `connect`, `derive`, `refine`, `dependency`. Direct matrix output supports text, Markdown, CSV and TSV; `#matrix` and `#matrix:` use the same rendering. Document Diagram blocks treat matrices as tables for every diagram form. | `semantics/relationship_edges.go` `Model.RelationshipEdgesOf`; `semantics/expose.go` `Model.ExposedMembers`, `Model.ViewExposureConditions`; `resolve/filter.go` `ImportedMembersInto`, `ViewExposureConditions`; `doc/queryexec/related.go` delegates relationship scans; `view/view.go` `Renderer.KindOf`, `Renderer.Render`, `Renderer.RenderExposed`; `view/matrix.go` `matrixShownKinds`, `renderMatrix`, `matrixEdges`; `docrender/markdown.go`, `docrender/html.go` | `semantics/relationship_edges_test.go`; `semantics/expose_test.go`; `view/matrix_test.go` and `view/testdata/matrix.sysml`; `cmd/sysml/render_test.go`; `repl/view_render_test.go`; `lsp/render_test.go`; `docplan/diagram_test.go`; `docrender/diagram_test.go`; `docrender/html_diagram_test.go` | ✅ Implemented (the ordinary exposed-element path is preserved; unnamed-member enumeration is opt-in to matrix rendering) | + ## Implementation Files ### Runtime Execution (`internal/exec/runtime/`) diff --git a/docs/project/view-rendering-forms.md b/docs/project/view-rendering-forms.md index 53658f1371..17f749b78f 100644 --- a/docs/project/view-rendering-forms.md +++ b/docs/project/view-rendering-forms.md @@ -13,8 +13,9 @@ PlantUML and D2 compare, and what the writers emit — the ## The rendering and its forms A view renders into a `view.Rendering` (`internal/ir/view/view.go`): the kind (`tree`, -`interconnection`, `state`, `action`, `case`, `mixed`, `sequence`, `table`, and the [GeneralView graphs](#generalview-graphs) -`requirement`, `definition` and `package`), typed nodes with an identifier, a +`interconnection`, `state`, `action`, `case`, `mixed`, `sequence`, `table`, `matrix`, and the +[GeneralView graphs](#generalview-graphs) `requirement`, `definition` and `package`), typed nodes +with an identifier, a kind, a name, the declared type of a typed usage, an optional detail holding the notes (`initial`, `already shown`, `own flow`) and their children, edges with a label and an `EdgeKind` (connection, binding, transition, succession, flow, composition, association, include, anchor, @@ -44,22 +45,62 @@ A **form** is a writer over that tree (`internal/ir/view/form.go`): | Form | Writer | Kinds | Role | | --- | --- | --- | --- | | `text` | `text.go` | every kind | What a person reads at a terminal | -| `markdown` | `markdown.go` | `table` | The machine-readable form of a table | -| `csv`, `tsv` | `delimited.go` | `table` | A table as comma- or tab-separated values, for spreadsheets and scripts | +| `markdown` | `markdown.go` | `table`, `matrix` | The machine-readable form of a table or relationship matrix | +| `csv`, `tsv` | `delimited.go` | `table`, `matrix` | A table or relationship matrix as comma- or tab-separated values, for spreadsheets and scripts | | `mermaid` | `mermaid.go` | `tree`, `interconnection`, `state`, `action`, `case`, `mixed`, `sequence`, `requirement`, `definition`, `package` | The default machine-readable form of the graph-shaped kinds | | `dot` | `dot.go` | `tree`, `interconnection`, `state`, `action`, `case`, `mixed`, `requirement`, `definition`, `package` | Graphviz DOT, the alternative to Mermaid | | `plantuml` | `plantuml.go` | `tree`, `interconnection`, `state`, `action`, `case`, `mixed`, `sequence`, `requirement`, `definition`, `package` | PlantUML in the Pilot visualizer's B&W style, for PlantUML toolchains | | `d2` | `d2.go` | `tree`, `interconnection`, `state`, `action`, `sequence`, `requirement`, `definition`, `package` | [D2](https://d2lang.com) in the same look, for D2 toolchains; nested containers and D2's own sequence diagram | -D2 does not yet write case or mixed renderings and refuses them with the typed -`WrongFormError`. - -`Kind.MachineForm` chooses the form a tool gets when none is asked for — `markdown` for a table, +`Kind.MachineForm` chooses the form a tool gets when none is asked for — `markdown` for a table or matrix, `mermaid` for everything else — and `Kind.SupportsForm` decides whether a kind can be written in a form at all. Asking for a form the kind is not written in is one typed `WrongFormError`, naming -the kind, the form asked and the form the kind uses, on every surface: the CLI stops with status 2 -(`-render-all` skips the view and says so), the REPL prints the usage, the LSP refuses the request, -and a document's `Diagram` block is refused at planning time. +the kind, the form asked and the form the kind uses, on the CLI (`-render-all` skips the view and +says so), in the REPL and in an LSP render request. A document `Diagram` block renders table and +matrix kinds as tables in every diagram form; other incompatible forms are refused at planning +time. +D2 does not write case, mixed, table or matrix renderings and refuses them with the typed +`WrongFormError`. + +## Relationship matrices + +`matrix` is a tabular rendering for a standard `GridView`. It is selected only when a positive, +resolved `@T` selector in the view's own or inherited filters, or in the filters of its own or +inherited exposes, names a relationship kind below. A selector nested under logical `not` does +not activate it. SysML metaclass selectors match by identity; the two metadata selectors match +their specializations as well. An explicit `render asElementTable;` remains an ordinary table, +and an unknown or unrelated selector leaves the view's existing table behavior unchanged. + +| Selector | Relationship kinds | +| --- | --- | +| `SysML::SatisfyRequirementUsage` | `satisfy` | +| `SysML::VerificationCaseUsage`, `SysML::VerificationCaseDefinition` | `verify` | +| `SysML::AllocationUsage` | `allocate` | +| `SysML::ConnectionUsage` | `connect`, `allocate`, `derive` | +| `SysML::InterfaceUsage` | `connect` | +| `SysML::Dependency` | `dependency`, `refine` | +| `ModelingMetadata::Refinement` | `refine` | +| `RequirementDerivation::DerivationMetadata` | `derive` | + +The matrix's rows are relationship sources and its columns are targets. It admits unnamed members +through the matrix-only `ExposedMembers` path, then walks named and unnamed owned members to the +tree depth limit, without descending into nested views. Ordinary tables and other renderings keep +their existing named-member exposure and do not gain unnamed rows. A source and target occupy +their first-seen +positions; repeated edges between a pair collapse into one cell, whose comma-separated keywords +are ordered `satisfy`, `verify`, `allocate`, `connect`, `derive`, `refine`, `dependency`. Labels +use the table's qualified element names, and each row retains its source origin. + +An exposed top-level member whose subtree contributes no displayed edge is named in a notice, in +exposure order; the notice names the selected relationship kinds in the fixed cell-keyword order. +An empty matrix distinguishes a view that exposed nothing from one whose exposed members have no +relationships. On the CLI and in the REPL, `#matrix` renders all loaded content; in a workspace +render request such as LSP, it renders the current document's top-level declarations. The engine +and gRPC `RenderView` surfaces have no current-document context, so their pseudo-views require a +target such as `#matrix:`, which renders one declared element directly. Pseudo-views do not +change exposure for ordinary tables or any other rendering kind. Like a table, a matrix is written +in `text`, `markdown`, `csv` and `tsv`. Mermaid, DOT, PlantUML and D2 have no table grammar, so +requesting one of those forms is a typed wrong-form error naming `matrix`. ## Standard views first @@ -73,7 +114,7 @@ results from runs, and layout. | --- | --- | --- | | Use case diagram | `GeneralView` with a case-family filter (`filter @SysML::UseCaseUsage;`); `CaseView` or `render asCaseDiagram;` from `OpenSysMLRenderings` is the shorter form | standard | | Requirement, definition and package graphs | `GeneralView` with a requirement, definition/usage or package filter ([GeneralView graphs](#generalview-graphs)) | standard | -| Relationship matrix | `GridView` with a relationship filter | standard | +| Relationship matrix | `GridView` with a positive, resolved relationship filter ([selector rules](#relationship-matrices)) | standard | | Mixed diagram | `MixedView` or `render asMixedDiagram;` from `OpenSysMLRenderings` | extension | | Run timeline and run sequence | `-render-run` on the CLI, `%render-run` in the REPL; no view declares them | CLI and REPL only | | Verdicts overlay | `Diagram::overlay = "verdicts"` from `DocumentQueries` in a document; `-render-overlay verdicts`, `%render … verdicts` and the LSP `overlay` option elsewhere ([The verdicts overlay](#the-verdicts-overlay)) | extension | @@ -362,7 +403,7 @@ form is chosen **per diagram** (`docrender.DiagramOptions.formFor`): in the PDF whichever engine — never silently. A document mixing positioned and unpositioned views therefore gets a Graphviz figure for each of -the former and a Mermaid graph for each of the latter, and a table-kind view is a table in every +the former and a Mermaid graph for each of the latter, and a table or matrix view is a table in every case. The rule lives in `docrender` so the CLI, the REPL, the LSP and the PDF backend agree. ## What the DOT writer emits @@ -1081,7 +1122,7 @@ refuse D2: | CLI, REPL, LSP, documents | `-render-style pilot\|cameo` beside `-render-palette`, on `-render`, `-render-all` and the document renderers; `%render dot [palette] [pilot\|cameo]` and `%render-document dot [style]`; `"style": "cameo"` on `opensysml/render`, the styles listed by the `openSysmlRenderStyles` capability; `docrender.MarkdownOptions.Style`/`HTMLOptions.Style` and `docpdf.Options.Style`. A form that draws no style writes a `not represented: style …` notice; an unknown name is a typed `*view.UnknownDrawingStyleError` naming the styles there are | [`docs/reference/cli.md`](../reference/cli.md#rendering-a-view), [`docs/reference/repl-commands.md`](../reference/repl-commands.md#rendering-a-view), [`docs/reference/lsp.md`](../reference/lsp.md) | | CLI, REPL, LSP, documents | `-render-ports minimal\|full` on `-render` and `-render-all`; `%render
[minimal\|full]` in any order with the palette and style; `"ports": "full"` on `opensysml/render`, the displays listed by the `openSysmlRenderPorts` capability; `Diagram::ports` in a document, carried as `view.Options.Ports` (`invalid-ports`, `unsupported-ports` errors) | [`docs/reference/cli.md`](../reference/cli.md#rendering-a-view), [`docs/reference/repl-commands.md`](../reference/repl-commands.md#rendering-a-view), [`docs/reference/lsp.md`](../reference/lsp.md), [`docs/manual/authoring.md`](../manual/authoring.md#diagrams) | | VS Code | The diagram panel's **Style** list and `opensysml.diagram.style`: `pilot` draws the panel's SVG under this section's B&W rules, `cameo` asks the server for the [Cameo look](#the-cameo-style), a palette name fills its nodes from the `fill` and `border` the server returns | [`editors/vscode/README.md`](../../editors/vscode/README.md#the-diagram-panel) | -| CLI, REPL, LSP, VS Code | A table view takes `csv` or `tsv` as well: `-render -render-form csv\|tsv` (`-render-all` writes `.csv` or `.tsv` for each table and skips every other view), `%render csv\|tsv`, `"form": "csv"` or `"tsv"` on `opensysml/render`. Either is a header record of the columns, then a record per row, fields quoted as RFC 4180 quotes them; a notice is never inside the records: the CLI writes it to standard error, LSP returns it in the response, and `%render` lists it after a blank line | [`docs/reference/cli.md`](../reference/cli.md#rendering-a-view), [`docs/reference/repl-commands.md`](../reference/repl-commands.md#rendering-a-view), [`docs/reference/lsp.md`](../reference/lsp.md) | +| CLI, REPL, LSP, VS Code | A table or matrix view takes `csv` or `tsv` as well: `-render -render-form csv\|tsv` (`-render-all` writes `.csv` or `.tsv` for each tabular view and skips every other view), `%render csv\|tsv`, `"form": "csv"` or `"tsv"` on `opensysml/render`. Either is a header record of the columns, then a record per row, fields quoted as RFC 4180 quotes them; a notice is never inside the records: the CLI writes it to standard error, LSP returns it in the response, and `%render` lists it after a blank line | [`docs/reference/cli.md`](../reference/cli.md#rendering-a-view), [`docs/reference/repl-commands.md`](../reference/repl-commands.md#rendering-a-view), [`docs/reference/lsp.md`](../reference/lsp.md) | | Documents | `-render-document`/`-render-documents … -diagram-form mermaid\|dot\|plantuml\|d2`, `%render-document mermaid\|dot\|plantuml\|d2`, `"diagramForm"` on `opensysml/renderDocument`: graph-shaped blocks use Mermaid, DOT, PlantUML or D2 source; HTML carries `data-palette` and `data-style`, and local Mermaid pictures are inlined before source is collected. PDF draws with the selected tool; absent optional DOT/PlantUML/D2 tools leave readable source under a notice, while a missing Mermaid CLI is an error. A `Diagram` block states what is drawn, not the notation; its palette and style apply where the selected form supports them | [`docs/manual/authoring.md`](../manual/authoring.md#diagrams), [`docs/manual/outputs.md`](../manual/outputs.md), [`docs/reference/environment.md`](../reference/environment.md) | | CLI, REPL, LSP, documents | `-render-overlay verdicts` on `-render` and `-render-all`; `%render [...] verdicts`; `"overlay": "verdicts"` on `opensysml/render`, the overlays listed by the `openSysmlRenderOverlays` capability and each node's `verdict` in the reply; `Diagram::overlay` in a document (`invalid-overlay`, `unsupported-overlay` errors) | [`docs/reference/cli.md`](../reference/cli.md#rendering-a-view), [`docs/reference/repl-commands.md`](../reference/repl-commands.md#rendering-a-view), [`docs/reference/lsp.md`](../reference/lsp.md), [`docs/manual/authoring.md`](../manual/authoring.md#diagrams) | @@ -1189,15 +1230,15 @@ and did not change. A view-render RPC added later would take the form as a strin keyword-leading line; the same `
`-joined label in the flowchart, state and sequence Mermaid grammars; the escaping of `<`, `>`, `"` and `#` in a Mermaid label. - `cmd/sysml/render_test.go`, `internal/frontend/repl/view_render_test.go`, `internal/frontend/lsp/render_test.go`: - each form on each surface — DOT refused for a table or sequence, PlantUML and D2 for a table and - written for a sequence; `-render-all` writing `.dot`, `.puml` and `.d2`; palettes accepted by Mermaid + each form on each surface — DOT refused for a table, matrix or sequence; PlantUML and D2 refused for a + table or matrix and written for a sequence; `-render-all` writing `.dot`, `.puml` and `.d2`; palettes accepted by Mermaid and refused by name with the palettes there are. - `internal/ir/docplan`, `docir`, `docrender`: the `Diagram` block's `palette` accepted, refused when unknown (`invalid-palette`) or stated on a kind with no graphical form (`unsupported-palette`), carried into the document IR and onto the HTML figures. - `internal/doc/docrender`, `docpdf`, `cmd/sysml`, `internal/frontend/repl`, `internal/frontend/lsp`: the render-time diagram form defaulting to Mermaid, written as a `dot`, `plantuml` or `d2` fence and a - `
`, `
` or `
` for every graph-shaped block with tables left
+  `
`, `
` or `
` for every graph-shaped block with tabular views left
   as tables, refused for an unknown form and for a kind with no DOT form.
 - `internal/doc/docpdf/diagrams_test.go`, `cmd/sysml/render_document_pdf_test.go`: with fake tools, a DOT block drawn by the `dot` that
   `OPENSYSML_DOT` names, a PlantUML block by `java -jar  -tsvg -pipe` fed on stdin and a D2
@@ -1276,7 +1317,9 @@ and did not change. A view-render RPC added later would take the form as a strin
   walk; a Graphviz installation is used only by hand to look at them.
 - A GeneralView graph is selected only by the filter shapes [its section](#generalview-graphs)
   lists; a conjunction or a user metadata filter keeps the tree even where it would admit only
-  requirements. No requirements table is drawn: a `GridView` is the table.
+  requirements. GeneralView does not also draw a requirements table: a `GridView` without a
+  qualifying relationship filter renders as an element table, while a `GridView` with a positive,
+  resolved relationship selector renders as a relationship matrix.
 - The verdicts overlay runs every verification case verifying a drawn requirement each time it is
   drawn; a workspace (the LSP) runs them over the declared model, without the runtime a REPL
   session or document keeps.
diff --git a/docs/reference/api.md b/docs/reference/api.md
index bd1ffe15b1..484525606f 100644
--- a/docs/reference/api.md
+++ b/docs/reference/api.md
@@ -1208,7 +1208,7 @@ HTML needs the `render_document_html` capability, and PDF is not offered, since
 it needs the CLI's converter toolchain.
 
 `RenderView` answers a named view or targeted pseudo-view as ordered nodes,
-edges, table rows, notes, source spans and optional canvas/geometry/style data,
+edges, table and matrix rows, notes, source spans and optional canvas/geometry/style data,
 using the engine renderer's `view.Data` rather than diagram pictures. Its
 `ports` field is empty or `minimal` by default; `full` includes all declared
 ports. It is advertised by `render_view`, and a service without that capability
diff --git a/docs/reference/cli.md b/docs/reference/cli.md
index 9ea962cf0a..e95e22a18b 100644
--- a/docs/reference/cli.md
+++ b/docs/reference/cli.md
@@ -244,16 +244,16 @@ the same member-path parser as `Project` and `OrderBy`.
 | `--image-base-url ` | | With `--migrate`: the absolute http(s) URL a comment's relative `` — a path the View Editor serves, such as `/projects/.../png` — is resolved against, so the migrated document's `Image` block points at the server instead of losing the image (see [SysML v1 migration](sysml-v1-migration.md)) |
 | `--render ` | | Render this view of the model (every file named, loaded as one) instead of running it, in the form its `render` member states (see [Rendering a view](#rendering-a-view)) |
 | `--render-all ` | | Render every declared view into the directory, one artifact per view |
-| `--render-form ` | | Form `--render` or `--render-all` writes: `text`, `mermaid`, `markdown`, `dot`, `plantuml`, `d2`, `csv` or `tsv` (default: destination-dependent for `--render`, each kind's machine-readable form for `--render-all`). D2 writes `tree`, `interconnection`, `state`, `action` and `sequence` renderings; case and mixed views refuse it with a typed error |
+| `--render-form ` | | Form `--render` or `--render-all` writes: `text`, `mermaid`, `markdown`, `dot`, `plantuml`, `d2`, `csv` or `tsv` (default: destination-dependent for `--render`, each kind's machine-readable form for `--render-all`). D2 writes `tree`, `interconnection`, `state`, `action`, `sequence`, `requirement`, `definition` and `package` renderings; case and mixed views refuse it with a typed error |
 | `--render-palette ` | | Palette the `dot`, `mermaid`, `plantuml` or `d2` form of `--render` or `--render-all` fills nodes with, by keyword family: `okabe-ito`, `tol-bright`, `tol-muted`, `tol-light`, `brewer-set2`, `brewer-dark2`, `viridis` or `cividis`; black and white when absent. Mermaid sequence diagrams cannot fill individual participants; text and Markdown ignore palettes. An unknown name is refused with the names there are (see [Rendering a view](#rendering-a-view)) |
 | `--render-link