Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
984fa33
docs(roadmap): correct General View edges scope per primary spec rese…
Jul 12, 2026
c819bbd
feat(language): add Dependency and Binding semantic model support
Jul 12, 2026
ace093d
feat(core): wire Connect/Allocate/Dependency/Binding/Subsetting into …
Jul 12, 2026
71002dc
test(core): add layout tests for Connect/Allocate/Dependency/Binding/…
Jul 12, 2026
42172fd
test: add Dependency/Binding facts to WorkspaceLoaderTests and OMG co…
Jul 12, 2026
0ac9e9b
docs(design): update semantic model and layout strategy design docs
Jul 12, 2026
3a2fa2c
docs(reqstream,verification): add requirements and verification for C…
Jul 12, 2026
bbfecb9
feat(gallery): add Connect/Allocate/Dependency/Subsetting examples to…
Jul 12, 2026
4cc2144
style: apply dotnet format auto-fixes from fix.ps1
Jul 12, 2026
29955a5
fix: resolve lint issues (cspell false positive, markdown line length…
Jul 12, 2026
d140c89
Fix ReferenceResolver instance-path collapse and add dropped-edge dia…
Jul 12, 2026
0f855ef
Fix 5th resolver-blast-radius test found via full suite run
Jul 12, 2026
d08996a
Simplify gallery drone model; scope dropped-edge diagnostic to avoid …
Jul 12, 2026
6fc4c83
Update design/verification/reqstream docs for resolver fix and diagno…
Jul 12, 2026
f0f3764
chore: revert spurious stdlib.json.gz regeneration noise
Jul 12, 2026
f4f9c48
fix(language): keep feature-chain resolution instance-relative after …
Jul 12, 2026
d3cc033
docs+test: address formal-review findings (F1-F4)
Jul 12, 2026
781f704
docs(roadmap): capture State Transition View implied-source (initial-…
Jul 12, 2026
b5eea86
fix(language): derive implicit name for redefining usages with no dec…
Jul 12, 2026
53476fc
fix(language): handle dot-chained redefinition references in implicit…
Jul 12, 2026
14b6eec
docs(reqstream): fix stale test references after Binding OMG fixture …
Jul 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .cspell.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ words:
- Skia
- Subsetting
- subsettings
- subsetted
- Subsetted
- Sugiyama
- Tagawa
- Toda
Expand Down
37 changes: 35 additions & 2 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,22 @@ The work falls into three themes:
Render the relationships currently omitted from the General View, each routed via
`ChannelRouter` and carrying the correct spec end shape:

- Subsetting (where shown as edges), feature typing, dependency, containment,
connection/binding, allocation.
- Subsetting (where shown as edges), dependency, connection/binding, allocation.
- Fix `ref` usages, currently drawn as a hollow-diamond Membership edge: SysML v2 removed
"shared aggregation" (the UML/SysML v1 hollow-diamond concept), so `ref` should render as a
dependency-style edge (dashed, open arrowhead) instead.
- Shared-bus generalization (multiple subtypes merging into one line to a supertype) as an
optional readability refinement.

Per the OMG SysML v2 spec's Graphical Notation chapter (§8.2.3), `item`, `occurrence`,
`action`, `state`, and `requirement` usages are **not** edge-connected boxes — they are
canonically rendered as compartment rows on their owning box (same mechanism as
`attribute`), which `GeneralViewLayoutStrategy.BuildCompartments` already does generically
for any feature keyword. No new edge kind or "containment broadening" is needed for these;
earlier roadmap phrasing implying a `containment` edge kind was based on secondary-source
notation tables that conflated other relationships (item flow, action succession,
requirement satisfy/derive) with containment, and has been corrected here.

**Scope:** `AstBuilder`/semantic exposure of the relationship kinds as needed;
`GeneralViewLayoutStrategy` edge emission; resolver coverage. Also extend the drone gallery
model (`docs/gallery/models/01-drone-general.sysml`, which today has no real `connect`/`bind`,
Expand Down Expand Up @@ -90,6 +101,28 @@ primitives (bar, diamond, pentagon, note). `LayoutActivation`/`LayoutBand` alrea
**Visual gate:** sequence shows activation bars + a fragment; action flow shows a fork/join and
a decision/merge with correct shapes.

### State Transition View: implied-source (initial-pseudostate) transitions

SysML v2 allows a state transition with no explicit source state (`then TargetState;`),
meaning an implicit/default transition taken automatically on entry to the enclosing region —
the UML/SysML "initial pseudostate" concept. Today `ReferenceResolver`'s `Transition` edge is
only recorded when both a source and target resolve, so an omitted source produces no edge at
all (a documented limitation, not a crash) — confirmed against the real OMG corpus fixture
`training/12.BindingConnectors/...` cross-checks during the General View relationship-edges
work.

Closing this requires more than an edge-resolution tweak: `AstBuilder`/`ReferenceResolver` need
to distinguish "no source specified" from "source failed to resolve," and
`StateTransitionViewLayoutStrategy` needs to render the conventional small filled-circle
initial-pseudostate marker with an edge into the target state, rather than only ever connecting
two named states.

**Scope:** `AstBuilder` (transition parsing), `SysmlEdge`/`ReferenceResolver` (distinguishing
implied-source from unresolved-source), `StateTransitionViewLayoutStrategy` (initial-pseudostate
marker + edge rendering).
**Visual gate:** a state machine with a `then InitialState;`-shaped entry transition renders a
filled-circle initial marker with an edge into that state.

### Interconnection View: genuine cross-boundary connector routing

`InterconnectionViewLayoutStrategy` now resolves a connection endpoint's full dotted reference
Expand Down

Large diffs are not rendered by default.

39 changes: 37 additions & 2 deletions docs/design/sysml2-tools-language/semantic/model/ast-builder.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ stack with `::` to form the fully-qualified name.
| `VisitAllocationUsage` | `AllocationUsageContext` | `SysmlConnectionNode` (`ConnectionKeyword = "allocation"`) |
| `VisitSatisfyRequirementUsage` | `SatisfyRequirementUsageContext` | `SysmlSatisfyNode` |
| `VisitRequirementUsage` | `RequirementUsageContext` | `SysmlFeatureNode` (`FeatureKeyword = "requirement"`) |
| `VisitDependency` | `DependencyContext` | `SysmlDependencyNode` |
| `VisitBindingConnectorAsUsage` | `BindingConnectorAsUsageContext` | `SysmlConnectionNode` (kind `binding`) |

`GetDeclaredName(IdentificationContext)` handles the three grammar alternatives:

Expand Down Expand Up @@ -63,6 +65,19 @@ forms identically without needing to branch on which token was used. The raw ref
captured verbatim — including qualified `Owner::feature` forms — with no resolution attempted;
resolution happens later, in `ReferenceResolver`.

When a usage has no explicit declared name (`GetDeclaredName` returns `null`) but does have a
redefinition (`redefined is not null`), `BuildUsageNode` derives an implicit name via
`SimpleNameFromReference(redefined)` — the trailing `::`- or `.`-separated segment of the
redefined feature's reference text, whichever separator occurs furthest to the right (an
`ownedRedefinition` is grammatically `qualifiedName ( DOT qualifiedName )*`, so the reference can
be a dotted feature-chain path like `tank.fuelTankPort`, not just a `::`-qualified name) — and
uses this `effectiveName` everywhere the declared name would otherwise be used: the
namespace-stack push/pop, the constructed node's `QualifiedName`, and its `Name` property. This
mirrors SysML v2's own naming rule that an implicitly-named redefining usage inherits the
redefined feature's name (e.g. `port redefines fuelTankPort { ... }` is named `fuelTankPort`),
and allows such usages — and any references to them (including `bind` connector ends) — to
resolve correctly instead of remaining anonymous and unresolvable.

`BuildUsageNode` also calls `ExtractSubsettingTargetNames(decl?.featureSpecializationPart())`,
setting the result on the constructed `SysmlFeatureNode`'s inherited `SupertypeNames` property —
mirroring `ExtractRedefinedFeature`'s structure (first checking
Expand Down Expand Up @@ -113,6 +128,26 @@ surfaced and was fixed during this unit's implementation for the `CollectTypeBod
`AllocationUsageDeclarationContext.connectorPart()` exposes the exact same `ConnectorPartContext`
shape as `ConnectionUsageContext.connectorPart()`, so no new endpoint-extraction logic is needed.

`VisitBindingConnectorAsUsage` builds a `SysmlConnectionNode` with `ConnectionKeyword = "binding"`
for the common `bind A = B;` (`bindingConnectorAsUsage`) grammar shape, reusing the shared
`ConnectorEndReference` helper against each of the rule's `connectorEndMember()` entries — the
same helper `connectionUsage`'s endpoint extraction already uses. The longer
`bindingConnector`/`typeBody` grammar form has zero corpus evidence and is a documented,
intentional non-goal (not attempted). `ReferenceResolver` resolves `"binding"`-keyword
`SysmlConnectionNode` endpoints via the same dotted-feature-chain walk it already applies to
`"connection"`/`"message"`.

`VisitDependency` builds a `SysmlDependencyNode` for a standalone `dependency A, B to C, D;`
declaration. The grammar's `dependency` rule exposes a single flat `qualifiedName()` list (no
separate "from" vs. "to" sub-rules), so `VisitDependency` splits it positionally: every
`qualifiedName()` whose start token index is before the `TO()` terminal's token index is a
"from" (client) name, and every one after is a "to" (supplier) name. This also correctly handles
the grammar's optional `FROM` keyword (e.g. `dependency z to x, y;`, with no explicit `from`
before `z`) since the split is driven purely by position relative to `TO`, never by the
presence/absence of the `FROM` keyword token itself. `ReferenceResolver` resolves every
`FromNames` entry against every `ToNames` entry (a cross product), emitting one
`SysmlEdgeKind.Dependency` edge per resolved pair.

