From fdde672ef1eb0cd99a7348dabb66118023e69119 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Sun, 4 Oct 2026 02:35:47 +0000 Subject: [PATCH 1/6] fix(parser): reject frame, filter and accept members outside the bodies that own them Co-Authored-By: jason.han --- .../testing-pilot-corpora-gate/SKILL.md | 4 +- .../testing-pilot-differential/SKILL.md | 8 +- .../testing-pilot-execution-referee/SKILL.md | 4 +- .../skills/testing-pilot-rejection/SKILL.md | 4 +- .agents/skills/testing-pilot-xpect/SKILL.md | 4 +- .agents/skills/testing-sysml-repl/SKILL.md | 2 +- README.md | 10 +- .../view-body-member-grammar.fixed.md | 3 + docs/guide/04-repl.md | 13 +- docs/internals/architecture.md | 6 +- docs/project/pilot-differential-baseline.json | 74 ++------ docs/project/pilot-differential.md | 34 ++-- docs/project/pilot-rejection-baseline.json | 69 +++++-- docs/project/pilot-rejection.md | 13 +- docs/project/spec-compliance.md | 2 +- docs/reference/repl-commands.md | 2 - examples/disposal-robot-demo/robot.sysml | 4 +- examples/self-model/views.sysml | 1 - examples/views-demo.sysml | 5 +- internal/check/passes/nonstandard_notation.go | 50 ++++- .../check/passes/nonstandard_notation_test.go | 23 +++ internal/frontend/repl/view_test.go | 14 +- .../semantic/resolve/accept_payload_test.go | 6 +- .../semantic/resolve/event_feature_test.go | 2 +- internal/semantic/semantics/conformance.go | 113 ++--------- .../semantic/semantics/conformance_test.go | 132 +++++-------- internal/syntax/parser/behavior.go | 22 ++- internal/syntax/parser/defusage.go | 26 ++- internal/syntax/parser/namespace.go | 33 ++-- internal/syntax/parser/owned_member_test.go | 26 ++- internal/syntax/parser/parser.go | 19 +- ...imported_references.canonical.golden.sysml | 2 +- .../convert/imported_references.golden.sysml | 2 +- .../convert/imported_references.golden.ttl | 179 +++++++++--------- .../convert/imported_references.sysml | 2 +- tests/parser/negative_test.go | 15 ++ ...arget-succession-outside-action-body.sysml | 7 + .../grammar/g82-frame-in-view-body.sysml | 7 + .../grammar/g83-frame-in-view-def-body.sysml | 7 + ...-filter-outside-package-or-view-body.sysml | 6 + .../g85-accept-outside-action-body.sysml | 7 + 41 files changed, 512 insertions(+), 450 deletions(-) create mode 100644 changes/unreleased/view-body-member-grammar.fixed.md create mode 100644 tools/referee/reject/testdata/negative/extensions/x11-target-succession-outside-action-body.sysml create mode 100644 tools/referee/reject/testdata/negative/grammar/g82-frame-in-view-body.sysml create mode 100644 tools/referee/reject/testdata/negative/grammar/g83-frame-in-view-def-body.sysml create mode 100644 tools/referee/reject/testdata/negative/grammar/g84-filter-outside-package-or-view-body.sysml create mode 100644 tools/referee/reject/testdata/negative/grammar/g85-accept-outside-action-body.sysml diff --git a/.agents/skills/testing-pilot-corpora-gate/SKILL.md b/.agents/skills/testing-pilot-corpora-gate/SKILL.md index 8ddb963cb5..9b02cfdf5f 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 — `381 file(s), 343 fully agreeing; 38 agreed -diagnostic(s), 43 only ours, 1629 only the pilot's` at the `2026-08` pin, so read it from +the headline the committed baseline holds — `381 file(s), 345 fully agreeing; 38 agreed +diagnostic(s), 43 only ours, 1623 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 706cdd00b6..2642a54fe7 100644 --- a/.agents/skills/testing-pilot-differential/SKILL.md +++ b/.agents/skills/testing-pilot-differential/SKILL.md @@ -23,8 +23,8 @@ GNU-format diagnostics **relative to `--root`**. Consequences for testing: - `-validator /nonexistent` now says `run ./scripts/download-pilot-sysml-validator.sh`. - Measured at the `2026-08` pin after bare parameters took their effective range `[0..*]`, removing the adjudicated `Behaviors.kerml:14` multiplicity warning (the `[1]` `RocketEquation` inputs keep - its warning at `delta-v-budget.sysml:93`): `381 file(s), 343 fully agreeing; 38 agreed, 43 only - ours, 1629 only the pilot's`, JSON totals `openSysMLDiagnostics 83 / pilotDiagnostics 1669 / + its warning at `delta-v-budget.sysml:93`): `381 file(s), 345 fully agreeing; 38 agreed, 43 only + ours, 1623 only the pilot's`, JSON totals `openSysMLDiagnostics 83 / pilotDiagnostics 1663 / severityMismatch 2`; the two new only-ours rows 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 @@ -146,8 +146,8 @@ parameters took their effective range `[0..*]` and removed the adjudicated `Beha 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`; a live run gives `381 file(s), 343 fully -agreeing; 38 agreed, 43 only ours, 1629 only the pilot's`, byte-identical to the committed baseline, and +`takePicture[*]` in `Camera Example/Camera.sysml`; a live run gives `381 file(s), 345 fully +agreeing; 38 agreed, 43 only ours, 1623 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 7b9d620281..e57b6b256e 100644 --- a/.agents/skills/testing-pilot-execution-referee/SKILL.md +++ b/.agents/skills/testing-pilot-execution-referee/SKILL.md @@ -148,8 +148,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 (`381 file(s), 343 fully agreeing; 38 agreed - diagnostic(s), 43 only ours, 1629 only the pilot's` at the `2026-08` pin — read it from the baseline JSON, not from this line, since each + committed baseline holds (`381 file(s), 345 fully agreeing; 38 agreed + diagnostic(s), 43 only ours, 1623 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-rejection/SKILL.md b/.agents/skills/testing-pilot-rejection/SKILL.md index 6cb2c1dd44..db3f258650 100644 --- a/.agents/skills/testing-pilot-rejection/SKILL.md +++ b/.agents/skills/testing-pilot-rejection/SKILL.md @@ -9,7 +9,7 @@ Sibling of `testing-pilot-differential` and `testing-pilot-xpect` (same pin `scripts/pilot-pin.sh`, same committed-baseline shape), but pointed the other way: the differential measures what the reference accepts and we reject; this oracle measures what the reference **rejects and we accept** — permissiveness gaps. Its corpus is committed under -`tools/referee/reject/testdata/negative/` (312 hand-written invalid models, one violated rule + citation +`tools/referee/reject/testdata/negative/` (317 hand-written invalid models, one violated rule + citation in each file's mandatory `// Invalid: ...` first line), so no corpus download exists. Method and findings: `docs/project/pilot-rejection.md`. @@ -36,7 +36,7 @@ succession rules, the feature-value overriding rule, the enumeration-variation r the metadata typing, annotated-element and body rules, the trigger-argument typing rules, the owning-body member rules, the cross-subsetting rules, the variant port rule, the association arity, binary-link end count and multiplicity-bound typing rules, the result-expression ownership rules, and the annotation-ownership, binding-arity, feature-chain conformance, conjugated typing and end-feature rules, the end-multiplicity, return-owner and single-conjugator rules, the keyword-first relationship end kinds, the prefix and body-context cases, the redefinition name-resolution cases, the transition accept usage-typing rule, and the connector-end accessibility of a qualified definition feature), with a fresh library cache: -`312 case(s): 303 both reject, 0 only the pilot rejects, 9 only we reject, 0 both accept`, +`317 case(s): 308 both reject, 0 only the pilot rejects, 9 only we reject, 0 both accept`, byte-identical to the committed baseline. Any `both accept` case is a bug in the corpus (the case is not actually invalid under the loaded standard library) — fix the case, never ignore it. A candidate the pilot accepts because it does not enforce the named constraint is not a case either: diff --git a/.agents/skills/testing-pilot-xpect/SKILL.md b/.agents/skills/testing-pilot-xpect/SKILL.md index 3d64189a1f..d4e26820c2 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 `381 file(s), 343 fully agreeing; 38 agreed diagnostic(s), 43 -only ours, 1629 only the pilot's`. Read the number out of +at the `2026-08` pin that is `381 file(s), 345 fully agreeing; 38 agreed diagnostic(s), 43 +only ours, 1623 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/.agents/skills/testing-sysml-repl/SKILL.md b/.agents/skills/testing-sysml-repl/SKILL.md index 1d5532d7ea..a43f741167 100644 --- a/.agents/skills/testing-sysml-repl/SKILL.md +++ b/.agents/skills/testing-sysml-repl/SKILL.md @@ -4223,7 +4223,7 @@ Read-only printer of the session buffer or one element: `%view Demo::report` prints `exposes` then `viewpoint conformance` with `satisfy structure (from Demo::StructureView): violated` and one line per concern -(`conforms` / `violated (framed by the viewpoint but not by the view)` / `unevaluable` + reason). +(`conforms` / `violated` / `unevaluable`, each with its reason where it has one). A satisfy target that is a `requirementUsage` is diagnosed at load *and* reported as `unevaluable (satisfy target spec is a requirementUsage, not a viewpoint)` — never a silent pass. `%view` registers no object: `%instances` afterwards says none created, a repeat is identical, and a diff --git a/README.md b/README.md index 74280fe783..a702358c75 100644 --- a/README.md +++ b/README.md @@ -312,11 +312,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:** 343 of 381 files agree diagnostic-by-diagnostic; 43 diagnostics are ours alone and 1629 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:** 345 of 381 files agree diagnostic-by-diagnostic; 43 diagnostics are ours alone and 1623 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 312 invalid models we wrote ourselves, the reference rejects 3 that we accept by default, and 300 both reject; 3 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 344 of 381 files agreeing, 42 diagnostics ours alone and 1629 the reference's alone, 0 declared rows we are silent on, and 0 of 312 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. +- **Permissiveness gaps:** of 317 invalid models we wrote ourselves, the reference rejects 3 that we accept by default, and 305 both reject; 3 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 346 of 381 files agreeing, 42 diagnostics ours alone and 1623 the reference's alone, 0 declared rows we are silent on, and 0 of 317 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. @@ -328,8 +328,8 @@ 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:** 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). **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:** 381 files compared diagnostic-by-diagnostic against the pinned OMG pilot implementation (`2026-08`), 343 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. +**Reference differential:** 381 files compared diagnostic-by-diagnostic against the pinned OMG pilot implementation (`2026-08`), 345 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? 317 hand-written invalid models validated by both implementations, 308 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/view-body-member-grammar.fixed.md b/changes/unreleased/view-body-member-grammar.fixed.md new file mode 100644 index 0000000000..5856f101bf --- /dev/null +++ b/changes/unreleased/view-body-member-grammar.fixed.md @@ -0,0 +1,3 @@ +- **Reject `frame`, `filter` and `accept` members written outside the bodies that own them.** A `frame` is a RequirementBodyItem — a requirement, concern or viewpoint body, never a view's; a `filter` is a PackageBodyElement and a view/view-def body item — a package or the file root admits it, a viewpoint, rendering or part-def body does not; an `accept` is an ActionBodyItem — an action, calculation or case body (and the state body that already read it), not a view or part def. Each is now reported at the member instead of parsing silently, matching the reference grammar. +- **Report `then ;`, `if then ;` and `else ;` written outside an action or state body.** A target succession sequences members of an action, calculation or case body, and a state body reads it as a target transition; anywhere else it is OpenSysML extension notation, warned by default and rejected under `-strict`. +- **`%view` no longer asks a view to frame the concerns its satisfied viewpoint frames.** Viewpoint conformance is evaluated over the concern the *viewpoint* frames, applied to the elements the view exposes; `frame` was never legal in a view body, so a view with a valid viewpoint now reports `conforms`/`violated` instead of a framing mismatch it could never satisfy. diff --git a/docs/guide/04-repl.md b/docs/guide/04-repl.md index 22f15972f7..b99c719b02 100644 --- a/docs/guide/04-repl.md +++ b/docs/guide/04-repl.md @@ -210,11 +210,12 @@ conforms to the viewpoints it satisfies. Conformance checking is read-only and r declaration order. Each `satisfy` gets a verdict of `conforms`, `violated` or `unevaluable`, followed by a verdict for each concern the viewpoint frames; a concern whose condition fails names the exposed element it failed for and the reason. A `satisfy` inherited from a -specialized view is marked `(from )`. A concern the viewpoint frames but the view does -not is reported as `violated`. A concern that states no condition, or names one that does not -resolve, is reported as `unevaluable` with the reason, not as a pass. These verdicts, and -the way a nested view's framing is treated, are this implementation's own decisions, because -SysML v2 leaves verification verdict semantics non-normative. +specialized view is marked `(from )`. A view frames nothing of its own — a view body +admits no `frame` member — so each concern the viewpoint frames is checked against the exposed +elements directly. A concern that states no condition, or names one that does not +resolve, is reported as `unevaluable` with the reason, not as a pass. These verdicts are this +implementation's own decisions, because SysML v2 leaves verification verdict semantics +non-normative. ``` sysml> package Demo { @@ -250,8 +251,6 @@ sysml> package Demo { ...> viewpoint structure : StructurePerspective; ...> view def StructureView { ...> satisfy structure; - ...> frame concern budget : MassBudget; - ...> frame concern modularity : Modularity; ...> } ...> view report : StructureView { ...> expose vehicle; diff --git a/docs/internals/architecture.md b/docs/internals/architecture.md index b5b3e11301..946d2fff2f 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:** 343 of 381 files agree diagnostic-by-diagnostic; 43 diagnostics are ours alone and 1629 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:** 345 of 381 files agree diagnostic-by-diagnostic; 43 diagnostics are ours alone and 1623 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 312 invalid models we wrote ourselves, the reference rejects 3 that we accept by default, and 300 both reject; 3 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 344 of 381 files agreeing, 42 diagnostics ours alone and 1629 the reference's alone, 0 declared rows we are silent on, and 0 of 312 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. +- **Permissiveness gaps:** of 317 invalid models we wrote ourselves, the reference rejects 3 that we accept by default, and 305 both reject; 3 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 346 of 381 files agreeing, 42 diagnostics ours alone and 1623 the reference's alone, 0 declared rows we are silent on, and 0 of 317 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 1f476f0c21..9ded5708e1 100644 --- a/docs/project/pilot-differential-baseline.json +++ b/docs/project/pilot-differential-baseline.json @@ -61,7 +61,7 @@ "dir": "examples", "origin": "ours", "files": 45, - "digest": "sha256:8b0d334dcd91c03c9e2b12bc9be59204e0ec714520deaab52d81e3454c2c4744" + "digest": "sha256:2f20e7f105673eb0f3705cd4bc0ed06a00c2953e3aab318f140cc1b4ec6c6c1d" }, { "name": "probes", @@ -71,17 +71,17 @@ "digest": "sha256:b0153c55bbfcdacabab911725dc44e0e7f4d3a501a281b1f3f2a13cda19737c6" } ], - "recorded": "2026-10-03" + "recorded": "2026-10-04" }, "totals": { "files": 381, - "filesFullyAgreeing": 343, + "filesFullyAgreeing": 345, "agreement": 38, "severityMismatch": 2, "openSysMLOnly": 43, - "pilotOnly": 1629, + "pilotOnly": 1623, "openSysMLDiagnostics": 83, - "pilotDiagnostics": 1669 + "pilotDiagnostics": 1663 }, "roots": [ { @@ -861,13 +861,13 @@ "dir": "examples", "totals": { "files": 45, - "filesFullyAgreeing": 30, + "filesFullyAgreeing": 32, "agreement": 4, "severityMismatch": 1, "openSysMLOnly": 6, - "pilotOnly": 1596, + "pilotOnly": 1590, "openSysMLDiagnostics": 11, - "pilotDiagnostics": 1601 + "pilotDiagnostics": 1595 }, "files": [ { @@ -2623,18 +2623,6 @@ "severity": "error", "category": "unmapped", "count": 1 - }, - { - "line": 625, - "severity": "error", - "category": "syntax", - "count": 1 - }, - { - "line": 627, - "severity": "error", - "category": "syntax", - "count": 1 } ] }, @@ -7661,26 +7649,6 @@ } ] }, - { - "path": "self-model/views.sysml", - "agreement": [], - "severityMismatch": [], - "openSysMLOnly": [], - "pilotOnly": [ - { - "line": 197, - "severity": "error", - "category": "syntax", - "count": 1 - }, - { - "line": 199, - "severity": "error", - "category": "syntax", - "count": 1 - } - ] - }, { "path": "solver-demo.sysml", "agreement": [], @@ -7984,26 +7952,6 @@ "count": 1 } ] - }, - { - "path": "views-demo.sysml", - "agreement": [], - "severityMismatch": [], - "openSysMLOnly": [], - "pilotOnly": [ - { - "line": 176, - "severity": "error", - "category": "syntax", - "count": 1 - }, - { - "line": 178, - "severity": "error", - "category": "syntax", - "count": 1 - } - ] } ] }, @@ -8446,13 +8394,13 @@ ], "totals": { "files": 381, - "filesFullyAgreeing": 344, + "filesFullyAgreeing": 346, "agreement": 38, "severityMismatch": 2, "openSysMLOnly": 42, - "pilotOnly": 1629, + "pilotOnly": 1623, "openSysMLDiagnostics": 82, - "pilotDiagnostics": 1669 + "pilotDiagnostics": 1663 }, "findings": [ { diff --git a/docs/project/pilot-differential.md b/docs/project/pilot-differential.md index be743c9d10..95617424f4 100644 --- a/docs/project/pilot-differential.md +++ b/docs/project/pilot-differential.md @@ -237,9 +237,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` | 19 | 10 | 44 | 68 | 34 | 1 | 9 | 33 | -| `examples` | 45 | 30 | 11 | 1601 | 4 | 1 | 6 | 1596 | +| `examples` | 45 | 32 | 11 | 1595 | 4 | 1 | 6 | 1590 | | `tools/referee/diff/testdata` (probes) | 4 | 1 | 6 | 0 | 0 | 0 | 6 | 0 | -| **Total** | **381** | **343** | **83** | **1669** | **38** | **2** | **43** | **1629** | +| **Total** | **381** | **345** | **83** | **1663** | **38** | **2** | **43** | **1623** | **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 @@ -281,7 +281,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`, 2 `multiplicity`; `probes` 6 `unmapped`. Only-pilot: `testdata` 20 `kind-mismatch`, 3 `unmapped`, 3 `syntax`, 7 -`unresolved-reference`; `examples` 6 `syntax`, 29 `unmapped`, 673 `kind-mismatch`, 888 +`unresolved-reference`; `examples` 29 `unmapped`, 673 `kind-mismatch`, 888 `unresolved-reference`. ### Legend of the Red Dragon departure round @@ -742,9 +742,9 @@ no parser, validator or runtime code — every demo's `%`-command output is unch |---|---|---:| | `action-executor-demo.sysml` | `then done;` in place of a standalone `done;` declaration | 3 → **0** | | `phase-c-behavioral-bodies.sysml` | `entry`/`do`/`exit ` and named effect actions; declared Boolean features accepted with `accept when` | 3 → **0** | -| `views-demo.sysml` | the descent flow as `first`/`fork`/`join`/`decide` with successions; `Descender::mass` declared without a default the two landers rebind; the framing view declared last | 8 → **2** | +| `views-demo.sysml` | the descent flow as `first`/`fork`/`join`/`decide` with successions; `Descender::mass` declared without a default the two landers rebind | 8 → **0** | | `solver-demo.sysml` | `assert constraint` for an analysis case's own conditions; each objective redefines the subject it inherits | 15 → **5** | -| `disposal-robot-demo/robot.sysml` | the same objective subject redefinition; the framing view declared last | 17 → **7** | +| `disposal-robot-demo/robot.sysml` | the same objective subject redefinition | 17 → **1** | | `pseudostates-demo.sysml` | a state named `ready` rather than one shadowing the library's `start` | 8 → **7** | `require ` outside a requirement body and a standalone `done;` are the two the audit @@ -771,11 +771,11 @@ What remains is adjudicated as extension notation this project supports delibera The exemption is the analysis case alone; a `case`, `verification` or `use case` declaration with a second objective is reported as the reference reports it, since no other case kind has the lexicographic semantics. -- **`frame concern` in a view usage** (`views-demo.sysml`, `robot.sysml`) — `FramedConcernMember` is - a requirement-body member in the pilot grammar, not a view-body one, and the demos frame a concern - in the view because that is what `%view` evaluates the exposed elements against. Declaring the - framing view last confines the reference's recovery to the file's closing lines, 2 syntax rows - each instead of the whole view package. +The `frame concern` member the demos once wrote in a view usage is retired, not adjudicated: +`FramedConcernMember` is a requirement-body member in the pilot grammar, not a view-body one, so we +now reject it the way the reference always did, and `%view` evaluates the exposed elements against +the concern the satisfied viewpoint frames instead. `views-demo.sysml` and `robot.sysml` dropped the +member, and the 2 syntax rows each are gone with it. The `testdata` fixtures the same notation appears in are unchanged: `passes/import_no_visibility.sysml` and `parse/namespaces.sysml` exist to exercise the diagnostics they carry, so their rows stay @@ -827,8 +827,8 @@ cascades through the rest of the file. The movement is entirely one file, | Count | Before the initializer rewrite | Now | |---|---:|---:| -| only pilot | 82 | **1629** | -| pilot diagnostics | 123 | **1669** | +| only pilot | 82 | **1623** | +| pilot diagnostics | 123 | **1663** | | severity-only | 9 | **2** | The rewrite itself took only-pilot to 61 and pilot diagnostics to 101; the `Now` column states @@ -853,7 +853,7 @@ view and analysis packages, and each is a construct the pinned artifact does not | What the demo writes | Rows | What the pilot reports | |---|---:|---| -| `frame concern` in a view usage | 2 syntax | the member is not in its view grammar, and the cascade takes the file's closing brace | +| `frame concern` in a view usage (removed since — a view frames nothing; the satisfied viewpoint does, and we reject the member too) | 2 → **0** syntax | the member is not in its view grammar, and the cascade takes the file's closing brace | | `view … : StateTransitionView` / `: ActionFlowView`, `render asElementTable` | 3 `unresolved-reference`, 4 `kind-mismatch` | our standard view definitions and rendering, which its libraries do not publish | | `objective … { require constraint … }` with `attribute :>> best` | 6 `unmapped` | one subject per requirement, no rebinding of `best`, one objective per analysis case (the rebinding rows retired with the objective-evaluation round, which states the value as the library's `eval`) | | a second objective for a lexicographic optimum | 2 `unmapped` | `Only one objective is allowed` | @@ -1051,14 +1051,14 @@ page's history. | Count | Now | |---|---:| -| overall: fully agreeing / only ours / our diagnostics | **343 / 43 / 83** | -| only pilot | **1629** | -| pilot diagnostics | **1669** | +| overall: fully agreeing / only ours / our diagnostics | **345 / 43 / 83** | +| only pilot | **1623** | +| pilot diagnostics | **1663** | | severity-only | **2** | | unmapped, our side | **35** | | kerml-examples: only ours | **9** | | pilot-examples: only ours | **12** | -| examples: only pilot | **1596** | +| examples: only pilot | **1590** | 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/pilot-rejection-baseline.json b/docs/project/pilot-rejection-baseline.json index 1588d4c916..347a021fda 100644 --- a/docs/project/pilot-rejection-baseline.json +++ b/docs/project/pilot-rejection-baseline.json @@ -27,15 +27,15 @@ "name": "negative-corpus", "dir": "tools/referee/reject/testdata/negative", "origin": "ours", - "files": 312, - "digest": "sha256:2e30292021138a84093d0a233f0d20f3796cf641ec3e9f7edaf291ab4bb1bbbd" + "files": 317, + "digest": "sha256:2e15a8018f95e879039f9c9fd7eb31b87ddd04437df53080239bebc3f5185ba7" } ], - "recorded": "2026-10-02" + "recorded": "2026-10-04" }, "totals": { - "cases": 312, - "bothReject": 303, + "cases": 317, + "bothReject": 308, "pilotOnlyRejects": 0, "oursOnlyRejects": 9, "bothAccept": 0 @@ -48,16 +48,16 @@ "sources": [ { "source": "extensions", - "cases": 10, - "bothReject": 10, + "cases": 11, + "bothReject": 11, "pilotOnlyRejects": 0, "oursOnlyRejects": 0, "bothAccept": 0 }, { "source": "grammar", - "cases": 108, - "bothReject": 108, + "cases": 112, + "bothReject": 112, "pilotOnlyRejects": 0, "oursOnlyRejects": 0, "bothAccept": 0 @@ -187,6 +187,17 @@ "pilotErrors": 3, "defaultBucket": "pilot-only-rejects" }, + { + "path": "extensions/x11-target-succession-outside-action-body.sysml", + "source": "extensions", + "rule": "TargetSuccessionMember is an ActionBodyItem; a view body admits no 'then \u003ctarget\u003e;' succession (SysML.xtext:1367 ActionBodyItem, :1693 TargetSuccessionMember, :2359 ViewBodyItem).", + "mode": "strict", + "bucket": "both-reject", + "oursErrors": 2, + "pilotErrors": 2, + "defaultBucket": "both-reject", + "defaultErrors": 1 + }, { "path": "grammar/g01-unclosed-package-body.sysml", "source": "grammar", @@ -916,6 +927,42 @@ "oursErrors": 1, "pilotErrors": 3 }, + { + "path": "grammar/g82-frame-in-view-body.sysml", + "source": "grammar", + "rule": "FramedConcernMember is a RequirementBodyItem; a view body admits no 'frame' (SysML.xtext:2062 RequirementBodyItem, :2359 ViewBodyItem).", + "mode": "default", + "bucket": "both-reject", + "oursErrors": 1, + "pilotErrors": 2 + }, + { + "path": "grammar/g83-frame-in-view-def-body.sysml", + "source": "grammar", + "rule": "FramedConcernMember is a RequirementBodyItem; a view def body admits no 'frame' (SysML.xtext:2062 RequirementBodyItem, :2329 ViewDefinitionBodyItem).", + "mode": "default", + "bucket": "both-reject", + "oursErrors": 1, + "pilotErrors": 2 + }, + { + "path": "grammar/g84-filter-outside-package-or-view-body.sysml", + "source": "grammar", + "rule": "ElementFilterMember is a PackageBodyElement, ViewDefinitionBodyItem and ViewBodyItem; a rendering def body admits no 'filter' (SysML.xtext:227 ElementFilterMember, :2329 ViewDefinitionBodyItem, :2359 ViewBodyItem).", + "mode": "default", + "bucket": "both-reject", + "oursErrors": 1, + "pilotErrors": 2 + }, + { + "path": "grammar/g85-accept-outside-action-body.sysml", + "source": "grammar", + "rule": "AcceptNode is an ActionBodyItem; a view body admits no 'accept' (SysML.xtext:1367 ActionBodyItem, :1434 AcceptNode, :2359 ViewBodyItem).", + "mode": "default", + "bucket": "both-reject", + "oursErrors": 1, + "pilotErrors": 2 + }, { "path": "grammar/k01-specializes-without-target.kerml", "source": "grammar", @@ -3037,8 +3084,8 @@ } ], "totals": { - "cases": 312, - "bothReject": 303, + "cases": 317, + "bothReject": 308, "pilotOnlyRejects": 0, "oursOnlyRejects": 9, "bothAccept": 0 diff --git a/docs/project/pilot-rejection.md b/docs/project/pilot-rejection.md index 20ff07ed47..9020adf8fb 100644 --- a/docs/project/pilot-rejection.md +++ b/docs/project/pilot-rejection.md @@ -116,7 +116,7 @@ systematically from four sources, one subdirectory each: pilot's grammar rejects before its validator would), and a constructed payload whose `new` names a package rather than a type (`send-constructor-non-type`). -What this corpus cannot see: it tests the invalid models we thought to write. **We authored all 312 +What this corpus cannot see: it tests the invalid models we thought to write. **We authored all 317 cases ourselves**, so the denominator measures our coverage of the rejection surface, not our conformance: it is a **sample, not a proof** — a clean bucket here does not mean OpenSysML rejects everything the reference rejects, and no official conformance suite exists to make that claim @@ -164,14 +164,14 @@ measured at their own round and are not the current baseline. Under the default `-conformance auto`: ``` -312 case(s): 303 both reject, 0 only the pilot rejects, 9 only we reject, 0 both accept +317 case(s): 308 both reject, 0 only the pilot rejects, 9 only we reject, 0 both accept of which 3 agree only because we were asked strictly (the default mode accepts them, by design) ``` | Source | Cases | Both reject | Pilot only | Ours only | Both accept | | --- | --- | --- | --- | --- | --- | -| extensions | 10 | 10 | 0 | 0 | 0 | -| grammar | 108 | 108 | 0 | 0 | 0 | +| extensions | 11 | 11 | 0 | 0 | 0 | +| grammar | 112 | 112 | 0 | 0 | 0 | | semantic | 159 | 150 | 0 | 9 | 0 | | xpect | 35 | 35 | 0 | 0 | 0 | @@ -236,6 +236,9 @@ The action-body parameter prefix-order cases `g80`–`g81` then raised the corpu `allocate ToastBread::applyHeat to Toaster::heating`; a connector with no featuring type reaches only features that have none, so both validators reject each end under `validateConnectorTypeFeaturing`) to 311, and `x10` (KerML's `connector` declared in a `.sysml` file, which the pinned SysML grammar has no production for — `no viable alternative at input 'c'` — and which we warn as `kerml-notation` by default and reject under strict) to 312. +The owning-body members `g82`–`g85` (`frame` in a view and a view def body, `filter` in a +rendering body, `accept` in a view body — each a syntax error under the pinned grammar) and `x11` +(`then ;` in a view body, a succession member no non-action body admits) brought it to 317. The KerML constraints in that source reopened 14 gaps — all of them semantic rules the pilot enforces and we did not; the named-argument validation that landed alongside closed one of them (`k33`), the constructor @@ -280,7 +283,7 @@ when it was first written, six were closed by the validation work itself — `p0 Read those two as agreement *when asked strictly*, not as gaps that disappeared. An opt-in check is weaker evidence than a default one: it says the strict question has an answer we agree on, not that the pipeline a user gets by default rejects the notation — by design it does not. And -because we authored all 312 cases ourselves, a small gap count means we ran out of questions we +because we authored all 317 cases ourselves, a small gap count means we ran out of questions we thought to ask, not that we stopped being permissive: the denominator measures our coverage of the rejection surface, not our conformance. diff --git a/docs/project/spec-compliance.md b/docs/project/spec-compliance.md index 9d5a16843b..55fa61e99c 100644 --- a/docs/project/spec-compliance.md +++ b/docs/project/spec-compliance.md @@ -2104,7 +2104,7 @@ semantics layer over the conjugation parity of the typing/specialization chain. | A diagram node's label follows the graphical notation's header (SysML v2 §8.2.2.2): the element's name first, with ` : Type` after it for a typed usage, the kind on the next line in guillemets, and notes after that; an anonymous element leads with its kind and has no keyword line. The declared type is carried separately as `Node.Type`, the detail carries notes alone, and no writer parses type out of detail. Mermaid flowchart nodes and subgraph titles use Markdown only when every label line is safe: head lines are bold, the keyword is italic, details are plain, separated by real newlines; punctuation, list-like prefixes, non-multiplicity `*` and non-intraword `_` fall back to the escaped plain label. State, sequence and edge labels remain unchanged; flowchart titles add `subGraphTitleMargin` frontmatter when necessary. DOT uses HTML-like labels, PlantUML uses creole, and text keeps keyword-leading declaration order | `view/label.go` `labelHead`, `labelLines`; `view/mermaid.go` `mermaidLabel`, `writeFlowchartFrontmatter`; `view/dot.go` `dotLabel`, `dotEscape`, `dotLabelExtent`; `view/plantuml.go` `plantumlLabel`, `plantumlText`; `view/text.go` `writeNodeText`; `view/view.go` `Node.Type`; `lsp/render.go` `renderNode.Type`; `editors/vscode/src/protocol.ts` `RenderNode.type` | `view/label_test.go`; `view/mermaid_test.go:TestMermaidMarkdownLabelsFallBackWhenUnsafe`; `view/render_test.go:TestMermaidLabelsAreEscaped`; `view/testdata/*.{mermaid,dot,plantuml,text}.golden`; `docrender/testdata/telescope_report.*.golden.*` | ✅ Faithful to the notation; Mermaid Markdown labels fall back when unsafe | | `%render [text\|mermaid\|markdown\|dot\|plantuml\|csv\|tsv [palette]]` renders a declared or pseudo-view at the prompt; `sysml -render [-render-form
] [-render-palette ] [-o file]` renders one outside it, and `sysml -render-all [-render-form ]` writes every declared view separately | `repl/view.go` `Session.ViewRendering`, `renderPseudoView`, `Views`, `renderLines`, `viewRenderer` (a semantic model and resolver of its own over the session's symbol index, so no runtime is built); `model/selection.go` shares declaration-order view enumeration and targetless top-level selection across model and session paths; `repl/meta.go` (the `%render` arm, help entry), `repl/complete.go` (the form completion); `cmd/sysml/render.go` `runRender`, `runRenderAll`, `loadRenderingModel`, `writeArtifactFile` (one artifact on stdout or `-o`, or one file per declared view with render-all reporting on stderr); `cmd/sysml/main.go` (rendering modes are mutually exclusive with conversion and checks) | `repl/view_render_test.go` `TestRenderDefaultsToATree`, `TestRenderWritesMermaidWhenAskedFor`, `TestRenderOfATabularView`, `TestRenderOfAnUnknownNameReports`, `TestPseudoViewsRenderThroughTheSession`, `TestPseudoViewErrorsUseTheSessionLookupAndListAlternatives`, `TestTargetlessPseudoViewSpansLoadedDocuments`, `TestViewsListsSessionViewsInDeclarationOrder`, `TestRenderMisuseShowsUsage`, `TestRenderIsInHelpAndCompletion`, `TestRenderBetweenStepsDisturbsNothing`; `cmd/sysml/render_test.go` `TestRenderWritesTheArtifactOnStdout`, `TestRenderTextFormAndOutputFile`, `TestRenderOfATabularView`, `TestRenderAllWritesOneMachineArtifactPerView`, `TestRenderAllForcedTextUsesTxtAndUnboundedWidth`, `TestRenderAllWithoutViewsIsUnevaluable`, `TestRenderAllMutualExclusions`, `TestRenderOfATableAsDelimitedValues`, `TestRenderAllForcedDelimitedFormWritesTablesOnly`; `view/delimited_test.go` | ✅ Faithful (`%render` is a read: it materializes nothing, adds no object to the session, leaves the submission buffer alone and leaves an `%action`/`%state` debugging session stepping the same graph and objects — pinned by `TestRenderBetweenStepsDisturbsNothing`. A targeted pseudo-view uses the session's normal simple or qualified lookup; a targetless one renders loaded documents in order. `%view`'s report is unchanged) | | Diagram geometry in notation — an OpenSysML extension, not a specification rule: `DiagramLayout::Layout` (`x`, `y`, optional `width`, `height`, `collapsed`) positions the element it annotates, `DiagramLayout::Route` (`points`, a flattened `x0, y0, x1, y1, …` sequence, `ordered nonunique` since a segment repeats a coordinate) steers the edge whose declaring element it annotates, `DiagramLayout::Canvas` (`unit`, `width`, `height`) sizes a view's drawing surface; pixels, y down, origin top-left. For a (view, element) pair an `about` annotation declared in the view's body applies first, the element's own inline annotation second, none third; a view's second `about` annotation of one kind for one element is ignored in favor of the first. The geometry rides the rendering tree as values (`view.Node.Geometry`, `view.Edge.Route`, `view.Rendering.Canvas`) in every graph-shaped rendering — tree, interconnection, state, action — through `Render` and `RenderExposed` (no view, so element fallback only); the sequence diagram and the table carry none. The writers keep it visible: Mermaid as `%% canvas:`, `%% layout:` and `%% route:` comments after the header, text as `at (x, y)`, `size w×h`, `collapsed` and `via (x, y) …` suffixes, `opensysml/render` as optional `x`/`y`/`width`/`height`/`collapsed`/`route`/`canvas`; a model without annotations renders byte-identically | `libs/stdlib/OpenSysML Libraries/DiagramLayout.sysml`; `semantics/layout.go` `Model.LayoutSitesOf`, `LayoutOf`, `RouteOf`, `CanvasOf` (lazy, memoized, over the metadata side table `ElementMetadataOf`/`AnnotationSitesOf`); `semantics/masking.go` `directRedefinedFeatures` → `implicitMetadataBodyRedefinitions` (a metadata body's `points = (…)` is judged by the redefined `nonunique` feature); `ast/metadata.go` `DeclaredMetadata` (transition and succession bodies); `lower/state_graph.go` `StateGraph.DeclOf`, `lower/action_graph.go` (a succession body of annotations only); `view/geometry.go` `Point`, `Geometry`, `Canvas`, `Renderer.geometryOf`/`routeOf`/`canvasOf`, `view/place.go` `Renderer.Draws`/`DrawsAnywhere`, `view/clone.go`, `view/mermaid.go`, `view/text.go`; `lsp/render.go`; `passes/diagram_layout.go` `DiagramLayoutPass` at `LevelConstraint` (`diagram-layout-unplaced` warning for a `Layout`/`Route` on an element the judged rendering draws no node/edge for, `diagram-layout-value` error for odd `points` or a non-constant binding, `diagram-layout-canvas` error outside a view, `diagram-layout-duplicate` warning; source `constraint`) | `parse/view_layout_metadata.golden`; `semantics/layout_test.go` `TestLayoutOfPrefersTheViewBodyOverTheInlineAnnotation`, `TestLayoutOfPlacesAnElementInOneViewOnly`, `TestLayoutOfReadsAnAboutAnnotationNestedInTheViewBody`, `TestLayoutOfFirstViewLocalAnnotationWins`, `TestRouteOfReadsWaypointPairs`, `TestRouteOfReportsAnOddPointCount`, `TestLayoutOfReportsANonConstantBinding`, `TestCanvasOfReadsTheViewsCanvas`; `semantics/uniqueness_test.go` `TestIsUniqueInheritsIntoMetadataBody`; `view/geometry_test.go` (view-local over inline, one view of two, connection/transition/succession routes, canvas, writers, `RenderExposed`, clone, no-annotation byte identity) with `view/testdata/layout.{text,mermaid}.golden`; `lower/action_succession_test.go`; `lsp/render_test.go` `TestRenderCarriesLayoutGeometry`; `passes/diagram_layout_test.go` (one test per diagnostic); `cmd/sysml/check_test.go` `TestCheckReportsDiagramLayoutFindings`; `repl/view_layout_test.go` | ⚠️ Approximate (self-assessed: nothing in the specification to be faithful to — the notation is standard user-defined metadata, the meaning is OpenSysML's, proposed for standardization in `omg-issues.md`. No writer of this build lays a diagram out from the positions: Mermaid and text keep them visible, a layout-honoring form is pending, as is write-back from a graphical editor. Positions are absolute, so a container's children are not repositioned with it) | -| A view's conformance to the viewpoints it satisfies is evaluated (SysML v2 7.24 Views and Viewpoints; a concern is a requirement, so its conditions are evaluated by the requirement engine) | `semantics/conformance.go` `Model.ViewConformance` (per satisfy claiming conformance — `IsViewpointSatisfy`, since a satisfy stating a subject asserts its requirement of that subject instead, as the stdlib `View` does: `Model.SatisfyTarget`, then every concern `Model.FramedConcernsOf` reports for the viewpoint, matched against the view's own, inherited and nested framings, and evaluated against the exposed elements the concern's subject admits through the `ConcernEvaluator` the caller supplies — implemented in `repl/view.go` `concernEvaluator` over `runtime.Context.CheckSatisfactionOn`, so condition evaluation is not reimplemented); `passes/constraint.go` `checkViewSatisfyTarget` (code `view-satisfy-viewpoint`) | `semantics/conformance_test.go` (framing direct, inherited, nested and inherited-from-a-viewpoint; a missing concern; a false condition; an unevaluable one; no admissible subject; an unresolved stakeholder; a satisfy that is no viewpoint; determinism), `passes/constraint_test.go` `TestConstraintViewSatisfyNonViewpointRequirement`, `TestConstraintViewSatisfyViewpointOK`, `TestConstraintSatisfyOutsideAViewIsNotChecked`, `TestConstraintViewSatisfyRequirementBySubjectOK`, `conformance/viewpoint_framed_concern_conditions.sysml`, `viewpoint_concern_without_condition.sysml` | ⚠️ Approximate (the structural question — is every framed concern framed by the view — and the conditions are evaluated faithfully; the **verdict rules are tool-defined**, since SysML v2 leaves verification verdicts non-normative: a concern must hold of *every* exposed element its subject admits, a concern framed by a nested view counts for its container, and what cannot be evaluated — no condition, no admissible subject, an unresolved party, a framing whose concern reference does not resolve, a viewpoint framing no concern at all — is reported as unevaluable with a reason, never as a pass. **Adjudicated: left as found, deliberately.** This row states no name-resolution rule; its verdict rules are non-normative, so no referee can promote it, and its implementation is in `semantics/`, which this change did not own) | +| A view's conformance to the viewpoints it satisfies is evaluated (SysML v2 7.24 Views and Viewpoints; a concern is a requirement, so its conditions are evaluated by the requirement engine) | `semantics/conformance.go` `Model.ViewConformance` (per satisfy claiming conformance — `IsViewpointSatisfy`, since a satisfy stating a subject asserts its requirement of that subject instead, as the stdlib `View` does: `Model.SatisfyTarget`, then every concern `Model.FramedConcernsOf` reports for the viewpoint, evaluated against the exposed elements the concern's subject admits through the `ConcernEvaluator` the caller supplies — a view frames nothing of its own, a view body admitting no `frame` member — implemented in `repl/view.go` `concernEvaluator` over `runtime.Context.CheckSatisfactionOn`, so condition evaluation is not reimplemented); `passes/constraint.go` `checkViewSatisfyTarget` (code `view-satisfy-viewpoint`) | `semantics/conformance_test.go` (a framed concern evaluated against the exposed elements; a false condition; an unevaluable one; no admissible subject; a framing inherited from a supertype viewpoint; an unresolved stakeholder; a satisfy inherited from a view def; a satisfy that is no viewpoint; determinism), `passes/constraint_test.go` `TestConstraintViewSatisfyNonViewpointRequirement`, `TestConstraintViewSatisfyViewpointOK`, `TestConstraintSatisfyOutsideAViewIsNotChecked`, `TestConstraintViewSatisfyRequirementBySubjectOK`, `conformance/viewpoint_framed_concern_conditions.sysml`, `viewpoint_concern_without_condition.sysml` | ⚠️ Approximate (every concern the viewpoint frames is evaluated against the exposed elements; the **verdict rules are tool-defined**, since SysML v2 leaves verification verdicts non-normative: a concern must hold of *every* exposed element its subject admits, and what cannot be evaluated — no condition, no admissible subject, an unresolved party, a framing whose concern reference does not resolve, a viewpoint framing no concern at all — is reported as unevaluable with a reason, never as a pass. **Adjudicated: left as found, deliberately.** This row states no name-resolution rule; its verdict rules are non-normative, so no referee can promote it, and its implementation is in `semantics/`, which this change did not own) | | A filter condition is a model-level predicate over one candidate element, with the candidate as the implicit `self` (`@Safety`, `@@Safety`, `and`/`or`/`xor`/`not`/`implies`, and a comparison of an annotation's feature) | `semantics/filter.go` `Model.CompileElementFilter` / `EvalElementFilter` / `SatisfiesElementFilter` (compiled once per condition, memoized per candidate) over the annotations `semantics/annotations.go` collects — prefix metadata (`#Safety part def P;`), a `metadata` member of the element's body, and `metadata m about X;` — with conformance through `Model.AllSupertypes`, so `@Safety` matches a metadata type specializing `Safety`, and a metaclass classification (`@SysML::PartUsage`) reads the candidate's own kind | `semantics/filter_test.go`, `model/element_filter_test.go`, `semantics/cached_library_test.go` `TestNamespaceFilterOverALibraryIsTheSameParsedAndRestored` (the condition as parsed and the same condition compiled into an index-cache record classify alike), `TestAnnotationFactsOfALibraryElementSurviveTheCache` | ✅ Faithful (evaluated against a *symbol*, not a value: there is no instance at name-resolution time, which is what a model-level condition is defined over. A KerML declaration keyword implies its metaclass (`struct` → `Structure`, `class` → `Class`, `assoc` → `Association`, `datatype` → `DataType`, and the rest of KerML §8.2: `semantics/annotations.go` `kermlMetaclassNames`), and a metaclass *feature* (`Element::name`, `Type::isAbstract`, `Feature::isComposite`) is answered from the candidate element itself (`reflectiveFeatureValue`), from an annotation only when one binds it. A feature of a metaclass the candidate *is* an instance of but whose value no declaration gives is `ErrFilterUnevaluable` — reported through `filter-not-evaluable`, never decided false, which is the distinction that was dropping candidates and reporting their names unresolved (`kerml-examples` `Simple Tests/Filtering.kerml:38,47,48`, now clean, as the pinned `validate-kerml` is). Refereed: the pinned validators are silent on `testdata/passes/f93_element_filter.{kerml,sysml}` and on `Filtering.kerml`, and so are we (`passes/f93_element_filter_scope_test.go`). `@`/`@@` in the *runtime* value evaluator remains unimplemented, line 214 — a separate layer, `runtime/`) | | A filter condition that is not boolean-valued, or that OpenSysML cannot evaluate | `passes/filter.go` `ElementFilterPass` at `LevelType`, over `semantics/filter.go` `Model.CheckElementFilter`; codes `filter-not-boolean` (error — a condition that is not a predicate can select nothing) and `filter-not-evaluable` (warning). Each fault is reported once, on the membership stating the condition (KerML §8.2.4), however many operands carry it | `passes/filter_test.go` `TestFilterNotBooleanIsReported`, `TestFilterNotEvaluableIsReported`, `TestFilterConditionsInTheSupportedSubsetAreClean` | ✅ Faithful (an unevaluable condition is **reported and not applied**, so the elements it would have selected from all stay visible: a verdict OpenSysML could not reach never silently hides model content — maintainer decision, recorded here because the spec does not say). A feature bound to nothing is *not* that case: reading it yields the empty sequence, which an ordering propagates and `==`/`!=` decide against any value (KerML `DataFunctions`, where an ordering takes `DataValue[1]` and equality `[0..1]`), so `@Safety and Safety::isMandatory == true` does not hold of an element annotated `@Safety` alone and does not surface it — a verdict, not a reported failure. An annotation inherits the values its metadata type declares, so a default the type gives the feature is what the comparison reads (`semantics/annotations.go` `addTypeDefaults`; `semantics/filter_test.go` `TestFilterUnsetAnnotationFeature`, `TestFilterAnnotationFeatureDefault`, `model/element_filter_test.go` `TestFilteredImportRejectsAnUnboundAnnotationFeature`) | | A KerML declaration specializes the library type its keyword implies (`class` → `Occurrences::Occurrence`, `struct` → `Objects::Object`, `assoc`/`association` → `Links::Link`, `behavior` → `Performances::Performance`, `function`/`predicate` → the matching evaluation, `interaction` → `Links::Link` as an association (KerML 1.1 §8.3.5.4; `Links::BinaryLink` with two ends), `metaclass` → `Metaobjects::Metaobject`, `datatype` → `Base::DataValue`, `classifier`/`type` → `Base::Anything`), so the library members it implies are inherited (KerML 1.0 §8.4.2) | `semantics/implicit.go` `implicitKerMLBases`, read by `implicitBase` when `source.KindOf` says the document is KerML (the same file-kind mechanism, no second notion) — a bare `feature` is left with no base rather than being given the SysML `attribute` one; `libs/record.go` (cache format 17) stores the semantic supertype edges so a restored library symbol inherits alike | `semantics/implicit_test.go:TestKerMLImplicitDefinitionBases`, `:TestSysMLImplicitDefinitionBasesRemainKindBased`, `semantics/cached_library_test.go:TestInheritedImplicitBaseIsTheSameParsedAndRestored`, `libs/loader_cache_test.go:TestLoaderCachePreservesRedefinitionMemberEdges` | ✅ Faithful, self-assessed (the keyword table covers the KerML §8.2 declaration kinds, and the implied *feature* bases §8.4.2 also defines are now modelled: `implicitKerMLFeatureBases` maps a feature keyword to the base feature it subsets — `step`/`behavior` → `Performances::performances`, `expr`/`function` → `Performances::evaluations`, `bool`/`predicate` → `booleanEvaluations`, `inv` → `trueEvaluations`, `class` → `Occurrences::occurrences`, `struct` → `Objects::objects`, `datatype` → `Base::dataValues`, `assoc`/`connector` → `Links::links`, `binding` → `Links::selfLinks`, `succession` → `Occurrences::happensBeforeLinks`, `interaction` → `Transfers::transfers`, `flow` → `Transfers::flowTransfers`, `metaclass` → `Metaobjects::metaobjects` — contributed as members, like the base usage, not as conformance. Every name in both tables is checked against what the bundled library declares (`semantics/w7a_library_bases_test.go:TestW7AImplicitBaseTablesNameLibraryElements`) and the member contribution by `implicit_test.go:TestW7AKerMLFeatureBaseContributesMembers`. **Evidence is KerML §8.4.2 plus these tests, not a referee**: an implicit base surfaces in no reference diagnostic — a probe declaring the keyword forms is accepted by `validate-kerml` and by us alike — so no matched run can settle it. The feature table is read for feature declarations only: a keyword that declares a *type* — `class`, `struct`, `assoc`, `behavior`, `predicate`, `interaction` — takes its base from the type table above and nothing from this one) | diff --git a/docs/reference/repl-commands.md b/docs/reference/repl-commands.md index 9cd3fd2fd8..5080e80369 100644 --- a/docs/reference/repl-commands.md +++ b/docs/reference/repl-commands.md @@ -377,8 +377,6 @@ sysml> package Demo { ...> viewpoint structure : StructurePerspective; ...> view def StructureView { ...> satisfy structure; - ...> frame concern budget : MassBudget; - ...> frame concern modularity : Modularity; ...> } ...> view report : StructureView { ...> expose vehicle; diff --git a/examples/disposal-robot-demo/robot.sysml b/examples/disposal-robot-demo/robot.sysml index 801fc9747e..8bed02ef94 100644 --- a/examples/disposal-robot-demo/robot.sysml +++ b/examples/disposal-robot-demo/robot.sysml @@ -615,13 +615,13 @@ package RobotViews { } // States no rendering, so a tree renders it, with the interconnection view - // nested. `%view` checks the robots exposed against the concern framed here. + // nested. `%view` checks the robots exposed against the concern the + // satisfied viewpoint frames. view overview { expose Robot::fielded; expose Robot::heavyMockup; view interfaceSubview :> interfaces; satisfy operationsPerspective; - frame concern mass : MassBudget; } } diff --git a/examples/self-model/views.sysml b/examples/self-model/views.sysml index 8aeebd4d43..1dc6842319 100644 --- a/examples/self-model/views.sysml +++ b/examples/self-model/views.sysml @@ -194,6 +194,5 @@ package OpenSysMLViews { view pipelineSubview :> pipelineStructure; satisfy maintainerPerspective; - frame concern latency : OpenSysMLInvariants::KeystrokeLatency; } } diff --git a/examples/views-demo.sysml b/examples/views-demo.sysml index df096022af..8ae6f9cc38 100644 --- a/examples/views-demo.sysml +++ b/examples/views-demo.sysml @@ -170,9 +170,8 @@ package LanderViews { expose Lander::heavyDescender; view interfaceSubview :> interfaces; - // What `%view` checks the exposed landers against: the concern the - // viewpoint frames, framed by this view too. + // `%view` checks the exposed landers against the concern the + // satisfied viewpoint frames. satisfy massPerspective; - frame concern mass : Lander::MassBudget; } } diff --git a/internal/check/passes/nonstandard_notation.go b/internal/check/passes/nonstandard_notation.go index 590022fb5a..062315b657 100644 --- a/internal/check/passes/nonstandard_notation.go +++ b/internal/check/passes/nonstandard_notation.go @@ -96,6 +96,10 @@ type notationWalker struct { // inActionBody records that the body being walked admits ActionBodyItem // members (SysML.xtext:1367). inActionBody bool + // inStateBody records that the body being walked is a state body, whose + // one-name `then ;` is a target transition rather than a target + // succession (SysML.xtext TargetTransitionUsage). + inStateBody bool // inViewDefBody records that the body being walked is a ViewDefinitionBody // (SysML.xtext ViewDefinitionBodyItem), which admits no Expose. inViewDefBody bool @@ -238,13 +242,14 @@ func (w *notationWalker) walk(members []ast.Node) { // walkDeclaration walks the body of a declaration under the body kind that // declaration opens. func (w *notationWalker) walkDeclaration(members []ast.Node, declaration ast.Node) { - action, viewDef := w.inActionBody, w.inViewDefBody + action, viewDef, state := w.inActionBody, w.inViewDefBody, w.inStateBody w.inActionBody = admitsActionBodyItems(declaration) w.inViewDefBody = isViewDefinition(declaration) + w.inStateBody = isStateDeclaration(declaration) w.bodies = append(w.bodies, bodyFrame{members: members}) w.walk(members) w.bodies = w.bodies[:len(w.bodies)-1] - w.inActionBody, w.inViewDefBody = action, viewDef + w.inActionBody, w.inViewDefBody, w.inStateBody = action, viewDef, state } // walkPackageMembers walks a namespace or package body, the member lists a fix @@ -272,6 +277,29 @@ func isViewDefinition(node ast.Node) bool { return ok && def.Kind == ast.DefView } +// walkStateBody walks a state body's members, where a one-name `then +// ;` spells the target of an entry transition. +func (w *notationWalker) walkStateBody(members []ast.Node) { + if len(members) == 0 { + return + } + action, viewDef, state := w.inActionBody, w.inViewDefBody, w.inStateBody + w.inActionBody, w.inViewDefBody, w.inStateBody = false, false, true + w.walk(members) + w.inActionBody, w.inViewDefBody, w.inStateBody = action, viewDef, state +} + +// isStateDeclaration reports whether the declaration opens a state body. +func isStateDeclaration(node ast.Node) bool { + switch n := node.(type) { + case *ast.Definition: + return n.Kind == ast.DefState + case *ast.Usage: + return n.Kind == ast.UsageState + } + return false +} + // admitsActionBodyItems reports whether the body a declaration opens is an // ActionBody (SysML.xtext:1361) or one that includes ActionBodyItem: a // CalculationBody (:1947) or a CaseBody (:2183). @@ -305,13 +333,25 @@ func admitsActionBodyItems(node ast.Node) bool { // usage the pilot has no production for it and its source is unstated. func (w *notationWalker) targetSuccession(n, previous ast.Node) { keyword := targetSuccessionKeyword(n) - if keyword == "" || !w.sysml || previous == nil || admitsTargetSuccession(previous) { + if keyword == "" || !w.sysml { return } notation := "`" + keyword + " ;`" if keyword == "if" { notation = "`if then ;`" } + // TargetSuccessionMember is an ActionBodyItem (SysML.xtext:1367), and a + // state body reads the same notation as a target transition; anywhere else + // the member sequences nothing the grammar admits. + if !w.inActionBody && !w.inStateBody { + w.extension(keywordSpan(n, keyword), notation+" outside an action or state body", + "a target succession sequences members of an action, calculation or case body, "+ + "and a state body reads it as a target transition; this body admits neither") + return + } + if previous == nil || admitsTargetSuccession(previous) { + return + } w.extension(keywordSpan(n, keyword), notation+" after a member that is not an action node", "a target succession sequences from the member written right before it, so only an action node, "+ "a behavior usage, a one-ended `first ;` or another target succession may precede it") @@ -409,9 +449,9 @@ func (w *notationWalker) stateNode(n *ast.StateNode) { w.walkActionBody(n.Entry) w.walkActionBody(n.Do) w.walkActionBody(n.Exit) - w.walk(n.Substates) + w.walkStateBody(n.Substates) for _, region := range n.Regions { - w.walk([]ast.Node{region}) + w.walkStateBody(region.States) } } diff --git a/internal/check/passes/nonstandard_notation_test.go b/internal/check/passes/nonstandard_notation_test.go index f9538829e8..97966bd1d7 100644 --- a/internal/check/passes/nonstandard_notation_test.go +++ b/internal/check/passes/nonstandard_notation_test.go @@ -267,6 +267,29 @@ func TestTargetSuccessionAfterANonActionMemberIsAnExtension(t *testing.T) { wantSilent(t, "a.kerml", "behavior A { step a; then a; }") } +// TargetSuccessionMember is an ActionBodyItem, and a state body reads +// `then ;` as a target transition; in a body that admits neither it is +// an extension whatever member precedes it. +func TestTargetSuccessionOutsideAnActionOrStateBodyIsAnExtension(t *testing.T) { + const outside = "`then ;` outside an action or state body" + wantNotation(t, "a.sysml", "view v { action x; then y; }", CodeNonstandardNotation, outside) + wantNotation(t, "a.sysml", "view def V { action x; then y; }", CodeNonstandardNotation, outside) + wantNotation(t, "a.sysml", "part def P { action x; then y; }", CodeNonstandardNotation, outside) + wantNotation(t, "a.sysml", "viewpoint def V { frame f; then y; }", CodeNonstandardNotation, outside) + + got := notationDiags(t, "a.sysml", "view v { action x; then y; }", diag.ConformanceStrict) + if len(got) != 1 || got[0].Severity != diag.SeverityError || !got[0].Notation { + t.Fatalf("strict got %+v, want one notation error", got) + } + + wantSilent(t, "a.sysml", "action def A { action x; then y; }") + wantSilent(t, "a.sysml", "action def A { action x; if true then y; else y; }") + wantSilent(t, "a.sysml", "state def S { state x; then y; state y; }") + wantSilent(t, "a.sysml", "state s { state x; then t; state t; }") + wantSilent(t, "a.sysml", "action def A { action x; then action y; }") + wantSilent(t, "a.sysml", "action def A { action x; action y; first x then y; }") +} + // RequirementConstraintMember belongs to a RequirementBody; anywhere else the // parser rejects `assume`/`require` outright (see the negative parser tests). func TestRequirementConstraintInsideARequirementBodyIsSilent(t *testing.T) { diff --git a/internal/frontend/repl/view_test.go b/internal/frontend/repl/view_test.go index 6a3545cbe5..14390ac31f 100644 --- a/internal/frontend/repl/view_test.go +++ b/internal/frontend/repl/view_test.go @@ -158,8 +158,6 @@ const conformanceModel = `package Demo { view report : StructureView { expose Demo::vehicle; expose Demo::wheel; - frame concern modularity : Modularity; - frame concern documented : Documented; } }` @@ -176,7 +174,7 @@ func conformanceSession(t *testing.T) *Session { } // %view reports the conformance of a view to the viewpoint it satisfies: a -// concern that holds, one that does not, one the view never framed, and where +// concern that holds, one that does not, one stating no condition, and where // the satisfy came from. func TestViewReportsViewpointConformance(t *testing.T) { out, _, err := conformanceSession(t).RunMeta("%view Demo::report") @@ -187,7 +185,7 @@ func TestViewReportsViewpointConformance(t *testing.T) { for _, want := range []string{ "viewpoint conformance", "satisfy structure (from Demo::StructureView)", - "concern budget: violated (framed by the viewpoint but not by the view)", + "concern budget: violated", "concern modularity: conforms", "concern documented: unevaluable", "states no condition to evaluate", @@ -209,9 +207,6 @@ func TestViewReportsAViolatedConcernPerElement(t *testing.T) { private import Demo::*; view budgeted : StructureView { expose Demo::vehicle; - frame concern budget : MassBudget; - frame concern modularity : Modularity; - frame concern documented : Documented; } }`) for _, d := range res.Diagnostics { @@ -315,7 +310,6 @@ func TestViewLeavesNoAmbiguityForALaterCheck(t *testing.T) { view carView : StructureView { expose Checked::car; expose Checked::spare; - frame concern modularity : Modularity; } }`) for _, d := range res.Diagnostics { @@ -373,7 +367,7 @@ func TestViewSharesTheObjectOfAQuotedName(t *testing.T) { res := s.Submit(`package Quoted { private import Demo::*; part 'road car' : Vehicle; - view quotedView : StructureView { expose Quoted::'road car'; frame concern modularity : Modularity; } + view quotedView : StructureView { expose Quoted::'road car'; } }`) for _, d := range res.Diagnostics { if d.Severity == diag.SeverityError { @@ -421,7 +415,7 @@ func TestViewChecksAConcernWhoseSubjectIsAnything(t *testing.T) { concern def Named { subject s : Base::Anything; require constraint { 1.0 > 2.0 } } viewpoint def AnyPerspective { frame concern named : Named; } viewpoint anything : AnyPerspective; - view anyView { expose Demo::wheel; satisfy anything; frame concern named : Named; } + view anyView { expose Demo::wheel; satisfy anything; } }`) for _, d := range res.Diagnostics { if d.Severity == diag.SeverityError { diff --git a/internal/semantic/resolve/accept_payload_test.go b/internal/semantic/resolve/accept_payload_test.go index 4ffbe9d703..834e6b6dae 100644 --- a/internal/semantic/resolve/accept_payload_test.go +++ b/internal/semantic/resolve/accept_payload_test.go @@ -184,14 +184,14 @@ func TestAcceptPayloadDoesNotEscapeBody(t *testing.T) { } } -// Only a behavior body shares a feature space: a part declaring an action node -// does not read that node's payload. +// Only a behavior body shares a feature space: a part whose action body +// declares an action node does not read that node's payload. func TestAcceptPayloadNotSharedByAPartBody(t *testing.T) { r := resolveDoc(t, "d.sysml", ` package P { item def Warning; part p { - action wait accept msg : Warning; + action a { action wait accept msg : Warning; } attribute seen = msg; } } diff --git a/internal/semantic/resolve/event_feature_test.go b/internal/semantic/resolve/event_feature_test.go index 50c7d06c98..4e4d726cd5 100644 --- a/internal/semantic/resolve/event_feature_test.go +++ b/internal/semantic/resolve/event_feature_test.go @@ -10,7 +10,7 @@ func TestResolveEventOccurrenceFeature(t *testing.T) { "occurrence def O; part def P { event occurrence o : O; }", "occurrence def O; part def P { event occurrence o : O; attribute a = o; }", "occurrence def O; part def P { occurrence o : O; event o; }", - "occurrence def O; part def P { event occurrence o : O; state s; state t; accept o then s; }", + "occurrence def O; part def P { event occurrence o : O; state s { state t; accept o then t; } }", "occurrence def O; action def A { event occurrence o : O; accept o; }", } { r := resolveDoc(t, "", src) diff --git a/internal/semantic/semantics/conformance.go b/internal/semantic/semantics/conformance.go index 072f6cdbc5..5482606c98 100644 --- a/internal/semantic/semantics/conformance.go +++ b/internal/semantic/semantics/conformance.go @@ -26,13 +26,13 @@ type Verdict int const ( // VerdictConforms is a question answered in the affirmative. VerdictConforms Verdict = iota - // VerdictViolated is a question answered in the negative: a concern the view - // does not frame, or a condition that evaluated to false. + // VerdictViolated is a question answered in the negative: a condition that + // evaluated to false. VerdictViolated // VerdictUnevaluable is a question no answer could be reached for. VerdictUnevaluable - // VerdictNotEvaluated is a structurally conforming concern whose conditions - // were not evaluated, because the caller supplied no evaluator. + // VerdictNotEvaluated is a concern whose conditions were not evaluated, + // because the caller supplied no evaluator. VerdictNotEvaluated ) @@ -78,10 +78,6 @@ type ConcernConformance struct { Target *symbols.Symbol // Name is how the framed concern is written, for reporting. Name string - // FramedBy is the view's framing of the same concern and FramedIn the view - // declaring it. Both are nil when the view does not frame the concern. - FramedBy *symbols.Symbol - FramedIn *symbols.Symbol // Checks are the per-element verdicts, in exposed-element order. Checks []ConcernCheck Verdict Verdict @@ -124,9 +120,9 @@ type ViewConformance struct { } // ViewConformance evaluates whether view conforms to the viewpoints its body -// satisfies: every concern a viewpoint frames must be framed by the view and -// must hold of what the view exposes. A nil evaluator answers the structural -// question alone; a non-view is ErrNotAView, a view satisfying nothing no error. +// satisfies: every concern a viewpoint frames must hold of what the view +// exposes. A nil evaluator answers the structural question alone; a non-view +// is ErrNotAView, a view satisfying nothing no error. func (m *Model) ViewConformance(view *symbols.Symbol, eval ConcernEvaluator) (*ViewConformance, error) { if view == nil || !IsView(view) { return nil, ErrNotAView @@ -136,9 +132,8 @@ func (m *Model) ViewConformance(view *symbols.Symbol, eval ConcernEvaluator) (*V return nil, err } out := &ViewConformance{View: view, Exposed: exposed, Verdict: VerdictConforms} - framings := m.viewFramings(view) for _, sat := range m.SatisfyMembersOf(view) { - out.Viewpoints = append(out.Viewpoints, m.viewpointConformance(view, sat, framings, exposed, eval)) + out.Viewpoints = append(out.Viewpoints, m.viewpointConformance(view, sat, exposed, eval)) } for _, vp := range out.Viewpoints { out.Verdict = worse(out.Verdict, vp.Verdict) @@ -169,8 +164,8 @@ func (m *Model) SatisfyMembersOf(view *symbols.Symbol) []*symbols.Symbol { return out } -// FramedConcernsOf returns the `frame` concern usages a viewpoint or view -// declares followed by those it inherits, in declaration order and once each. +// FramedConcernsOf returns the `frame` concern usages a viewpoint declares +// followed by those it inherits, in declaration order and once each. func (m *Model) FramedConcernsOf(sym *symbols.Symbol) []*symbols.Symbol { if sym == nil { return nil @@ -261,61 +256,8 @@ func (m *Model) declaredConcernTarget(sym *symbols.Symbol) *symbols.Symbol { return nil } -// viewFraming is one framing a view makes, and the view that declared it. -type viewFraming struct { - frame *symbols.Symbol - target *symbols.Symbol - in *symbols.Symbol -} - -// viewFramings collects a view's own framings, then its supertypes', then those -// of the views nested in it — a nested view frames for its container, since the -// tree as a whole is what the container's conformance addresses (tool-defined). -func (m *Model) viewFramings(view *symbols.Symbol) []viewFraming { - var out []viewFraming - seen := map[*symbols.Symbol]bool{} - var walk func(sym *symbols.Symbol, depth int) - walk = func(sym *symbols.Symbol, depth int) { - if sym == nil || seen[sym] || depth > 32 { - return - } - seen[sym] = true - out = append(out, m.framingsDeclaredBy(sym)...) - nested, err := m.NestedViews(sym) - if err != nil { - return - } - for _, child := range nested { - walk(child, depth+1) - } - } - walk(view, 0) - return out -} - -// framingsDeclaredBy returns the framings of a view and of the views it -// specializes, each attributed to the view that declares it. -func (m *Model) framingsDeclaredBy(view *symbols.Symbol) []viewFraming { - var out []viewFraming - seen := map[*symbols.Symbol]bool{} - add := func(owner *symbols.Symbol) { - for _, fc := range usageMembersOfKind(owner, ast.UsageFramedConcern) { - if seen[fc] { - continue - } - seen[fc] = true - out = append(out, viewFraming{frame: fc, target: m.FramedConcernTarget(fc), in: owner}) - } - } - add(view) - for _, super := range m.AllSupertypes(view) { - add(super) - } - return out -} - // viewpointConformance evaluates one satisfy member of a view. -func (m *Model) viewpointConformance(view, sat *symbols.Symbol, framings []viewFraming, exposed []*symbols.Symbol, eval ConcernEvaluator) ViewpointConformance { +func (m *Model) viewpointConformance(view, sat *symbols.Symbol, exposed []*symbols.Symbol, eval ConcernEvaluator) ViewpointConformance { out := ViewpointConformance{Satisfy: sat, SatisfiedIn: sat.Owner()} target, ref := m.SatisfyTarget(sat) out.Ref = ref @@ -334,7 +276,7 @@ func (m *Model) viewpointConformance(view, sat *symbols.Symbol, framings []viewF out.Parties = append(out.Parties, m.partyBindings(view)...) out.Verdict = VerdictConforms for _, fc := range m.FramedConcernsOf(target) { - out.Concerns = append(out.Concerns, m.concernConformance(fc, framings, exposed, eval)) + out.Concerns = append(out.Concerns, m.concernConformance(fc, exposed, eval)) } // A viewpoint framing no concern asks nothing of the view, so conforming to // it would be a verdict reached by checking nothing. @@ -354,27 +296,17 @@ func (m *Model) viewpointConformance(view, sat *symbols.Symbol, framings []viewF return out } -// concernConformance answers whether the view frames one concern of the -// viewpoint and, when an evaluator is supplied, whether it holds of what the -// view exposes. -func (m *Model) concernConformance(fc *symbols.Symbol, framings []viewFraming, exposed []*symbols.Symbol, eval ConcernEvaluator) ConcernConformance { +// concernConformance answers whether one concern of the viewpoint holds of +// what the view exposes. A view frames nothing of its own — a view body admits +// no FramedConcernMember — so the concern is reported and, when an evaluator +// is supplied, evaluated. +func (m *Model) concernConformance(fc *symbols.Symbol, exposed []*symbols.Symbol, eval ConcernEvaluator) ConcernConformance { out := ConcernConformance{Concern: fc, Target: m.FramedConcernTarget(fc), Name: framedConcernName(fc)} if ref := m.unresolvedConcernRef(fc); ref != "" { out.Verdict = VerdictUnevaluable out.Reason = fmt.Sprintf("the framing names %s, which does not resolve", ref) return out } - for _, f := range framings { - if m.framesTheSame(out, f) { - out.FramedBy, out.FramedIn = f.frame, f.in - break - } - } - if out.FramedBy == nil { - out.Verdict = VerdictViolated - out.Reason = "framed by the viewpoint but not by the view" - return out - } if eval == nil { out.Verdict = VerdictNotEvaluated return out @@ -402,17 +334,6 @@ func (m *Model) unresolvedConcernRef(fc *symbols.Symbol) string { return "" } -// framesTheSame reports whether a view's framing frames the concern the -// viewpoint's does: the same concern, or one specializing the other. Where -// either side names no concern — an untyped `frame concern mass;` — the written -// name decides, which is all the two framings share (tool-defined). -func (m *Model) framesTheSame(c ConcernConformance, f viewFraming) bool { - if c.Target != nil && f.target != nil { - return c.Target == f.target || m.Conforms(f.target, c.Target) || m.Conforms(c.Target, f.target) - } - return c.Target == f.frame || (c.Name != "" && c.Name == framedConcernName(f.frame)) -} - // evaluateConcern evaluates a framed concern against the exposed elements its // subject admits, and must hold of every one of them (tool-defined). An exposed // set with no element the subject admits is unevaluable, never a pass. diff --git a/internal/semantic/semantics/conformance_test.go b/internal/semantic/semantics/conformance_test.go index 4f02604bdc..846cda676e 100644 --- a/internal/semantic/semantics/conformance_test.go +++ b/internal/semantic/semantics/conformance_test.go @@ -86,13 +86,13 @@ const conformanceLib = ` concern def CostConcern { subject s : Vehicle; require constraint { s.mass > 1.0 } } ` -// A view framing every concern its viewpoint frames conforms, and each concern -// is evaluated against the exposed elements its subject admits. +// A view satisfying a viewpoint conforms when each concern the viewpoint +// frames holds of the exposed elements its subject admits. func TestViewConformanceFramedAndHolding(t *testing.T) { m, root := buildModel(t, conformanceLib+` viewpoint def VP { frame concern mass : MassConcern; } viewpoint vp : VP; - view v { expose vehicle; expose wheel; satisfy vp; frame concern mass : MassConcern; } + view v { expose vehicle; expose wheel; satisfy vp; } `) eval := &fakeEvaluator{} report := conformance(t, m, sym(t, root, "v"), eval) @@ -101,22 +101,24 @@ func TestViewConformanceFramedAndHolding(t *testing.T) { wantNames(t, "concerns evaluated", eval.asked, []string{"mass/vehicle"}) } -// A concern the viewpoint frames and the view does not is a non-conformance, -// reported as such rather than left silent. -func TestViewConformanceMissingConcernIsViolated(t *testing.T) { +// A view frames nothing of its own — a view body admits no `frame` member — +// so without an evaluator every framed concern is not evaluated, and with one +// each conforms or is violated per its condition alone. +func TestViewConformanceViewFramesNothing(t *testing.T) { m, root := buildModel(t, conformanceLib+` viewpoint def VP { frame concern mass : MassConcern; frame concern cost : CostConcern; } viewpoint vp : VP; - view v { expose vehicle; satisfy vp; frame concern mass : MassConcern; } + view v { expose vehicle; satisfy vp; } `) - report := conformance(t, m, sym(t, root, "v"), &fakeEvaluator{}) - vp := onlyViewpoint(t, report) + vp := onlyViewpoint(t, conformance(t, m, sym(t, root, "v"), nil)) wantNames(t, "concerns of vp", concernNames(vp), []string{"mass", "cost"}) + wantVerdict(t, "verdict of mass", vp.Concerns[0].Verdict, VerdictNotEvaluated, vp.Concerns[0].Reason) + wantVerdict(t, "verdict of cost", vp.Concerns[1].Verdict, VerdictNotEvaluated, vp.Concerns[1].Reason) + + eval := &fakeEvaluator{fails: map[string]bool{"cost": true}} + vp = onlyViewpoint(t, conformance(t, m, sym(t, root, "v"), eval)) + wantVerdict(t, "verdict of mass", vp.Concerns[0].Verdict, VerdictConforms, vp.Concerns[0].Reason) wantVerdict(t, "verdict of cost", vp.Concerns[1].Verdict, VerdictViolated, vp.Concerns[1].Reason) - if !strings.Contains(vp.Concerns[1].Reason, "not by the view") { - t.Fatalf("reason for cost = %q, want it to say the view does not frame it", vp.Concerns[1].Reason) - } - wantVerdict(t, "verdict of v", report.Verdict, VerdictViolated, "a concern is not framed") } // A concern whose condition is false of an exposed element is a violation, one @@ -126,7 +128,7 @@ func TestViewConformanceFalseConditionIsViolated(t *testing.T) { viewpoint def VP { frame concern mass : MassConcern; } viewpoint vp : VP; part other : Vehicle; - view v { expose vehicle; expose other; satisfy vp; frame concern mass : MassConcern; } + view v { expose vehicle; expose other; satisfy vp; } `) eval := &fakeEvaluator{fails: map[string]bool{"mass": true}} report := conformance(t, m, sym(t, root, "v"), eval) @@ -144,7 +146,7 @@ func TestViewConformanceUnevaluableConcern(t *testing.T) { m, root := buildModel(t, conformanceLib+` viewpoint def VP { frame concern mass : MassConcern; } viewpoint vp : VP; - view v { expose vehicle; satisfy vp; frame concern mass : MassConcern; } + view v { expose vehicle; satisfy vp; } `) eval := &fakeEvaluator{broken: map[string]bool{"mass": true}} vp := onlyViewpoint(t, conformance(t, m, sym(t, root, "v"), eval)) @@ -160,7 +162,7 @@ func TestViewConformanceNoSubjectIsUnevaluable(t *testing.T) { m, root := buildModel(t, conformanceLib+` viewpoint def VP { frame concern mass : MassConcern; } viewpoint vp : VP; - view v { expose wheel; satisfy vp; frame concern mass : MassConcern; } + view v { expose wheel; satisfy vp; } `) eval := &fakeEvaluator{} vp := onlyViewpoint(t, conformance(t, m, sym(t, root, "v"), eval)) @@ -178,7 +180,7 @@ func TestViewConformanceExposingNothingIsUnevaluable(t *testing.T) { m, root := buildModel(t, conformanceLib+` viewpoint def VP { frame concern mass : MassConcern; } viewpoint vp : VP; - view v { satisfy vp; frame concern mass : MassConcern; } + view v { satisfy vp; } `) vp := onlyViewpoint(t, conformance(t, m, sym(t, root, "v"), &fakeEvaluator{})) wantVerdict(t, "verdict of mass", vp.Concerns[0].Verdict, VerdictUnevaluable, vp.Concerns[0].Reason) @@ -194,7 +196,7 @@ func TestViewConformanceInheritedSatisfy(t *testing.T) { viewpoint def VP { frame concern mass : MassConcern; } viewpoint vp : VP; view def StructureView { satisfy vp; } - view v : StructureView { expose vehicle; frame concern mass : MassConcern; } + view v : StructureView { expose vehicle; } `) vp := onlyViewpoint(t, conformance(t, m, sym(t, root, "v"), &fakeEvaluator{})) if vp.SatisfiedIn != sym(t, root, "StructureView") { @@ -203,46 +205,17 @@ func TestViewConformanceInheritedSatisfy(t *testing.T) { wantVerdict(t, "verdict of mass", vp.Concerns[0].Verdict, VerdictConforms, vp.Concerns[0].Reason) } -// A framing inherited from a view definition frames for the view. -func TestViewConformanceInheritedFraming(t *testing.T) { - m, root := buildModel(t, conformanceLib+` - viewpoint def VP { frame concern mass : MassConcern; } - viewpoint vp : VP; - view def StructureView { frame concern mass : MassConcern; } - view v : StructureView { expose vehicle; satisfy vp; } - `) - vp := onlyViewpoint(t, conformance(t, m, sym(t, root, "v"), &fakeEvaluator{})) - wantVerdict(t, "verdict of mass", vp.Concerns[0].Verdict, VerdictConforms, vp.Concerns[0].Reason) - if vp.Concerns[0].FramedIn != sym(t, root, "StructureView") { - t.Fatalf("mass framed in %v, want the view definition StructureView that declares it", vp.Concerns[0].FramedIn) - } -} - -// A concern framed by a nested view frames for its container (tool-defined: the -// container's conformance is what the view tree as a whole addresses). -func TestViewConformanceNestedViewFraming(t *testing.T) { - m, root := buildModel(t, conformanceLib+` - viewpoint def VP { frame concern mass : MassConcern; } - viewpoint vp : VP; - view v { expose vehicle; satisfy vp; view inner { frame concern mass : MassConcern; } } - `) - vp := onlyViewpoint(t, conformance(t, m, sym(t, root, "v"), &fakeEvaluator{})) - wantVerdict(t, "verdict of mass", vp.Concerns[0].Verdict, VerdictConforms, vp.Concerns[0].Reason) - if vp.Concerns[0].FramedIn == nil || localName(vp.Concerns[0].FramedIn.Name) != "inner" { - t.Fatalf("mass framed in %v, want the nested view inner", vp.Concerns[0].FramedIn) - } -} - // A concern a viewpoint inherits is framed by the viewpoint too, so the view -// must frame it. +// answers it the way it answers the declared ones. func TestViewConformanceInheritedViewpointFraming(t *testing.T) { m, root := buildModel(t, conformanceLib+` viewpoint def BaseVP { frame concern mass : MassConcern; } viewpoint def VP :> BaseVP { frame concern cost : CostConcern; } viewpoint vp : VP; - view v { expose vehicle; satisfy vp; frame concern cost : CostConcern; } + view v { expose vehicle; satisfy vp; } `) - vp := onlyViewpoint(t, conformance(t, m, sym(t, root, "v"), &fakeEvaluator{})) + eval := &fakeEvaluator{fails: map[string]bool{"mass": true}} + vp := onlyViewpoint(t, conformance(t, m, sym(t, root, "v"), eval)) wantNames(t, "concerns of vp", concernNames(vp), []string{"cost", "mass"}) wantVerdict(t, "verdict of mass", vp.Concerns[1].Verdict, VerdictViolated, vp.Concerns[1].Reason) } @@ -255,7 +228,7 @@ func TestViewConformanceReferenceFramingMatches(t *testing.T) { part vehicle : Vehicle; concern modularity { subject s : Vehicle; require constraint { true } } viewpoint perspective { frame modularity; } - view v { expose vehicle; satisfy perspective; frame modularity; } + view v { expose vehicle; satisfy perspective; } `) eval := &fakeEvaluator{} vp := onlyViewpoint(t, conformance(t, m, sym(t, root, "v"), eval)) @@ -287,7 +260,7 @@ func TestViewConformanceUnresolvedStakeholder(t *testing.T) { m, root := buildModel(t, conformanceLib+` viewpoint def VP { stakeholder se : Missing; frame concern mass : MassConcern; } viewpoint vp : VP; - view v { expose vehicle; satisfy vp; frame concern mass : MassConcern; } + view v { expose vehicle; satisfy vp; } `) vp := onlyViewpoint(t, conformance(t, m, sym(t, root, "v"), &fakeEvaluator{})) var reasons []string @@ -309,7 +282,7 @@ func TestViewConformanceResolvedStakeholdersAreQuiet(t *testing.T) { part def Engineer; viewpoint def VP { stakeholder se : Engineer; stakeholder anyone; frame concern mass : MassConcern; } viewpoint vp : VP; - view v { expose vehicle; satisfy vp; frame concern mass : MassConcern; } + view v { expose vehicle; satisfy vp; } `) vp := onlyViewpoint(t, conformance(t, m, sym(t, root, "v"), &fakeEvaluator{})) for _, p := range vp.Parties { @@ -320,17 +293,17 @@ func TestViewConformanceResolvedStakeholdersAreQuiet(t *testing.T) { wantVerdict(t, "verdict of vp", vp.Verdict, VerdictConforms, vp.Reason) } -// Without an evaluator only the structural question is answered: a framed -// concern is not evaluated, and a missing one is still a violation. +// Without an evaluator only the structural question is answered: every framed +// concern is not evaluated. func TestViewConformanceWithoutEvaluator(t *testing.T) { m, root := buildModel(t, conformanceLib+` viewpoint def VP { frame concern mass : MassConcern; frame concern cost : CostConcern; } viewpoint vp : VP; - view v { expose vehicle; satisfy vp; frame concern mass : MassConcern; } + view v { expose vehicle; satisfy vp; } `) vp := onlyViewpoint(t, conformance(t, m, sym(t, root, "v"), nil)) wantVerdict(t, "verdict of mass", vp.Concerns[0].Verdict, VerdictNotEvaluated, vp.Concerns[0].Reason) - wantVerdict(t, "verdict of cost", vp.Concerns[1].Verdict, VerdictViolated, vp.Concerns[1].Reason) + wantVerdict(t, "verdict of cost", vp.Concerns[1].Verdict, VerdictNotEvaluated, vp.Concerns[1].Reason) } // A viewpoint framing no concern asks nothing of the view, so no conformance @@ -365,28 +338,24 @@ func TestViewConformanceUnresolvedFramedConcern(t *testing.T) { } } -// A framing referencing a concern usage frames that usage, not the definition it -// is typed by, whose values the usage may mask. Two usages of one definition are -// therefore different concerns. +// A framing referencing a concern usage frames that usage, not the definition +// it is typed by, whose values the usage may mask — so it is the usage whose +// conditions are evaluated. func TestViewConformanceFramesTheNamedConcernUsage(t *testing.T) { m, root := buildModel(t, conformanceLib+` concern tight : MassConcern; concern loose : MassConcern; viewpoint def VP { frame tight; } viewpoint vp : VP; - view framingTheUsage { expose vehicle; satisfy vp; frame tight; } - view framingAnother { expose vehicle; satisfy vp; frame loose; } + view v { expose vehicle; satisfy vp; } `) eval := &fakeEvaluator{} - vp := onlyViewpoint(t, conformance(t, m, sym(t, root, "framingTheUsage"), eval)) + vp := onlyViewpoint(t, conformance(t, m, sym(t, root, "v"), eval)) if got := vp.Concerns[0].Target; got != sym(t, root, "tight") { t.Fatalf("framed concern target = %v, want the tight usage", got) } wantVerdict(t, "verdict of tight", vp.Concerns[0].Verdict, VerdictConforms, vp.Concerns[0].Reason) wantNames(t, "concerns evaluated", eval.asked, []string{"tight/vehicle"}) - - other := onlyViewpoint(t, conformance(t, m, sym(t, root, "framingAnother"), &fakeEvaluator{})) - wantVerdict(t, "verdict of tight", other.Concerns[0].Verdict, VerdictViolated, other.Concerns[0].Reason) } // A satisfy stating a subject asserts a requirement of that subject, as the @@ -403,7 +372,6 @@ func TestViewConformanceIgnoresSatisfyWithASubject(t *testing.T) { satisfy requirement conformance : VehicleSpecification by vehicle; satisfy spec by vehicle; satisfy vp; - frame concern mass : MassConcern; } `) report := conformance(t, m, sym(t, root, "v"), &fakeEvaluator{}) @@ -414,24 +382,18 @@ func TestViewConformanceIgnoresSatisfyWithASubject(t *testing.T) { wantVerdict(t, "verdict of v", report.Verdict, VerdictConforms, vp.Reason) } -// A view framing a concern without naming its type frames it under that name: -// the framings share no resolved concern, so the name is what matches, either -// way round. -func TestViewConformanceUntypedFramingMatchesByName(t *testing.T) { +// An untyped `frame concern mass;` in the viewpoint declares a concern under +// that name, which evaluation then answers. +func TestViewConformanceUntypedViewpointFraming(t *testing.T) { m, root := buildModel(t, conformanceLib+` - viewpoint def VP { frame concern mass : MassConcern; } - viewpoint vp : VP; viewpoint def UntypedVP { frame concern mass; } viewpoint untypedVp : UntypedVP; - view untypedByTheView { expose vehicle; satisfy vp; frame concern mass; } - view untypedByTheViewpoint { expose vehicle; satisfy untypedVp; frame concern mass : MassConcern; } + view v { expose vehicle; satisfy untypedVp; } `) - for _, name := range []string{"untypedByTheView", "untypedByTheViewpoint"} { - vp := onlyViewpoint(t, conformance(t, m, sym(t, root, name), &fakeEvaluator{})) - if vp.Concerns[0].FramedBy == nil { - t.Errorf("%s: mass reported as not framed (%s), want it framed under its name", name, vp.Concerns[0].Reason) - } - } + eval := &fakeEvaluator{} + vp := onlyViewpoint(t, conformance(t, m, sym(t, root, "v"), eval)) + wantVerdict(t, "verdict of mass", vp.Concerns[0].Verdict, VerdictConforms, vp.Concerns[0].Reason) + wantNames(t, "concerns evaluated", eval.asked, []string{"mass/vehicle"}) } // Satisfies and framings come back in declaration order even where a named @@ -448,9 +410,6 @@ func TestViewConformanceOrdersMixedNamedAndAnonymousMembers(t *testing.T) { expose vehicle; satisfy vp; satisfy requirement named : OtherVP; - frame alpha; - frame concern middle : MassConcern; - frame omega; } `) report := conformance(t, m, sym(t, root, "v"), &fakeEvaluator{}) @@ -491,9 +450,6 @@ func TestViewConformanceIsDeterministic(t *testing.T) { view v { expose vehicle; expose second; satisfy vp; satisfy other; - frame concern mass : MassConcern; - frame concern cost : CostConcern; - frame concern cost2 : CostConcern; } `) view := sym(t, root, "v") diff --git a/internal/syntax/parser/behavior.go b/internal/syntax/parser/behavior.go index e83ed4935a..a37096a37c 100644 --- a/internal/syntax/parser/behavior.go +++ b/internal/syntax/parser/behavior.go @@ -661,9 +661,14 @@ func (p *Parser) parseInitialNode(tok lexer.Token) ast.Node { } // The succession a `first … then …` states ends in a body of its own - // (SysML.xtext ActionTargetSuccession, which ends in UsageBody); a - // one-ended `first a { … }` ends in a RelationshipBody, no ActionBody. - members, hasBody := p.parseNodeBodyContext(start, "initial node", bodyOther) + // (SysML.xtext ActionTargetSuccession, whose UsageBody holds the action + // members of the body the edge is written in); a one-ended `first a { … }` + // ends in a RelationshipBody, no ActionBody. + ctx := bodyOther + if successor != nil && p.bodyContext().admitsAcceptNode() { + ctx = bodySuccession + } + members, hasBody := p.parseNodeBodyContext(start, "initial node", ctx) node := &ast.InitialNode{ First: first, @@ -1031,12 +1036,19 @@ func (p *Parser) parseSuccessionEdgeWithMultiplicity(tok lexer.Token, allowBody return node } - // An action target succession ends in a UsageBody (SysML.xtext:1698). + // An action target succession ends in a UsageBody (SysML.xtext:1698), + // which holds the action members of the body the edge is written in: a + // `then { accept … }` in an action body holds accept nodes like the + // statements around it. var members []ast.Node hasBody := false if allowBody && p.accept2(lexer.LBrace) { hasBody = true - leave := p.pushBodyContext(bodyOther) + ctx := bodyOther + if p.bodyContext().admitsAcceptNode() { + ctx = bodySuccession + } + leave := p.pushBodyContext(ctx) members = p.parseMixedBody() leave() } else { diff --git a/internal/syntax/parser/defusage.go b/internal/syntax/parser/defusage.go index 3fc6ec7175..12f9751a5f 100644 --- a/internal/syntax/parser/defusage.go +++ b/internal/syntax/parser/defusage.go @@ -2026,6 +2026,12 @@ var ownedMembers = map[string]ownedMember{ "do": {"the do action of a state", "state", []bodyContext{bodyState}}, "exit": {"the exit action of a state", "state", []bodyContext{bodyState}}, "render": {"the rendering of a view", "view", []bodyContext{bodyViewDef, bodyView}}, + // FramedConcernMember is a RequirementBodyItem only (SysML.xtext): a view + // frames nothing, which the conformance rules now reflect too. + "frame": {"the concern a requirement or viewpoint frames", "requirement", []bodyContext{bodyRequirement}}, + // ElementFilterMember is a PackageBodyElement, a ViewDefinitionBodyItem and + // a ViewBodyItem (SysML.xtext), so a package, the root or a view admits it. + "filter": {"an element filter", "package or view", []bodyContext{bodyPackage, bodyViewDef, bodyView}}, // TransitionUsageMember is a StateBodyItem only (SysML.xtext); a transition between // action nodes is an extension the notation pass reports, so those bodies read it too. "transition": {"a transition between states", "state", []bodyContext{bodyState, bodyAction, bodyCalc, bodyCase}}, @@ -2048,8 +2054,9 @@ func memberKeyword(notation string) string { } // BodyAdmitsMember reports whether the body of owner — a Definition or Usage; -// any other node, the document root included, opens a plain namespace body — -// offers the member keyword kw introduces; as for MemberOwner, kw may be a notation. +// a package or the document root opens a package body, anything else a plain +// namespace body — offers the member keyword kw introduces; as for MemberOwner, +// kw may be a notation. func BodyAdmitsMember(owner ast.Node, kw string) bool { m, ok := ownedMembers[memberKeyword(kw)] if !ok { @@ -2094,6 +2101,8 @@ func declarationBodyContext(owner ast.Node) bodyContext { body = usageBodyContext(d.Kind) case *ast.SubstateMember: body = usageBodyContext(ast.UsageState) + case *ast.Package, *ast.RootNamespace: + body = bodyPackage } return body } @@ -2871,6 +2880,11 @@ func (p *Parser) parseBodyMember() ast.Node { return p.parseActionMember() } if p.atAcceptNodeAt(p.prefixLookahead()) { + if !p.bodyContext().admitsAcceptNode() { + en := p.errorNodeSkip(start, msgExpectedBodyMember) + en.SetLeadingTrivia(trivia) + return en + } return p.parseAcceptNode(start, vis, trivia, p.parsePrefixMetadata()) } // Delegate to parseDefUsage which handles prefixes; a prefixed @@ -3046,6 +3060,14 @@ func (p *Parser) parseBodyMember() ast.Node { // (`accept when x > 1`) — and is parsed by the one payload parser triggers // also use, so every spelling reaches lowering the same way. if p.atAcceptNode() { + // AcceptNode is an ActionBodyItem (SysML.xtext): outside an + // action-admitting body it stands where no member may, like the + // behavioral keywords. A state body keeps reading it as today. + if !p.bodyContext().admitsAcceptNode() { + en := p.errorNodeSkip(start, msgExpectedBodyMember) + en.SetLeadingTrivia(trivia) + return en + } return p.parseAcceptNode(start, vis, trivia, nil) } diff --git a/internal/syntax/parser/namespace.go b/internal/syntax/parser/namespace.go index 5e407adcc8..f45ae681af 100644 --- a/internal/syntax/parser/namespace.go +++ b/internal/syntax/parser/namespace.go @@ -389,7 +389,16 @@ func (p *Parser) parseDeclaration(start int) ast.Node { case p.atKeyword("multiplicity"): return p.parseMultiplicityDecl(start) case p.atKeyword("filter"): - return p.parseFilter(start) + var misplaced *ast.ErrorNode + if !p.bodyAdmitsMember("filter") { + misplaced = p.misplacedMember(p.peek()) + } + f := p.parseFilter(start) + if misplaced != nil { + misplaced.NodeSpan = f.Span() + return misplaced + } + return f case p.atKeyword("locale"): // An anonymous comment carrying a locale (SysML.xtext Comment: the // `comment` keyword is optional). @@ -426,15 +435,17 @@ func (p *Parser) parseDeclaration(start int) ast.Node { } } -// parseNamespaceBody parses `{ member* }` or `;`. Returns (members, hasBody). -// The caller has already consumed the declaration head up to this point. -func (p *Parser) parseNamespaceBody() ([]ast.Node, bool) { +// parseNamespaceBody parses `{ member* }` or `;` under the body notation ctx +// (a package's own body, or the plain namespace body of an import, alias, +// namespace, dependency or multiplicity). Returns (members, hasBody). The +// caller has already consumed the declaration head up to this point. +func (p *Parser) parseNamespaceBody(ctx bodyContext) ([]ast.Node, bool) { if p.accept2(lexer.Semicolon) || !p.expectBodyOrEnd("declaration") { return nil, false } // A package/namespace body has its own notation; it never inherits the // enclosing body's (e.g. an interface's default-end allowance). - defer p.pushBodyContext(bodyOther)() + defer p.pushBodyContext(ctx)() var members []ast.Node for !p.atEOF() && !p.at(lexer.RBrace) { p.memberStart() @@ -665,7 +676,7 @@ func (p *Parser) parseImport(start int, vis ast.Visibility) *ast.Import { p.parseImportTail(imp) - imp.Body, imp.HasBody = p.parseNamespaceBody() + imp.Body, imp.HasBody = p.parseNamespaceBody(bodyOther) imp.NodeSpan = p.spanFrom(start) return imp } @@ -731,7 +742,7 @@ func (p *Parser) parseAlias(start int, vis ast.Visibility) *ast.Alias { } else { al.For = p.parseQualifiedName() } - al.Body, al.HasBody = p.parseNamespaceBody() + al.Body, al.HasBody = p.parseNamespaceBody(bodyOther) al.NodeSpan = p.spanFrom(start) return al } @@ -773,7 +784,7 @@ func (p *Parser) parseDependency(start int) ast.Node { } else { dep.Suppliers = p.parseQualifiedNameList() } - dep.Body, dep.HasBody = p.parseNamespaceBody() + dep.Body, dep.HasBody = p.parseNamespaceBody(bodyOther) dep.NodeSpan = p.spanFrom(start) return dep } @@ -844,7 +855,7 @@ func (p *Parser) parsePackage(start int) ast.Node { return p.errorNodeSkip(start, "expected 'package'") } id := p.parseIdentification() - members, hasBody := p.parseNamespaceBody() + members, hasBody := p.parseNamespaceBody(bodyPackage) pkg := &ast.Package{ Prefixes: prefixes, Ident: id, @@ -864,7 +875,7 @@ func (p *Parser) parseNamespace(start int) ast.Node { return p.errorNodeSkip(start, "expected 'namespace'") } id := p.parseIdentification() - members, hasBody := p.parseNamespaceBody() + members, hasBody := p.parseNamespaceBody(bodyOther) ns := &ast.Namespace{Prefixes: prefixes, Ident: id, Members: members, HasBody: hasBody} ns.NodeSpan = p.spanFrom(start) return ns @@ -967,7 +978,7 @@ func (p *Parser) parseMultiplicityDecl(start int) ast.Node { } // Parse body or semicolon - members, hasBody := p.parseNamespaceBody() + members, hasBody := p.parseNamespaceBody(bodyOther) md := &ast.MultiplicityDecl{ Ident: ident, diff --git a/internal/syntax/parser/owned_member_test.go b/internal/syntax/parser/owned_member_test.go index ca99505b72..56e72e5169 100644 --- a/internal/syntax/parser/owned_member_test.go +++ b/internal/syntax/parser/owned_member_test.go @@ -114,6 +114,17 @@ func TestMemberOutsideOwningBodyIsRejected(t *testing.T) { {"render in part usage under view", "view def V { part p { render r; } }", "render", `kind="render"`}, {"render in typed usage under nested view", "view def V { view v { x { render r; } } }", "render", `kind="render"`}, {"render in typed usage under view usage", "view v { x : T { render r; } }", "render", `kind="render"`}, + // FramedConcernMember is a RequirementBodyItem and ElementFilterMember a + // package/view-body one (SysML.xtext), so neither parses in these bodies. + {"frame in view", "view V { frame concern c : C; }", "frame", `kind="frame"`}, + {"frame reference in view", "view V { frame c; }", "frame", `kind="frame"`}, + {"frame in view def", "view def V { frame concern c : C; }", "frame", `kind="frame"`}, + {"frame in rendering def", "rendering def R { frame c; }", "frame", `kind="frame"`}, + {"frame in part def", "part def P { frame c; }", "frame", `kind="frame"`}, + {"filter in viewpoint def", "viewpoint def V { filter true; }", "filter", "FilterMember"}, + {"filter in rendering def", "rendering def R { filter true; }", "filter", "FilterMember"}, + {"filter in part def", "part def P { filter true; }", "filter", "FilterMember"}, + {"filter in namespace", "package P { namespace N { filter true; } }", "filter", "FilterMember"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -154,7 +165,7 @@ func TestMemberOutsideOwningBodyIsRejected(t *testing.T) { // KerML reserves none of the member keywords, so each names an ordinary feature // in a `.kerml` file, in every shape the KerML Feature production offers. func TestOwnedMemberKeywordsAreKerMLNames(t *testing.T) { - for _, word := range []string{"actor", "subject", "stakeholder", "objective", "entry", "do", "exit", "render"} { + for _, word := range []string{"actor", "subject", "stakeholder", "objective", "entry", "do", "exit", "render", "frame"} { for _, shape := range []string{ "feature %s : A;", "feature %s;", @@ -203,6 +214,19 @@ func TestMemberInsideOwningBodyStaysClean(t *testing.T) { {"viewpoint usage", "viewpoint vp { subject v : V; actor a : Person; stakeholder s : Person; }"}, {"satisfy body", "part p { satisfy r by p { subject v : V; actor a : Person; } }"}, {"frame body", "requirement def R { frame c { stakeholder s : Person; actor a : Person; } }"}, + {"frame in requirement", "concern def C; requirement r { frame concern c : C; frame c; }"}, + {"frame in concern", "concern def C; concern k { frame concern c : C; frame c; }"}, + {"frame in viewpoint def", "concern def C; viewpoint def V { frame concern c : C; frame c; }"}, + {"frame in viewpoint usage", "concern def C; viewpoint v { frame concern c : C; frame c; }"}, + {"filter in root", "filter true;"}, + {"filter in package", "package P { filter true; }"}, + {"filter in library package", "library package L { filter true; }"}, + {"filter in view", "view v { filter true; }"}, + {"filter in view def", "view def V { filter true; }"}, + {"accept in action body", "action def A { item def S; action a { accept S; } }"}, + {"accept in calc body", "item def S; calc c { accept S; }"}, + {"accept in case body", "item def S; case c { accept S; }"}, + {"accept in state body", "state s { state t; accept after 5 then t; }"}, {"objective body", "case def C { objective o { subject v : V; actor a : Person; stakeholder s : Person; } }"}, {"case def", "case def C { subject v : V; actor a : Person; objective o; }"}, {"case usage", "case c { subject v : V; actor a : Person; objective { require constraint { x > 0 } } }"}, diff --git a/internal/syntax/parser/parser.go b/internal/syntax/parser/parser.go index 04c58a88e0..03b6ac9f9d 100644 --- a/internal/syntax/parser/parser.go +++ b/internal/syntax/parser/parser.go @@ -112,6 +112,12 @@ const ( // only the view usage admits `expose`. bodyViewDef bodyView + // bodyPackage is a PackageBodyElement body: a package's or the file root's. + bodyPackage + // bodySuccession is the UsageBody an action target succession ends in + // (SysML.xtext:1698): it takes the action members of the body around it — + // accept nodes included — but no initial node or target succession of its own. + bodySuccession ) // carriesActions reports whether the body admits ActionBodyItem (action, calc, @@ -124,6 +130,14 @@ func (c bodyContext) carriesActions() bool { return false } +// admitsAcceptNode reports whether the body admits an AcceptNode: the +// action-admitting bodies (SysML.xtext ActionBodyItem), a state body and the +// usage body an action target succession carries, whose own members are read +// the way they always have been. +func (c bodyContext) admitsAcceptNode() bool { + return c.carriesActions() || c == bodyState || c == bodySuccession +} + // pushBodyContext enters a body of the given notation and returns the function // that leaves it. func (p *Parser) pushBodyContext(c bodyContext) func() { @@ -132,10 +146,11 @@ func (p *Parser) pushBodyContext(c bodyContext) func() { return func() { p.bodyCtx = p.bodyCtx[:depth] } } -// bodyContext returns the notation of the innermost body being parsed. +// bodyContext returns the notation of the innermost body being parsed; the +// file root reads as a package body (SysML.xtext PackageBodyElement). func (p *Parser) bodyContext() bodyContext { if len(p.bodyCtx) == 0 { - return bodyOther + return bodyPackage } return p.bodyCtx[len(p.bodyCtx)-1] } diff --git a/tests/export/testdata/convert/imported_references.canonical.golden.sysml b/tests/export/testdata/convert/imported_references.canonical.golden.sysml index 6e0d826b4a..c39c30b2e6 100644 --- a/tests/export/testdata/convert/imported_references.canonical.golden.sysml +++ b/tests/export/testdata/convert/imported_references.canonical.golden.sysml @@ -57,6 +57,6 @@ package Filtered { public import Meta::*[@Safety]; part def C { attribute other : Tagged; - filter @Safety; } + filter @Safety; } diff --git a/tests/export/testdata/convert/imported_references.golden.sysml b/tests/export/testdata/convert/imported_references.golden.sysml index 621c024dbd..32b2d9fa6f 100644 --- a/tests/export/testdata/convert/imported_references.golden.sysml +++ b/tests/export/testdata/convert/imported_references.golden.sysml @@ -56,6 +56,6 @@ package Filtered { public import Meta::*[@Safety]; part def C { attribute other : Tagged; - filter @Safety; } + filter @Safety; } diff --git a/tests/export/testdata/convert/imported_references.golden.ttl b/tests/export/testdata/convert/imported_references.golden.ttl index 62c27f9081..88964bd9b0 100644 --- a/tests/export/testdata/convert/imported_references.golden.ttl +++ b/tests/export/testdata/convert/imported_references.golden.ttl @@ -1622,13 +1622,14 @@ elmt:Filtered sysx:sourceLanguage "sysml" ; sysml:declaredName "Filtered" ; sysx:hasBody "true"^^xsd:boolean ; - sysml:ownedRelationship elmt:Filtered___400, elmt:Filtered__C_om ; + sysml:ownedRelationship elmt:Filtered___400, elmt:Filtered__C_om, elmt:Filtered___402 ; sysml:ownedImport elmt:Filtered___400 ; sysml:ownedMember elmt:Filtered__C ; - sysml:ownedMembership elmt:Filtered__C_om ; + sysml:ownedMembership elmt:Filtered__C_om, elmt:Filtered___402 ; sysx:sourceText "package Filtered {\n" ; sysx:sourceTail "}\n" ; - json:ownedRelationship "[{\"@id\":\"Filtered___400\"},{\"@id\":\"Filtered__C_om\"}]" . + json:ownedRelationship "[{\"@id\":\"Filtered___400\"},{\"@id\":\"Filtered__C_om\"},{\"@id\":\"Filtered___402\"}]" ; + json:ownedMembership "[{\"@id\":\"Filtered__C_om\"},{\"@id\":\"Filtered___402\"}]" . elmt:Filtered___400 a sysml:NamespaceImport ; @@ -1765,7 +1766,7 @@ elmt:Filtered__C sysml:qualifiedName "Filtered::C" ; sysx:sourceLine "57"^^xsd:integer ; sysx:sourceColumn "5"^^xsd:integer ; - sysx:sourceEndLine "60"^^xsd:integer ; + sysx:sourceEndLine "59"^^xsd:integer ; sysx:sourceEndColumn "6"^^xsd:integer ; sysml:elementId "Filtered__C" ; sysx:memberIndex "1"^^xsd:integer ; @@ -1776,14 +1777,12 @@ elmt:Filtered__C sysml:declaredName "C" ; sysx:hasBody "true"^^xsd:boolean ; sysml:ownedMember elmt:Filtered__C__other ; - sysml:ownedMembership elmt:Filtered__C__other_om, elmt:Filtered__C___401 ; - sysml:ownedRelationship elmt:Filtered__C__other_om, elmt:Filtered__C___401 ; + sysml:ownedMembership elmt:Filtered__C__other_om ; + sysml:ownedRelationship elmt:Filtered__C__other_om ; sysml:ownedFeature elmt:Filtered__C__other ; sysml:ownedFeatureMembership elmt:Filtered__C__other_om ; sysx:sourceText " part def C {\n" ; - sysx:sourceTail " }\n" ; - json:ownedMembership "[{\"@id\":\"Filtered__C__other_om\"},{\"@id\":\"Filtered__C___401\"}]" ; - json:ownedRelationship "[{\"@id\":\"Filtered__C__other_om\"},{\"@id\":\"Filtered__C___401\"}]" . + sysx:sourceTail " }\n" . elmt:Filtered__C_om a sysml:OwningMembership ; @@ -1828,104 +1827,104 @@ elmt:Filtered__C__other_om sysml:ownedMemberFeature elmt:Filtered__C__other ; sysml:owningType elmt:Filtered__C . -elmt:Filtered__C___401 +elmt:Filtered___402 a sysml:ElementFilterMembership ; - sysml:qualifiedName "Filtered::C::@1" ; - sysx:sourceLine "59"^^xsd:integer ; - sysx:sourceColumn "9"^^xsd:integer ; - sysx:sourceEndLine "59"^^xsd:integer ; - sysx:sourceEndColumn "24"^^xsd:integer ; - sysml:elementId "Filtered__C___401" ; - sysx:memberIndex "1"^^xsd:integer ; - sysml:owningNamespace elmt:Filtered__C ; - sysml:owner elmt:Filtered__C ; - sysml:owningRelatedElement elmt:Filtered__C ; - sysml:membershipOwningNamespace elmt:Filtered__C ; - sysml:condition expr:Filtered__C___401_pfilter ; - sysml:ownedRelatedElement expr:Filtered__C___401_pfilter ; - sysml:memberElement expr:Filtered__C___401_pfilter ; - sysml:ownedMemberElement expr:Filtered__C___401_pfilter ; - sysx:sourceText " filter @Safety;\n" . - -expr:Filtered__C___401_pfilter + sysml:qualifiedName "Filtered::@2" ; + sysx:sourceLine "60"^^xsd:integer ; + sysx:sourceColumn "5"^^xsd:integer ; + sysx:sourceEndLine "60"^^xsd:integer ; + sysx:sourceEndColumn "20"^^xsd:integer ; + sysml:elementId "Filtered___402" ; + sysx:memberIndex "2"^^xsd:integer ; + sysml:owningNamespace elmt:Filtered ; + sysml:owner elmt:Filtered ; + sysml:owningRelatedElement elmt:Filtered ; + sysml:membershipOwningNamespace elmt:Filtered ; + sysml:condition expr:Filtered___402_pfilter ; + sysml:ownedRelatedElement expr:Filtered___402_pfilter ; + sysml:memberElement expr:Filtered___402_pfilter ; + sysml:ownedMemberElement expr:Filtered___402_pfilter ; + sysx:sourceText " filter @Safety;\n" . + +expr:Filtered___402_pfilter a sysml:OperatorExpression ; sysx:sourceText "@Safety" ; - sysml:elementId "Filtered__C___401_pfilter" ; + sysml:elementId "Filtered___402_pfilter" ; sysml:operator "@" ; sysx:typeArgument elmt:Meta__Safety ; - sysml:ownedRelationship expr:Filtered__C___401_pfilter_pin0_om, expr:Filtered__C___401_pfilter_pout_om ; - sysml:ownedMembership expr:Filtered__C___401_pfilter_pin0_om, expr:Filtered__C___401_pfilter_pout_om ; - sysml:ownedFeatureMembership expr:Filtered__C___401_pfilter_pin0_om, expr:Filtered__C___401_pfilter_pout_om ; - sysml:ownedFeature expr:Filtered__C___401_pfilter_pin0, expr:Filtered__C___401_pfilter_pout ; - sysml:parameter expr:Filtered__C___401_pfilter_pin0, expr:Filtered__C___401_pfilter_pout ; - sysml:input expr:Filtered__C___401_pfilter_pin0 ; - sysml:result expr:Filtered__C___401_pfilter_pout ; - sysml:owner elmt:Filtered__C___401 ; - sysml:owningRelationship elmt:Filtered__C___401 ; - sysml:owningMembership elmt:Filtered__C___401 ; - json:ownedRelationship "[{\"@id\":\"Filtered__C___401_pfilter_pin0_om\"},{\"@id\":\"Filtered__C___401_pfilter_pout_om\"}]" ; - json:ownedMembership "[{\"@id\":\"Filtered__C___401_pfilter_pin0_om\"},{\"@id\":\"Filtered__C___401_pfilter_pout_om\"}]" ; - json:ownedFeatureMembership "[{\"@id\":\"Filtered__C___401_pfilter_pin0_om\"},{\"@id\":\"Filtered__C___401_pfilter_pout_om\"}]" ; - json:ownedFeature "[{\"@id\":\"Filtered__C___401_pfilter_pin0\"},{\"@id\":\"Filtered__C___401_pfilter_pout\"}]" ; - json:parameter "[{\"@id\":\"Filtered__C___401_pfilter_pin0\"},{\"@id\":\"Filtered__C___401_pfilter_pout\"}]" . - -expr:Filtered__C___401_pfilter_pin0 + sysml:ownedRelationship expr:Filtered___402_pfilter_pin0_om, expr:Filtered___402_pfilter_pout_om ; + sysml:ownedMembership expr:Filtered___402_pfilter_pin0_om, expr:Filtered___402_pfilter_pout_om ; + sysml:ownedFeatureMembership expr:Filtered___402_pfilter_pin0_om, expr:Filtered___402_pfilter_pout_om ; + sysml:ownedFeature expr:Filtered___402_pfilter_pin0, expr:Filtered___402_pfilter_pout ; + sysml:parameter expr:Filtered___402_pfilter_pin0, expr:Filtered___402_pfilter_pout ; + sysml:input expr:Filtered___402_pfilter_pin0 ; + sysml:result expr:Filtered___402_pfilter_pout ; + sysml:owner elmt:Filtered___402 ; + sysml:owningRelationship elmt:Filtered___402 ; + sysml:owningMembership elmt:Filtered___402 ; + json:ownedRelationship "[{\"@id\":\"Filtered___402_pfilter_pin0_om\"},{\"@id\":\"Filtered___402_pfilter_pout_om\"}]" ; + json:ownedMembership "[{\"@id\":\"Filtered___402_pfilter_pin0_om\"},{\"@id\":\"Filtered___402_pfilter_pout_om\"}]" ; + json:ownedFeatureMembership "[{\"@id\":\"Filtered___402_pfilter_pin0_om\"},{\"@id\":\"Filtered___402_pfilter_pout_om\"}]" ; + json:ownedFeature "[{\"@id\":\"Filtered___402_pfilter_pin0\"},{\"@id\":\"Filtered___402_pfilter_pout\"}]" ; + json:parameter "[{\"@id\":\"Filtered___402_pfilter_pin0\"},{\"@id\":\"Filtered___402_pfilter_pout\"}]" . + +expr:Filtered___402_pfilter_pin0 a sysml:Feature ; - sysml:elementId "Filtered__C___401_pfilter_pin0" ; + sysml:elementId "Filtered___402_pfilter_pin0" ; sysml:direction "in" ; - sysml:owner expr:Filtered__C___401_pfilter ; - sysml:owningRelationship expr:Filtered__C___401_pfilter_pin0_om ; - sysml:owningMembership expr:Filtered__C___401_pfilter_pin0_om ; - sysml:ownedTyping expr:Filtered__C___401_pfilter_pin0_ft0 ; - sysml:ownedSpecialization expr:Filtered__C___401_pfilter_pin0_ft0 ; - sysml:ownedRelationship expr:Filtered__C___401_pfilter_pin0_ft0 . - -expr:Filtered__C___401_pfilter_pin0_om + sysml:owner expr:Filtered___402_pfilter ; + sysml:owningRelationship expr:Filtered___402_pfilter_pin0_om ; + sysml:owningMembership expr:Filtered___402_pfilter_pin0_om ; + sysml:ownedTyping expr:Filtered___402_pfilter_pin0_ft0 ; + sysml:ownedSpecialization expr:Filtered___402_pfilter_pin0_ft0 ; + sysml:ownedRelationship expr:Filtered___402_pfilter_pin0_ft0 . + +expr:Filtered___402_pfilter_pin0_om a sysml:ParameterMembership ; - sysml:elementId "Filtered__C___401_pfilter_pin0_om" ; - sysml:owner expr:Filtered__C___401_pfilter ; - sysml:memberElement expr:Filtered__C___401_pfilter_pin0 ; - sysml:ownedMemberElement expr:Filtered__C___401_pfilter_pin0 ; - sysml:ownedRelatedElement expr:Filtered__C___401_pfilter_pin0 ; - sysml:owningRelatedElement expr:Filtered__C___401_pfilter ; - sysml:membershipOwningNamespace expr:Filtered__C___401_pfilter ; - sysml:ownedMemberFeature expr:Filtered__C___401_pfilter_pin0 ; - sysml:ownedMemberParameter expr:Filtered__C___401_pfilter_pin0 . - -expr:Filtered__C___401_pfilter_pin0_ft0 + sysml:elementId "Filtered___402_pfilter_pin0_om" ; + sysml:owner expr:Filtered___402_pfilter ; + sysml:memberElement expr:Filtered___402_pfilter_pin0 ; + sysml:ownedMemberElement expr:Filtered___402_pfilter_pin0 ; + sysml:ownedRelatedElement expr:Filtered___402_pfilter_pin0 ; + sysml:owningRelatedElement expr:Filtered___402_pfilter ; + sysml:membershipOwningNamespace expr:Filtered___402_pfilter ; + sysml:ownedMemberFeature expr:Filtered___402_pfilter_pin0 ; + sysml:ownedMemberParameter expr:Filtered___402_pfilter_pin0 . + +expr:Filtered___402_pfilter_pin0_ft0 a sysml:FeatureTyping ; - sysml:elementId "Filtered__C___401_pfilter_pin0_ft0" ; - sysml:typedFeature expr:Filtered__C___401_pfilter_pin0 ; - sysml:owningFeature expr:Filtered__C___401_pfilter_pin0 ; - sysml:specific expr:Filtered__C___401_pfilter_pin0 ; - sysml:source expr:Filtered__C___401_pfilter_pin0 ; - sysml:owningRelatedElement expr:Filtered__C___401_pfilter_pin0 ; + sysml:elementId "Filtered___402_pfilter_pin0_ft0" ; + sysml:typedFeature expr:Filtered___402_pfilter_pin0 ; + sysml:owningFeature expr:Filtered___402_pfilter_pin0 ; + sysml:specific expr:Filtered___402_pfilter_pin0 ; + sysml:source expr:Filtered___402_pfilter_pin0 ; + sysml:owningRelatedElement expr:Filtered___402_pfilter_pin0 ; sysml:type elmt:Meta__Safety ; sysml:general elmt:Meta__Safety ; sysml:target elmt:Meta__Safety ; - sysml:owner expr:Filtered__C___401_pfilter_pin0 ; - sysml:relatedElement expr:Filtered__C___401_pfilter_pin0, elmt:Meta__Safety ; - json:relatedElement "[{\"@id\":\"Filtered__C___401_pfilter_pin0\"},{\"@id\":\"Meta__Safety\"}]" . + sysml:owner expr:Filtered___402_pfilter_pin0 ; + sysml:relatedElement expr:Filtered___402_pfilter_pin0, elmt:Meta__Safety ; + json:relatedElement "[{\"@id\":\"Filtered___402_pfilter_pin0\"},{\"@id\":\"Meta__Safety\"}]" . -expr:Filtered__C___401_pfilter_pout +expr:Filtered___402_pfilter_pout a sysml:Feature ; - sysml:elementId "Filtered__C___401_pfilter_pout" ; + sysml:elementId "Filtered___402_pfilter_pout" ; sysml:direction "out" ; - sysml:owner expr:Filtered__C___401_pfilter ; - sysml:owningRelationship expr:Filtered__C___401_pfilter_pout_om ; - sysml:owningMembership expr:Filtered__C___401_pfilter_pout_om . + sysml:owner expr:Filtered___402_pfilter ; + sysml:owningRelationship expr:Filtered___402_pfilter_pout_om ; + sysml:owningMembership expr:Filtered___402_pfilter_pout_om . -expr:Filtered__C___401_pfilter_pout_om +expr:Filtered___402_pfilter_pout_om a sysml:ReturnParameterMembership ; - sysml:elementId "Filtered__C___401_pfilter_pout_om" ; - sysml:owner expr:Filtered__C___401_pfilter ; - sysml:memberElement expr:Filtered__C___401_pfilter_pout ; - sysml:ownedMemberElement expr:Filtered__C___401_pfilter_pout ; - sysml:ownedRelatedElement expr:Filtered__C___401_pfilter_pout ; - sysml:owningRelatedElement expr:Filtered__C___401_pfilter ; - sysml:membershipOwningNamespace expr:Filtered__C___401_pfilter ; - sysml:ownedMemberFeature expr:Filtered__C___401_pfilter_pout ; - sysml:ownedMemberParameter expr:Filtered__C___401_pfilter_pout . + sysml:elementId "Filtered___402_pfilter_pout_om" ; + sysml:owner expr:Filtered___402_pfilter ; + sysml:memberElement expr:Filtered___402_pfilter_pout ; + sysml:ownedMemberElement expr:Filtered___402_pfilter_pout ; + sysml:ownedRelatedElement expr:Filtered___402_pfilter_pout ; + sysml:owningRelatedElement expr:Filtered___402_pfilter ; + sysml:membershipOwningNamespace expr:Filtered___402_pfilter ; + sysml:ownedMemberFeature expr:Filtered___402_pfilter_pout ; + sysml:ownedMemberParameter expr:Filtered___402_pfilter_pout . elmt:OtherPkg__Inner__wheels_ft0 a sysml:FeatureTyping ; diff --git a/tests/export/testdata/convert/imported_references.sysml b/tests/export/testdata/convert/imported_references.sysml index 621c024dbd..32b2d9fa6f 100644 --- a/tests/export/testdata/convert/imported_references.sysml +++ b/tests/export/testdata/convert/imported_references.sysml @@ -56,6 +56,6 @@ package Filtered { public import Meta::*[@Safety]; part def C { attribute other : Tagged; - filter @Safety; } + filter @Safety; } diff --git a/tests/parser/negative_test.go b/tests/parser/negative_test.go index e37403a16d..a5340ab50e 100644 --- a/tests/parser/negative_test.go +++ b/tests/parser/negative_test.go @@ -268,6 +268,21 @@ func TestNegative(t *testing.T) { // ValuePart, unlike the performed action reference that shares its shape. {"render_reference_value", "view def V { render r = 3; }"}, + // FramedConcernMember is a RequirementBodyItem, ElementFilterMember a + // package/view-body item, and AcceptNode an ActionBodyItem (SysML.xtext): + // each is rejected by the bodies its grammar omits. + {"frame_in_view_body", "view V { frame concern c : C; }"}, + {"frame_ref_in_view_body", "view V { frame c; }"}, + {"frame_in_view_def_body", "view def V { frame concern c : C; }"}, + {"frame_in_rendering_body", "rendering def R { frame c; }"}, + {"frame_in_part_def_body", "part def P { frame c; }"}, + {"filter_in_viewpoint_def_body", "viewpoint def V { filter true; }"}, + {"filter_in_rendering_body", "rendering def R { filter true; }"}, + {"filter_in_part_def_body", "part def P { filter true; }"}, + {"accept_in_view_body", "view V { accept S; }"}, + {"accept_action_in_view_body", "view V { action a accept S; }"}, + {"accept_in_part_def_body", "part def P { accept S; }"}, + // The sequence index and the collection notations: `#` indexes through a // parenthesized index and `.?` selects through a body, so each is // rejected where the notation it needs is absent rather than parsed as diff --git a/tools/referee/reject/testdata/negative/extensions/x11-target-succession-outside-action-body.sysml b/tools/referee/reject/testdata/negative/extensions/x11-target-succession-outside-action-body.sysml new file mode 100644 index 0000000000..63b6b80d1a --- /dev/null +++ b/tools/referee/reject/testdata/negative/extensions/x11-target-succession-outside-action-body.sysml @@ -0,0 +1,7 @@ +// Invalid: TargetSuccessionMember is an ActionBodyItem; a view body admits no 'then ;' succession (SysML.xtext:1367 ActionBodyItem, :1693 TargetSuccessionMember, :2359 ViewBodyItem). +package X11TargetSuccessionOutsideActionBody { + view V { + action x; + then y; + } +} diff --git a/tools/referee/reject/testdata/negative/grammar/g82-frame-in-view-body.sysml b/tools/referee/reject/testdata/negative/grammar/g82-frame-in-view-body.sysml new file mode 100644 index 0000000000..b5bc8287fe --- /dev/null +++ b/tools/referee/reject/testdata/negative/grammar/g82-frame-in-view-body.sysml @@ -0,0 +1,7 @@ +// Invalid: FramedConcernMember is a RequirementBodyItem; a view body admits no 'frame' (SysML.xtext:2062 RequirementBodyItem, :2359 ViewBodyItem). +package G82FrameInViewBody { + concern def C; + view V { + frame concern c : C; + } +} diff --git a/tools/referee/reject/testdata/negative/grammar/g83-frame-in-view-def-body.sysml b/tools/referee/reject/testdata/negative/grammar/g83-frame-in-view-def-body.sysml new file mode 100644 index 0000000000..0f8a149778 --- /dev/null +++ b/tools/referee/reject/testdata/negative/grammar/g83-frame-in-view-def-body.sysml @@ -0,0 +1,7 @@ +// Invalid: FramedConcernMember is a RequirementBodyItem; a view def body admits no 'frame' (SysML.xtext:2062 RequirementBodyItem, :2329 ViewDefinitionBodyItem). +package G83FrameInViewDefBody { + concern def C; + view def V { + frame c; + } +} diff --git a/tools/referee/reject/testdata/negative/grammar/g84-filter-outside-package-or-view-body.sysml b/tools/referee/reject/testdata/negative/grammar/g84-filter-outside-package-or-view-body.sysml new file mode 100644 index 0000000000..d6ce17a26a --- /dev/null +++ b/tools/referee/reject/testdata/negative/grammar/g84-filter-outside-package-or-view-body.sysml @@ -0,0 +1,6 @@ +// Invalid: ElementFilterMember is a PackageBodyElement, ViewDefinitionBodyItem and ViewBodyItem; a rendering def body admits no 'filter' (SysML.xtext:227 ElementFilterMember, :2329 ViewDefinitionBodyItem, :2359 ViewBodyItem). +package G84FilterOutsidePackageOrViewBody { + rendering def R { + filter true; + } +} diff --git a/tools/referee/reject/testdata/negative/grammar/g85-accept-outside-action-body.sysml b/tools/referee/reject/testdata/negative/grammar/g85-accept-outside-action-body.sysml new file mode 100644 index 0000000000..837f0f2640 --- /dev/null +++ b/tools/referee/reject/testdata/negative/grammar/g85-accept-outside-action-body.sysml @@ -0,0 +1,7 @@ +// Invalid: AcceptNode is an ActionBodyItem; a view body admits no 'accept' (SysML.xtext:1367 ActionBodyItem, :1434 AcceptNode, :2359 ViewBodyItem). +package G85AcceptOutsideActionBody { + item def S; + view V { + accept S; + } +} From ef0f402ad68bcb6067222ac36491fbb9c62504e5 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Sun, 4 Oct 2026 02:35:49 +0000 Subject: [PATCH 2/6] fix(stdlib): declare the OpenSysML extension libraries as non-standard Co-Authored-By: jason.han --- ...xtension-libraries-non-standard.changed.md | 1 + docs/project/diagram-layout-annotations.md | 2 +- docs/project/element-identity-annotations.md | 2 +- docs/project/omg-issues.md | 4 ++-- internal/workspace/libs/stdlib.snapshot | Bin 3777208 -> 3777208 bytes .../OpenSysML Libraries/DiagramLayout.sysml | 2 +- .../IdentityMetadata.sysml | 2 +- .../MigrationMetadata.sysml | 2 +- .../OpenSysML Libraries/SysMLValidation.sysml | 2 +- 9 files changed, 9 insertions(+), 8 deletions(-) create mode 100644 changes/unreleased/extension-libraries-non-standard.changed.md diff --git a/changes/unreleased/extension-libraries-non-standard.changed.md b/changes/unreleased/extension-libraries-non-standard.changed.md new file mode 100644 index 0000000000..9abdfa1a6f --- /dev/null +++ b/changes/unreleased/extension-libraries-non-standard.changed.md @@ -0,0 +1 @@ +- **Declare the bundled OpenSysML extension libraries as non-standard.** `DiagramLayout`, `IdentityMetadata`, `MigrationMetadata` and `SysMLValidation` were written `standard library package`, which both asserted membership of the normative Kernel/SysML libraries and drew a pilot warning (`User library packages should not be marked as standard`); they are project extensions and now read `library package`. RDF export marks them `isStandard=false` accordingly. diff --git a/docs/project/diagram-layout-annotations.md b/docs/project/diagram-layout-annotations.md index 8c67bc12ac..274b26ddab 100644 --- a/docs/project/diagram-layout-annotations.md +++ b/docs/project/diagram-layout-annotations.md @@ -50,7 +50,7 @@ Libraries/DiagramLayout.sysml`, counted by the stdlib conformance gate with the extensions): ```sysml -standard library package DiagramLayout { +library package DiagramLayout { metadata def Layout { attribute x : ScalarValues::Real; attribute y : ScalarValues::Real; diff --git a/docs/project/element-identity-annotations.md b/docs/project/element-identity-annotations.md index b49d0e6b43..98d774df6d 100644 --- a/docs/project/element-identity-annotations.md +++ b/docs/project/element-identity-annotations.md @@ -54,7 +54,7 @@ Two metadata definitions, shipped as a non-normative OpenSysML library extension stdlib already carries two such files; the conformance gate counts them separately): ```sysml -standard library package IdentityMetadata { +library package IdentityMetadata { doc /* Binds notation to repository identity. Non-normative OpenSysML * extension, proposed for standardization; see the design record. */ diff --git a/docs/project/omg-issues.md b/docs/project/omg-issues.md index e1ab4b4703..43386d6f30 100644 --- a/docs/project/omg-issues.md +++ b/docs/project/omg-issues.md @@ -2092,7 +2092,7 @@ form, implementable today because user-defined metadata is already conforming notation: ```sysml -standard library package IdentityMetadata { +library package IdentityMetadata { metadata def ElementId { attribute id : ScalarValues::String; } @@ -2154,7 +2154,7 @@ notation — or as dedicated surface syntax if the taskforce prefers. A minimal library form: ```sysml -standard library package DiagramLayout { +library package DiagramLayout { metadata def Layout { attribute x : ScalarValues::Real; attribute y : ScalarValues::Real; diff --git a/internal/workspace/libs/stdlib.snapshot b/internal/workspace/libs/stdlib.snapshot index 96f157de91b882f559049a907751142fd698ac27..bf25bbb336b2fe81b1d45b6c239aec685cd62bb3 100644 GIT binary patch delta 6564 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