`VisitSatisfyRequirementUsage` builds a `SysmlSatisfyNode` for `satisfy X by Y;` usages. The
satisfied requirement's raw reference text is taken from `ownedReferenceSubsetting()` when the
`satisfy <ref>` form is used, falling back to the declared/typed name of the
Expand Down Expand Up @@ -220,8 +255,8 @@ results without propagating failures.
dispatch over the CST.
- `SysMLv2Parser` — provides all CST context types consumed by the visitor methods.
- `SysmlNode` hierarchy (`SysmlPackageNode`, `SysmlDefinitionNode`, `SysmlViewNode`,
`SysmlViewpointNode`, `SysmlSatisfyNode`, `SysmlConnectionNode`) — AST node types constructed
by the visitor.
`SysmlViewpointNode`, `SysmlSatisfyNode`, `SysmlConnectionNode`, `SysmlDependencyNode`) — AST
node types constructed by the visitor.
- `SysmlAnnotation` / `SysmlAnnotationKind` — captured comment/documentation data attached to
`SysmlNode.Annotations`.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,15 @@
files and uses depth-first search to detect cycles.
2. **Reference resolution (pass 1)** — checks each `SupertypeName`, `SysmlFeatureNode.FeatureTyping`,
`SysmlFeatureNode.RedefinedFeatureName`, `ImportedName`, `VerifiedRequirementNames` entry,
`SysmlSatisfyNode` subject/requirement, and `SysmlConnectionNode`
(`ConnectionKeyword == "allocation"`) endpoint in all AST nodes against
`SysmlSatisfyNode` subject/requirement, `SysmlConnectionNode`
(`ConnectionKeyword == "allocation"`) endpoint, and `SysmlDependencyNode` from/to name lists
in all AST nodes against
the symbol table, emitting a Warning for any name not found and recording a `SysmlEdge` for
any name (or pair of names) that resolves.
3. **Feature-chain resolution (pass 2)** — after pass 1 has completed for every file root,
resolves dotted feature chains (e.g. `engine.fuelPort`) referenced by `SysmlConnectionNode`
(`ConnectionKeyword == "connection"` or `"message"`) endpoints and `SysmlTransitionNode`
`Source`/`Target` into `Connect`/`Transition`-kind edges.
(`ConnectionKeyword == "connection"`, `"message"`, or `"binding"`) endpoints and
`SysmlTransitionNode` `Source`/`Target` into `Connect`/`Binding`/`Transition`-kind edges.

##### Import Graph

Expand Down Expand Up @@ -71,10 +72,12 @@ guarantees every `Typing`/`Supertype` edge needed by the walk already exists.
`ResolveFeatureChains` mirrors `ResolveNode`'s namespace-stack push/pop condition exactly
(`(node is SysmlPackageNode or SysmlDefinitionNode or SysmlFeatureNode) && node.Name is not null`)
so that segment-0 resolution scope cannot silently diverge between the two passes. For each
`SysmlConnectionNode` with `ConnectionKeyword` `"connection"` or `"message"` (the `"allocation"`
variant is excluded — it keeps its existing, unit-3, single-segment-only behavior), and for each
`SysmlConnectionNode` with `ConnectionKeyword` `"connection"`, `"message"`, or `"binding"` (the
`"allocation"` variant is excluded — it keeps its existing, unit-3, single-segment-only
behavior), and for each
`SysmlTransitionNode`, both sides (`EndpointA`/`EndpointB` or `Source`/`Target`) are resolved via
`TryResolveFeatureChain`, and a `Connect`/`Transition` edge is emitted only when **both** sides
`TryResolveFeatureChain`, and a `Connect`/`Binding`/`Transition` edge is emitted only when **both**
sides
resolve — mirroring the existing Satisfy/Allocate both-sides-must-resolve contract. New edges are
appended to `node.ResolvedEdges` (pass-1 edges, if any, are preserved) and to the aggregate edge
list.
Expand All @@ -84,12 +87,31 @@ reference text on `.`. Segment 0 is resolved via the existing `TryResolve` four-
participates in the same scope/import resolution as any other single-name reference); a
single-segment "chain" is handled by the remaining-segment loop simply never executing. Each
subsequent segment is resolved relative to the previous segment's node (looked up via
`SymbolTable.Lookup`) using `FindFeatureMember`.
`SymbolTable.Lookup`) using `FindFeatureMember`. Two parallel accumulators are tracked across the
loop: `current` (the real declared node's qualified name, which continues to drive the *next*
segment's `SymbolTable.Lookup` — this part of the walk is unchanged) and `instancePath` (the value
ultimately returned as `resolvedName`). For a segment resolved via `FindFeatureMember`'s
direct-child branch, `instancePath` is simply set to the member's own (already instance-relative)
qualified name — unchanged from before. For a segment resolved via the type-hierarchy fallback
branch (`viaTypeFallback == true`, see `FindFeatureMember` below), `instancePath` is instead set to
`{previous instancePath}::{raw segment text}` — preserving the instance-relative path rather than
collapsing to the type's own declared qualified name. This distinction matters for the dominant
real-world `connect`/`bind` shape: two sibling features (e.g. ports) declared directly in their
owning `part def`s, referenced from an enclosing part via bare usages with no per-instance nested
redeclaration — every such reference resolves via the fallback branch, and without this
`instancePath` accumulator the returned qualified name would collapse to the shared port type's own
declared path (e.g. `PowerPort`'s owning type's own `power`/`output` declaration), causing
`GeneralViewLayoutStrategy.ResolveOwningBox` to (incorrectly) map both endpoints to the same box.