Date: Sun, 4 Oct 2026 02:48:41 +0000 Subject: [PATCH 3/6] docs(changelog): narrow the target-succession entry to the one-name then form Co-Authored-By: jason.han --- changes/unreleased/view-body-member-grammar.fixed.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changes/unreleased/view-body-member-grammar.fixed.md b/changes/unreleased/view-body-member-grammar.fixed.md index 5856f101bf..47fe77a0b5 100644 --- a/changes/unreleased/view-body-member-grammar.fixed.md +++ b/changes/unreleased/view-body-member-grammar.fixed.md @@ -1,3 +1,3 @@ - **Reject `frame`, `filter` and `accept` members written outside the bodies that own them.** A `frame` is a RequirementBodyItem — a requirement, concern or viewpoint body, never a view's; a `filter` is a PackageBodyElement and a view/view-def body item — a package or the file root admits it, a viewpoint, rendering or part-def body does not; an `accept` is an ActionBodyItem — an action, calculation or case body (and the state body that already read it), not a view or part def. Each is now reported at the member instead of parsing silently, matching the reference grammar. -- **Report `then ;`, `if then ;` and `else ;` written outside an action or state body.** A target succession sequences members of an action, calculation or case body, and a state body reads it as a target transition; anywhere else it is OpenSysML extension notation, warned by default and rejected under `-strict`. +- **Report `then ;` written outside an action or state body.** A target succession sequences members of an action, calculation or case body, and a state body reads it as a target transition; anywhere else it is OpenSysML extension notation, warned by default and rejected under `-strict`. - **`%view` no longer asks a view to frame the concerns its satisfied viewpoint frames.** Viewpoint conformance is evaluated over the concern the *viewpoint* frames, applied to the elements the view exposes; `frame` was never legal in a view body, so a view with a valid viewpoint now reports `conforms`/`violated` instead of a framing mismatch it could never satisfy. From 906f72579fc996c662c89e1b4bf6f0ab0dba3fa0 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Sun, 4 Oct 2026 02:52:00 +0000 Subject: [PATCH 4/6] docs(changelog): state the RDF consequence of the non-standard libraries precisely Co-Authored-By: jason.han --- changes/unreleased/extension-libraries-non-standard.changed.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changes/unreleased/extension-libraries-non-standard.changed.md b/changes/unreleased/extension-libraries-non-standard.changed.md index 9abdfa1a6f..db6020e264 100644 --- a/changes/unreleased/extension-libraries-non-standard.changed.md +++ b/changes/unreleased/extension-libraries-non-standard.changed.md @@ -1 +1 @@ -- **Declare the bundled OpenSysML extension libraries as non-standard.** `DiagramLayout`, `IdentityMetadata`, `MigrationMetadata` and `SysMLValidation` were written `standard library package`, which both asserted membership of the normative Kernel/SysML libraries and drew a pilot warning (`User library packages should not be marked as standard`); they are project extensions and now read `library package`. RDF export marks them `isStandard=false` accordingly. +- **Declare the bundled OpenSysML extension libraries as non-standard.** `DiagramLayout`, `IdentityMetadata`, `MigrationMetadata` and `SysMLValidation` were written `standard library package`, which both asserted membership of the normative Kernel/SysML libraries and drew a pilot warning (`User library packages should not be marked as standard`); they are project extensions and now read `library package`. RDF export no longer marks them `sysml:isStandard true`. From 07f5c92515ff9603a760fe717ef4cc0041f31c43 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Sun, 4 Oct 2026 03:47:41 +0000 Subject: [PATCH 5/6] test(runtime): declare accept receivers in action bodies Co-Authored-By: jason.han --- internal/exec/runtime/signal_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/exec/runtime/signal_test.go b/internal/exec/runtime/signal_test.go index 98628f6ce5..75bd93c0e4 100644 --- a/internal/exec/runtime/signal_test.go +++ b/internal/exec/runtime/signal_test.go @@ -1685,9 +1685,9 @@ func TestAddressedSendToAnObjectNeedsThatObject(t *testing.T) { func TestAddressedSendToReceiverOfAnotherObjectCarriesItsIdentity(t *testing.T) { idx, _, ctx := buildRuntime(t, "", parseAndBuild(t, `package test { private import ScalarValues::*; - part def Node { action reader accept n : Integer; } + part def Node { action reader { accept n : Integer; } } part def Talker { - action reader accept n : Integer; + action reader { accept n : Integer; } action talk { first start; done; succession first start then done; } } part alpha : Node; @@ -1987,7 +1987,7 @@ func TestAddressedSendToQualifiedElementOfATwinObject(t *testing.T) { port def PingPort { in item ping : Integer; } part def Node { port inPort : PingPort; - action reader accept n : Integer; + action reader { accept n : Integer; } action listen { first start; done; succession first start then done; } } part alpha : Node; From 50515a3f0c09dc5d2ce61763d87778d07076786c Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Mon, 5 Oct 2026 00:35:25 +0000 Subject: [PATCH 6/6] chore(oracle): re-record pilot baselines after merging develop Co-Authored-By: jason.han --- docs/project/pilot-differential-baseline.json | 2 +- docs/project/pilot-rejection-baseline.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/project/pilot-differential-baseline.json b/docs/project/pilot-differential-baseline.json index 250d8d4eac..d4c76ac9b0 100644 --- a/docs/project/pilot-differential-baseline.json +++ b/docs/project/pilot-differential-baseline.json @@ -71,7 +71,7 @@ "digest": "sha256:b0153c55bbfcdacabab911725dc44e0e7f4d3a501a281b1f3f2a13cda19737c6" } ], - "recorded": "2026-10-04" + "recorded": "2026-10-05" }, "totals": { "files": 384, diff --git a/docs/project/pilot-rejection-baseline.json b/docs/project/pilot-rejection-baseline.json index 17b6314efc..64dca0299f 100644 --- a/docs/project/pilot-rejection-baseline.json +++ b/docs/project/pilot-rejection-baseline.json @@ -31,7 +31,7 @@ "digest": "sha256:bfbaaaa8dcd73173f98774ddbd20f6124788fc7f377fb45a419f4dca3a8ef272" } ], - "recorded": "2026-10-04" + "recorded": "2026-10-05" }, "totals": { "cases": 317,