`FindFeatureMember(node, name)` tries `node`'s own direct children first — an inline nested usage
or redefinition shadows a same-named definition-level member (confirmed by the OMG fixture
`2c-PartsInterconnection-MultipleDecompositions.sysml`'s `port :>> pe = c1.pb` pattern) — falling
back to the member's `Typing`-edge target's own hierarchy only when no direct child matches.
`FindFeatureMember(node, name, out viaTypeFallback)` tries `node`'s own direct children first — an
inline nested usage or redefinition shadows a same-named definition-level member (confirmed by the
OMG fixture `2c-PartsInterconnection-MultipleDecompositions.sysml`'s `port :>> pe = c1.pb`
pattern) — setting `viaTypeFallback = false` and returning immediately when found. Only when no
direct child matches does it fall back to the member's `Typing`-edge target's own hierarchy,
setting `viaTypeFallback = true` for that branch; the returned node's own `QualifiedName` in this
case is the *type's own* declared path, not instance-relative — it is the caller
(`TryResolveFeatureChain`) that reconstructs the instance-relative path using `viaTypeFallback`, as
described above.

`FindMemberInTypeHierarchy(typeNode, name, visited)` finds a member in `typeNode`'s own direct
children, or — recursively — in any `Supertype`-edge ancestor's direct children, walking the
Expand Down Expand Up @@ -139,6 +161,14 @@ no edge):
= resolved first end, `Target` = resolved second end) only when both ends resolve, using the
identical both-sides-must-resolve contract as `Satisfy`. Regular `"connection"`/`"message"`
keyword variants remain intentionally unresolved (out of scope for this unit).
- **`SysmlDependencyNode` (Dependency)** — resolves every entry in `FromNames` and every entry in
`ToNames` independently (each unresolvable name produces its own Warning, per the
`resolvedInFile` de-duplication rule), then emits one `SysmlEdgeKind.Dependency` edge per
resolved (from, to) pair — a cross product, per the grammar's list-of-lists shape (e.g.
`dependency a, b to c, d;` resolves to up to 4 edges). Unlike the two-sided
Satisfy/Allocate/Connect contract, a `Dependency` edge is emitted per-pair rather than
all-or-nothing: an unresolvable name on one side does not suppress edges for the other
resolvable names.
- **`SysmlViewNode` (Expose)** — resolves each `GetExposedNames()` entry (the `QualifiedName` of
each `ExposeMember`) into its own `SysmlEdgeKind.Expose` edge, or the standard
unresolved-reference Warning diagnostic when it does not resolve. An `ExposeMember`'s own
Expand Down Expand Up @@ -222,9 +252,11 @@ unresolved names are present.
- `SysmlNode` hierarchy — traversed to collect `SupertypeNames`, `ImportedNames`, and
`VerifiedRequirementNames`; checks for `SysmlFeatureNode.FeatureTyping` and
`SysmlFeatureNode.RedefinedFeatureName`, `SysmlSatisfyNode`
(`SubjectName`/`RequirementName`), `SysmlConnectionNode` with `ConnectionKeyword ==
(`SubjectName`/`RequirementName`), `SysmlDependencyNode` (`FromNames`/`ToNames`),
`SysmlConnectionNode` with `ConnectionKeyword ==
"allocation"` (`EndpointA`/`EndpointB`), `SysmlConnectionNode` with `ConnectionKeyword ==
"connection"` or `"message"`, `SysmlTransitionNode` (`Source`/`Target`), and `SysmlViewNode`
"connection"`, `"message"`, or `"binding"`, `SysmlTransitionNode` (`Source`/`Target`), and
`SysmlViewNode`
(`GetExposedNames()`; `RenderTargetName`/`FilterExpressionText`/each `ExposeMember`'s
`BracketFilterExpressionText` are never read); reads
`ResolvedEdges` (`Typing`/`Supertype` kinds during feature-chain resolution; `Supertype`
Expand Down
22 changes: 20 additions & 2 deletions docs/design/sysml2-tools-language/semantic/model/sysml-edge.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@
`SysmlEdge` and `SysmlEdgeKind` model a single resolved directed reference between two
qualified names in the semantic model. Edges are produced by `ReferenceResolver` while
walking supertype, feature-typing, redefinition, import, satisfy, verify, allocate, connect,
transition, and expose references, and are the raw material indexed by `SemanticIndex`.
transition, expose, metadata-typing, dependency, and binding references, and are the raw
material indexed by `SemanticIndex`.

##### Types

`SysmlEdgeKind` is an enum with ten members:
`SysmlEdgeKind` is an enum with thirteen members:

- `Supertype` — a specialization reference (`SupertypeNames` / `specializes` / `:>`).
- `Typing` — a feature typing reference (`SysmlFeatureNode.FeatureTyping`, the type after `:`).
Expand Down Expand Up @@ -48,6 +49,23 @@ transition, and expose references, and are the raw material indexed by `Semantic
targeting the resolved redefined-feature reference. Rendered by `GeneralViewLayoutStrategy` as
a solid line with a hollow-triangle-crossbar end marker at the owning definition of the
redefined feature.
- `MetadataType` — a metadata annotation's type reference (`SysmlMetadataNode.TypeReference` /
`@Type` / `{@Type{...}}`), sourced from the annotation and targeting the resolved `metadata
def` declaration it references.
- `Dependency` — a standalone dependency declaration (`dependency A, B to C, D;`), sourced from
each resolvable name in `SysmlDependencyNode.FromNames` and targeting each resolvable name in
`SysmlDependencyNode.ToNames`, one `Dependency` edge per resolved (from, to) pair (cross
product); an unresolvable name on either side is skipped (with a Warning diagnostic) rather
than blocking the other pairs. Rendered by `GeneralViewLayoutStrategy` as a dashed line with an
open-chevron end marker at the target (depended-upon) box; the pre-existing `ref`-typed
membership case is rendered with the same dashed/open-chevron style (see
`general-view-layout-strategy.md`).
- `Binding` — a resolved binding-connector reference (`bind A = B;`), sourced from the first
connector end and targeting the second (`SysmlConnectionNode` with
`ConnectionKeyword == "binding"`, reusing the `EndpointA`/`EndpointB` shape and the same
dotted-feature-chain walk as `Connect`); recorded only when both endpoints resolve. Rendered
by `GeneralViewLayoutStrategy` as a solid line with no end marker and an optional `=` midpoint
label.

`SysmlEdge` is a sealed positional record with three properties:

Expand Down
Loading
Loading