diff --git a/README.md b/README.md index c9da9bb..c674731 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ OpenMBEE View Editor brings source editing, named model views and reviewed graph - **Edit highlighted SysML source.** CodeMirror provides line numbers, bracket matching/closing, indentation, find/replace and draft undo/redo. Apply or discard changes, undo/redo accepted source transactions, and keep the last valid model when a draft fails validation. - **Query the draft model.** Request engine-backed completion, symbol information and project-file definitions against the complete current source set. - **Inspect requirement graphs.** Browse requirement definitions/usages, declared identifiers and documentation, typing, hierarchy and positive/negated satisfaction links. Follow exact source declarations; diagram links do not report executed test results. -- **Open source-defined views.** Navigate named SysML views independently of the active source file. Inspect nested parts, typed inherited ports and resolved connection endpoints. +- **Open source-defined views.** Navigate named SysML views independently of the active source file. Inspect nested and inherited parts, typed inherited ports and resolved connection endpoints. - **Navigate source issues.** Open an issue’s reported file and line in the source editor. Missing or stale locations are explained while drafts and the last valid diagram are retained. - **Browse the model hierarchy.** Expand packages, definitions and declared members in the Model tree. Inspect type/kind labels; use arrow keys, type-ahead and Enter/Space to navigate and select. - **Review graphical changes.** Rename parts, create native views, add typed part usages and connect compatible directed ports through an exact source-change preview. @@ -184,7 +184,7 @@ npm run test:e2e # fresh build, real engines and visual compa npm run preview -- --host 127.0.0.1 ``` -PR CI runs all fast checks plus fourteen critical browser workflows, adding visual comparisons for code changes. Full browser coverage runs weekly, manually and for release/main/hotfix PRs and pushes. Run `npm run test:e2e -- --project=functional --grep @critical` to reproduce the smaller browser gate; `npm run test:e2e` still runs everything. See [CI coverage](docs/testing.md#ci-coverage) for selection and deferred coverage. +PR CI runs all fast checks plus the critical browser workflows, adding visual comparisons for code changes. Full browser coverage runs weekly, manually and for release/main/hotfix PRs and pushes. Run `npm run test:e2e -- --project=functional --grep @critical` to reproduce the smaller browser gate; `npm run test:e2e` still runs everything. See [CI coverage](docs/testing.md#ci-coverage) for selection and deferred coverage. Functional checks can use installed Chrome with `PLAYWRIGHT_BROWSER_CHANNEL=chrome npm run test:e2e -- --project=functional`. Visual comparisons always use bundled Chromium and reviewed platform-specific baselines. The browser suite starts its own fresh production server; port **4175** must be free. See [browser testing and baseline review](docs/testing.md). diff --git a/docs/architecture/source-assistance-recovery.md b/docs/architecture/source-assistance-recovery.md index e7dc783..2b4eb3a 100644 --- a/docs/architecture/source-assistance-recovery.md +++ b/docs/architecture/source-assistance-recovery.md @@ -24,7 +24,7 @@ Local recovery uses one IndexedDB record per browser origin: database `sysml-vie After edits, the workbench debounces an immutable project snapshot for 750 ms. Dirty projects and unapplied drafts qualify, including drafts retained after a download. Recovery serialization does not mark the project downloaded or add history. Restored projects remain unsaved until downloaded, including when their drafts match applied source. While local recovery is enabled, when work returns to a clean state after discard, undo or download, an ordered atomic clear removes this session’s older recovery copy. Pending startup or foreign copies remain protected. The enabled preference is a separate boolean in localStorage under `sysml-view-recovery-enabled-v1`. -A startup record blocks replacement until an explicit restore/discard choice. Restore validates the project through the real workspace engine before replacing it, confirms replacement of dirty current work, and accepts the recovery snapshot synchronously before observing subsequent work. The active view is checked against the restored model. Failed restore retains the stored record and current project. +A startup record blocks replacement until an explicit restore/discard choice. Restore validates the project through the real workspace engine before replacing it, confirms replacement of dirty current work, and accepts the recovery snapshot synchronously before observing subsequent work. The active view is checked against the restored model. Failed restore retains the stored record and current project. After the project is accepted, a subsequent named-view or exploration refresh failure belongs to the workbench action/error channel, not the recovery action. It remains visible while current and is superseded by later workbench actions; a clean parse alone does not establish successful visualization. A validation failure before replacement remains recovery-owned and keeps the pending copy. Writes and deletion use an atomic compare-and-swap transaction against the exact previously read string. A competing tab's change causes a visible conflict. Its message states that Retry recovery only reads the saved copy without replacing the current project. After Retry, the pending copy shows its project name and saved time, with expandable filenames, unapplied-draft status and applied/draft UTF-8 byte sizes; Restore recovery replaces the open project (with dirty-work confirmation), while Discard recovery deletes the saved copy and keeps the live project. Users are advised to download current work before restoring. The live copy has no invented save timestamp; pending edits are explicitly described as remaining in this tab until a choice permits recovery saving. Denied storage and write failures retain the editable project and permit retry. Malformed or future string envelopes remain protected until explicit discard. An unsupported non-string IndexedDB value is retained and reported unavailable; this version cannot discard that value through the app. diff --git a/docs/architecture/structural-views.md b/docs/architecture/structural-views.md index e7a9ac6..951cb96 100644 --- a/docs/architecture/structural-views.md +++ b/docs/architecture/structural-views.md @@ -19,6 +19,34 @@ The retained bridge now adds `reference` and declared `direction` metadata by lo The upstream pins, archives and licenses are unchanged. The bridge overlay, builder and shared fixture bytes contribute to prepared provenance. Preparation copies the same example sources into the Go test package, tests their ownership/directions/reference/endpoints/context/notices, and rebuilds the derived WASM. Local before/after raw captures and generated provenance are retained in ignored `sandbox/assembly-before-trace.json` and `sandbox/assembly-trace.json`; these are local evidence, not distributable fixtures or upstream conformance claims. +## Exposure and private imports + +A named view’s exposure is not an owned-members-only query. SysML exposure ignores visibility: a private import can contribute members to `expose Package::*`, even though an ordinary name lookup outside that package cannot see them. A wildcard import of `StandardViewDefinitions::*` can therefore bring its publicly reexported SysML/KerML metamodel surface into a wildcard exposure. The native renderer may expand the selected elements further; selected subjects and rendered descendants are different sets. + +Use explicit exposures to choose a small scope, and named imports to reduce the imported surface. For example: + +```sysml +package Demo { part def Sat; } +package Probe { + private import Demo::Sat; + private import StandardViewDefinitions::GeneralView; + part p : Sat; + view focused : GeneralView { expose Probe::p; } +} +``` + +Changing the two imports to public imports would not make this explicit exposure broader. Conversely, changing `expose Probe::p` to `expose Probe::*` admits imported members as well as owned members; named imports reduce that set but do not change its meaning. Private imports remain private for ordinary external references. Each intermediate import still determines which source memberships it admits; an outer exposure does not recursively open every private dependency. The current pinned engine has a reproduced intermediate-import defect: a private source declaration can enter the selected subjects through an ordinary import, and another import layer can incorrectly admit a private owned declaration. That defect is separate from the valid immediate private-import behavior above; the [retained upstream investigation and executable fixture](../research/wp26-exposure-import-membership.md) record its failing cases and why an experimental fix was rejected. The editor does not silently remove imported/library subjects from authored exposure, and large graphs alone are not proof of a visibility failure. + +The basis is [SysML 2.0 Part 1](https://www.omg.org/spec/SysML/2.0/Language/PDF), §8.3.26.2–4 (Expose and NamespaceExpose), together with [KerML 1.0](https://www.omg.org/spec/KerML/1.0/PDF), §8.3.2.4.5–6 (Namespace and NamespaceImport). These rules do not prescribe exact renderer node counts, guarantee large-view readability or establish complete editor conformance. + +## Inherited structural occurrences + +Authored interconnection views retain the native renderer’s effective structural members, including inherited parts under typed usages. Each occurrence binds the source declaration to its complete usage context: `Sat::c` under `sat1` and under `sat2` share a declaration but have different canvas identities. Native traversal still determines recursion and depth bounds. Attributes remain source-backed compartments; synthetic subjects and unsupported constraints remain excluded. Connector selection still requires the existing owner/exposure policy, so retaining inherited nodes does not automatically expose a type’s internal connections. + +Temporary exploration and tree views retain their existing declaration-ownership scope. This change is specific to authored interconnection views. The bridge reserves the previous projection’s node and port IDs before assigning IDs to newly visible occurrences. This preserves compatible positions in unchanged version-1 projects without introducing a general persistent identity guarantee across source edits. The artifact remains parent-first even when reserved IDs are not numerically ordered. When a partial saved presentation puts an old child in a new child’s automatic slot, the projection keeps restored subtrees anchored and places automatic inherited siblings into free slots inside the canonical owner bounds. The native inherited flag is validated in the artifact contract; ordinary authored-node placement remains unchanged. Those bounds and drag extents remain unchanged because version 1 saves positions alone. If the saved geometry leaves no fitting slot, the editor keeps the saved positions and warns that automatic positions overlap; move saved nodes to make room. This does not provide general manual overlap prevention. + +Keyboard Enter/Space selection now updates Properties for structural nodes as well as the other diagram families. Source reveal remains disabled while a draft is unapplied. + ## Geometry and interaction The structural projection lives under `workbench/diagrams/`; the workspace remains independent of React Flow and ELK. ELK calculates compound bounds from the engine's parent graph. Definitions/usages retain textual kind labels; references additionally have a `ref` label and dashed border. Typed inherited ports are selectable buttons with direction text and type labels, with handles on the appropriate boundary. The connector line itself does not assert a flow direction. diff --git a/docs/capabilities.md b/docs/capabilities.md index ec30035..56e8efe 100644 --- a/docs/capabilities.md +++ b/docs/capabilities.md @@ -36,3 +36,7 @@ The [visualization example library](../examples/README.md) records representativ The read-only requirements table inspects all applied requirement declarations, identifiers, owned documentation and declared satisfaction/verification links. It supports search, name sorting and CSV export. The traceability matrix uses requirements as rows and linked satisfiers/verifiers as columns, retaining exact declaration identities and negation. It does not infer missing relationships, compute verification verdicts or edit model content. Unknown/incomplete extraction is unavailable rather than an empty result. Source reveal requires that file's draft to match applied source. [Requirements data contract](architecture/requirements-data.md). The optional **Guide** in the project header introduces Explorer, layout, source application, model representations and project download for any open project. It can be dismissed and restarted, and does not alter project data. + +Named-view wildcard exposure can include private imported memberships and public reexports from standard libraries. This follows exposure semantics rather than ordinary external name visibility. Use explicit element exposures and named imports to bound the selected scope; see [exposure and private imports](architecture/structural-views.md#exposure-and-private-imports). Large-view performance and readability remain separate limitations. + +Authored interconnection views retain inherited structural members in their exact usage contexts. Compatible legacy node/port IDs remain reserved for unchanged saved projects; newly visible occurrences receive separate IDs. Temporary exploration and tree views retain declaration ownership. Connector selection and source-backed attribute compartments keep their existing scope. See [inherited structural occurrences](architecture/structural-views.md#inherited-structural-occurrences). diff --git a/docs/research/wp26-exposure-import-membership.md b/docs/research/wp26-exposure-import-membership.md new file mode 100644 index 0000000..b241ea4 --- /dev/null +++ b/docs/research/wp26-exposure-import-membership.md @@ -0,0 +1,68 @@ +# WP26 exposure and intermediate import membership + +Investigation recorded 2026-11-11 for [view-editor issue #20](https://github.com/Open-MBEE/sysml_view_editor/issues/20), on editor `890fe88d2bc31a137e156fa57d3783ca5ed7e807`, OpenSysML 0.12.0 / `760c4d1fc8edfa4e214423099065c0a7c1b24e2d`. Prepared WASM SHA-256: `9149b23d80b7fd0b6935b374f0e9102c270bc5f7d595b43a8186d01dece7b663`. + +## Findings + +The report's owned-only expectation is incorrect: a view exposure can admit private imported memberships. Wildcard standard-view imports bring public metamodel reexports into selection, then the renderer can expand the selected elements. Explicit element exposures and named imports are useful ways to bound that scope. They are not a repair for a visibility defect. + +A separate intermediate-import defect is reproduced. The current resolver mixes flattened index reexports with namespace membership enumeration, bypassing the admission rules of intermediate imports. It also tests original declaration visibility where imported membership visibility depends on the import route. Unexpected declarations occur in the selected set before rendering. + +Minimal shape: + +```sysml +package ReviewSource { + public part def Visible; + private part def Hidden; +} +package Mid { + private import ReviewSource::*; + private part def OwnHidden; +} +package Probe { + private import Mid::*; + view focused : StandardViewDefinitions::GeneralView { expose Mid::*; } +} +``` + +The immediate exposure should select `Mid::OwnHidden` and `ReviewSource::Visible`. It also selects `ReviewSource::Hidden` at this pin. Changing the exposure to `Probe::*` should select only Probe's own view; the ordinary import of Mid cannot reexport Mid's private owned or privately imported memberships. The current pin incorrectly selects `Mid::OwnHidden`. The retained tests put some views outside the target namespaces so their expected sets do not include a view declaration accidentally. + +The governing rules are [SysML 2.0 Part 1](https://www.omg.org/spec/SysML/2.0/Language/PDF), §8.3.26.2 and §8.3.26.4, and [KerML 1.0](https://www.omg.org/spec/KerML/1.0/PDF), §8.3.2.4.5–6. Exposure fixes its own `isImportAll` to true. Each selected intermediate import still invokes its own `importedMemberships` with its own `isImportAll`; an outer exposure does not recursively change every ordinary import to import-all. Imported membership visibility at the next boundary comes from its admitting import route. + +## Retained upstream regression + +[The executable Go fixture](../../tests/fixtures/opensysml-0.12-exposure-membership_test.go) targets the pinned upstream combined frontend package. It is a research reproduction, not a passing editor regression or a skipped test in the normal editor suite. At the current pin, seven of ten origin-selection cases fail. Ordinary-reference and native-traversal controls pass. The split-namespace control passes on the current pin and must remain passing in any proposed fix. + +After normal engine preparation, from the repository root, run it without editing the prepared source: + +```sh +mkdir -p sandbox/exposure-membership +python3 - <<'PY' +import json +from pathlib import Path +root = Path.cwd() +target = root / 'sandbox/opensysml-012/source/internal/frontend/combined/wp26_exposure_test.go' +fixture = root / 'tests/fixtures/opensysml-0.12-exposure-membership_test.go' +(root / 'sandbox/exposure-membership/overlay.json').write_text( + json.dumps({'Replace': {str(target): str(fixture)}}) +) +PY +GOCACHE="$PWD/sandbox/exposure-membership/go-cache" \ +GOMODCACHE="$PWD/sandbox/opensysml-012/go-modules" \ +GOPROXY=off GOSUMDB=off GOTOOLCHAIN=local GOFLAGS=-mod=readonly \ +sandbox/opensysml-012/go/bin/go -C sandbox/opensysml-012/source test \ + -overlay "$PWD/sandbox/exposure-membership/overlay.json" \ + ./internal/frontend/combined -run TestIssue20 -count=1 -v +``` + +The expected failure is the exact selected-origin mismatch, not a dependency/build failure. Tests compare source declaration slices and origins, not ordinal IDs or diagram node counts. They cover ordinary/private/public/import-all routes, named and absent imports, private outside lookup, native traversal versus selected scope, and namespace declarations split across two source files. + +## Rejected experiment and upstream request + +An experimental resolver overlay made all ten simple selection cases pass, but dropped a valid import contributed by a second declaration of the same namespace in another document. It is not integrated. Resolver, semantics and symbols suites passed on that experiment; the final combined-package suite failed its split-namespace regression. Those partial passes are not sufficient acceptance evidence. + +A safe change must enumerate memberships across every declaration and recorded library scope, preserve import-route visibility and each intermediate import's `isImportAll`, and retain valid alternate routes, filters, hiding and cycle behavior. The relevant upstream seams are `internal/semantic/resolve/filter.go` (`namespaceChildren`, `declaredChildren`, `appendNamespaceMembers`), `visibility.go`, and flattened reexport indexing in `internal/semantic/symbols/index.go`. + +Request an upstream resolver/index fix with the retained tests. A corrected upstream pin is preferable; any temporary retained resolver overlay needs explicit source/patch identities, licenses, normal provenance regeneration, and full integration checks. Filtering private/library nodes in the renderer would hide the symptom while changing valid authored exposure semantics. + +No safe production fix, hosted browser timing measurement, deployment or upstream acceptance is claimed. Raw diagnostic and experimental captures remain in the local session's ignored evidence directories; the fixture above retains the reproducible source-level contract independently of those directories. Issue #18's inherited-member projection is a separate mechanism and must not be used to compensate for import selection. diff --git a/docs/testing.md b/docs/testing.md index 4e562b9..6225e5f 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -28,12 +28,12 @@ The browser-channel override applies only to functional checks. Visual checks al The required `checks` job always runs formatting, architecture, TypeScript, unit/script tests and the dependency audit. Browser coverage is selected separately: -| Trigger | Browser coverage | -| ----------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| Non-release PR | Sixteen `@critical` real-engine scenarios: entry, source-defined views, invalid drafts, rename and cancellation, native view creation, typed part/connection authoring, source undo/redo, exact save/reopen, engine failure recovery, IndexedDB restore, exact requirements/satisfaction browsing and no-view exploration | -| Non-release PR with code, assets, examples, tooling or unknown paths | All visual comparisons as well as the critical scenarios | -| Documentation-only non-release PR | Critical scenarios; no visual comparisons | -| PR targeting `main`, `release/**`, `hotfix/**`; weekly Monday at 04:00 UTC; manual dispatch; or push to `main`, `release/**`, `hotfix/**` | Complete functional and visual suites | +| Trigger | Browser coverage | +| ----------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Non-release PR | The `@critical` real-engine scenarios: entry, source-defined views, invalid drafts, rename and cancellation, native view creation, typed part/connection authoring, source undo/redo, exact save/reopen, engine failure recovery, IndexedDB restore, exact requirements/satisfaction browsing and no-view exploration | +| Non-release PR with code, assets, examples, tooling or unknown paths | All visual comparisons as well as the critical scenarios | +| Documentation-only non-release PR | Critical scenarios; no visual comparisons | +| PR targeting `main`, `release/**`, `hotfix/**`; weekly Monday at 04:00 UTC; manual dispatch; or push to `main`, `release/**`, `hotfix/**` | Complete functional and visual suites | There is no duplicate run on a merge push to `develop`; its PR must pass `checks`. Scheduled runs use the repository's default branch. The full run must pass before release, and remains available manually on any branch. Scheduled failures need triage; they do not retroactively block an already merged PR. The reduced PR suite intentionally defers the extended preservation/failure matrix, language assistance, engine identity investigations and example-library sweeps to full runs. @@ -128,3 +128,7 @@ The 0.12 assembly overview fixture records thirteen source-backed edges (ownersh `recovery.spec.ts` exercises the competing-tab conflict through both explicit Restore and Discard decisions. It checks the Retry explanation, exact stored timestamp, both live drafts and the unchanged foreign IndexedDB record before a decision. Restore additionally cancels dirty-work confirmation before accepting replacement; Discard retains the local project and resumes saving its queued copy. Visual recovery-conflict and pending-copy captures use a controlled foreign IndexedDB record with a fixed timestamp, while exercising the real manager’s compare-and-swap and Retry paths. They cover desktop/narrow widths in both themes, dialog bounds and keyboard focus return; they are presentation evidence, not a substitute for the real two-tab preservation scenarios. The critical recovery restore scenario now writes distinguishable successive drafts, waits for the latest exact durable project, reloads and inspects the timestamp and expandable file summary before restoring. Applied/draft byte counts are checked against independently computed UTF-8 sizes, including Unicode, and inspecting the summary must leave the durable record unchanged. The restored source and both saved view layouts are compared exactly with that latest snapshot. Pending-copy visual captures expand the file list. These checks establish bounded successful-save behavior, not a reproduction of the report’s earlier condensed recovery copy. + +`recovery-error-lifetime.spec.ts` restores a real IndexedDB copy whose source parses cleanly but fails exploration origin validation. It retains that current operational error across another parse-clean failing revision, verifies recovery no longer owns the accepted replacement’s visualization failure, and clears the obsolete error only after a later model actually explores successfully. It compares exact accepted source and retains desktop/390px light/dark captures. This is separate from rejected project validation, which the recovery suite still checks as a pending recovery action. + +The inherited-member critical regression imports a positioned project captured with the previous real engine. It checks exact inherited declaration/context identities, legacy IDs and displayed positions, keyboard Properties/source navigation, independent inherited-node movement, both view layouts, rejected draft preservation and fresh-context portable reopen. Automatic layout alone does not write presentation positions; the test explicitly moves nodes before requiring saved geometry. diff --git a/docs/verification.md b/docs/verification.md index ed13b27..c29d37b 100644 --- a/docs/verification.md +++ b/docs/verification.md @@ -1,5 +1,47 @@ # Verification record +## Inherited usage members — issue #18 + +Recorded 2026-11-11 on `fix/wp26-inspection`, after local commits `d96ee70` (#14) and `4eff493` (#20 investigation). The authored interconnection bridge now retains native inherited structural occurrences and their full declaration/usage contexts. Tree and temporary exploration behavior, inherited attribute compartments and connector-selection policy remain bounded as documented in [structural views](architecture/structural-views.md#inherited-structural-occurrences). + +The combined Go package contains 16 exact new semantic cases covering redefinitions, repeated and nested contexts, transitive inheritance, legacy node/port identities, attributes/exclusions, recursion, depth and endpoint semantics. Isolated negative controls failed with inherited pruning restored and with legacy-ID reservation disabled. Independent design and final integration reviews found no blocking production defects; their initial semantic-assertion and browser-preservation gaps were addressed. These are pinned behavior checks, not full SysML conformance evidence. + +Normal `npm run engines:prepare` passed the complete combined Go package (3.172 seconds) and rebuilt the prepared WASM (`sandbox/wp26-session/inherited-prepare.log`). OpenSysML remains 0.12.0 / `760c4d1fc8edfa4e214423099065c0a7c1b24e2d`; Go remains 1.27.3. Generated overlay hash is `fa5556f905554120acf3933757b857cea496dd6cad3d9fa0ac9b8a2c33e9aeb9`, and the new WASM SHA-256 is `95c6dced93044eeb2a29191e697a590964c5f6722d997c8b8de70cba5d73fb76`. Prepared assets/provenance remain ignored generated files. + +The legacy project fixture was captured by importing and moving nodes through the actual previous prepared engine (`9149b23d80b7fd0b6935b374f0e9102c270bc5f7d595b43a8186d01dece7b663`). The new browser regression failed against that engine because `sat1/c` was absent (`inherited-browser-before.log`). After bridge integration, Enter selection exposed a separate structural Properties handoff defect (`inherited-browser-after.log`), now fixed by forwarding node-selection changes independently of geometry handling. Later test failures incorrectly assumed automatic layout immediately writes saved presentations; the test now explicitly moves nodes before requiring saved geometry. Those failures remain recorded in `inherited-browser-keyboard.log` and `inherited-browser-final.log`. + +The broader first run passed 17 of 18 scenarios; its one failure was the old whole-artifact equality assertion in the 0.10 compatibility test. All original declaration-to-symbol bindings remain exactly equal across all 16 retained views. Only compressor structure/installation add descendants (35 and 82 new node/port correspondences). A separate reviewed fixture asserts these exact additions and the now-visible actual port endpoints while leaving the original compatibility fixture unchanged. Source-origin/name/context spans were checked against the unchanged documents (`compatibility-origin-audit.txt`); the compressor catalog and its obsolete collapsed-endpoint limitation were updated accordingly. A direct prepared-engine catalog probe found no other changed node-name expectations. + +The focused real-engine scenario passed after those corrections (`inherited-browser-final2.log`): exact declarations/usage contexts, legacy IDs and displayed positions, keyboard source reveal, both named-view layouts, failed-draft retention and fresh-browser-context portable reopen. A subsequent refinement directly checks that moving one inherited occurrence leaves the other stationary. The critical browser gate plus the full compatibility test then passed all 18 scenarios in 4.9 minutes (`inherited-critical.log`), and the real air-compressor library scenario passed (`inherited-visual-new.log`). These runs preceded the placement correction below. + +Visual inspection rejected the first four new snapshots: a newly visible c shared an automatic slot with a restored a. The new placement unit regression failed before the fix (`inherited-layout-red.log`), then passed; the expanded 13-test structure suite also covers an unsaved ancestor with a saved descendant and owner/drag-extent growth (`inherited-layout-green3.log`). Independent review found no blocker and confirmed the bounded caveat that two anchored containers can still overlap after growth. The browser now directly checks separate sibling cards at legacy restore and fresh reopen. Its first post-fix run failed after a large test drag itself created manual overlap; the corrected gesture stays inside the free gap and preserves the independent-movement assertion. The final focused run passed (`inherited-layout-browser2.log`). Original rejected screenshots are retained under `inherited-layout-overlap/`. Before the final compatibility refinement below, `npm run test:e2e -- --project=visual --output=sandbox/playwright/inherited-visual-final` passed all 89 scenarios in 9.4 minutes without retries, skips or updates to existing baselines (`inherited-visual-final.log`). The four new inherited-occurrence baselines were regenerated only after correcting the observed overlap, inspected at 1440px/768px in light/dark, and then passed normal comparison. `npm run check` passed formatting, architecture, TypeScript, 163 unit tests and ten script checks (`inherited-check-final4.log`). Fresh production builds and normal engine preparation ran through Playwright. The following affected functional run passed eight of nine scenarios but failed the unchanged authoring test’s fresh-reopen geometry assertion (`inherited-preservation-final.log`). The candidate had enlarged owner/drag bounds that version 1 does not serialize, permitting saved coordinates that a fresh canonical layout then rejected. A new full-position round-trip unit assertion failed against that candidate (`layout-roundtrip-red.log`). + +The final correction freezes canonical owner bounds/extents, searches free slots inside them (including slots vacated above or beside restored siblings), and preserves anchored subtrees. If no fitting slot exists it reports automatic overlap without moving saved geometry or loosening the existing outside-owner checks. The speculative owner-growth test was replaced with canonical-slot reuse and round-trip invariants; the existing authoring preservation assertion was unchanged. All 15 structure tests pass, including an unsaved ancestor with a saved descendant and the explicit no-space warning (`layout-bounded-unit.log`). Independent design review confirmed that positions alone cannot distinguish valid former expanded bounds from incompatible coordinates. The generic automatic-sibling variant still failed the same authoring fresh-reopen case in the 13-scenario run (12 passed, one failed; `inherited-bounded-final2.log`). Saved-project/engine/rendered trace attachments under `sandbox/playwright/authoring-layout-diagnosis/` showed that it also changed the native placement of the newly authored owned `auxiliary`; the subsequent connected view has different canonical bounds. That generic placement change exceeded the inherited-member repair’s scope. + +The final projection uses the native `inherited` flag, now validated as optional artifact metadata, to place only automatic inherited occurrences around fixed siblings. Ordinary authored-node placement is unchanged. The unit coverage explicitly retains that boundary, while the real-engine inherited occurrence assertions check the native flags as well as declarations and contexts. On the final scoped code, `npm run check` passed formatting, architecture, TypeScript, 166 unit tests and ten script tests (`inherited-check-final6.log`). The final combined browser selection passed all 13 scenarios in 3.2 minutes: all nine authoring/preservation/inherited-member workflows and the four inherited-member visual comparisons, without baseline updates (`inherited-scoped-regressions.log`, output `sandbox/playwright/inherited-scoped-regressions/`). The log retains the exact project/file/grep command. The unchanged creation/connect/undo/redo/save/fresh-reopen case and inherited-member case also passed a focused two-test confirmation (`inherited-scoped-final.log`). A previous attempt was interrupted by SIGTERM before a result; its leftover owned server was stopped before a clean rerun (`inherited-bounded-final.log`). + +The full 89 visual comparisons above passed before the final scoped refinement; the four affected visual cases were compared again on the final code. No existing baselines were changed. Other full functional scenarios were not rerun after this last refinement. Local checks do not establish hosted CI or deployment for this branch. Full functional coverage outside the recorded subsets, Firefox/WebKit, complete accessibility audits, large-model performance, hosted CI for these local commits and deployment were not run. All paths above are under ignored `sandbox/wp26-session/` unless stated otherwise. + +## Exposure visibility investigation — issue #20 + +Recorded 2026-11-11 at editor `890fe88d` / prepared OpenSysML 0.12.0 (`760c4d1fc8edfa4e214423099065c0a7c1b24e2d`). The original large wildcard graph reproduces, but the report's owned-members-only expectation is not the SysML exposure rule. Independent review checked the primary specifications and confirmed that immediate exposure can admit private imported memberships. The corrected structural/capability documentation includes a small explicit-exposure/named-import example. + +Independent review also found a real intermediate-import defect. Source admission is broadened before rendering, and a flattened reexport path loses import-route visibility. The [upstream-ready investigation](research/wp26-exposure-import-membership.md) and [executable source fixture](../tests/fixtures/opensysml-0.12-exposure-membership_test.go) retain expected declaration/origin sets, positive controls, split-namespace coverage and exact reproduction commands. The fixture is deliberately a separate failing upstream research reproduction, not a disabled passing editor test: the coordinator ran it against the unchanged pin and observed seven failing origin-selection cases, while ordinary lookup, traversal controls and the split-namespace control passed (`sandbox/wp26-session/exposure/upstream-known-failure.log`). + +A proposed resolver experiment passed the simple cases but dropped a valid import contributed by another document's declaration of the same namespace. It was rejected and is not present in the editor. A membership-aware upstream resolver/index fix remains needed; issue #20 is not claimed fixed or closed, and no upstream issue or PR has been published by this investigation. + +The normal bridge now retains `TestStudioExposureMembershipScope`, which passes the current immediate-private/public, explicit, named-import, no-import and outside-lookup controls without brittle graph-size assertions. The coordinator's isolated overlay run passed, and the complete current combined Go package passed (`sandbox/wp26-session/exposure/test.log`, `combined-baseline.log`). The local preparation/build during the #14 checkpoint also ran the normal bridge tests; the resulting prepared WASM retained SHA-256 `9149b23d80b7fd0b6935b374f0e9102c270bc5f7d595b43a8186d01dece7b663`. Neither these passing controls nor large node counts establish full import/renderer conformance. Hosted timing, deployment and upstream acceptance remain unverified. + +## Recovery visualization error lifetime — issue #14 + +Recorded 2026-11-11 on `fix/wp26-inspection`, based on `890fe88d`. The accepted recovery replacement and the following diagram refresh are now separate error domains. A rejected project open remains recovery-owned and retains the pending record. After acceptance, a named-view/exploration failure uses the generation-aware workbench action channel, so it stays actionable while current and does not survive a later successful action as an obsolete recovery alert. Exploration origin validation is unchanged. + +The real-engine fixture `package P { part def C; requirement def R { subject c : C; } }` has no parse diagnostics but currently fails exploration correspondence validation. That legitimate current failure is preserved; a zero parse-diagnostic count does not imply successful rendering. The browser regression writes and restores an actual IndexedDB copy, confirms repeated parse-clean exploration failures remain visible in Issues and outside the recovery banner, then applies a model whose exploration succeeds before asserting obsolete alerts are absent. Exact applied source is checked before and after. It reproduces the failure mechanism, not the reporter's unretained historical recovery envelope or deployed build. + +The initial browser regression failed against the old implementation after successful replacement exploration because the prior recovery alert remained (`sandbox/issue14-implementation/red.log`). The implementation's final focused run passed (`green.log` in the same directory); an intermediate capture-order fixture failure and its correction are retained separately. Independent review found no blocking defect in the scoped ownership/generation change, using source and retained browser traces. Desktop/390px light/dark current-error captures were inspected; the operational error remains readable in the workbench Issues surface. + +Coordinator confirmation: `fnm exec --using 26.3.1 npm run test:e2e -- --project=functional recovery-error-lifetime.spec.ts recovery.spec.ts` passed all eleven scenarios after a fresh build (`sandbox/wp26-session/issue14-commit-browser.log`). `npm run check` passed formatting, architecture, TypeScript, 160 Vitest tests and ten script tests (`sandbox/wp26-session/issue14-commit-check.log`). Genuine rejected restore, named-view selection, exact drafts/geometry, storage denial and competing-tab preservation remain covered by the existing recovery suite. Full unrelated browser/visual coverage, hosted CI and deployment are not established by these local results. + ## Recovery contents preview — issue #15 Recorded 2026-11-11, extending the local issue #23 work on `fix/recovery-conflict-clarity`. [Issue #15](https://github.com/Open-MBEE/sysml_view_editor/issues/15) now has an expandable pending-copy summary in the launcher/banner and recovery settings: source-file count, unapplied-draft count, filenames, per-file draft status and applied/draft UTF-8 byte sizes. The copy’s name and actual saved time remain visible. The summary explicitly excludes changes that failed to save; it does not invent a current-tab save time or provide snapshot history. No persistence format or storage behavior changed. diff --git a/engine-bridge/README.md b/engine-bridge/README.md index 7fd98d5..985673e 100644 --- a/engine-bridge/README.md +++ b/engine-bridge/README.md @@ -28,3 +28,5 @@ Read-only `Explore` uses the same validated applied-source revision as named-vie Structural attribute compartments retain exact source slices for effective non-library attribute members through the pinned semantic model's `MembersOf` contract. They expose declarations rather than evaluated values. Native action/state artifacts are rendered by the workbench with existing correspondence and bounded graph validation; this adds no execution API or upstream pin change. The 0.12 port normalizes native optional labels, anonymous names and empty graph arrays to the existing browser contract. Temporary exploration retains explicit ownership; interconnection attributes retain the app's source-backed compartments. `EvaluateRelationships` also returns source-bound requirement rows and satisfaction assertion/negation for the read-only table/matrix. See [compatibility evidence](../docs/research/opensysml-0.12-compatibility.md) and [requirements data](../docs/architecture/requirements-data.md). + +Authored interconnection normalization retains native inherited structural occurrences with complete declaration/usage correspondence, while reserving legacy node/port IDs for unchanged saved presentations. Tree and temporary exploration filtering remain unchanged. Native depth/recursion bounds, attribute compartments and explicit connector-selection policy are retained. The combined Go regressions cover exact occurrence semantics, legacy identities and unchanged exploration output. diff --git a/engine-bridge/opensysml/evaluate_view.go b/engine-bridge/opensysml/evaluate_view.go index 69b2bcb..d6d77c6 100644 --- a/engine-bridge/opensysml/evaluate_view.go +++ b/engine-bridge/opensysml/evaluate_view.go @@ -106,10 +106,10 @@ func (e *Engine) evaluateView(req *JRenderViewRequest) ([]byte, error) { sort.Strings(out.Diagnostics) return marshal(out) } - return cached.normalizedRendering(out, selected, rendering) + return cached.normalizedRendering(out, selected, rendering, true) } -func (cached *cachedModel) normalizedRendering(out EvaluatedView, selected []*symbols.Symbol, rendering *view.Rendering) ([]byte, error) { +func (cached *cachedModel) normalizedRendering(out EvaluatedView, selected []*symbols.Symbol, rendering *view.Rendering, authored bool) ([]byte, error) { var err error byOrigin := map[string]string{} declarations := map[string]*symbols.Symbol{} @@ -161,9 +161,10 @@ func (cached *cachedModel) normalizedRendering(out EvaluatedView, selected []*sy shown[element] = true } data := rendering.DataWith(view.Options{Ports: view.PortsFull, Show: shown}) - // The supported tree/structural projection retains explicit declaration - // ownership. 0.12 additionally expands inherited usages; selected roots - // remain native, while their implicit inherited children stay out of scope. + legacyIDs := map[string]string{} + // Exploration and trees retain declaration ownership. Authored structural + // views retain native effective structural occurrences; inherited attributes + // remain source-backed compartments. Native traversal supplies the bounds. if data.Kind == view.KindTree || data.Kind == view.KindInterconnection { kept := map[string]bool{} redirect := map[string]string{} @@ -185,7 +186,16 @@ func (cached *cachedModel) normalizedRendering(out EvaluatedView, selected []*sy } unsupportedConstraint = data.Kind == view.KindInterconnection && (sym.Kind == symbols.SymbolConstraintDef || sym.Kind == symbols.SymbolConstraintUsage) } - if node.Inherited || syntheticSubject || unsupportedConstraint || (node.Parent != "" && !kept[node.Parent]) { + inheritedOutOfScope := node.Inherited + if authored && data.Kind == view.KindInterconnection { + if sym := declarations[originKey(node.Origin)]; sym != nil { + inheritedOutOfScope = node.Inherited && (sym.Kind == symbols.SymbolAttributeUsage || sym.Kind == symbols.SymbolAttributeDef) + } + } + if !node.Inherited && !syntheticSubject && !unsupportedConstraint && (node.Parent == "" || legacyIDs[node.Parent] != "") { + legacyIDs[node.ID] = fmt.Sprintf("n%d", len(legacyIDs)) + } + if inheritedOutOfScope || syntheticSubject || unsupportedConstraint || (node.Parent != "" && !kept[node.Parent]) { redirect[node.ID] = redirect[node.Parent] continue } @@ -229,13 +239,18 @@ func (cached *cachedModel) normalizedRendering(out EvaluatedView, selected []*sy } data.Edges = edges } - // Version-1 presentations key positions by the native ordinal symbol IDs. - // After projecting away new inherited/attribute nodes, retain the existing - // declaration-order numbering and rewrite every dependent endpoint/port. + // Keep version-1 IDs for every occurrence the legacy projection retained. + // Allocate newly visible occurrences after them without reordering the native + // parent-first array, then rewrite all dependent endpoints and port IDs. if data.Kind == view.KindTree || data.Kind == view.KindInterconnection { ids := map[string]string{"": ""} - for i, node := range data.Nodes { - id := fmt.Sprintf("n%d", i) + next := len(legacyIDs) + for _, node := range data.Nodes { + id := legacyIDs[node.ID] + if id == "" { + id = fmt.Sprintf("n%d", next) + next++ + } ids[node.ID] = id for j, port := range node.Ports { ids[port.ID] = fmt.Sprintf("%s.%d", id, j) diff --git a/engine-bridge/opensysml/exploration.go b/engine-bridge/opensysml/exploration.go index 45659cf..17c0a40 100644 --- a/engine-bridge/opensysml/exploration.go +++ b/engine-bridge/opensysml/exploration.go @@ -85,7 +85,7 @@ func (e *Engine) explore(req *JExploreRequest) ([]byte, error) { out.Diagnostics = append(out.Diagnostics, err.Error()) return marshalExploration(out, req.Request) } - raw, err := cached.normalizedRendering(out, selected, rendering) + raw, err := cached.normalizedRendering(out, selected, rendering, false) if err != nil { return nil, err } diff --git a/engine-bridge/opensysml/validated_view_test.go b/engine-bridge/opensysml/validated_view_test.go index ee67402..862e470 100644 --- a/engine-bridge/opensysml/validated_view_test.go +++ b/engine-bridge/opensysml/validated_view_test.go @@ -4,6 +4,8 @@ import ( "context" "encoding/json" "os" + "reflect" + "strconv" "strings" "testing" ) @@ -364,11 +366,341 @@ func TestStudioRetainsDeclaredBoundaryConnections(t *testing.T) { artifact := result["artifact"].(map[string]any) nodes := artifact["nodes"].([]any) edges := artifact["edges"].([]any) - if len(nodes) != 3 || len(edges) != 1 { - t.Fatalf("Inherited internals leaked or declared connection lost: %v", artifact) + if len(nodes) != 7 || len(edges) != 1 { + t.Fatalf("Effective internals or declared bridge lost: %v", artifact) } edge := edges[0].(map[string]any) - if edge["from"] != "n1" || edge["to"] != "n2" || edge["fromPort"] != nil || edge["toPort"] != nil || edge["label"] != "bridge" { + if edge["from"] != "n3" || edge["to"] != "n6" || edge["fromPort"] != "n3.0" || edge["toPort"] != "n6.0" || edge["label"] != "bridge" { t.Fatalf("Incorrect retained usage boundaries: %v", edge) } } + +func TestStudioExposureMembershipScope(t *testing.T) { + server, err := New("v0.12.0") + if err != nil { + t.Fatal(err) + } + call := func(method string, params any) map[string]any { + t.Helper() + input, err := json.Marshal(params) + if err != nil { + t.Fatal(err) + } + raw, err := server.Call(context.Background(), method, input) + if err != nil { + t.Fatal(err) + } + var result map[string]any + if err := json.Unmarshal(raw, &result); err != nil { + t.Fatal(err) + } + return result + } + errorsOf := func(parsed map[string]any) []string { + var errors []string + diagnostics, _ := parsed["diagnostics"].([]any) + for _, item := range diagnostics { + d := item.(map[string]any) + if d["severity"] == "error" { + errors = append(errors, d["message"].(string)) + } + } + return errors + } + const demo = "package Demo { part def Sat; }\n" + evaluate := func(source, view string) map[string]string { + t.Helper() + parsed := call("ParseSources", map[string]any{"strictConformance": true, "documents": []any{map[string]any{"name": "model.sysml", "content": demo + source}}}) + if errors := errorsOf(parsed); len(errors) > 0 { + t.Fatalf("positive fixture rejected: %v", errors) + } + result := call("EvaluateView", map[string]any{"modelHash": parsed["modelHash"], "view": view}) + if result["status"] != "complete" || result["renderStatus"] != "available" { + t.Fatalf("unavailable view: %v", result) + } + selected, ok := result["selected"].([]any) + if !ok { + t.Fatalf("missing selected membership: %v", result) + } + names := map[string]string{} + for _, raw := range selected { + subject := raw.(map[string]any) + origin, ok := subject["origin"].(map[string]any) + if !ok || origin["file"] == "" || subject["id"] == "" { + t.Fatalf("selected subject lacks source identity: %v", subject) + } + name, ok := subject["qualifiedName"].(string) + if !ok || name == "" { + t.Fatalf("selected subject lacks qualified name: %v", subject) + } + names[name] = origin["file"].(string) + } + return names + } + body := func(visibility, exposure string) string { + return "package Probe { " + visibility + " import Demo::*; " + visibility + " import StandardViewDefinitions::*; part p : Sat; view star : GeneralView { expose " + exposure + "; } }" + } + private := evaluate(body("private", "Probe::*"), "Probe::star") + public := evaluate(body("public", "Probe::*"), "Probe::star") + if !reflect.DeepEqual(private, public) { + t.Fatalf("exposure must ignore import visibility: private=%v public=%v", private, public) + } + for _, name := range []string{"Probe::p", "Probe::star", "Demo::Sat", "StandardViewDefinitions::GeneralView", "SysML::Systems::PartDefinition"} { + if _, ok := private[name]; !ok { + t.Fatalf("missing admitted membership %s", name) + } + } + hasLibrary := false + for _, file := range private { + if file != "model.sysml" { + hasLibrary = true + } + } + if !hasLibrary { + t.Fatal("wildcard exposure unexpectedly removed imported library memberships") + } + explicit := evaluate(body("private", "Probe::p"), "Probe::star") + if !reflect.DeepEqual(explicit, map[string]string{"Probe::p": "model.sysml"}) { + t.Fatalf("explicit exposure widened: %v", explicit) + } + named := evaluate("package Probe { private import Demo::Sat; private import StandardViewDefinitions::GeneralView; part p : Sat; view star : GeneralView { expose Probe::*; } }", "Probe::star") + expected := map[string]bool{"Probe::p": true, "Probe::star": true, "Demo::Sat": true, "StandardViewDefinitions::GeneralView": true} + if len(named) != len(expected) { + t.Fatalf("named imports selected unexpected memberships: %v", named) + } + for name := range named { + if !expected[name] { + t.Fatalf("named import leaked %s", name) + } + } + noImports := evaluate("package Probe { part p : Demo::Sat; view star : StandardViewDefinitions::GeneralView { expose Probe::*; } }", "Probe::star") + if !reflect.DeepEqual(noImports, map[string]string{"Probe::p": "model.sysml", "Probe::star": "model.sysml"}) { + t.Fatalf("no-import scope widened: %v", noImports) + } + hidden := evaluate("package Probe { private part hidden : Demo::Sat; } view star : StandardViewDefinitions::GeneralView { expose Probe::hidden; }", "star") + if !reflect.DeepEqual(hidden, map[string]string{"Probe::hidden": "model.sysml"}) { + t.Fatalf("explicit private exposure failed: %v", hidden) + } + for _, outside := range []string{"part q : Probe::Sat;", "private import Probe::*; part q : Sat;"} { + source := demo + "package Probe { private import Demo::*; } package Outside { " + outside + " }" + parsed := call("ParseSources", map[string]any{"strictConformance": true, "documents": []any{map[string]any{"name": "model.sysml", "content": source}}}) + errors := errorsOf(parsed) + if len(errors) == 0 { + t.Fatalf("ordinary lookup bypassed private import visibility: %s", outside) + } + targetNamed := false + for _, message := range errors { + if strings.Contains(message, "Sat") { + targetNamed = true + } + } + if !targetNamed { + t.Fatalf("ordinary lookup failed for unrelated reason: %v", errors) + } + } +} + +// TestStudioAuthoredEffectiveOccurrences fixes the pinned bridge contract at the +// declaration and occurrence level. Expected values are retained native evidence, +// not computed from the implementation under test. Explore expectations are the +// complete pre-expansion envelopes; source and view identity remain independent. +func TestStudioAuthoredEffectiveOccurrences(t *testing.T) { + for _, tc := range issue18Cases { + t.Run(tc.name, func(t *testing.T) { + server, err := New("v0.12.0") + if err != nil { + t.Fatal(err) + } + call := func(method string, params any) map[string]any { + t.Helper() + body, err := json.Marshal(params) + if err != nil { + t.Fatal(err) + } + raw, err := server.Call(context.Background(), method, body) + if err != nil { + t.Fatal(err) + } + var out map[string]any + if err := json.Unmarshal(raw, &out); err != nil { + t.Fatal(err) + } + return out + } + parsed := call("ParseSources", map[string]any{"strictConformance": true, "documents": []any{map[string]any{"name": "probe.sysml", "content": tc.source}}}) + view, subject := "V::v", "P::sat1" + if tc.name == "declared-boundary" { + view, subject = "P::v", "P::System" + } + result := call("EvaluateView", map[string]any{"modelHash": parsed["modelHash"], "view": view}) + if result["status"] != "complete" || result["renderStatus"] != "available" { + t.Fatalf("invalid view: %v", result) + } + assertIssue18JSON(t, "authored declarations, topology and occurrence bindings", issue18Semantics(t, result, tc.source), tc.want) + assertIssue18JSON(t, "unchanged selected scope", result["selected"], tc.selected) + explored := call("Explore", map[string]any{"modelHash": parsed["modelHash"], "request": map[string]any{"scope": map[string]any{"kind": "element", "subject": subject}, "representation": "interconnection"}}) + assertIssue18JSON(t, "unchanged complete Explore envelope", explored, tc.explore) + // Every legacy-visible node, port and correspondence keeps its complete + // value, including ordinals on a later sibling following new descendants. + got := issue18Semantics(t, result, tc.source).([]any) + var legacy []any + if err := json.Unmarshal([]byte(tc.legacy), &legacy); err != nil { + t.Fatal(err) + } + byID := map[string]any{} + for _, raw := range got { + r := raw.(map[string]any) + byID[r["symbol"].(string)] = raw + } + for _, raw := range legacy { + r := raw.(map[string]any) + id := r["symbol"].(string) + if !reflect.DeepEqual(byID[id], raw) { + t.Errorf("legacy identity %s changed: got %v want %v", id, byID[id], raw) + } + } + }) + } +} + +func assertIssue18JSON(t *testing.T, label string, got any, expected string) { + t.Helper() + var want any + if err := json.Unmarshal([]byte(expected), &want); err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(got, want) { + b, _ := json.Marshal(got) + t.Fatalf("%s\ngot: %s\nwant: %s", label, b, expected) + } +} + +func issue18Semantics(t *testing.T, result map[string]any, source string) any { + t.Helper() + artifact := result["artifact"].(map[string]any) + bindings := map[string]map[string]any{} + for _, raw := range result["correspondence"].([]any) { + c := raw.(map[string]any) + id := c["symbol"].(string) + if bindings[id] != nil { + t.Fatalf("duplicate binding %s", id) + } + bindings[id] = c + } + var rows []any + contexts := map[string][]any{} + ports := map[string]string{} + seen := map[string]bool{} + add := func(symbol string, value map[string]any, parent string) { + t.Helper() + c := bindings[symbol] + if c == nil { + t.Fatalf("missing binding %s", symbol) + } + subject := c["subject"].(map[string]any) + origin := subject["origin"].(map[string]any) + start, length := int(origin["offset"].(float64)), int(origin["length"].(float64)) + if origin["file"] != "probe.sysml" || start < 0 || length < 1 || start+length > len(source) { + t.Fatalf("invalid origin %v", origin) + } + ctx := c["context"].([]any) + want := append(append([]any{}, contexts[parent]...), subject["id"]) + if !reflect.DeepEqual(ctx, want) { + t.Fatalf("%s incomplete occurrence context: %v want %v", symbol, ctx, want) + } + contexts[symbol] = ctx + rows = append(rows, map[string]any{"symbol": symbol, "value": value, "binding": c, "declaration": source[start : start+length]}) + } + for _, raw := range artifact["nodes"].([]any) { + n := raw.(map[string]any) + id := n["id"].(string) + parent, _ := n["parent"].(string) + if seen[id] || (parent != "" && !seen[parent]) { + t.Fatalf("duplicate or non-parent-first node %v", n) + } + seen[id] = true + add(id, n, parent) + if ps, ok := n["ports"].([]any); ok { + for _, raw := range ps { + port := raw.(map[string]any) + pid := port["id"].(string) + if ports[pid] != "" { + t.Fatalf("duplicate port %s", pid) + } + ports[pid] = id + add(pid, port, id) + } + } + } + for i, raw := range artifact["edges"].([]any) { + e := raw.(map[string]any) + for _, end := range []string{"from", "to"} { + node := e[end].(string) + if !seen[node] { + t.Fatalf("missing endpoint %v", e) + } + if pin, ok := e[end+"Port"].(string); ok && ports[pin] != node { + t.Fatalf("wrong endpoint port %v", e) + } + } + // Connector contexts are intentionally not expanded in this bounded policy. + id := "edge:" + strconv.Itoa(i) + c := bindings[id] + if c == nil { + t.Fatalf("missing edge binding %s", id) + } + subject := c["subject"].(map[string]any) + o := subject["origin"].(map[string]any) + start, length := int(o["offset"].(float64)), int(o["length"].(float64)) + rows = append(rows, map[string]any{"symbol": id, "value": e, "binding": c, "declaration": source[start : start+length]}) + } + return rows +} + +var issue18Cases = []struct{ name, source, want, selected, explore, legacy string }{ + // all: exact source declarations, parents, types, ports, contexts and legacy ordinals. + {`all`, `package P {part def Wheel; part def SmallWheel :> Wheel; part def Sat { part a:Wheel; part b:Wheel; part c:Wheel; } part sat1:Sat {part :>> a:SmallWheel; part :>> b:SmallWheel; part :>> c:SmallWheel;} part sat2:Sat;} package V {private import StandardViewDefinitions::*; view v:InterconnectionView {expose P::sat1; expose P::sat2; expose P::sat1::c;} }`, `[{"symbol":"n0","value":{"id":"n0","kind":"part","name":"P::sat1","origin":{"endCol":201,"endLine":1,"file":"probe.sysml","startCol":117,"startLine":1},"type":"Sat"},"binding":{"context":["probe.sysml@116:84"],"subject":{"id":"probe.sysml@116:84","origin":{"file":"probe.sysml","length":84,"nameLength":4,"nameOffset":121,"offset":116},"qualifiedName":"P::sat1"},"symbol":"n0"},"declaration":"part sat1:Sat {part :>> a:SmallWheel; part :>> b:SmallWheel; part :>> c:SmallWheel;}"},{"symbol":"n1","value":{"id":"n1","kind":"part","name":"a","origin":{"endCol":154,"endLine":1,"file":"probe.sysml","startCol":132,"startLine":1},"parent":"n0","type":"SmallWheel"},"binding":{"context":["probe.sysml@116:84","probe.sysml@131:22"],"subject":{"id":"probe.sysml@131:22","origin":{"file":"probe.sysml","length":22,"nameLength":1,"nameOffset":140,"offset":131},"qualifiedName":"P::sat1::a"},"symbol":"n1"},"declaration":"part :>> a:SmallWheel;"},{"symbol":"n2","value":{"id":"n2","kind":"part","name":"b","origin":{"endCol":177,"endLine":1,"file":"probe.sysml","startCol":155,"startLine":1},"parent":"n0","type":"SmallWheel"},"binding":{"context":["probe.sysml@116:84","probe.sysml@154:22"],"subject":{"id":"probe.sysml@154:22","origin":{"file":"probe.sysml","length":22,"nameLength":1,"nameOffset":163,"offset":154},"qualifiedName":"P::sat1::b"},"symbol":"n2"},"declaration":"part :>> b:SmallWheel;"},{"symbol":"n3","value":{"id":"n3","kind":"part","name":"c","origin":{"endCol":200,"endLine":1,"file":"probe.sysml","startCol":178,"startLine":1},"parent":"n0","type":"SmallWheel"},"binding":{"context":["probe.sysml@116:84","probe.sysml@177:22"],"subject":{"id":"probe.sysml@177:22","origin":{"file":"probe.sysml","length":22,"nameLength":1,"nameOffset":186,"offset":177},"qualifiedName":"P::sat1::c"},"symbol":"n3"},"declaration":"part :>> c:SmallWheel;"},{"symbol":"n4","value":{"id":"n4","kind":"part","name":"P::sat2","origin":{"endCol":216,"endLine":1,"file":"probe.sysml","startCol":202,"startLine":1},"type":"Sat"},"binding":{"context":["probe.sysml@201:14"],"subject":{"id":"probe.sysml@201:14","origin":{"file":"probe.sysml","length":14,"nameLength":4,"nameOffset":206,"offset":201},"qualifiedName":"P::sat2"},"symbol":"n4"},"declaration":"part sat2:Sat;"},{"symbol":"n5","value":{"id":"n5","inherited":true,"kind":"part","name":"a","origin":{"endCol":86,"endLine":1,"file":"probe.sysml","startCol":73,"startLine":1},"parent":"n4","type":"Wheel"},"binding":{"context":["probe.sysml@201:14","probe.sysml@72:13"],"subject":{"id":"probe.sysml@72:13","origin":{"file":"probe.sysml","length":13,"nameLength":1,"nameOffset":77,"offset":72},"qualifiedName":"P::Sat::a"},"symbol":"n5"},"declaration":"part a:Wheel;"},{"symbol":"n6","value":{"id":"n6","inherited":true,"kind":"part","name":"b","origin":{"endCol":100,"endLine":1,"file":"probe.sysml","startCol":87,"startLine":1},"parent":"n4","type":"Wheel"},"binding":{"context":["probe.sysml@201:14","probe.sysml@86:13"],"subject":{"id":"probe.sysml@86:13","origin":{"file":"probe.sysml","length":13,"nameLength":1,"nameOffset":91,"offset":86},"qualifiedName":"P::Sat::b"},"symbol":"n6"},"declaration":"part b:Wheel;"},{"symbol":"n7","value":{"id":"n7","inherited":true,"kind":"part","name":"c","origin":{"endCol":114,"endLine":1,"file":"probe.sysml","startCol":101,"startLine":1},"parent":"n4","type":"Wheel"},"binding":{"context":["probe.sysml@201:14","probe.sysml@100:13"],"subject":{"id":"probe.sysml@100:13","origin":{"file":"probe.sysml","length":13,"nameLength":1,"nameOffset":105,"offset":100},"qualifiedName":"P::Sat::c"},"symbol":"n7"},"declaration":"part c:Wheel;"}]`, `[{"id":"probe.sysml@116:84","origin":{"file":"probe.sysml","length":84,"nameLength":4,"nameOffset":121,"offset":116},"qualifiedName":"P::sat1"},{"id":"probe.sysml@201:14","origin":{"file":"probe.sysml","length":14,"nameLength":4,"nameOffset":206,"offset":201},"qualifiedName":"P::sat2"},{"id":"probe.sysml@177:22","origin":{"file":"probe.sysml","length":22,"nameLength":1,"nameOffset":186,"offset":177},"qualifiedName":"P::sat1::c"}]`, `{"artifact":{"edges":[],"kind":"interconnection","nodes":[{"id":"n0","kind":"part","name":"P::sat1","origin":{"endCol":201,"endLine":1,"file":"probe.sysml","startCol":117,"startLine":1},"type":"Sat"},{"id":"n1","kind":"part","name":"a","origin":{"endCol":154,"endLine":1,"file":"probe.sysml","startCol":132,"startLine":1},"parent":"n0","type":"SmallWheel"},{"id":"n2","kind":"part","name":"b","origin":{"endCol":177,"endLine":1,"file":"probe.sysml","startCol":155,"startLine":1},"parent":"n0","type":"SmallWheel"},{"id":"n3","kind":"part","name":"c","origin":{"endCol":200,"endLine":1,"file":"probe.sysml","startCol":178,"startLine":1},"parent":"n0","type":"SmallWheel"}],"shown":["parameter-symbols","port-symbols","compartments","edge-labels","keywords","multiplicities","typings","qualifiers"],"stated":"Read-only exploration"},"correspondence":[{"context":["probe.sysml@116:84"],"subject":{"id":"probe.sysml@116:84","origin":{"file":"probe.sysml","length":84,"nameLength":4,"nameOffset":121,"offset":116},"qualifiedName":"P::sat1"},"symbol":"n0"},{"context":["probe.sysml@116:84","probe.sysml@131:22"],"subject":{"id":"probe.sysml@131:22","origin":{"file":"probe.sysml","length":22,"nameLength":1,"nameOffset":140,"offset":131},"qualifiedName":"P::sat1::a"},"symbol":"n1"},{"context":["probe.sysml@116:84","probe.sysml@154:22"],"subject":{"id":"probe.sysml@154:22","origin":{"file":"probe.sysml","length":22,"nameLength":1,"nameOffset":163,"offset":154},"qualifiedName":"P::sat1::b"},"symbol":"n2"},{"context":["probe.sysml@116:84","probe.sysml@177:22"],"subject":{"id":"probe.sysml@177:22","origin":{"file":"probe.sysml","length":22,"nameLength":1,"nameOffset":186,"offset":177},"qualifiedName":"P::sat1::c"},"symbol":"n3"}],"diagnostics":[],"modelHash":"c9defe4c38b03163d7c0fa8c6e161027945fa804431971c115026f1681877550","renderStatus":"available","request":{"representation":"interconnection","scope":{"kind":"element","subject":"P::sat1"}},"schemaVersion":1,"selected":[{"id":"probe.sysml@116:84","origin":{"file":"probe.sysml","length":84,"nameLength":4,"nameOffset":121,"offset":116},"qualifiedName":"P::sat1"}],"status":"complete"}`, `[{"symbol":"n0","value":{"id":"n0","kind":"part","name":"P::sat1","origin":{"endCol":201,"endLine":1,"file":"probe.sysml","startCol":117,"startLine":1},"type":"Sat"},"binding":{"context":["probe.sysml@116:84"],"subject":{"id":"probe.sysml@116:84","origin":{"file":"probe.sysml","length":84,"nameLength":4,"nameOffset":121,"offset":116},"qualifiedName":"P::sat1"},"symbol":"n0"},"declaration":"part sat1:Sat {part :>> a:SmallWheel; part :>> b:SmallWheel; part :>> c:SmallWheel;}"},{"symbol":"n1","value":{"id":"n1","kind":"part","name":"a","origin":{"endCol":154,"endLine":1,"file":"probe.sysml","startCol":132,"startLine":1},"parent":"n0","type":"SmallWheel"},"binding":{"context":["probe.sysml@116:84","probe.sysml@131:22"],"subject":{"id":"probe.sysml@131:22","origin":{"file":"probe.sysml","length":22,"nameLength":1,"nameOffset":140,"offset":131},"qualifiedName":"P::sat1::a"},"symbol":"n1"},"declaration":"part :>> a:SmallWheel;"},{"symbol":"n2","value":{"id":"n2","kind":"part","name":"b","origin":{"endCol":177,"endLine":1,"file":"probe.sysml","startCol":155,"startLine":1},"parent":"n0","type":"SmallWheel"},"binding":{"context":["probe.sysml@116:84","probe.sysml@154:22"],"subject":{"id":"probe.sysml@154:22","origin":{"file":"probe.sysml","length":22,"nameLength":1,"nameOffset":163,"offset":154},"qualifiedName":"P::sat1::b"},"symbol":"n2"},"declaration":"part :>> b:SmallWheel;"},{"symbol":"n3","value":{"id":"n3","kind":"part","name":"c","origin":{"endCol":200,"endLine":1,"file":"probe.sysml","startCol":178,"startLine":1},"parent":"n0","type":"SmallWheel"},"binding":{"context":["probe.sysml@116:84","probe.sysml@177:22"],"subject":{"id":"probe.sysml@177:22","origin":{"file":"probe.sysml","length":22,"nameLength":1,"nameOffset":186,"offset":177},"qualifiedName":"P::sat1::c"},"symbol":"n3"},"declaration":"part :>> c:SmallWheel;"},{"symbol":"n4","value":{"id":"n4","kind":"part","name":"P::sat2","origin":{"endCol":216,"endLine":1,"file":"probe.sysml","startCol":202,"startLine":1},"type":"Sat"},"binding":{"context":["probe.sysml@201:14"],"subject":{"id":"probe.sysml@201:14","origin":{"file":"probe.sysml","length":14,"nameLength":4,"nameOffset":206,"offset":201},"qualifiedName":"P::sat2"},"symbol":"n4"},"declaration":"part sat2:Sat;"}]`}, + // anonymous: exact source declarations, parents, types, ports, contexts and legacy ordinals. + {`anonymous`, `package P {part def Leaf; part def Base { part : Leaf; part named:Leaf; } part sat1:Base; part sat2:Base;} package V {private import StandardViewDefinitions::*; view v:InterconnectionView {expose P::sat1; expose P::sat2;} }`, `[{"symbol":"n0","value":{"id":"n0","kind":"part","name":"P::sat1","origin":{"endCol":90,"endLine":1,"file":"probe.sysml","startCol":75,"startLine":1},"type":"Base"},"binding":{"context":["probe.sysml@74:15"],"subject":{"id":"probe.sysml@74:15","origin":{"file":"probe.sysml","length":15,"nameLength":4,"nameOffset":79,"offset":74},"qualifiedName":"P::sat1"},"symbol":"n0"},"declaration":"part sat1:Base;"},{"symbol":"n2","value":{"id":"n2","inherited":true,"kind":"part","name":"","origin":{"endCol":55,"endLine":1,"file":"probe.sysml","startCol":43,"startLine":1},"parent":"n0","type":"Leaf"},"binding":{"context":["probe.sysml@74:15","probe.sysml@42:12"],"subject":{"id":"probe.sysml@42:12","origin":{"file":"probe.sysml","length":12,"nameLength":0,"nameOffset":0,"offset":42}},"symbol":"n2"},"declaration":"part : Leaf;"},{"symbol":"n3","value":{"id":"n3","inherited":true,"kind":"part","name":"named","origin":{"endCol":72,"endLine":1,"file":"probe.sysml","startCol":56,"startLine":1},"parent":"n0","type":"Leaf"},"binding":{"context":["probe.sysml@74:15","probe.sysml@55:16"],"subject":{"id":"probe.sysml@55:16","origin":{"file":"probe.sysml","length":16,"nameLength":5,"nameOffset":60,"offset":55},"qualifiedName":"P::Base::named"},"symbol":"n3"},"declaration":"part named:Leaf;"},{"symbol":"n1","value":{"id":"n1","kind":"part","name":"P::sat2","origin":{"endCol":106,"endLine":1,"file":"probe.sysml","startCol":91,"startLine":1},"type":"Base"},"binding":{"context":["probe.sysml@90:15"],"subject":{"id":"probe.sysml@90:15","origin":{"file":"probe.sysml","length":15,"nameLength":4,"nameOffset":95,"offset":90},"qualifiedName":"P::sat2"},"symbol":"n1"},"declaration":"part sat2:Base;"},{"symbol":"n4","value":{"id":"n4","inherited":true,"kind":"part","name":"","origin":{"endCol":55,"endLine":1,"file":"probe.sysml","startCol":43,"startLine":1},"parent":"n1","type":"Leaf"},"binding":{"context":["probe.sysml@90:15","probe.sysml@42:12"],"subject":{"id":"probe.sysml@42:12","origin":{"file":"probe.sysml","length":12,"nameLength":0,"nameOffset":0,"offset":42}},"symbol":"n4"},"declaration":"part : Leaf;"},{"symbol":"n5","value":{"id":"n5","inherited":true,"kind":"part","name":"named","origin":{"endCol":72,"endLine":1,"file":"probe.sysml","startCol":56,"startLine":1},"parent":"n1","type":"Leaf"},"binding":{"context":["probe.sysml@90:15","probe.sysml@55:16"],"subject":{"id":"probe.sysml@55:16","origin":{"file":"probe.sysml","length":16,"nameLength":5,"nameOffset":60,"offset":55},"qualifiedName":"P::Base::named"},"symbol":"n5"},"declaration":"part named:Leaf;"}]`, `[{"id":"probe.sysml@74:15","origin":{"file":"probe.sysml","length":15,"nameLength":4,"nameOffset":79,"offset":74},"qualifiedName":"P::sat1"},{"id":"probe.sysml@90:15","origin":{"file":"probe.sysml","length":15,"nameLength":4,"nameOffset":95,"offset":90},"qualifiedName":"P::sat2"}]`, `{"artifact":{"edges":[],"kind":"interconnection","nodes":[{"id":"n0","kind":"part","name":"P::sat1","origin":{"endCol":90,"endLine":1,"file":"probe.sysml","startCol":75,"startLine":1},"type":"Base"}],"shown":["parameter-symbols","port-symbols","compartments","edge-labels","keywords","multiplicities","typings","qualifiers"],"stated":"Read-only exploration"},"correspondence":[{"context":["probe.sysml@74:15"],"subject":{"id":"probe.sysml@74:15","origin":{"file":"probe.sysml","length":15,"nameLength":4,"nameOffset":79,"offset":74},"qualifiedName":"P::sat1"},"symbol":"n0"}],"diagnostics":[],"modelHash":"74586c8f1e7c12d23a4ada840473f0c6fdf0c0c047e492b58136c9985384619a","renderStatus":"available","request":{"representation":"interconnection","scope":{"kind":"element","subject":"P::sat1"}},"schemaVersion":1,"selected":[{"id":"probe.sysml@74:15","origin":{"file":"probe.sysml","length":15,"nameLength":4,"nameOffset":79,"offset":74},"qualifiedName":"P::sat1"}],"status":"complete"}`, `[{"symbol":"n0","value":{"id":"n0","kind":"part","name":"P::sat1","origin":{"endCol":90,"endLine":1,"file":"probe.sysml","startCol":75,"startLine":1},"type":"Base"},"binding":{"context":["probe.sysml@74:15"],"subject":{"id":"probe.sysml@74:15","origin":{"file":"probe.sysml","length":15,"nameLength":4,"nameOffset":79,"offset":74},"qualifiedName":"P::sat1"},"symbol":"n0"},"declaration":"part sat1:Base;"},{"symbol":"n1","value":{"id":"n1","kind":"part","name":"P::sat2","origin":{"endCol":106,"endLine":1,"file":"probe.sysml","startCol":91,"startLine":1},"type":"Base"},"binding":{"context":["probe.sysml@90:15"],"subject":{"id":"probe.sysml@90:15","origin":{"file":"probe.sysml","length":15,"nameLength":4,"nameOffset":95,"offset":90},"qualifiedName":"P::sat2"},"symbol":"n1"},"declaration":"part sat2:Base;"}]`}, + // boundary: exact source declarations, parents, types, ports, contexts and legacy ordinals. + {`boundary`, `package P {port def Pin; part def Leaf { in port input:Pin; out port output:Pin; } part def Base { part a:Leaf; part b:Leaf; connection internal connect a.output to b.input; } part sat1:Base { connection bridge connect a.output to b.input; } part sat2:Base;} package V {private import StandardViewDefinitions::*; view v:InterconnectionView {expose P::sat1; expose P::sat2;} }`, `[{"symbol":"n0","value":{"id":"n0","kind":"part","name":"P::sat1","origin":{"endCol":242,"endLine":1,"file":"probe.sysml","startCol":177,"startLine":1},"type":"Base"},"binding":{"context":["probe.sysml@176:65"],"subject":{"id":"probe.sysml@176:65","origin":{"file":"probe.sysml","length":65,"nameLength":4,"nameOffset":181,"offset":176},"qualifiedName":"P::sat1"},"symbol":"n0"},"declaration":"part sat1:Base { connection bridge connect a.output to b.input; }"},{"symbol":"n2","value":{"id":"n2","inherited":true,"kind":"part","name":"a","origin":{"endCol":112,"endLine":1,"file":"probe.sysml","startCol":100,"startLine":1},"parent":"n0","ports":[{"direction":"in","id":"n2.0","inherited":true,"name":"input","type":"Pin"},{"direction":"out","id":"n2.1","inherited":true,"name":"output","type":"Pin"}],"type":"Leaf"},"binding":{"context":["probe.sysml@176:65","probe.sysml@99:12"],"subject":{"id":"probe.sysml@99:12","origin":{"file":"probe.sysml","length":12,"nameLength":1,"nameOffset":104,"offset":99},"qualifiedName":"P::Base::a"},"symbol":"n2"},"declaration":"part a:Leaf;"},{"symbol":"n2.0","value":{"direction":"in","id":"n2.0","inherited":true,"name":"input","type":"Pin"},"binding":{"context":["probe.sysml@176:65","probe.sysml@99:12","probe.sysml@41:18"],"subject":{"id":"probe.sysml@41:18","origin":{"file":"probe.sysml","length":18,"nameLength":5,"nameOffset":49,"offset":41},"qualifiedName":"P::Leaf::input"},"symbol":"n2.0"},"declaration":"in port input:Pin;"},{"symbol":"n2.1","value":{"direction":"out","id":"n2.1","inherited":true,"name":"output","type":"Pin"},"binding":{"context":["probe.sysml@176:65","probe.sysml@99:12","probe.sysml@60:20"],"subject":{"id":"probe.sysml@60:20","origin":{"file":"probe.sysml","length":20,"nameLength":6,"nameOffset":69,"offset":60},"qualifiedName":"P::Leaf::output"},"symbol":"n2.1"},"declaration":"out port output:Pin;"},{"symbol":"n3","value":{"id":"n3","inherited":true,"kind":"part","name":"b","origin":{"endCol":125,"endLine":1,"file":"probe.sysml","startCol":113,"startLine":1},"parent":"n0","ports":[{"direction":"in","id":"n3.0","inherited":true,"name":"input","type":"Pin"},{"direction":"out","id":"n3.1","inherited":true,"name":"output","type":"Pin"}],"type":"Leaf"},"binding":{"context":["probe.sysml@176:65","probe.sysml@112:12"],"subject":{"id":"probe.sysml@112:12","origin":{"file":"probe.sysml","length":12,"nameLength":1,"nameOffset":117,"offset":112},"qualifiedName":"P::Base::b"},"symbol":"n3"},"declaration":"part b:Leaf;"},{"symbol":"n3.0","value":{"direction":"in","id":"n3.0","inherited":true,"name":"input","type":"Pin"},"binding":{"context":["probe.sysml@176:65","probe.sysml@112:12","probe.sysml@41:18"],"subject":{"id":"probe.sysml@41:18","origin":{"file":"probe.sysml","length":18,"nameLength":5,"nameOffset":49,"offset":41},"qualifiedName":"P::Leaf::input"},"symbol":"n3.0"},"declaration":"in port input:Pin;"},{"symbol":"n3.1","value":{"direction":"out","id":"n3.1","inherited":true,"name":"output","type":"Pin"},"binding":{"context":["probe.sysml@176:65","probe.sysml@112:12","probe.sysml@60:20"],"subject":{"id":"probe.sysml@60:20","origin":{"file":"probe.sysml","length":20,"nameLength":6,"nameOffset":69,"offset":60},"qualifiedName":"P::Leaf::output"},"symbol":"n3.1"},"declaration":"out port output:Pin;"},{"symbol":"n1","value":{"id":"n1","kind":"part","name":"P::sat2","origin":{"endCol":258,"endLine":1,"file":"probe.sysml","startCol":243,"startLine":1},"type":"Base"},"binding":{"context":["probe.sysml@242:15"],"subject":{"id":"probe.sysml@242:15","origin":{"file":"probe.sysml","length":15,"nameLength":4,"nameOffset":247,"offset":242},"qualifiedName":"P::sat2"},"symbol":"n1"},"declaration":"part sat2:Base;"},{"symbol":"n4","value":{"id":"n4","inherited":true,"kind":"part","name":"a","origin":{"endCol":112,"endLine":1,"file":"probe.sysml","startCol":100,"startLine":1},"parent":"n1","ports":[{"direction":"in","id":"n4.0","inherited":true,"name":"input","type":"Pin"},{"direction":"out","id":"n4.1","inherited":true,"name":"output","type":"Pin"}],"type":"Leaf"},"binding":{"context":["probe.sysml@242:15","probe.sysml@99:12"],"subject":{"id":"probe.sysml@99:12","origin":{"file":"probe.sysml","length":12,"nameLength":1,"nameOffset":104,"offset":99},"qualifiedName":"P::Base::a"},"symbol":"n4"},"declaration":"part a:Leaf;"},{"symbol":"n4.0","value":{"direction":"in","id":"n4.0","inherited":true,"name":"input","type":"Pin"},"binding":{"context":["probe.sysml@242:15","probe.sysml@99:12","probe.sysml@41:18"],"subject":{"id":"probe.sysml@41:18","origin":{"file":"probe.sysml","length":18,"nameLength":5,"nameOffset":49,"offset":41},"qualifiedName":"P::Leaf::input"},"symbol":"n4.0"},"declaration":"in port input:Pin;"},{"symbol":"n4.1","value":{"direction":"out","id":"n4.1","inherited":true,"name":"output","type":"Pin"},"binding":{"context":["probe.sysml@242:15","probe.sysml@99:12","probe.sysml@60:20"],"subject":{"id":"probe.sysml@60:20","origin":{"file":"probe.sysml","length":20,"nameLength":6,"nameOffset":69,"offset":60},"qualifiedName":"P::Leaf::output"},"symbol":"n4.1"},"declaration":"out port output:Pin;"},{"symbol":"n5","value":{"id":"n5","inherited":true,"kind":"part","name":"b","origin":{"endCol":125,"endLine":1,"file":"probe.sysml","startCol":113,"startLine":1},"parent":"n1","ports":[{"direction":"in","id":"n5.0","inherited":true,"name":"input","type":"Pin"},{"direction":"out","id":"n5.1","inherited":true,"name":"output","type":"Pin"}],"type":"Leaf"},"binding":{"context":["probe.sysml@242:15","probe.sysml@112:12"],"subject":{"id":"probe.sysml@112:12","origin":{"file":"probe.sysml","length":12,"nameLength":1,"nameOffset":117,"offset":112},"qualifiedName":"P::Base::b"},"symbol":"n5"},"declaration":"part b:Leaf;"},{"symbol":"n5.0","value":{"direction":"in","id":"n5.0","inherited":true,"name":"input","type":"Pin"},"binding":{"context":["probe.sysml@242:15","probe.sysml@112:12","probe.sysml@41:18"],"subject":{"id":"probe.sysml@41:18","origin":{"file":"probe.sysml","length":18,"nameLength":5,"nameOffset":49,"offset":41},"qualifiedName":"P::Leaf::input"},"symbol":"n5.0"},"declaration":"in port input:Pin;"},{"symbol":"n5.1","value":{"direction":"out","id":"n5.1","inherited":true,"name":"output","type":"Pin"},"binding":{"context":["probe.sysml@242:15","probe.sysml@112:12","probe.sysml@60:20"],"subject":{"id":"probe.sysml@60:20","origin":{"file":"probe.sysml","length":20,"nameLength":6,"nameOffset":69,"offset":60},"qualifiedName":"P::Leaf::output"},"symbol":"n5.1"},"declaration":"out port output:Pin;"},{"symbol":"edge:0","value":{"from":"n2","fromPort":"n2.1","kind":"connection","label":"bridge","origin":{"endCol":240,"endLine":1,"file":"probe.sysml","startCol":194,"startLine":1},"to":"n3","toPort":"n3.0"},"binding":{"context":[],"subject":{"id":"probe.sysml@193:46","origin":{"file":"probe.sysml","length":46,"nameLength":6,"nameOffset":204,"offset":193},"qualifiedName":"P::sat1::bridge"},"symbol":"edge:0"},"declaration":"connection bridge connect a.output to b.input;"}]`, `[{"id":"probe.sysml@176:65","origin":{"file":"probe.sysml","length":65,"nameLength":4,"nameOffset":181,"offset":176},"qualifiedName":"P::sat1"},{"id":"probe.sysml@242:15","origin":{"file":"probe.sysml","length":15,"nameLength":4,"nameOffset":247,"offset":242},"qualifiedName":"P::sat2"}]`, `{"artifact":{"edges":[{"from":"n0","kind":"connection","label":"bridge","origin":{"endCol":240,"endLine":1,"file":"probe.sysml","startCol":194,"startLine":1},"to":"n0"}],"kind":"interconnection","nodes":[{"id":"n0","kind":"part","name":"P::sat1","origin":{"endCol":242,"endLine":1,"file":"probe.sysml","startCol":177,"startLine":1},"type":"Base"}],"shown":["parameter-symbols","port-symbols","compartments","edge-labels","keywords","multiplicities","typings","qualifiers"],"stated":"Read-only exploration"},"correspondence":[{"context":["probe.sysml@176:65"],"subject":{"id":"probe.sysml@176:65","origin":{"file":"probe.sysml","length":65,"nameLength":4,"nameOffset":181,"offset":176},"qualifiedName":"P::sat1"},"symbol":"n0"},{"context":[],"subject":{"id":"probe.sysml@193:46","origin":{"file":"probe.sysml","length":46,"nameLength":6,"nameOffset":204,"offset":193},"qualifiedName":"P::sat1::bridge"},"symbol":"edge:0"}],"diagnostics":[],"modelHash":"419b135831a6800f6fec852b84d05c2c1e5a1d466bdcb1448c406c6948ad3511","renderStatus":"available","request":{"representation":"interconnection","scope":{"kind":"element","subject":"P::sat1"}},"schemaVersion":1,"selected":[{"id":"probe.sysml@176:65","origin":{"file":"probe.sysml","length":65,"nameLength":4,"nameOffset":181,"offset":176},"qualifiedName":"P::sat1"}],"status":"complete"}`, `[{"symbol":"n0","value":{"id":"n0","kind":"part","name":"P::sat1","origin":{"endCol":242,"endLine":1,"file":"probe.sysml","startCol":177,"startLine":1},"type":"Base"},"binding":{"context":["probe.sysml@176:65"],"subject":{"id":"probe.sysml@176:65","origin":{"file":"probe.sysml","length":65,"nameLength":4,"nameOffset":181,"offset":176},"qualifiedName":"P::sat1"},"symbol":"n0"},"declaration":"part sat1:Base { connection bridge connect a.output to b.input; }"},{"symbol":"n1","value":{"id":"n1","kind":"part","name":"P::sat2","origin":{"endCol":258,"endLine":1,"file":"probe.sysml","startCol":243,"startLine":1},"type":"Base"},"binding":{"context":["probe.sysml@242:15"],"subject":{"id":"probe.sysml@242:15","origin":{"file":"probe.sysml","length":15,"nameLength":4,"nameOffset":247,"offset":242},"qualifiedName":"P::sat2"},"symbol":"n1"},"declaration":"part sat2:Base;"}]`}, + // cycle: exact source declarations, parents, types, ports, contexts and legacy ordinals. + {`cycle`, `package P {part def Loop { part child:Loop; } part sat1:Loop; part sat2:Loop;} package V {private import StandardViewDefinitions::*; view v:InterconnectionView {expose P::sat1; expose P::sat2;} }`, `[{"symbol":"n0","value":{"id":"n0","kind":"part","name":"P::sat1","origin":{"endCol":62,"endLine":1,"file":"probe.sysml","startCol":47,"startLine":1},"type":"Loop"},"binding":{"context":["probe.sysml@46:15"],"subject":{"id":"probe.sysml@46:15","origin":{"file":"probe.sysml","length":15,"nameLength":4,"nameOffset":51,"offset":46},"qualifiedName":"P::sat1"},"symbol":"n0"},"declaration":"part sat1:Loop;"},{"symbol":"n2","value":{"detail":"recursive through Loop; inherited members not shown","id":"n2","inherited":true,"kind":"part","name":"child","origin":{"endCol":44,"endLine":1,"file":"probe.sysml","startCol":28,"startLine":1},"parent":"n0","type":"Loop"},"binding":{"context":["probe.sysml@46:15","probe.sysml@27:16"],"subject":{"id":"probe.sysml@27:16","origin":{"file":"probe.sysml","length":16,"nameLength":5,"nameOffset":32,"offset":27},"qualifiedName":"P::Loop::child"},"symbol":"n2"},"declaration":"part child:Loop;"},{"symbol":"n1","value":{"id":"n1","kind":"part","name":"P::sat2","origin":{"endCol":78,"endLine":1,"file":"probe.sysml","startCol":63,"startLine":1},"type":"Loop"},"binding":{"context":["probe.sysml@62:15"],"subject":{"id":"probe.sysml@62:15","origin":{"file":"probe.sysml","length":15,"nameLength":4,"nameOffset":67,"offset":62},"qualifiedName":"P::sat2"},"symbol":"n1"},"declaration":"part sat2:Loop;"},{"symbol":"n3","value":{"detail":"recursive through Loop; inherited members not shown","id":"n3","inherited":true,"kind":"part","name":"child","origin":{"endCol":44,"endLine":1,"file":"probe.sysml","startCol":28,"startLine":1},"parent":"n1","type":"Loop"},"binding":{"context":["probe.sysml@62:15","probe.sysml@27:16"],"subject":{"id":"probe.sysml@27:16","origin":{"file":"probe.sysml","length":16,"nameLength":5,"nameOffset":32,"offset":27},"qualifiedName":"P::Loop::child"},"symbol":"n3"},"declaration":"part child:Loop;"}]`, `[{"id":"probe.sysml@46:15","origin":{"file":"probe.sysml","length":15,"nameLength":4,"nameOffset":51,"offset":46},"qualifiedName":"P::sat1"},{"id":"probe.sysml@62:15","origin":{"file":"probe.sysml","length":15,"nameLength":4,"nameOffset":67,"offset":62},"qualifiedName":"P::sat2"}]`, `{"artifact":{"edges":[],"kind":"interconnection","nodes":[{"id":"n0","kind":"part","name":"P::sat1","origin":{"endCol":62,"endLine":1,"file":"probe.sysml","startCol":47,"startLine":1},"type":"Loop"}],"shown":["parameter-symbols","port-symbols","compartments","edge-labels","keywords","multiplicities","typings","qualifiers"],"stated":"Read-only exploration"},"correspondence":[{"context":["probe.sysml@46:15"],"subject":{"id":"probe.sysml@46:15","origin":{"file":"probe.sysml","length":15,"nameLength":4,"nameOffset":51,"offset":46},"qualifiedName":"P::sat1"},"symbol":"n0"}],"diagnostics":[],"modelHash":"a5dccc5267a4261a97b9ee53666ae135c269da702be28b0e3a9f3dc8df9aa063","renderStatus":"available","request":{"representation":"interconnection","scope":{"kind":"element","subject":"P::sat1"}},"schemaVersion":1,"selected":[{"id":"probe.sysml@46:15","origin":{"file":"probe.sysml","length":15,"nameLength":4,"nameOffset":51,"offset":46},"qualifiedName":"P::sat1"}],"status":"complete"}`, `[{"symbol":"n0","value":{"id":"n0","kind":"part","name":"P::sat1","origin":{"endCol":62,"endLine":1,"file":"probe.sysml","startCol":47,"startLine":1},"type":"Loop"},"binding":{"context":["probe.sysml@46:15"],"subject":{"id":"probe.sysml@46:15","origin":{"file":"probe.sysml","length":15,"nameLength":4,"nameOffset":51,"offset":46},"qualifiedName":"P::sat1"},"symbol":"n0"},"declaration":"part sat1:Loop;"},{"symbol":"n1","value":{"id":"n1","kind":"part","name":"P::sat2","origin":{"endCol":78,"endLine":1,"file":"probe.sysml","startCol":63,"startLine":1},"type":"Loop"},"binding":{"context":["probe.sysml@62:15"],"subject":{"id":"probe.sysml@62:15","origin":{"file":"probe.sysml","length":15,"nameLength":4,"nameOffset":67,"offset":62},"qualifiedName":"P::sat2"},"symbol":"n1"},"declaration":"part sat2:Loop;"}]`}, + // declared-boundary: exact source declarations, parents, types, ports, contexts and legacy ordinals. + {`declared-boundary`, `package P { + port def Signal; + part def Sender { out port p : Signal; } + part def Receiver { in port p : Signal; } + part def Subsystem { + part sender : Sender; part receiver : Receiver; + connection internal connect sender.p to receiver.p; + } + part def System { + part a : Subsystem; part b : Subsystem; + connection bridge connect a.sender.p to b.receiver.p; + } + view v : StandardViewDefinitions::InterconnectionView { expose System; } +}`, `[{"symbol":"n0","value":{"id":"n0","kind":"part def","name":"P::System","origin":{"endCol":3,"endLine":12,"file":"probe.sysml","startCol":2,"startLine":9}},"binding":{"context":["probe.sysml@245:118"],"subject":{"id":"probe.sysml@245:118","origin":{"file":"probe.sysml","length":118,"nameLength":6,"nameOffset":254,"offset":245},"qualifiedName":"P::System"},"symbol":"n0"},"declaration":"part def System {\n part a : Subsystem; part b : Subsystem;\n connection bridge connect a.sender.p to b.receiver.p;\n }"},{"symbol":"n1","value":{"id":"n1","kind":"part","name":"a","origin":{"endCol":22,"endLine":10,"file":"probe.sysml","startCol":3,"startLine":10},"parent":"n0","type":"Subsystem"},"binding":{"context":["probe.sysml@245:118","probe.sysml@265:19"],"subject":{"id":"probe.sysml@265:19","origin":{"file":"probe.sysml","length":19,"nameLength":1,"nameOffset":270,"offset":265},"qualifiedName":"P::System::a"},"symbol":"n1"},"declaration":"part a : Subsystem;"},{"symbol":"n3","value":{"id":"n3","inherited":true,"kind":"part","name":"sender","origin":{"endCol":24,"endLine":6,"file":"probe.sysml","startCol":3,"startLine":6},"parent":"n1","ports":[{"direction":"out","id":"n3.0","inherited":true,"name":"p","type":"Signal"}],"type":"Sender"},"binding":{"context":["probe.sysml@245:118","probe.sysml@265:19","probe.sysml@139:21"],"subject":{"id":"probe.sysml@139:21","origin":{"file":"probe.sysml","length":21,"nameLength":6,"nameOffset":144,"offset":139},"qualifiedName":"P::Subsystem::sender"},"symbol":"n3"},"declaration":"part sender : Sender;"},{"symbol":"n3.0","value":{"direction":"out","id":"n3.0","inherited":true,"name":"p","type":"Signal"},"binding":{"context":["probe.sysml@245:118","probe.sysml@265:19","probe.sysml@139:21","probe.sysml@49:20"],"subject":{"id":"probe.sysml@49:20","origin":{"file":"probe.sysml","length":20,"nameLength":1,"nameOffset":58,"offset":49},"qualifiedName":"P::Sender::p"},"symbol":"n3.0"},"declaration":"out port p : Signal;"},{"symbol":"n4","value":{"id":"n4","inherited":true,"kind":"part","name":"receiver","origin":{"endCol":50,"endLine":6,"file":"probe.sysml","startCol":25,"startLine":6},"parent":"n1","ports":[{"direction":"in","id":"n4.0","inherited":true,"name":"p","type":"Signal"}],"type":"Receiver"},"binding":{"context":["probe.sysml@245:118","probe.sysml@265:19","probe.sysml@161:25"],"subject":{"id":"probe.sysml@161:25","origin":{"file":"probe.sysml","length":25,"nameLength":8,"nameOffset":166,"offset":161},"qualifiedName":"P::Subsystem::receiver"},"symbol":"n4"},"declaration":"part receiver : Receiver;"},{"symbol":"n4.0","value":{"direction":"in","id":"n4.0","inherited":true,"name":"p","type":"Signal"},"binding":{"context":["probe.sysml@245:118","probe.sysml@265:19","probe.sysml@161:25","probe.sysml@93:19"],"subject":{"id":"probe.sysml@93:19","origin":{"file":"probe.sysml","length":19,"nameLength":1,"nameOffset":101,"offset":93},"qualifiedName":"P::Receiver::p"},"symbol":"n4.0"},"declaration":"in port p : Signal;"},{"symbol":"n2","value":{"id":"n2","kind":"part","name":"b","origin":{"endCol":42,"endLine":10,"file":"probe.sysml","startCol":23,"startLine":10},"parent":"n0","type":"Subsystem"},"binding":{"context":["probe.sysml@245:118","probe.sysml@285:19"],"subject":{"id":"probe.sysml@285:19","origin":{"file":"probe.sysml","length":19,"nameLength":1,"nameOffset":290,"offset":285},"qualifiedName":"P::System::b"},"symbol":"n2"},"declaration":"part b : Subsystem;"},{"symbol":"n5","value":{"id":"n5","inherited":true,"kind":"part","name":"sender","origin":{"endCol":24,"endLine":6,"file":"probe.sysml","startCol":3,"startLine":6},"parent":"n2","ports":[{"direction":"out","id":"n5.0","inherited":true,"name":"p","type":"Signal"}],"type":"Sender"},"binding":{"context":["probe.sysml@245:118","probe.sysml@285:19","probe.sysml@139:21"],"subject":{"id":"probe.sysml@139:21","origin":{"file":"probe.sysml","length":21,"nameLength":6,"nameOffset":144,"offset":139},"qualifiedName":"P::Subsystem::sender"},"symbol":"n5"},"declaration":"part sender : Sender;"},{"symbol":"n5.0","value":{"direction":"out","id":"n5.0","inherited":true,"name":"p","type":"Signal"},"binding":{"context":["probe.sysml@245:118","probe.sysml@285:19","probe.sysml@139:21","probe.sysml@49:20"],"subject":{"id":"probe.sysml@49:20","origin":{"file":"probe.sysml","length":20,"nameLength":1,"nameOffset":58,"offset":49},"qualifiedName":"P::Sender::p"},"symbol":"n5.0"},"declaration":"out port p : Signal;"},{"symbol":"n6","value":{"id":"n6","inherited":true,"kind":"part","name":"receiver","origin":{"endCol":50,"endLine":6,"file":"probe.sysml","startCol":25,"startLine":6},"parent":"n2","ports":[{"direction":"in","id":"n6.0","inherited":true,"name":"p","type":"Signal"}],"type":"Receiver"},"binding":{"context":["probe.sysml@245:118","probe.sysml@285:19","probe.sysml@161:25"],"subject":{"id":"probe.sysml@161:25","origin":{"file":"probe.sysml","length":25,"nameLength":8,"nameOffset":166,"offset":161},"qualifiedName":"P::Subsystem::receiver"},"symbol":"n6"},"declaration":"part receiver : Receiver;"},{"symbol":"n6.0","value":{"direction":"in","id":"n6.0","inherited":true,"name":"p","type":"Signal"},"binding":{"context":["probe.sysml@245:118","probe.sysml@285:19","probe.sysml@161:25","probe.sysml@93:19"],"subject":{"id":"probe.sysml@93:19","origin":{"file":"probe.sysml","length":19,"nameLength":1,"nameOffset":101,"offset":93},"qualifiedName":"P::Receiver::p"},"symbol":"n6.0"},"declaration":"in port p : Signal;"},{"symbol":"edge:0","value":{"from":"n3","fromPort":"n3.0","kind":"connection","label":"bridge","origin":{"endCol":56,"endLine":11,"file":"probe.sysml","startCol":3,"startLine":11},"to":"n6","toPort":"n6.0"},"binding":{"context":[],"subject":{"id":"probe.sysml@307:53","origin":{"file":"probe.sysml","length":53,"nameLength":6,"nameOffset":318,"offset":307},"qualifiedName":"P::System::bridge"},"symbol":"edge:0"},"declaration":"connection bridge connect a.sender.p to b.receiver.p;"}]`, `[{"id":"probe.sysml@245:118","origin":{"file":"probe.sysml","length":118,"nameLength":6,"nameOffset":254,"offset":245},"qualifiedName":"P::System"}]`, `{"artifact":{"edges":[{"from":"n1","kind":"connection","label":"bridge","origin":{"endCol":56,"endLine":11,"file":"probe.sysml","startCol":3,"startLine":11},"to":"n2"}],"kind":"interconnection","nodes":[{"id":"n0","kind":"part def","name":"P::System","origin":{"endCol":3,"endLine":12,"file":"probe.sysml","startCol":2,"startLine":9}},{"id":"n1","kind":"part","name":"a","origin":{"endCol":22,"endLine":10,"file":"probe.sysml","startCol":3,"startLine":10},"parent":"n0","type":"Subsystem"},{"id":"n2","kind":"part","name":"b","origin":{"endCol":42,"endLine":10,"file":"probe.sysml","startCol":23,"startLine":10},"parent":"n0","type":"Subsystem"}],"shown":["parameter-symbols","port-symbols","compartments","edge-labels","keywords","multiplicities","typings","qualifiers"],"stated":"Read-only exploration"},"correspondence":[{"context":["probe.sysml@245:118"],"subject":{"id":"probe.sysml@245:118","origin":{"file":"probe.sysml","length":118,"nameLength":6,"nameOffset":254,"offset":245},"qualifiedName":"P::System"},"symbol":"n0"},{"context":["probe.sysml@245:118","probe.sysml@265:19"],"subject":{"id":"probe.sysml@265:19","origin":{"file":"probe.sysml","length":19,"nameLength":1,"nameOffset":270,"offset":265},"qualifiedName":"P::System::a"},"symbol":"n1"},{"context":["probe.sysml@245:118","probe.sysml@285:19"],"subject":{"id":"probe.sysml@285:19","origin":{"file":"probe.sysml","length":19,"nameLength":1,"nameOffset":290,"offset":285},"qualifiedName":"P::System::b"},"symbol":"n2"},{"context":[],"subject":{"id":"probe.sysml@307:53","origin":{"file":"probe.sysml","length":53,"nameLength":6,"nameOffset":318,"offset":307},"qualifiedName":"P::System::bridge"},"symbol":"edge:0"}],"diagnostics":[],"modelHash":"a13398d5839e8f37e8a56e45de0ce4ca105eb61c7e922aeda9ca4da2a0957d61","renderStatus":"available","request":{"representation":"interconnection","scope":{"kind":"element","subject":"P::System"}},"schemaVersion":1,"selected":[{"id":"probe.sysml@245:118","origin":{"file":"probe.sysml","length":118,"nameLength":6,"nameOffset":254,"offset":245},"qualifiedName":"P::System"}],"status":"complete"}`, `[{"symbol":"n0","value":{"id":"n0","kind":"part def","name":"P::System","origin":{"endCol":3,"endLine":12,"file":"probe.sysml","startCol":2,"startLine":9}},"binding":{"context":["probe.sysml@245:118"],"subject":{"id":"probe.sysml@245:118","origin":{"file":"probe.sysml","length":118,"nameLength":6,"nameOffset":254,"offset":245},"qualifiedName":"P::System"},"symbol":"n0"},"declaration":"part def System {\n part a : Subsystem; part b : Subsystem;\n connection bridge connect a.sender.p to b.receiver.p;\n }"},{"symbol":"n1","value":{"id":"n1","kind":"part","name":"a","origin":{"endCol":22,"endLine":10,"file":"probe.sysml","startCol":3,"startLine":10},"parent":"n0","type":"Subsystem"},"binding":{"context":["probe.sysml@245:118","probe.sysml@265:19"],"subject":{"id":"probe.sysml@265:19","origin":{"file":"probe.sysml","length":19,"nameLength":1,"nameOffset":270,"offset":265},"qualifiedName":"P::System::a"},"symbol":"n1"},"declaration":"part a : Subsystem;"},{"symbol":"n2","value":{"id":"n2","kind":"part","name":"b","origin":{"endCol":42,"endLine":10,"file":"probe.sysml","startCol":23,"startLine":10},"parent":"n0","type":"Subsystem"},"binding":{"context":["probe.sysml@245:118","probe.sysml@285:19"],"subject":{"id":"probe.sysml@285:19","origin":{"file":"probe.sysml","length":19,"nameLength":1,"nameOffset":290,"offset":285},"qualifiedName":"P::System::b"},"symbol":"n2"},"declaration":"part b : Subsystem;"}]`}, + // depth: exact source declarations, parents, types, ports, contexts and legacy ordinals. + {`depth`, `package P {port def Pin; part def Leaf { in port input:Pin; out port output:Pin; } part def Mid { part leaf:Leaf; } part def Base { part a:Mid; part b:Mid; } part sat1:Base { connection bridge connect a.leaf.output to b.leaf.input; } part sat2:Base;} package V {private import StandardViewDefinitions::*; view v:InterconnectionView {attribute depth = 1;expose P::sat1; expose P::sat2;} }`, `[{"symbol":"n0","value":{"id":"n0","kind":"part","name":"P::sat1","origin":{"endCol":234,"endLine":1,"file":"probe.sysml","startCol":159,"startLine":1},"type":"Base"},"binding":{"context":["probe.sysml@158:75"],"subject":{"id":"probe.sysml@158:75","origin":{"file":"probe.sysml","length":75,"nameLength":4,"nameOffset":163,"offset":158},"qualifiedName":"P::sat1"},"symbol":"n0"},"declaration":"part sat1:Base { connection bridge connect a.leaf.output to b.leaf.input; }"},{"symbol":"n2","value":{"detail":"nested deeper than 1 level; not shown","id":"n2","inherited":true,"kind":"part","name":"a","origin":{"endCol":144,"endLine":1,"file":"probe.sysml","startCol":133,"startLine":1},"parent":"n0","type":"Mid"},"binding":{"context":["probe.sysml@158:75","probe.sysml@132:11"],"subject":{"id":"probe.sysml@132:11","origin":{"file":"probe.sysml","length":11,"nameLength":1,"nameOffset":137,"offset":132},"qualifiedName":"P::Base::a"},"symbol":"n2"},"declaration":"part a:Mid;"},{"symbol":"n3","value":{"detail":"nested deeper than 1 level; not shown","id":"n3","inherited":true,"kind":"part","name":"b","origin":{"endCol":156,"endLine":1,"file":"probe.sysml","startCol":145,"startLine":1},"parent":"n0","type":"Mid"},"binding":{"context":["probe.sysml@158:75","probe.sysml@144:11"],"subject":{"id":"probe.sysml@144:11","origin":{"file":"probe.sysml","length":11,"nameLength":1,"nameOffset":149,"offset":144},"qualifiedName":"P::Base::b"},"symbol":"n3"},"declaration":"part b:Mid;"},{"symbol":"n1","value":{"id":"n1","kind":"part","name":"P::sat2","origin":{"endCol":250,"endLine":1,"file":"probe.sysml","startCol":235,"startLine":1},"type":"Base"},"binding":{"context":["probe.sysml@234:15"],"subject":{"id":"probe.sysml@234:15","origin":{"file":"probe.sysml","length":15,"nameLength":4,"nameOffset":239,"offset":234},"qualifiedName":"P::sat2"},"symbol":"n1"},"declaration":"part sat2:Base;"},{"symbol":"n4","value":{"detail":"nested deeper than 1 level; not shown","id":"n4","inherited":true,"kind":"part","name":"a","origin":{"endCol":144,"endLine":1,"file":"probe.sysml","startCol":133,"startLine":1},"parent":"n1","type":"Mid"},"binding":{"context":["probe.sysml@234:15","probe.sysml@132:11"],"subject":{"id":"probe.sysml@132:11","origin":{"file":"probe.sysml","length":11,"nameLength":1,"nameOffset":137,"offset":132},"qualifiedName":"P::Base::a"},"symbol":"n4"},"declaration":"part a:Mid;"},{"symbol":"n5","value":{"detail":"nested deeper than 1 level; not shown","id":"n5","inherited":true,"kind":"part","name":"b","origin":{"endCol":156,"endLine":1,"file":"probe.sysml","startCol":145,"startLine":1},"parent":"n1","type":"Mid"},"binding":{"context":["probe.sysml@234:15","probe.sysml@144:11"],"subject":{"id":"probe.sysml@144:11","origin":{"file":"probe.sysml","length":11,"nameLength":1,"nameOffset":149,"offset":144},"qualifiedName":"P::Base::b"},"symbol":"n5"},"declaration":"part b:Mid;"},{"symbol":"edge:0","value":{"from":"n2","kind":"connection","label":"bridge","origin":{"endCol":232,"endLine":1,"file":"probe.sysml","startCol":176,"startLine":1},"to":"n3"},"binding":{"context":[],"subject":{"id":"probe.sysml@175:56","origin":{"file":"probe.sysml","length":56,"nameLength":6,"nameOffset":186,"offset":175},"qualifiedName":"P::sat1::bridge"},"symbol":"edge:0"},"declaration":"connection bridge connect a.leaf.output to b.leaf.input;"}]`, `[{"id":"probe.sysml@158:75","origin":{"file":"probe.sysml","length":75,"nameLength":4,"nameOffset":163,"offset":158},"qualifiedName":"P::sat1"},{"id":"probe.sysml@234:15","origin":{"file":"probe.sysml","length":15,"nameLength":4,"nameOffset":239,"offset":234},"qualifiedName":"P::sat2"}]`, `{"artifact":{"edges":[{"from":"n0","kind":"connection","label":"bridge","origin":{"endCol":232,"endLine":1,"file":"probe.sysml","startCol":176,"startLine":1},"to":"n0"}],"kind":"interconnection","nodes":[{"id":"n0","kind":"part","name":"P::sat1","origin":{"endCol":234,"endLine":1,"file":"probe.sysml","startCol":159,"startLine":1},"type":"Base"}],"shown":["parameter-symbols","port-symbols","compartments","edge-labels","keywords","multiplicities","typings","qualifiers"],"stated":"Read-only exploration"},"correspondence":[{"context":["probe.sysml@158:75"],"subject":{"id":"probe.sysml@158:75","origin":{"file":"probe.sysml","length":75,"nameLength":4,"nameOffset":163,"offset":158},"qualifiedName":"P::sat1"},"symbol":"n0"},{"context":[],"subject":{"id":"probe.sysml@175:56","origin":{"file":"probe.sysml","length":56,"nameLength":6,"nameOffset":186,"offset":175},"qualifiedName":"P::sat1::bridge"},"symbol":"edge:0"}],"diagnostics":[],"modelHash":"042a1c430b8176a32e0bb2d6ee4b0f871b88b36adce6f02309e9b98963b51998","renderStatus":"available","request":{"representation":"interconnection","scope":{"kind":"element","subject":"P::sat1"}},"schemaVersion":1,"selected":[{"id":"probe.sysml@158:75","origin":{"file":"probe.sysml","length":75,"nameLength":4,"nameOffset":163,"offset":158},"qualifiedName":"P::sat1"}],"status":"complete"}`, `[{"symbol":"n0","value":{"id":"n0","kind":"part","name":"P::sat1","origin":{"endCol":234,"endLine":1,"file":"probe.sysml","startCol":159,"startLine":1},"type":"Base"},"binding":{"context":["probe.sysml@158:75"],"subject":{"id":"probe.sysml@158:75","origin":{"file":"probe.sysml","length":75,"nameLength":4,"nameOffset":163,"offset":158},"qualifiedName":"P::sat1"},"symbol":"n0"},"declaration":"part sat1:Base { connection bridge connect a.leaf.output to b.leaf.input; }"},{"symbol":"n1","value":{"id":"n1","kind":"part","name":"P::sat2","origin":{"endCol":250,"endLine":1,"file":"probe.sysml","startCol":235,"startLine":1},"type":"Base"},"binding":{"context":["probe.sysml@234:15"],"subject":{"id":"probe.sysml@234:15","origin":{"file":"probe.sysml","length":15,"nameLength":4,"nameOffset":239,"offset":234},"qualifiedName":"P::sat2"},"symbol":"n1"},"declaration":"part sat2:Base;"}]`}, + // exclusions: exact source declarations, parents, types, ports, contexts and legacy ordinals. + {`exclusions`, `package P {private import ScalarValues::*; part def Leaf; constraint def Rule; requirement def Need; part def Base { attribute x:Real; constraint r:Rule; requirement req:Need { subject s:Leaf; } part a:Leaf; } part sat1:Base; part sat2:Base;} package V {private import StandardViewDefinitions::*; view v:InterconnectionView {expose P::sat1; expose P::sat2;} }`, `[{"symbol":"n0","value":{"attributeDeclarations":["attribute x:Real;"],"id":"n0","kind":"part","name":"P::sat1","origin":{"endCol":226,"endLine":1,"file":"probe.sysml","startCol":211,"startLine":1},"type":"Base"},"binding":{"context":["probe.sysml@210:15"],"subject":{"id":"probe.sysml@210:15","origin":{"file":"probe.sysml","length":15,"nameLength":4,"nameOffset":215,"offset":210},"qualifiedName":"P::sat1"},"symbol":"n0"},"declaration":"part sat1:Base;"},{"symbol":"n2","value":{"id":"n2","inherited":true,"kind":"requirement","name":"req","origin":{"endCol":195,"endLine":1,"file":"probe.sysml","startCol":155,"startLine":1},"parent":"n0","type":"Need"},"binding":{"context":["probe.sysml@210:15","probe.sysml@154:40"],"subject":{"id":"probe.sysml@154:40","origin":{"file":"probe.sysml","length":40,"nameLength":3,"nameOffset":166,"offset":154},"qualifiedName":"P::Base::req"},"symbol":"n2"},"declaration":"requirement req:Need { subject s:Leaf; }"},{"symbol":"n3","value":{"id":"n3","kind":"subject","name":"s","origin":{"endCol":193,"endLine":1,"file":"probe.sysml","startCol":178,"startLine":1},"parent":"n2","type":"Leaf"},"binding":{"context":["probe.sysml@210:15","probe.sysml@154:40","probe.sysml@177:15"],"subject":{"id":"probe.sysml@177:15","origin":{"file":"probe.sysml","length":15,"nameLength":1,"nameOffset":185,"offset":177},"qualifiedName":"P::Base::req::s"},"symbol":"n3"},"declaration":"subject s:Leaf;"},{"symbol":"n4","value":{"id":"n4","inherited":true,"kind":"part","name":"a","origin":{"endCol":208,"endLine":1,"file":"probe.sysml","startCol":196,"startLine":1},"parent":"n0","type":"Leaf"},"binding":{"context":["probe.sysml@210:15","probe.sysml@195:12"],"subject":{"id":"probe.sysml@195:12","origin":{"file":"probe.sysml","length":12,"nameLength":1,"nameOffset":200,"offset":195},"qualifiedName":"P::Base::a"},"symbol":"n4"},"declaration":"part a:Leaf;"},{"symbol":"n1","value":{"attributeDeclarations":["attribute x:Real;"],"id":"n1","kind":"part","name":"P::sat2","origin":{"endCol":242,"endLine":1,"file":"probe.sysml","startCol":227,"startLine":1},"type":"Base"},"binding":{"context":["probe.sysml@226:15"],"subject":{"id":"probe.sysml@226:15","origin":{"file":"probe.sysml","length":15,"nameLength":4,"nameOffset":231,"offset":226},"qualifiedName":"P::sat2"},"symbol":"n1"},"declaration":"part sat2:Base;"},{"symbol":"n5","value":{"id":"n5","inherited":true,"kind":"requirement","name":"req","origin":{"endCol":195,"endLine":1,"file":"probe.sysml","startCol":155,"startLine":1},"parent":"n1","type":"Need"},"binding":{"context":["probe.sysml@226:15","probe.sysml@154:40"],"subject":{"id":"probe.sysml@154:40","origin":{"file":"probe.sysml","length":40,"nameLength":3,"nameOffset":166,"offset":154},"qualifiedName":"P::Base::req"},"symbol":"n5"},"declaration":"requirement req:Need { subject s:Leaf; }"},{"symbol":"n6","value":{"id":"n6","kind":"subject","name":"s","origin":{"endCol":193,"endLine":1,"file":"probe.sysml","startCol":178,"startLine":1},"parent":"n5","type":"Leaf"},"binding":{"context":["probe.sysml@226:15","probe.sysml@154:40","probe.sysml@177:15"],"subject":{"id":"probe.sysml@177:15","origin":{"file":"probe.sysml","length":15,"nameLength":1,"nameOffset":185,"offset":177},"qualifiedName":"P::Base::req::s"},"symbol":"n6"},"declaration":"subject s:Leaf;"},{"symbol":"n7","value":{"id":"n7","inherited":true,"kind":"part","name":"a","origin":{"endCol":208,"endLine":1,"file":"probe.sysml","startCol":196,"startLine":1},"parent":"n1","type":"Leaf"},"binding":{"context":["probe.sysml@226:15","probe.sysml@195:12"],"subject":{"id":"probe.sysml@195:12","origin":{"file":"probe.sysml","length":12,"nameLength":1,"nameOffset":200,"offset":195},"qualifiedName":"P::Base::a"},"symbol":"n7"},"declaration":"part a:Leaf;"}]`, `[{"id":"probe.sysml@210:15","origin":{"file":"probe.sysml","length":15,"nameLength":4,"nameOffset":215,"offset":210},"qualifiedName":"P::sat1"},{"id":"probe.sysml@226:15","origin":{"file":"probe.sysml","length":15,"nameLength":4,"nameOffset":231,"offset":226},"qualifiedName":"P::sat2"}]`, `{"artifact":{"edges":[],"kind":"interconnection","nodes":[{"attributeDeclarations":["attribute x:Real;"],"id":"n0","kind":"part","name":"P::sat1","origin":{"endCol":226,"endLine":1,"file":"probe.sysml","startCol":211,"startLine":1},"type":"Base"}],"shown":["parameter-symbols","port-symbols","compartments","edge-labels","keywords","multiplicities","typings","qualifiers"],"stated":"Read-only exploration"},"correspondence":[{"context":["probe.sysml@210:15"],"subject":{"id":"probe.sysml@210:15","origin":{"file":"probe.sysml","length":15,"nameLength":4,"nameOffset":215,"offset":210},"qualifiedName":"P::sat1"},"symbol":"n0"}],"diagnostics":[],"modelHash":"e2e7695eca10dcd9a44fb7bce2d2dfd3e38863c602b0397c956a3a917489a0a0","renderStatus":"available","request":{"representation":"interconnection","scope":{"kind":"element","subject":"P::sat1"}},"schemaVersion":1,"selected":[{"id":"probe.sysml@210:15","origin":{"file":"probe.sysml","length":15,"nameLength":4,"nameOffset":215,"offset":210},"qualifiedName":"P::sat1"}],"status":"complete"}`, `[{"symbol":"n0","value":{"attributeDeclarations":["attribute x:Real;"],"id":"n0","kind":"part","name":"P::sat1","origin":{"endCol":226,"endLine":1,"file":"probe.sysml","startCol":211,"startLine":1},"type":"Base"},"binding":{"context":["probe.sysml@210:15"],"subject":{"id":"probe.sysml@210:15","origin":{"file":"probe.sysml","length":15,"nameLength":4,"nameOffset":215,"offset":210},"qualifiedName":"P::sat1"},"symbol":"n0"},"declaration":"part sat1:Base;"},{"symbol":"n1","value":{"attributeDeclarations":["attribute x:Real;"],"id":"n1","kind":"part","name":"P::sat2","origin":{"endCol":242,"endLine":1,"file":"probe.sysml","startCol":227,"startLine":1},"type":"Base"},"binding":{"context":["probe.sysml@226:15"],"subject":{"id":"probe.sysml@226:15","origin":{"file":"probe.sysml","length":15,"nameLength":4,"nameOffset":231,"offset":226},"qualifiedName":"P::sat2"},"symbol":"n1"},"declaration":"part sat2:Base;"}]`}, + // filter: exact source declarations, parents, types, ports, contexts and legacy ordinals. + {`filter`, `package P {port def Pin; part def Leaf { in port x:Pin; out port y:Pin; } part def Base { part a:Leaf; part b:Leaf; } part def Outer { part left:Base; part right:Base; } part sat1:Outer; part sat2:Outer;} package V {private import StandardViewDefinitions::*; view v:InterconnectionView {filter @SysML::PartUsage; expose P::sat1; expose P::sat2;} }`, `[{"symbol":"n0","value":{"id":"n0","kind":"part","name":"P::sat1","origin":{"endCol":187,"endLine":1,"file":"probe.sysml","startCol":171,"startLine":1},"type":"Outer"},"binding":{"context":["probe.sysml@170:16"],"subject":{"id":"probe.sysml@170:16","origin":{"file":"probe.sysml","length":16,"nameLength":4,"nameOffset":175,"offset":170},"qualifiedName":"P::sat1"},"symbol":"n0"},"declaration":"part sat1:Outer;"},{"symbol":"n2","value":{"id":"n2","inherited":true,"kind":"part","name":"left","origin":{"endCol":151,"endLine":1,"file":"probe.sysml","startCol":136,"startLine":1},"parent":"n0","type":"Base"},"binding":{"context":["probe.sysml@170:16","probe.sysml@135:15"],"subject":{"id":"probe.sysml@135:15","origin":{"file":"probe.sysml","length":15,"nameLength":4,"nameOffset":140,"offset":135},"qualifiedName":"P::Outer::left"},"symbol":"n2"},"declaration":"part left:Base;"},{"symbol":"n3","value":{"id":"n3","inherited":true,"kind":"part","name":"a","origin":{"endCol":103,"endLine":1,"file":"probe.sysml","startCol":91,"startLine":1},"parent":"n2","type":"Leaf"},"binding":{"context":["probe.sysml@170:16","probe.sysml@135:15","probe.sysml@90:12"],"subject":{"id":"probe.sysml@90:12","origin":{"file":"probe.sysml","length":12,"nameLength":1,"nameOffset":95,"offset":90},"qualifiedName":"P::Base::a"},"symbol":"n3"},"declaration":"part a:Leaf;"},{"symbol":"n4","value":{"id":"n4","inherited":true,"kind":"part","name":"b","origin":{"endCol":116,"endLine":1,"file":"probe.sysml","startCol":104,"startLine":1},"parent":"n2","type":"Leaf"},"binding":{"context":["probe.sysml@170:16","probe.sysml@135:15","probe.sysml@103:12"],"subject":{"id":"probe.sysml@103:12","origin":{"file":"probe.sysml","length":12,"nameLength":1,"nameOffset":108,"offset":103},"qualifiedName":"P::Base::b"},"symbol":"n4"},"declaration":"part b:Leaf;"},{"symbol":"n5","value":{"id":"n5","inherited":true,"kind":"part","name":"right","origin":{"endCol":168,"endLine":1,"file":"probe.sysml","startCol":152,"startLine":1},"parent":"n0","type":"Base"},"binding":{"context":["probe.sysml@170:16","probe.sysml@151:16"],"subject":{"id":"probe.sysml@151:16","origin":{"file":"probe.sysml","length":16,"nameLength":5,"nameOffset":156,"offset":151},"qualifiedName":"P::Outer::right"},"symbol":"n5"},"declaration":"part right:Base;"},{"symbol":"n6","value":{"id":"n6","inherited":true,"kind":"part","name":"a","origin":{"endCol":103,"endLine":1,"file":"probe.sysml","startCol":91,"startLine":1},"parent":"n5","type":"Leaf"},"binding":{"context":["probe.sysml@170:16","probe.sysml@151:16","probe.sysml@90:12"],"subject":{"id":"probe.sysml@90:12","origin":{"file":"probe.sysml","length":12,"nameLength":1,"nameOffset":95,"offset":90},"qualifiedName":"P::Base::a"},"symbol":"n6"},"declaration":"part a:Leaf;"},{"symbol":"n7","value":{"id":"n7","inherited":true,"kind":"part","name":"b","origin":{"endCol":116,"endLine":1,"file":"probe.sysml","startCol":104,"startLine":1},"parent":"n5","type":"Leaf"},"binding":{"context":["probe.sysml@170:16","probe.sysml@151:16","probe.sysml@103:12"],"subject":{"id":"probe.sysml@103:12","origin":{"file":"probe.sysml","length":12,"nameLength":1,"nameOffset":108,"offset":103},"qualifiedName":"P::Base::b"},"symbol":"n7"},"declaration":"part b:Leaf;"},{"symbol":"n1","value":{"id":"n1","kind":"part","name":"P::sat2","origin":{"endCol":204,"endLine":1,"file":"probe.sysml","startCol":188,"startLine":1},"type":"Outer"},"binding":{"context":["probe.sysml@187:16"],"subject":{"id":"probe.sysml@187:16","origin":{"file":"probe.sysml","length":16,"nameLength":4,"nameOffset":192,"offset":187},"qualifiedName":"P::sat2"},"symbol":"n1"},"declaration":"part sat2:Outer;"},{"symbol":"n8","value":{"id":"n8","inherited":true,"kind":"part","name":"left","origin":{"endCol":151,"endLine":1,"file":"probe.sysml","startCol":136,"startLine":1},"parent":"n1","type":"Base"},"binding":{"context":["probe.sysml@187:16","probe.sysml@135:15"],"subject":{"id":"probe.sysml@135:15","origin":{"file":"probe.sysml","length":15,"nameLength":4,"nameOffset":140,"offset":135},"qualifiedName":"P::Outer::left"},"symbol":"n8"},"declaration":"part left:Base;"},{"symbol":"n9","value":{"id":"n9","inherited":true,"kind":"part","name":"a","origin":{"endCol":103,"endLine":1,"file":"probe.sysml","startCol":91,"startLine":1},"parent":"n8","type":"Leaf"},"binding":{"context":["probe.sysml@187:16","probe.sysml@135:15","probe.sysml@90:12"],"subject":{"id":"probe.sysml@90:12","origin":{"file":"probe.sysml","length":12,"nameLength":1,"nameOffset":95,"offset":90},"qualifiedName":"P::Base::a"},"symbol":"n9"},"declaration":"part a:Leaf;"},{"symbol":"n10","value":{"id":"n10","inherited":true,"kind":"part","name":"b","origin":{"endCol":116,"endLine":1,"file":"probe.sysml","startCol":104,"startLine":1},"parent":"n8","type":"Leaf"},"binding":{"context":["probe.sysml@187:16","probe.sysml@135:15","probe.sysml@103:12"],"subject":{"id":"probe.sysml@103:12","origin":{"file":"probe.sysml","length":12,"nameLength":1,"nameOffset":108,"offset":103},"qualifiedName":"P::Base::b"},"symbol":"n10"},"declaration":"part b:Leaf;"},{"symbol":"n11","value":{"id":"n11","inherited":true,"kind":"part","name":"right","origin":{"endCol":168,"endLine":1,"file":"probe.sysml","startCol":152,"startLine":1},"parent":"n1","type":"Base"},"binding":{"context":["probe.sysml@187:16","probe.sysml@151:16"],"subject":{"id":"probe.sysml@151:16","origin":{"file":"probe.sysml","length":16,"nameLength":5,"nameOffset":156,"offset":151},"qualifiedName":"P::Outer::right"},"symbol":"n11"},"declaration":"part right:Base;"},{"symbol":"n12","value":{"id":"n12","inherited":true,"kind":"part","name":"a","origin":{"endCol":103,"endLine":1,"file":"probe.sysml","startCol":91,"startLine":1},"parent":"n11","type":"Leaf"},"binding":{"context":["probe.sysml@187:16","probe.sysml@151:16","probe.sysml@90:12"],"subject":{"id":"probe.sysml@90:12","origin":{"file":"probe.sysml","length":12,"nameLength":1,"nameOffset":95,"offset":90},"qualifiedName":"P::Base::a"},"symbol":"n12"},"declaration":"part a:Leaf;"},{"symbol":"n13","value":{"id":"n13","inherited":true,"kind":"part","name":"b","origin":{"endCol":116,"endLine":1,"file":"probe.sysml","startCol":104,"startLine":1},"parent":"n11","type":"Leaf"},"binding":{"context":["probe.sysml@187:16","probe.sysml@151:16","probe.sysml@103:12"],"subject":{"id":"probe.sysml@103:12","origin":{"file":"probe.sysml","length":12,"nameLength":1,"nameOffset":108,"offset":103},"qualifiedName":"P::Base::b"},"symbol":"n13"},"declaration":"part b:Leaf;"}]`, `[{"id":"probe.sysml@170:16","origin":{"file":"probe.sysml","length":16,"nameLength":4,"nameOffset":175,"offset":170},"qualifiedName":"P::sat1"},{"id":"probe.sysml@187:16","origin":{"file":"probe.sysml","length":16,"nameLength":4,"nameOffset":192,"offset":187},"qualifiedName":"P::sat2"}]`, `{"artifact":{"edges":[],"kind":"interconnection","nodes":[{"id":"n0","kind":"part","name":"P::sat1","origin":{"endCol":187,"endLine":1,"file":"probe.sysml","startCol":171,"startLine":1},"type":"Outer"}],"shown":["parameter-symbols","port-symbols","compartments","edge-labels","keywords","multiplicities","typings","qualifiers"],"stated":"Read-only exploration"},"correspondence":[{"context":["probe.sysml@170:16"],"subject":{"id":"probe.sysml@170:16","origin":{"file":"probe.sysml","length":16,"nameLength":4,"nameOffset":175,"offset":170},"qualifiedName":"P::sat1"},"symbol":"n0"}],"diagnostics":[],"modelHash":"6cf0f1acfb6437f6fdd93827d462446c03da6161d815db0892007ae483bc300f","renderStatus":"available","request":{"representation":"interconnection","scope":{"kind":"element","subject":"P::sat1"}},"schemaVersion":1,"selected":[{"id":"probe.sysml@170:16","origin":{"file":"probe.sysml","length":16,"nameLength":4,"nameOffset":175,"offset":170},"qualifiedName":"P::sat1"}],"status":"complete"}`, `[{"symbol":"n0","value":{"id":"n0","kind":"part","name":"P::sat1","origin":{"endCol":187,"endLine":1,"file":"probe.sysml","startCol":171,"startLine":1},"type":"Outer"},"binding":{"context":["probe.sysml@170:16"],"subject":{"id":"probe.sysml@170:16","origin":{"file":"probe.sysml","length":16,"nameLength":4,"nameOffset":175,"offset":170},"qualifiedName":"P::sat1"},"symbol":"n0"},"declaration":"part sat1:Outer;"},{"symbol":"n1","value":{"id":"n1","kind":"part","name":"P::sat2","origin":{"endCol":204,"endLine":1,"file":"probe.sysml","startCol":188,"startLine":1},"type":"Outer"},"binding":{"context":["probe.sysml@187:16"],"subject":{"id":"probe.sysml@187:16","origin":{"file":"probe.sysml","length":16,"nameLength":4,"nameOffset":192,"offset":187},"qualifiedName":"P::sat2"},"symbol":"n1"},"declaration":"part sat2:Outer;"}]`}, + // legacy: exact source declarations, parents, types, ports, contexts and legacy ordinals. + {`legacy`, `package P {port def Pin; part def Leaf { in port input:Pin; out port output:Pin; } part def Base { part inner:Leaf; } part sat1:Base { part tail:Leaf; } part sat2:Leaf;} package V {private import StandardViewDefinitions::*; view v:InterconnectionView {expose P::sat1; expose P::sat2;} }`, `[{"symbol":"n0","value":{"id":"n0","kind":"part","name":"P::sat1","origin":{"endCol":153,"endLine":1,"file":"probe.sysml","startCol":119,"startLine":1},"type":"Base"},"binding":{"context":["probe.sysml@118:34"],"subject":{"id":"probe.sysml@118:34","origin":{"file":"probe.sysml","length":34,"nameLength":4,"nameOffset":123,"offset":118},"qualifiedName":"P::sat1"},"symbol":"n0"},"declaration":"part sat1:Base { part tail:Leaf; }"},{"symbol":"n1","value":{"id":"n1","kind":"part","name":"tail","origin":{"endCol":151,"endLine":1,"file":"probe.sysml","startCol":136,"startLine":1},"parent":"n0","ports":[{"direction":"in","id":"n1.0","inherited":true,"name":"input","type":"Pin"},{"direction":"out","id":"n1.1","inherited":true,"name":"output","type":"Pin"}],"type":"Leaf"},"binding":{"context":["probe.sysml@118:34","probe.sysml@135:15"],"subject":{"id":"probe.sysml@135:15","origin":{"file":"probe.sysml","length":15,"nameLength":4,"nameOffset":140,"offset":135},"qualifiedName":"P::sat1::tail"},"symbol":"n1"},"declaration":"part tail:Leaf;"},{"symbol":"n1.0","value":{"direction":"in","id":"n1.0","inherited":true,"name":"input","type":"Pin"},"binding":{"context":["probe.sysml@118:34","probe.sysml@135:15","probe.sysml@41:18"],"subject":{"id":"probe.sysml@41:18","origin":{"file":"probe.sysml","length":18,"nameLength":5,"nameOffset":49,"offset":41},"qualifiedName":"P::Leaf::input"},"symbol":"n1.0"},"declaration":"in port input:Pin;"},{"symbol":"n1.1","value":{"direction":"out","id":"n1.1","inherited":true,"name":"output","type":"Pin"},"binding":{"context":["probe.sysml@118:34","probe.sysml@135:15","probe.sysml@60:20"],"subject":{"id":"probe.sysml@60:20","origin":{"file":"probe.sysml","length":20,"nameLength":6,"nameOffset":69,"offset":60},"qualifiedName":"P::Leaf::output"},"symbol":"n1.1"},"declaration":"out port output:Pin;"},{"symbol":"n3","value":{"id":"n3","inherited":true,"kind":"part","name":"inner","origin":{"endCol":116,"endLine":1,"file":"probe.sysml","startCol":100,"startLine":1},"parent":"n0","ports":[{"direction":"in","id":"n3.0","inherited":true,"name":"input","type":"Pin"},{"direction":"out","id":"n3.1","inherited":true,"name":"output","type":"Pin"}],"type":"Leaf"},"binding":{"context":["probe.sysml@118:34","probe.sysml@99:16"],"subject":{"id":"probe.sysml@99:16","origin":{"file":"probe.sysml","length":16,"nameLength":5,"nameOffset":104,"offset":99},"qualifiedName":"P::Base::inner"},"symbol":"n3"},"declaration":"part inner:Leaf;"},{"symbol":"n3.0","value":{"direction":"in","id":"n3.0","inherited":true,"name":"input","type":"Pin"},"binding":{"context":["probe.sysml@118:34","probe.sysml@99:16","probe.sysml@41:18"],"subject":{"id":"probe.sysml@41:18","origin":{"file":"probe.sysml","length":18,"nameLength":5,"nameOffset":49,"offset":41},"qualifiedName":"P::Leaf::input"},"symbol":"n3.0"},"declaration":"in port input:Pin;"},{"symbol":"n3.1","value":{"direction":"out","id":"n3.1","inherited":true,"name":"output","type":"Pin"},"binding":{"context":["probe.sysml@118:34","probe.sysml@99:16","probe.sysml@60:20"],"subject":{"id":"probe.sysml@60:20","origin":{"file":"probe.sysml","length":20,"nameLength":6,"nameOffset":69,"offset":60},"qualifiedName":"P::Leaf::output"},"symbol":"n3.1"},"declaration":"out port output:Pin;"},{"symbol":"n2","value":{"id":"n2","kind":"part","name":"P::sat2","origin":{"endCol":169,"endLine":1,"file":"probe.sysml","startCol":154,"startLine":1},"ports":[{"direction":"in","id":"n2.0","inherited":true,"name":"input","type":"Pin"},{"direction":"out","id":"n2.1","inherited":true,"name":"output","type":"Pin"}],"type":"Leaf"},"binding":{"context":["probe.sysml@153:15"],"subject":{"id":"probe.sysml@153:15","origin":{"file":"probe.sysml","length":15,"nameLength":4,"nameOffset":158,"offset":153},"qualifiedName":"P::sat2"},"symbol":"n2"},"declaration":"part sat2:Leaf;"},{"symbol":"n2.0","value":{"direction":"in","id":"n2.0","inherited":true,"name":"input","type":"Pin"},"binding":{"context":["probe.sysml@153:15","probe.sysml@41:18"],"subject":{"id":"probe.sysml@41:18","origin":{"file":"probe.sysml","length":18,"nameLength":5,"nameOffset":49,"offset":41},"qualifiedName":"P::Leaf::input"},"symbol":"n2.0"},"declaration":"in port input:Pin;"},{"symbol":"n2.1","value":{"direction":"out","id":"n2.1","inherited":true,"name":"output","type":"Pin"},"binding":{"context":["probe.sysml@153:15","probe.sysml@60:20"],"subject":{"id":"probe.sysml@60:20","origin":{"file":"probe.sysml","length":20,"nameLength":6,"nameOffset":69,"offset":60},"qualifiedName":"P::Leaf::output"},"symbol":"n2.1"},"declaration":"out port output:Pin;"}]`, `[{"id":"probe.sysml@118:34","origin":{"file":"probe.sysml","length":34,"nameLength":4,"nameOffset":123,"offset":118},"qualifiedName":"P::sat1"},{"id":"probe.sysml@153:15","origin":{"file":"probe.sysml","length":15,"nameLength":4,"nameOffset":158,"offset":153},"qualifiedName":"P::sat2"}]`, `{"artifact":{"edges":[],"kind":"interconnection","nodes":[{"id":"n0","kind":"part","name":"P::sat1","origin":{"endCol":153,"endLine":1,"file":"probe.sysml","startCol":119,"startLine":1},"type":"Base"},{"id":"n1","kind":"part","name":"tail","origin":{"endCol":151,"endLine":1,"file":"probe.sysml","startCol":136,"startLine":1},"parent":"n0","ports":[{"direction":"in","id":"n1.0","inherited":true,"name":"input","type":"Pin"},{"direction":"out","id":"n1.1","inherited":true,"name":"output","type":"Pin"}],"type":"Leaf"}],"shown":["parameter-symbols","port-symbols","compartments","edge-labels","keywords","multiplicities","typings","qualifiers"],"stated":"Read-only exploration"},"correspondence":[{"context":["probe.sysml@118:34"],"subject":{"id":"probe.sysml@118:34","origin":{"file":"probe.sysml","length":34,"nameLength":4,"nameOffset":123,"offset":118},"qualifiedName":"P::sat1"},"symbol":"n0"},{"context":["probe.sysml@118:34","probe.sysml@135:15"],"subject":{"id":"probe.sysml@135:15","origin":{"file":"probe.sysml","length":15,"nameLength":4,"nameOffset":140,"offset":135},"qualifiedName":"P::sat1::tail"},"symbol":"n1"},{"context":["probe.sysml@118:34","probe.sysml@135:15","probe.sysml@41:18"],"subject":{"id":"probe.sysml@41:18","origin":{"file":"probe.sysml","length":18,"nameLength":5,"nameOffset":49,"offset":41},"qualifiedName":"P::Leaf::input"},"symbol":"n1.0"},{"context":["probe.sysml@118:34","probe.sysml@135:15","probe.sysml@60:20"],"subject":{"id":"probe.sysml@60:20","origin":{"file":"probe.sysml","length":20,"nameLength":6,"nameOffset":69,"offset":60},"qualifiedName":"P::Leaf::output"},"symbol":"n1.1"}],"diagnostics":[],"modelHash":"610040e6fe9e0a220b804a4605fbcd8e62430973e40732dbb134ecad7301e8dd","renderStatus":"available","request":{"representation":"interconnection","scope":{"kind":"element","subject":"P::sat1"}},"schemaVersion":1,"selected":[{"id":"probe.sysml@118:34","origin":{"file":"probe.sysml","length":34,"nameLength":4,"nameOffset":123,"offset":118},"qualifiedName":"P::sat1"}],"status":"complete"}`, `[{"symbol":"n0","value":{"id":"n0","kind":"part","name":"P::sat1","origin":{"endCol":153,"endLine":1,"file":"probe.sysml","startCol":119,"startLine":1},"type":"Base"},"binding":{"context":["probe.sysml@118:34"],"subject":{"id":"probe.sysml@118:34","origin":{"file":"probe.sysml","length":34,"nameLength":4,"nameOffset":123,"offset":118},"qualifiedName":"P::sat1"},"symbol":"n0"},"declaration":"part sat1:Base { part tail:Leaf; }"},{"symbol":"n1","value":{"id":"n1","kind":"part","name":"tail","origin":{"endCol":151,"endLine":1,"file":"probe.sysml","startCol":136,"startLine":1},"parent":"n0","ports":[{"direction":"in","id":"n1.0","inherited":true,"name":"input","type":"Pin"},{"direction":"out","id":"n1.1","inherited":true,"name":"output","type":"Pin"}],"type":"Leaf"},"binding":{"context":["probe.sysml@118:34","probe.sysml@135:15"],"subject":{"id":"probe.sysml@135:15","origin":{"file":"probe.sysml","length":15,"nameLength":4,"nameOffset":140,"offset":135},"qualifiedName":"P::sat1::tail"},"symbol":"n1"},"declaration":"part tail:Leaf;"},{"symbol":"n1.0","value":{"direction":"in","id":"n1.0","inherited":true,"name":"input","type":"Pin"},"binding":{"context":["probe.sysml@118:34","probe.sysml@135:15","probe.sysml@41:18"],"subject":{"id":"probe.sysml@41:18","origin":{"file":"probe.sysml","length":18,"nameLength":5,"nameOffset":49,"offset":41},"qualifiedName":"P::Leaf::input"},"symbol":"n1.0"},"declaration":"in port input:Pin;"},{"symbol":"n1.1","value":{"direction":"out","id":"n1.1","inherited":true,"name":"output","type":"Pin"},"binding":{"context":["probe.sysml@118:34","probe.sysml@135:15","probe.sysml@60:20"],"subject":{"id":"probe.sysml@60:20","origin":{"file":"probe.sysml","length":20,"nameLength":6,"nameOffset":69,"offset":60},"qualifiedName":"P::Leaf::output"},"symbol":"n1.1"},"declaration":"out port output:Pin;"},{"symbol":"n2","value":{"id":"n2","kind":"part","name":"P::sat2","origin":{"endCol":169,"endLine":1,"file":"probe.sysml","startCol":154,"startLine":1},"ports":[{"direction":"in","id":"n2.0","inherited":true,"name":"input","type":"Pin"},{"direction":"out","id":"n2.1","inherited":true,"name":"output","type":"Pin"}],"type":"Leaf"},"binding":{"context":["probe.sysml@153:15"],"subject":{"id":"probe.sysml@153:15","origin":{"file":"probe.sysml","length":15,"nameLength":4,"nameOffset":158,"offset":153},"qualifiedName":"P::sat2"},"symbol":"n2"},"declaration":"part sat2:Leaf;"},{"symbol":"n2.0","value":{"direction":"in","id":"n2.0","inherited":true,"name":"input","type":"Pin"},"binding":{"context":["probe.sysml@153:15","probe.sysml@41:18"],"subject":{"id":"probe.sysml@41:18","origin":{"file":"probe.sysml","length":18,"nameLength":5,"nameOffset":49,"offset":41},"qualifiedName":"P::Leaf::input"},"symbol":"n2.0"},"declaration":"in port input:Pin;"},{"symbol":"n2.1","value":{"direction":"out","id":"n2.1","inherited":true,"name":"output","type":"Pin"},"binding":{"context":["probe.sysml@153:15","probe.sysml@60:20"],"subject":{"id":"probe.sysml@60:20","origin":{"file":"probe.sysml","length":20,"nameLength":6,"nameOffset":69,"offset":60},"qualifiedName":"P::Leaf::output"},"symbol":"n2.1"},"declaration":"out port output:Pin;"}]`}, + // nested: exact source declarations, parents, types, ports, contexts and legacy ordinals. + {`nested`, `package P {port def Pin; part def Leaf { in port x:Pin; out port y:Pin; } part def Base { part a:Leaf; part b:Leaf; } part def Outer { part left:Base; part right:Base; } part sat1:Outer; part sat2:Outer;} package V {private import StandardViewDefinitions::*; view v:InterconnectionView {expose P::sat1; expose P::sat2;} }`, `[{"symbol":"n0","value":{"id":"n0","kind":"part","name":"P::sat1","origin":{"endCol":187,"endLine":1,"file":"probe.sysml","startCol":171,"startLine":1},"type":"Outer"},"binding":{"context":["probe.sysml@170:16"],"subject":{"id":"probe.sysml@170:16","origin":{"file":"probe.sysml","length":16,"nameLength":4,"nameOffset":175,"offset":170},"qualifiedName":"P::sat1"},"symbol":"n0"},"declaration":"part sat1:Outer;"},{"symbol":"n2","value":{"id":"n2","inherited":true,"kind":"part","name":"left","origin":{"endCol":151,"endLine":1,"file":"probe.sysml","startCol":136,"startLine":1},"parent":"n0","type":"Base"},"binding":{"context":["probe.sysml@170:16","probe.sysml@135:15"],"subject":{"id":"probe.sysml@135:15","origin":{"file":"probe.sysml","length":15,"nameLength":4,"nameOffset":140,"offset":135},"qualifiedName":"P::Outer::left"},"symbol":"n2"},"declaration":"part left:Base;"},{"symbol":"n3","value":{"id":"n3","inherited":true,"kind":"part","name":"a","origin":{"endCol":103,"endLine":1,"file":"probe.sysml","startCol":91,"startLine":1},"parent":"n2","ports":[{"direction":"in","id":"n3.0","inherited":true,"name":"x","type":"Pin"},{"direction":"out","id":"n3.1","inherited":true,"name":"y","type":"Pin"}],"type":"Leaf"},"binding":{"context":["probe.sysml@170:16","probe.sysml@135:15","probe.sysml@90:12"],"subject":{"id":"probe.sysml@90:12","origin":{"file":"probe.sysml","length":12,"nameLength":1,"nameOffset":95,"offset":90},"qualifiedName":"P::Base::a"},"symbol":"n3"},"declaration":"part a:Leaf;"},{"symbol":"n3.0","value":{"direction":"in","id":"n3.0","inherited":true,"name":"x","type":"Pin"},"binding":{"context":["probe.sysml@170:16","probe.sysml@135:15","probe.sysml@90:12","probe.sysml@41:14"],"subject":{"id":"probe.sysml@41:14","origin":{"file":"probe.sysml","length":14,"nameLength":1,"nameOffset":49,"offset":41},"qualifiedName":"P::Leaf::x"},"symbol":"n3.0"},"declaration":"in port x:Pin;"},{"symbol":"n3.1","value":{"direction":"out","id":"n3.1","inherited":true,"name":"y","type":"Pin"},"binding":{"context":["probe.sysml@170:16","probe.sysml@135:15","probe.sysml@90:12","probe.sysml@56:15"],"subject":{"id":"probe.sysml@56:15","origin":{"file":"probe.sysml","length":15,"nameLength":1,"nameOffset":65,"offset":56},"qualifiedName":"P::Leaf::y"},"symbol":"n3.1"},"declaration":"out port y:Pin;"},{"symbol":"n4","value":{"id":"n4","inherited":true,"kind":"part","name":"b","origin":{"endCol":116,"endLine":1,"file":"probe.sysml","startCol":104,"startLine":1},"parent":"n2","ports":[{"direction":"in","id":"n4.0","inherited":true,"name":"x","type":"Pin"},{"direction":"out","id":"n4.1","inherited":true,"name":"y","type":"Pin"}],"type":"Leaf"},"binding":{"context":["probe.sysml@170:16","probe.sysml@135:15","probe.sysml@103:12"],"subject":{"id":"probe.sysml@103:12","origin":{"file":"probe.sysml","length":12,"nameLength":1,"nameOffset":108,"offset":103},"qualifiedName":"P::Base::b"},"symbol":"n4"},"declaration":"part b:Leaf;"},{"symbol":"n4.0","value":{"direction":"in","id":"n4.0","inherited":true,"name":"x","type":"Pin"},"binding":{"context":["probe.sysml@170:16","probe.sysml@135:15","probe.sysml@103:12","probe.sysml@41:14"],"subject":{"id":"probe.sysml@41:14","origin":{"file":"probe.sysml","length":14,"nameLength":1,"nameOffset":49,"offset":41},"qualifiedName":"P::Leaf::x"},"symbol":"n4.0"},"declaration":"in port x:Pin;"},{"symbol":"n4.1","value":{"direction":"out","id":"n4.1","inherited":true,"name":"y","type":"Pin"},"binding":{"context":["probe.sysml@170:16","probe.sysml@135:15","probe.sysml@103:12","probe.sysml@56:15"],"subject":{"id":"probe.sysml@56:15","origin":{"file":"probe.sysml","length":15,"nameLength":1,"nameOffset":65,"offset":56},"qualifiedName":"P::Leaf::y"},"symbol":"n4.1"},"declaration":"out port y:Pin;"},{"symbol":"n5","value":{"id":"n5","inherited":true,"kind":"part","name":"right","origin":{"endCol":168,"endLine":1,"file":"probe.sysml","startCol":152,"startLine":1},"parent":"n0","type":"Base"},"binding":{"context":["probe.sysml@170:16","probe.sysml@151:16"],"subject":{"id":"probe.sysml@151:16","origin":{"file":"probe.sysml","length":16,"nameLength":5,"nameOffset":156,"offset":151},"qualifiedName":"P::Outer::right"},"symbol":"n5"},"declaration":"part right:Base;"},{"symbol":"n6","value":{"id":"n6","inherited":true,"kind":"part","name":"a","origin":{"endCol":103,"endLine":1,"file":"probe.sysml","startCol":91,"startLine":1},"parent":"n5","ports":[{"direction":"in","id":"n6.0","inherited":true,"name":"x","type":"Pin"},{"direction":"out","id":"n6.1","inherited":true,"name":"y","type":"Pin"}],"type":"Leaf"},"binding":{"context":["probe.sysml@170:16","probe.sysml@151:16","probe.sysml@90:12"],"subject":{"id":"probe.sysml@90:12","origin":{"file":"probe.sysml","length":12,"nameLength":1,"nameOffset":95,"offset":90},"qualifiedName":"P::Base::a"},"symbol":"n6"},"declaration":"part a:Leaf;"},{"symbol":"n6.0","value":{"direction":"in","id":"n6.0","inherited":true,"name":"x","type":"Pin"},"binding":{"context":["probe.sysml@170:16","probe.sysml@151:16","probe.sysml@90:12","probe.sysml@41:14"],"subject":{"id":"probe.sysml@41:14","origin":{"file":"probe.sysml","length":14,"nameLength":1,"nameOffset":49,"offset":41},"qualifiedName":"P::Leaf::x"},"symbol":"n6.0"},"declaration":"in port x:Pin;"},{"symbol":"n6.1","value":{"direction":"out","id":"n6.1","inherited":true,"name":"y","type":"Pin"},"binding":{"context":["probe.sysml@170:16","probe.sysml@151:16","probe.sysml@90:12","probe.sysml@56:15"],"subject":{"id":"probe.sysml@56:15","origin":{"file":"probe.sysml","length":15,"nameLength":1,"nameOffset":65,"offset":56},"qualifiedName":"P::Leaf::y"},"symbol":"n6.1"},"declaration":"out port y:Pin;"},{"symbol":"n7","value":{"id":"n7","inherited":true,"kind":"part","name":"b","origin":{"endCol":116,"endLine":1,"file":"probe.sysml","startCol":104,"startLine":1},"parent":"n5","ports":[{"direction":"in","id":"n7.0","inherited":true,"name":"x","type":"Pin"},{"direction":"out","id":"n7.1","inherited":true,"name":"y","type":"Pin"}],"type":"Leaf"},"binding":{"context":["probe.sysml@170:16","probe.sysml@151:16","probe.sysml@103:12"],"subject":{"id":"probe.sysml@103:12","origin":{"file":"probe.sysml","length":12,"nameLength":1,"nameOffset":108,"offset":103},"qualifiedName":"P::Base::b"},"symbol":"n7"},"declaration":"part b:Leaf;"},{"symbol":"n7.0","value":{"direction":"in","id":"n7.0","inherited":true,"name":"x","type":"Pin"},"binding":{"context":["probe.sysml@170:16","probe.sysml@151:16","probe.sysml@103:12","probe.sysml@41:14"],"subject":{"id":"probe.sysml@41:14","origin":{"file":"probe.sysml","length":14,"nameLength":1,"nameOffset":49,"offset":41},"qualifiedName":"P::Leaf::x"},"symbol":"n7.0"},"declaration":"in port x:Pin;"},{"symbol":"n7.1","value":{"direction":"out","id":"n7.1","inherited":true,"name":"y","type":"Pin"},"binding":{"context":["probe.sysml@170:16","probe.sysml@151:16","probe.sysml@103:12","probe.sysml@56:15"],"subject":{"id":"probe.sysml@56:15","origin":{"file":"probe.sysml","length":15,"nameLength":1,"nameOffset":65,"offset":56},"qualifiedName":"P::Leaf::y"},"symbol":"n7.1"},"declaration":"out port y:Pin;"},{"symbol":"n1","value":{"id":"n1","kind":"part","name":"P::sat2","origin":{"endCol":204,"endLine":1,"file":"probe.sysml","startCol":188,"startLine":1},"type":"Outer"},"binding":{"context":["probe.sysml@187:16"],"subject":{"id":"probe.sysml@187:16","origin":{"file":"probe.sysml","length":16,"nameLength":4,"nameOffset":192,"offset":187},"qualifiedName":"P::sat2"},"symbol":"n1"},"declaration":"part sat2:Outer;"},{"symbol":"n8","value":{"id":"n8","inherited":true,"kind":"part","name":"left","origin":{"endCol":151,"endLine":1,"file":"probe.sysml","startCol":136,"startLine":1},"parent":"n1","type":"Base"},"binding":{"context":["probe.sysml@187:16","probe.sysml@135:15"],"subject":{"id":"probe.sysml@135:15","origin":{"file":"probe.sysml","length":15,"nameLength":4,"nameOffset":140,"offset":135},"qualifiedName":"P::Outer::left"},"symbol":"n8"},"declaration":"part left:Base;"},{"symbol":"n9","value":{"id":"n9","inherited":true,"kind":"part","name":"a","origin":{"endCol":103,"endLine":1,"file":"probe.sysml","startCol":91,"startLine":1},"parent":"n8","ports":[{"direction":"in","id":"n9.0","inherited":true,"name":"x","type":"Pin"},{"direction":"out","id":"n9.1","inherited":true,"name":"y","type":"Pin"}],"type":"Leaf"},"binding":{"context":["probe.sysml@187:16","probe.sysml@135:15","probe.sysml@90:12"],"subject":{"id":"probe.sysml@90:12","origin":{"file":"probe.sysml","length":12,"nameLength":1,"nameOffset":95,"offset":90},"qualifiedName":"P::Base::a"},"symbol":"n9"},"declaration":"part a:Leaf;"},{"symbol":"n9.0","value":{"direction":"in","id":"n9.0","inherited":true,"name":"x","type":"Pin"},"binding":{"context":["probe.sysml@187:16","probe.sysml@135:15","probe.sysml@90:12","probe.sysml@41:14"],"subject":{"id":"probe.sysml@41:14","origin":{"file":"probe.sysml","length":14,"nameLength":1,"nameOffset":49,"offset":41},"qualifiedName":"P::Leaf::x"},"symbol":"n9.0"},"declaration":"in port x:Pin;"},{"symbol":"n9.1","value":{"direction":"out","id":"n9.1","inherited":true,"name":"y","type":"Pin"},"binding":{"context":["probe.sysml@187:16","probe.sysml@135:15","probe.sysml@90:12","probe.sysml@56:15"],"subject":{"id":"probe.sysml@56:15","origin":{"file":"probe.sysml","length":15,"nameLength":1,"nameOffset":65,"offset":56},"qualifiedName":"P::Leaf::y"},"symbol":"n9.1"},"declaration":"out port y:Pin;"},{"symbol":"n10","value":{"id":"n10","inherited":true,"kind":"part","name":"b","origin":{"endCol":116,"endLine":1,"file":"probe.sysml","startCol":104,"startLine":1},"parent":"n8","ports":[{"direction":"in","id":"n10.0","inherited":true,"name":"x","type":"Pin"},{"direction":"out","id":"n10.1","inherited":true,"name":"y","type":"Pin"}],"type":"Leaf"},"binding":{"context":["probe.sysml@187:16","probe.sysml@135:15","probe.sysml@103:12"],"subject":{"id":"probe.sysml@103:12","origin":{"file":"probe.sysml","length":12,"nameLength":1,"nameOffset":108,"offset":103},"qualifiedName":"P::Base::b"},"symbol":"n10"},"declaration":"part b:Leaf;"},{"symbol":"n10.0","value":{"direction":"in","id":"n10.0","inherited":true,"name":"x","type":"Pin"},"binding":{"context":["probe.sysml@187:16","probe.sysml@135:15","probe.sysml@103:12","probe.sysml@41:14"],"subject":{"id":"probe.sysml@41:14","origin":{"file":"probe.sysml","length":14,"nameLength":1,"nameOffset":49,"offset":41},"qualifiedName":"P::Leaf::x"},"symbol":"n10.0"},"declaration":"in port x:Pin;"},{"symbol":"n10.1","value":{"direction":"out","id":"n10.1","inherited":true,"name":"y","type":"Pin"},"binding":{"context":["probe.sysml@187:16","probe.sysml@135:15","probe.sysml@103:12","probe.sysml@56:15"],"subject":{"id":"probe.sysml@56:15","origin":{"file":"probe.sysml","length":15,"nameLength":1,"nameOffset":65,"offset":56},"qualifiedName":"P::Leaf::y"},"symbol":"n10.1"},"declaration":"out port y:Pin;"},{"symbol":"n11","value":{"id":"n11","inherited":true,"kind":"part","name":"right","origin":{"endCol":168,"endLine":1,"file":"probe.sysml","startCol":152,"startLine":1},"parent":"n1","type":"Base"},"binding":{"context":["probe.sysml@187:16","probe.sysml@151:16"],"subject":{"id":"probe.sysml@151:16","origin":{"file":"probe.sysml","length":16,"nameLength":5,"nameOffset":156,"offset":151},"qualifiedName":"P::Outer::right"},"symbol":"n11"},"declaration":"part right:Base;"},{"symbol":"n12","value":{"id":"n12","inherited":true,"kind":"part","name":"a","origin":{"endCol":103,"endLine":1,"file":"probe.sysml","startCol":91,"startLine":1},"parent":"n11","ports":[{"direction":"in","id":"n12.0","inherited":true,"name":"x","type":"Pin"},{"direction":"out","id":"n12.1","inherited":true,"name":"y","type":"Pin"}],"type":"Leaf"},"binding":{"context":["probe.sysml@187:16","probe.sysml@151:16","probe.sysml@90:12"],"subject":{"id":"probe.sysml@90:12","origin":{"file":"probe.sysml","length":12,"nameLength":1,"nameOffset":95,"offset":90},"qualifiedName":"P::Base::a"},"symbol":"n12"},"declaration":"part a:Leaf;"},{"symbol":"n12.0","value":{"direction":"in","id":"n12.0","inherited":true,"name":"x","type":"Pin"},"binding":{"context":["probe.sysml@187:16","probe.sysml@151:16","probe.sysml@90:12","probe.sysml@41:14"],"subject":{"id":"probe.sysml@41:14","origin":{"file":"probe.sysml","length":14,"nameLength":1,"nameOffset":49,"offset":41},"qualifiedName":"P::Leaf::x"},"symbol":"n12.0"},"declaration":"in port x:Pin;"},{"symbol":"n12.1","value":{"direction":"out","id":"n12.1","inherited":true,"name":"y","type":"Pin"},"binding":{"context":["probe.sysml@187:16","probe.sysml@151:16","probe.sysml@90:12","probe.sysml@56:15"],"subject":{"id":"probe.sysml@56:15","origin":{"file":"probe.sysml","length":15,"nameLength":1,"nameOffset":65,"offset":56},"qualifiedName":"P::Leaf::y"},"symbol":"n12.1"},"declaration":"out port y:Pin;"},{"symbol":"n13","value":{"id":"n13","inherited":true,"kind":"part","name":"b","origin":{"endCol":116,"endLine":1,"file":"probe.sysml","startCol":104,"startLine":1},"parent":"n11","ports":[{"direction":"in","id":"n13.0","inherited":true,"name":"x","type":"Pin"},{"direction":"out","id":"n13.1","inherited":true,"name":"y","type":"Pin"}],"type":"Leaf"},"binding":{"context":["probe.sysml@187:16","probe.sysml@151:16","probe.sysml@103:12"],"subject":{"id":"probe.sysml@103:12","origin":{"file":"probe.sysml","length":12,"nameLength":1,"nameOffset":108,"offset":103},"qualifiedName":"P::Base::b"},"symbol":"n13"},"declaration":"part b:Leaf;"},{"symbol":"n13.0","value":{"direction":"in","id":"n13.0","inherited":true,"name":"x","type":"Pin"},"binding":{"context":["probe.sysml@187:16","probe.sysml@151:16","probe.sysml@103:12","probe.sysml@41:14"],"subject":{"id":"probe.sysml@41:14","origin":{"file":"probe.sysml","length":14,"nameLength":1,"nameOffset":49,"offset":41},"qualifiedName":"P::Leaf::x"},"symbol":"n13.0"},"declaration":"in port x:Pin;"},{"symbol":"n13.1","value":{"direction":"out","id":"n13.1","inherited":true,"name":"y","type":"Pin"},"binding":{"context":["probe.sysml@187:16","probe.sysml@151:16","probe.sysml@103:12","probe.sysml@56:15"],"subject":{"id":"probe.sysml@56:15","origin":{"file":"probe.sysml","length":15,"nameLength":1,"nameOffset":65,"offset":56},"qualifiedName":"P::Leaf::y"},"symbol":"n13.1"},"declaration":"out port y:Pin;"}]`, `[{"id":"probe.sysml@170:16","origin":{"file":"probe.sysml","length":16,"nameLength":4,"nameOffset":175,"offset":170},"qualifiedName":"P::sat1"},{"id":"probe.sysml@187:16","origin":{"file":"probe.sysml","length":16,"nameLength":4,"nameOffset":192,"offset":187},"qualifiedName":"P::sat2"}]`, `{"artifact":{"edges":[],"kind":"interconnection","nodes":[{"id":"n0","kind":"part","name":"P::sat1","origin":{"endCol":187,"endLine":1,"file":"probe.sysml","startCol":171,"startLine":1},"type":"Outer"}],"shown":["parameter-symbols","port-symbols","compartments","edge-labels","keywords","multiplicities","typings","qualifiers"],"stated":"Read-only exploration"},"correspondence":[{"context":["probe.sysml@170:16"],"subject":{"id":"probe.sysml@170:16","origin":{"file":"probe.sysml","length":16,"nameLength":4,"nameOffset":175,"offset":170},"qualifiedName":"P::sat1"},"symbol":"n0"}],"diagnostics":[],"modelHash":"edacd052e0281471b02497291c964a42ae8cccc4328f5130b2058c3c6ad128d5","renderStatus":"available","request":{"representation":"interconnection","scope":{"kind":"element","subject":"P::sat1"}},"schemaVersion":1,"selected":[{"id":"probe.sysml@170:16","origin":{"file":"probe.sysml","length":16,"nameLength":4,"nameOffset":175,"offset":170},"qualifiedName":"P::sat1"}],"status":"complete"}`, `[{"symbol":"n0","value":{"id":"n0","kind":"part","name":"P::sat1","origin":{"endCol":187,"endLine":1,"file":"probe.sysml","startCol":171,"startLine":1},"type":"Outer"},"binding":{"context":["probe.sysml@170:16"],"subject":{"id":"probe.sysml@170:16","origin":{"file":"probe.sysml","length":16,"nameLength":4,"nameOffset":175,"offset":170},"qualifiedName":"P::sat1"},"symbol":"n0"},"declaration":"part sat1:Outer;"},{"symbol":"n1","value":{"id":"n1","kind":"part","name":"P::sat2","origin":{"endCol":204,"endLine":1,"file":"probe.sysml","startCol":188,"startLine":1},"type":"Outer"},"binding":{"context":["probe.sysml@187:16"],"subject":{"id":"probe.sysml@187:16","origin":{"file":"probe.sysml","length":16,"nameLength":4,"nameOffset":192,"offset":187},"qualifiedName":"P::sat2"},"symbol":"n1"},"declaration":"part sat2:Outer;"}]`}, + // partial: exact source declarations, parents, types, ports, contexts and legacy ordinals. + {`partial`, `package P {part def Wheel; part def SmallWheel :> Wheel; part def Sat { part a:Wheel; part b:Wheel; part c:Wheel; } part sat1:Sat {part :>> a:SmallWheel; part :>> b:SmallWheel;} part sat2:Sat;} package V {private import StandardViewDefinitions::*; view v:InterconnectionView {expose P::sat1; expose P::sat2; expose P::sat1::c;} }`, `[{"symbol":"n0","value":{"id":"n0","kind":"part","name":"P::sat1","origin":{"endCol":178,"endLine":1,"file":"probe.sysml","startCol":117,"startLine":1},"type":"Sat"},"binding":{"context":["probe.sysml@116:61"],"subject":{"id":"probe.sysml@116:61","origin":{"file":"probe.sysml","length":61,"nameLength":4,"nameOffset":121,"offset":116},"qualifiedName":"P::sat1"},"symbol":"n0"},"declaration":"part sat1:Sat {part :>> a:SmallWheel; part :>> b:SmallWheel;}"},{"symbol":"n1","value":{"id":"n1","kind":"part","name":"a","origin":{"endCol":154,"endLine":1,"file":"probe.sysml","startCol":132,"startLine":1},"parent":"n0","type":"SmallWheel"},"binding":{"context":["probe.sysml@116:61","probe.sysml@131:22"],"subject":{"id":"probe.sysml@131:22","origin":{"file":"probe.sysml","length":22,"nameLength":1,"nameOffset":140,"offset":131},"qualifiedName":"P::sat1::a"},"symbol":"n1"},"declaration":"part :>> a:SmallWheel;"},{"symbol":"n2","value":{"id":"n2","kind":"part","name":"b","origin":{"endCol":177,"endLine":1,"file":"probe.sysml","startCol":155,"startLine":1},"parent":"n0","type":"SmallWheel"},"binding":{"context":["probe.sysml@116:61","probe.sysml@154:22"],"subject":{"id":"probe.sysml@154:22","origin":{"file":"probe.sysml","length":22,"nameLength":1,"nameOffset":163,"offset":154},"qualifiedName":"P::sat1::b"},"symbol":"n2"},"declaration":"part :>> b:SmallWheel;"},{"symbol":"n4","value":{"id":"n4","inherited":true,"kind":"part","name":"c","origin":{"endCol":114,"endLine":1,"file":"probe.sysml","startCol":101,"startLine":1},"parent":"n0","type":"Wheel"},"binding":{"context":["probe.sysml@116:61","probe.sysml@100:13"],"subject":{"id":"probe.sysml@100:13","origin":{"file":"probe.sysml","length":13,"nameLength":1,"nameOffset":105,"offset":100},"qualifiedName":"P::Sat::c"},"symbol":"n4"},"declaration":"part c:Wheel;"},{"symbol":"n3","value":{"id":"n3","kind":"part","name":"P::sat2","origin":{"endCol":193,"endLine":1,"file":"probe.sysml","startCol":179,"startLine":1},"type":"Sat"},"binding":{"context":["probe.sysml@178:14"],"subject":{"id":"probe.sysml@178:14","origin":{"file":"probe.sysml","length":14,"nameLength":4,"nameOffset":183,"offset":178},"qualifiedName":"P::sat2"},"symbol":"n3"},"declaration":"part sat2:Sat;"},{"symbol":"n5","value":{"id":"n5","inherited":true,"kind":"part","name":"a","origin":{"endCol":86,"endLine":1,"file":"probe.sysml","startCol":73,"startLine":1},"parent":"n3","type":"Wheel"},"binding":{"context":["probe.sysml@178:14","probe.sysml@72:13"],"subject":{"id":"probe.sysml@72:13","origin":{"file":"probe.sysml","length":13,"nameLength":1,"nameOffset":77,"offset":72},"qualifiedName":"P::Sat::a"},"symbol":"n5"},"declaration":"part a:Wheel;"},{"symbol":"n6","value":{"id":"n6","inherited":true,"kind":"part","name":"b","origin":{"endCol":100,"endLine":1,"file":"probe.sysml","startCol":87,"startLine":1},"parent":"n3","type":"Wheel"},"binding":{"context":["probe.sysml@178:14","probe.sysml@86:13"],"subject":{"id":"probe.sysml@86:13","origin":{"file":"probe.sysml","length":13,"nameLength":1,"nameOffset":91,"offset":86},"qualifiedName":"P::Sat::b"},"symbol":"n6"},"declaration":"part b:Wheel;"},{"symbol":"n7","value":{"id":"n7","inherited":true,"kind":"part","name":"c","origin":{"endCol":114,"endLine":1,"file":"probe.sysml","startCol":101,"startLine":1},"parent":"n3","type":"Wheel"},"binding":{"context":["probe.sysml@178:14","probe.sysml@100:13"],"subject":{"id":"probe.sysml@100:13","origin":{"file":"probe.sysml","length":13,"nameLength":1,"nameOffset":105,"offset":100},"qualifiedName":"P::Sat::c"},"symbol":"n7"},"declaration":"part c:Wheel;"}]`, `[{"id":"probe.sysml@116:61","origin":{"file":"probe.sysml","length":61,"nameLength":4,"nameOffset":121,"offset":116},"qualifiedName":"P::sat1"},{"id":"probe.sysml@178:14","origin":{"file":"probe.sysml","length":14,"nameLength":4,"nameOffset":183,"offset":178},"qualifiedName":"P::sat2"},{"id":"probe.sysml@100:13","origin":{"file":"probe.sysml","length":13,"nameLength":1,"nameOffset":105,"offset":100},"qualifiedName":"P::Sat::c"}]`, `{"artifact":{"edges":[],"kind":"interconnection","nodes":[{"id":"n0","kind":"part","name":"P::sat1","origin":{"endCol":178,"endLine":1,"file":"probe.sysml","startCol":117,"startLine":1},"type":"Sat"},{"id":"n1","kind":"part","name":"a","origin":{"endCol":154,"endLine":1,"file":"probe.sysml","startCol":132,"startLine":1},"parent":"n0","type":"SmallWheel"},{"id":"n2","kind":"part","name":"b","origin":{"endCol":177,"endLine":1,"file":"probe.sysml","startCol":155,"startLine":1},"parent":"n0","type":"SmallWheel"}],"shown":["parameter-symbols","port-symbols","compartments","edge-labels","keywords","multiplicities","typings","qualifiers"],"stated":"Read-only exploration"},"correspondence":[{"context":["probe.sysml@116:61"],"subject":{"id":"probe.sysml@116:61","origin":{"file":"probe.sysml","length":61,"nameLength":4,"nameOffset":121,"offset":116},"qualifiedName":"P::sat1"},"symbol":"n0"},{"context":["probe.sysml@116:61","probe.sysml@131:22"],"subject":{"id":"probe.sysml@131:22","origin":{"file":"probe.sysml","length":22,"nameLength":1,"nameOffset":140,"offset":131},"qualifiedName":"P::sat1::a"},"symbol":"n1"},{"context":["probe.sysml@116:61","probe.sysml@154:22"],"subject":{"id":"probe.sysml@154:22","origin":{"file":"probe.sysml","length":22,"nameLength":1,"nameOffset":163,"offset":154},"qualifiedName":"P::sat1::b"},"symbol":"n2"}],"diagnostics":[],"modelHash":"941f0d82b600928f90129f7e68e7b3c9927cbbd53a6a37a0d83d92336d47f243","renderStatus":"available","request":{"representation":"interconnection","scope":{"kind":"element","subject":"P::sat1"}},"schemaVersion":1,"selected":[{"id":"probe.sysml@116:61","origin":{"file":"probe.sysml","length":61,"nameLength":4,"nameOffset":121,"offset":116},"qualifiedName":"P::sat1"}],"status":"complete"}`, `[{"symbol":"n0","value":{"id":"n0","kind":"part","name":"P::sat1","origin":{"endCol":178,"endLine":1,"file":"probe.sysml","startCol":117,"startLine":1},"type":"Sat"},"binding":{"context":["probe.sysml@116:61"],"subject":{"id":"probe.sysml@116:61","origin":{"file":"probe.sysml","length":61,"nameLength":4,"nameOffset":121,"offset":116},"qualifiedName":"P::sat1"},"symbol":"n0"},"declaration":"part sat1:Sat {part :>> a:SmallWheel; part :>> b:SmallWheel;}"},{"symbol":"n1","value":{"id":"n1","kind":"part","name":"a","origin":{"endCol":154,"endLine":1,"file":"probe.sysml","startCol":132,"startLine":1},"parent":"n0","type":"SmallWheel"},"binding":{"context":["probe.sysml@116:61","probe.sysml@131:22"],"subject":{"id":"probe.sysml@131:22","origin":{"file":"probe.sysml","length":22,"nameLength":1,"nameOffset":140,"offset":131},"qualifiedName":"P::sat1::a"},"symbol":"n1"},"declaration":"part :>> a:SmallWheel;"},{"symbol":"n2","value":{"id":"n2","kind":"part","name":"b","origin":{"endCol":177,"endLine":1,"file":"probe.sysml","startCol":155,"startLine":1},"parent":"n0","type":"SmallWheel"},"binding":{"context":["probe.sysml@116:61","probe.sysml@154:22"],"subject":{"id":"probe.sysml@154:22","origin":{"file":"probe.sysml","length":22,"nameLength":1,"nameOffset":163,"offset":154},"qualifiedName":"P::sat1::b"},"symbol":"n2"},"declaration":"part :>> b:SmallWheel;"},{"symbol":"n3","value":{"id":"n3","kind":"part","name":"P::sat2","origin":{"endCol":193,"endLine":1,"file":"probe.sysml","startCol":179,"startLine":1},"type":"Sat"},"binding":{"context":["probe.sysml@178:14"],"subject":{"id":"probe.sysml@178:14","origin":{"file":"probe.sysml","length":14,"nameLength":4,"nameOffset":183,"offset":178},"qualifiedName":"P::sat2"},"symbol":"n3"},"declaration":"part sat2:Sat;"}]`}, + // ports: exact source declarations, parents, types, ports, contexts and legacy ordinals. + {`ports`, `package P {port def Pin; part def Base { in port a:Pin; out port b:Pin; inout port c:Pin; } part def Mid :> Base { out port :>> b:Pin; } part sat1:Mid; part sat2:Mid;} package V {private import StandardViewDefinitions::*; view v:InterconnectionView {expose P::sat1; expose P::sat2;} }`, `[{"symbol":"n0","value":{"id":"n0","kind":"part","name":"P::sat1","origin":{"endCol":152,"endLine":1,"file":"probe.sysml","startCol":138,"startLine":1},"ports":[{"direction":"out","id":"n0.0","inherited":true,"name":"b","type":"Pin"},{"direction":"in","id":"n0.1","inherited":true,"name":"a","type":"Pin"},{"direction":"inout","id":"n0.2","inherited":true,"name":"c","type":"Pin"}],"type":"Mid"},"binding":{"context":["probe.sysml@137:14"],"subject":{"id":"probe.sysml@137:14","origin":{"file":"probe.sysml","length":14,"nameLength":4,"nameOffset":142,"offset":137},"qualifiedName":"P::sat1"},"symbol":"n0"},"declaration":"part sat1:Mid;"},{"symbol":"n0.0","value":{"direction":"out","id":"n0.0","inherited":true,"name":"b","type":"Pin"},"binding":{"context":["probe.sysml@137:14","probe.sysml@115:19"],"subject":{"id":"probe.sysml@115:19","origin":{"file":"probe.sysml","length":19,"nameLength":1,"nameOffset":128,"offset":115},"qualifiedName":"P::Mid::b"},"symbol":"n0.0"},"declaration":"out port :>> b:Pin;"},{"symbol":"n0.1","value":{"direction":"in","id":"n0.1","inherited":true,"name":"a","type":"Pin"},"binding":{"context":["probe.sysml@137:14","probe.sysml@41:14"],"subject":{"id":"probe.sysml@41:14","origin":{"file":"probe.sysml","length":14,"nameLength":1,"nameOffset":49,"offset":41},"qualifiedName":"P::Base::a"},"symbol":"n0.1"},"declaration":"in port a:Pin;"},{"symbol":"n0.2","value":{"direction":"inout","id":"n0.2","inherited":true,"name":"c","type":"Pin"},"binding":{"context":["probe.sysml@137:14","probe.sysml@72:17"],"subject":{"id":"probe.sysml@72:17","origin":{"file":"probe.sysml","length":17,"nameLength":1,"nameOffset":83,"offset":72},"qualifiedName":"P::Base::c"},"symbol":"n0.2"},"declaration":"inout port c:Pin;"},{"symbol":"n1","value":{"id":"n1","kind":"part","name":"P::sat2","origin":{"endCol":167,"endLine":1,"file":"probe.sysml","startCol":153,"startLine":1},"ports":[{"direction":"out","id":"n1.0","inherited":true,"name":"b","type":"Pin"},{"direction":"in","id":"n1.1","inherited":true,"name":"a","type":"Pin"},{"direction":"inout","id":"n1.2","inherited":true,"name":"c","type":"Pin"}],"type":"Mid"},"binding":{"context":["probe.sysml@152:14"],"subject":{"id":"probe.sysml@152:14","origin":{"file":"probe.sysml","length":14,"nameLength":4,"nameOffset":157,"offset":152},"qualifiedName":"P::sat2"},"symbol":"n1"},"declaration":"part sat2:Mid;"},{"symbol":"n1.0","value":{"direction":"out","id":"n1.0","inherited":true,"name":"b","type":"Pin"},"binding":{"context":["probe.sysml@152:14","probe.sysml@115:19"],"subject":{"id":"probe.sysml@115:19","origin":{"file":"probe.sysml","length":19,"nameLength":1,"nameOffset":128,"offset":115},"qualifiedName":"P::Mid::b"},"symbol":"n1.0"},"declaration":"out port :>> b:Pin;"},{"symbol":"n1.1","value":{"direction":"in","id":"n1.1","inherited":true,"name":"a","type":"Pin"},"binding":{"context":["probe.sysml@152:14","probe.sysml@41:14"],"subject":{"id":"probe.sysml@41:14","origin":{"file":"probe.sysml","length":14,"nameLength":1,"nameOffset":49,"offset":41},"qualifiedName":"P::Base::a"},"symbol":"n1.1"},"declaration":"in port a:Pin;"},{"symbol":"n1.2","value":{"direction":"inout","id":"n1.2","inherited":true,"name":"c","type":"Pin"},"binding":{"context":["probe.sysml@152:14","probe.sysml@72:17"],"subject":{"id":"probe.sysml@72:17","origin":{"file":"probe.sysml","length":17,"nameLength":1,"nameOffset":83,"offset":72},"qualifiedName":"P::Base::c"},"symbol":"n1.2"},"declaration":"inout port c:Pin;"}]`, `[{"id":"probe.sysml@137:14","origin":{"file":"probe.sysml","length":14,"nameLength":4,"nameOffset":142,"offset":137},"qualifiedName":"P::sat1"},{"id":"probe.sysml@152:14","origin":{"file":"probe.sysml","length":14,"nameLength":4,"nameOffset":157,"offset":152},"qualifiedName":"P::sat2"}]`, `{"artifact":{"edges":[],"kind":"interconnection","nodes":[{"id":"n0","kind":"part","name":"P::sat1","origin":{"endCol":152,"endLine":1,"file":"probe.sysml","startCol":138,"startLine":1},"ports":[{"direction":"out","id":"n0.0","inherited":true,"name":"b","type":"Pin"},{"direction":"in","id":"n0.1","inherited":true,"name":"a","type":"Pin"},{"direction":"inout","id":"n0.2","inherited":true,"name":"c","type":"Pin"}],"type":"Mid"}],"shown":["parameter-symbols","port-symbols","compartments","edge-labels","keywords","multiplicities","typings","qualifiers"],"stated":"Read-only exploration"},"correspondence":[{"context":["probe.sysml@137:14"],"subject":{"id":"probe.sysml@137:14","origin":{"file":"probe.sysml","length":14,"nameLength":4,"nameOffset":142,"offset":137},"qualifiedName":"P::sat1"},"symbol":"n0"},{"context":["probe.sysml@137:14","probe.sysml@115:19"],"subject":{"id":"probe.sysml@115:19","origin":{"file":"probe.sysml","length":19,"nameLength":1,"nameOffset":128,"offset":115},"qualifiedName":"P::Mid::b"},"symbol":"n0.0"},{"context":["probe.sysml@137:14","probe.sysml@41:14"],"subject":{"id":"probe.sysml@41:14","origin":{"file":"probe.sysml","length":14,"nameLength":1,"nameOffset":49,"offset":41},"qualifiedName":"P::Base::a"},"symbol":"n0.1"},{"context":["probe.sysml@137:14","probe.sysml@72:17"],"subject":{"id":"probe.sysml@72:17","origin":{"file":"probe.sysml","length":17,"nameLength":1,"nameOffset":83,"offset":72},"qualifiedName":"P::Base::c"},"symbol":"n0.2"}],"diagnostics":[],"modelHash":"2bc3828efcda4157a7c45d0f81b1a0b3807b0a6324ab72569c2700ffe7c27edd","renderStatus":"available","request":{"representation":"interconnection","scope":{"kind":"element","subject":"P::sat1"}},"schemaVersion":1,"selected":[{"id":"probe.sysml@137:14","origin":{"file":"probe.sysml","length":14,"nameLength":4,"nameOffset":142,"offset":137},"qualifiedName":"P::sat1"}],"status":"complete"}`, `[{"symbol":"n0","value":{"id":"n0","kind":"part","name":"P::sat1","origin":{"endCol":152,"endLine":1,"file":"probe.sysml","startCol":138,"startLine":1},"ports":[{"direction":"out","id":"n0.0","inherited":true,"name":"b","type":"Pin"},{"direction":"in","id":"n0.1","inherited":true,"name":"a","type":"Pin"},{"direction":"inout","id":"n0.2","inherited":true,"name":"c","type":"Pin"}],"type":"Mid"},"binding":{"context":["probe.sysml@137:14"],"subject":{"id":"probe.sysml@137:14","origin":{"file":"probe.sysml","length":14,"nameLength":4,"nameOffset":142,"offset":137},"qualifiedName":"P::sat1"},"symbol":"n0"},"declaration":"part sat1:Mid;"},{"symbol":"n0.0","value":{"direction":"out","id":"n0.0","inherited":true,"name":"b","type":"Pin"},"binding":{"context":["probe.sysml@137:14","probe.sysml@115:19"],"subject":{"id":"probe.sysml@115:19","origin":{"file":"probe.sysml","length":19,"nameLength":1,"nameOffset":128,"offset":115},"qualifiedName":"P::Mid::b"},"symbol":"n0.0"},"declaration":"out port :>> b:Pin;"},{"symbol":"n0.1","value":{"direction":"in","id":"n0.1","inherited":true,"name":"a","type":"Pin"},"binding":{"context":["probe.sysml@137:14","probe.sysml@41:14"],"subject":{"id":"probe.sysml@41:14","origin":{"file":"probe.sysml","length":14,"nameLength":1,"nameOffset":49,"offset":41},"qualifiedName":"P::Base::a"},"symbol":"n0.1"},"declaration":"in port a:Pin;"},{"symbol":"n0.2","value":{"direction":"inout","id":"n0.2","inherited":true,"name":"c","type":"Pin"},"binding":{"context":["probe.sysml@137:14","probe.sysml@72:17"],"subject":{"id":"probe.sysml@72:17","origin":{"file":"probe.sysml","length":17,"nameLength":1,"nameOffset":83,"offset":72},"qualifiedName":"P::Base::c"},"symbol":"n0.2"},"declaration":"inout port c:Pin;"},{"symbol":"n1","value":{"id":"n1","kind":"part","name":"P::sat2","origin":{"endCol":167,"endLine":1,"file":"probe.sysml","startCol":153,"startLine":1},"ports":[{"direction":"out","id":"n1.0","inherited":true,"name":"b","type":"Pin"},{"direction":"in","id":"n1.1","inherited":true,"name":"a","type":"Pin"},{"direction":"inout","id":"n1.2","inherited":true,"name":"c","type":"Pin"}],"type":"Mid"},"binding":{"context":["probe.sysml@152:14"],"subject":{"id":"probe.sysml@152:14","origin":{"file":"probe.sysml","length":14,"nameLength":4,"nameOffset":157,"offset":152},"qualifiedName":"P::sat2"},"symbol":"n1"},"declaration":"part sat2:Mid;"},{"symbol":"n1.0","value":{"direction":"out","id":"n1.0","inherited":true,"name":"b","type":"Pin"},"binding":{"context":["probe.sysml@152:14","probe.sysml@115:19"],"subject":{"id":"probe.sysml@115:19","origin":{"file":"probe.sysml","length":19,"nameLength":1,"nameOffset":128,"offset":115},"qualifiedName":"P::Mid::b"},"symbol":"n1.0"},"declaration":"out port :>> b:Pin;"},{"symbol":"n1.1","value":{"direction":"in","id":"n1.1","inherited":true,"name":"a","type":"Pin"},"binding":{"context":["probe.sysml@152:14","probe.sysml@41:14"],"subject":{"id":"probe.sysml@41:14","origin":{"file":"probe.sysml","length":14,"nameLength":1,"nameOffset":49,"offset":41},"qualifiedName":"P::Base::a"},"symbol":"n1.1"},"declaration":"in port a:Pin;"},{"symbol":"n1.2","value":{"direction":"inout","id":"n1.2","inherited":true,"name":"c","type":"Pin"},"binding":{"context":["probe.sysml@152:14","probe.sysml@72:17"],"subject":{"id":"probe.sysml@72:17","origin":{"file":"probe.sysml","length":17,"nameLength":1,"nameOffset":83,"offset":72},"qualifiedName":"P::Base::c"},"symbol":"n1.2"},"declaration":"inout port c:Pin;"}]`}, + // synthetic: exact source declarations, parents, types, ports, contexts and legacy ordinals. + {`synthetic`, `package P {part def Leaf; requirement need; part def Base { part a:Leaf { satisfy need; } } part sat1:Base; part sat2:Base;} package V {private import StandardViewDefinitions::*; view v:InterconnectionView {expose P::sat1; expose P::sat2;} }`, `[{"symbol":"n0","value":{"id":"n0","kind":"part","name":"P::sat1","origin":{"endCol":108,"endLine":1,"file":"probe.sysml","startCol":93,"startLine":1},"type":"Base"},"binding":{"context":["probe.sysml@92:15"],"subject":{"id":"probe.sysml@92:15","origin":{"file":"probe.sysml","length":15,"nameLength":4,"nameOffset":97,"offset":92},"qualifiedName":"P::sat1"},"symbol":"n0"},"declaration":"part sat1:Base;"},{"symbol":"n2","value":{"id":"n2","inherited":true,"kind":"part","name":"a","origin":{"endCol":90,"endLine":1,"file":"probe.sysml","startCol":61,"startLine":1},"parent":"n0","type":"Leaf"},"binding":{"context":["probe.sysml@92:15","probe.sysml@60:29"],"subject":{"id":"probe.sysml@60:29","origin":{"file":"probe.sysml","length":29,"nameLength":1,"nameOffset":65,"offset":60},"qualifiedName":"P::Base::a"},"symbol":"n2"},"declaration":"part a:Leaf { satisfy need; }"},{"symbol":"n1","value":{"id":"n1","kind":"part","name":"P::sat2","origin":{"endCol":124,"endLine":1,"file":"probe.sysml","startCol":109,"startLine":1},"type":"Base"},"binding":{"context":["probe.sysml@108:15"],"subject":{"id":"probe.sysml@108:15","origin":{"file":"probe.sysml","length":15,"nameLength":4,"nameOffset":113,"offset":108},"qualifiedName":"P::sat2"},"symbol":"n1"},"declaration":"part sat2:Base;"},{"symbol":"n3","value":{"id":"n3","inherited":true,"kind":"part","name":"a","origin":{"endCol":90,"endLine":1,"file":"probe.sysml","startCol":61,"startLine":1},"parent":"n1","type":"Leaf"},"binding":{"context":["probe.sysml@108:15","probe.sysml@60:29"],"subject":{"id":"probe.sysml@60:29","origin":{"file":"probe.sysml","length":29,"nameLength":1,"nameOffset":65,"offset":60},"qualifiedName":"P::Base::a"},"symbol":"n3"},"declaration":"part a:Leaf { satisfy need; }"}]`, `[{"id":"probe.sysml@92:15","origin":{"file":"probe.sysml","length":15,"nameLength":4,"nameOffset":97,"offset":92},"qualifiedName":"P::sat1"},{"id":"probe.sysml@108:15","origin":{"file":"probe.sysml","length":15,"nameLength":4,"nameOffset":113,"offset":108},"qualifiedName":"P::sat2"}]`, `{"artifact":{"edges":[],"kind":"interconnection","nodes":[{"id":"n0","kind":"part","name":"P::sat1","origin":{"endCol":108,"endLine":1,"file":"probe.sysml","startCol":93,"startLine":1},"type":"Base"}],"shown":["parameter-symbols","port-symbols","compartments","edge-labels","keywords","multiplicities","typings","qualifiers"],"stated":"Read-only exploration"},"correspondence":[{"context":["probe.sysml@92:15"],"subject":{"id":"probe.sysml@92:15","origin":{"file":"probe.sysml","length":15,"nameLength":4,"nameOffset":97,"offset":92},"qualifiedName":"P::sat1"},"symbol":"n0"}],"diagnostics":[],"modelHash":"58bed341ac274554ebc351b14d1c59dd1979654a7e2ec733e6121bba728821d3","renderStatus":"available","request":{"representation":"interconnection","scope":{"kind":"element","subject":"P::sat1"}},"schemaVersion":1,"selected":[{"id":"probe.sysml@92:15","origin":{"file":"probe.sysml","length":15,"nameLength":4,"nameOffset":97,"offset":92},"qualifiedName":"P::sat1"}],"status":"complete"}`, `[{"symbol":"n0","value":{"id":"n0","kind":"part","name":"P::sat1","origin":{"endCol":108,"endLine":1,"file":"probe.sysml","startCol":93,"startLine":1},"type":"Base"},"binding":{"context":["probe.sysml@92:15"],"subject":{"id":"probe.sysml@92:15","origin":{"file":"probe.sysml","length":15,"nameLength":4,"nameOffset":97,"offset":92},"qualifiedName":"P::sat1"},"symbol":"n0"},"declaration":"part sat1:Base;"},{"symbol":"n1","value":{"id":"n1","kind":"part","name":"P::sat2","origin":{"endCol":124,"endLine":1,"file":"probe.sysml","startCol":109,"startLine":1},"type":"Base"},"binding":{"context":["probe.sysml@108:15"],"subject":{"id":"probe.sysml@108:15","origin":{"file":"probe.sysml","length":15,"nameLength":4,"nameOffset":113,"offset":108},"qualifiedName":"P::sat2"},"symbol":"n1"},"declaration":"part sat2:Base;"}]`}, + // transitive: exact source declarations, parents, types, ports, contexts and legacy ordinals. + {`transitive`, `package P {port def Pin; part def Leaf { in port x:Pin; out port y:Pin; } part def Base { part a:Leaf; part b:Leaf; part c:Leaf; } part def Mid :> Base { part :>> b:Leaf; } part def Top :> Mid { part :>> a:Leaf; } part sat1:Top; part sat2:Top;} package V {private import StandardViewDefinitions::*; view v:InterconnectionView {expose P::sat1; expose P::sat2;} }`, `[{"symbol":"n0","value":{"id":"n0","kind":"part","name":"P::sat1","origin":{"endCol":229,"endLine":1,"file":"probe.sysml","startCol":215,"startLine":1},"type":"Top"},"binding":{"context":["probe.sysml@214:14"],"subject":{"id":"probe.sysml@214:14","origin":{"file":"probe.sysml","length":14,"nameLength":4,"nameOffset":219,"offset":214},"qualifiedName":"P::sat1"},"symbol":"n0"},"declaration":"part sat1:Top;"},{"symbol":"n2","value":{"id":"n2","inherited":true,"kind":"part","name":"a","origin":{"endCol":212,"endLine":1,"file":"probe.sysml","startCol":196,"startLine":1},"parent":"n0","ports":[{"direction":"in","id":"n2.0","inherited":true,"name":"x","type":"Pin"},{"direction":"out","id":"n2.1","inherited":true,"name":"y","type":"Pin"}],"type":"Leaf"},"binding":{"context":["probe.sysml@214:14","probe.sysml@195:16"],"subject":{"id":"probe.sysml@195:16","origin":{"file":"probe.sysml","length":16,"nameLength":1,"nameOffset":204,"offset":195},"qualifiedName":"P::Top::a"},"symbol":"n2"},"declaration":"part :>> a:Leaf;"},{"symbol":"n2.0","value":{"direction":"in","id":"n2.0","inherited":true,"name":"x","type":"Pin"},"binding":{"context":["probe.sysml@214:14","probe.sysml@195:16","probe.sysml@41:14"],"subject":{"id":"probe.sysml@41:14","origin":{"file":"probe.sysml","length":14,"nameLength":1,"nameOffset":49,"offset":41},"qualifiedName":"P::Leaf::x"},"symbol":"n2.0"},"declaration":"in port x:Pin;"},{"symbol":"n2.1","value":{"direction":"out","id":"n2.1","inherited":true,"name":"y","type":"Pin"},"binding":{"context":["probe.sysml@214:14","probe.sysml@195:16","probe.sysml@56:15"],"subject":{"id":"probe.sysml@56:15","origin":{"file":"probe.sysml","length":15,"nameLength":1,"nameOffset":65,"offset":56},"qualifiedName":"P::Leaf::y"},"symbol":"n2.1"},"declaration":"out port y:Pin;"},{"symbol":"n3","value":{"id":"n3","inherited":true,"kind":"part","name":"b","origin":{"endCol":171,"endLine":1,"file":"probe.sysml","startCol":155,"startLine":1},"parent":"n0","ports":[{"direction":"in","id":"n3.0","inherited":true,"name":"x","type":"Pin"},{"direction":"out","id":"n3.1","inherited":true,"name":"y","type":"Pin"}],"type":"Leaf"},"binding":{"context":["probe.sysml@214:14","probe.sysml@154:16"],"subject":{"id":"probe.sysml@154:16","origin":{"file":"probe.sysml","length":16,"nameLength":1,"nameOffset":163,"offset":154},"qualifiedName":"P::Mid::b"},"symbol":"n3"},"declaration":"part :>> b:Leaf;"},{"symbol":"n3.0","value":{"direction":"in","id":"n3.0","inherited":true,"name":"x","type":"Pin"},"binding":{"context":["probe.sysml@214:14","probe.sysml@154:16","probe.sysml@41:14"],"subject":{"id":"probe.sysml@41:14","origin":{"file":"probe.sysml","length":14,"nameLength":1,"nameOffset":49,"offset":41},"qualifiedName":"P::Leaf::x"},"symbol":"n3.0"},"declaration":"in port x:Pin;"},{"symbol":"n3.1","value":{"direction":"out","id":"n3.1","inherited":true,"name":"y","type":"Pin"},"binding":{"context":["probe.sysml@214:14","probe.sysml@154:16","probe.sysml@56:15"],"subject":{"id":"probe.sysml@56:15","origin":{"file":"probe.sysml","length":15,"nameLength":1,"nameOffset":65,"offset":56},"qualifiedName":"P::Leaf::y"},"symbol":"n3.1"},"declaration":"out port y:Pin;"},{"symbol":"n4","value":{"id":"n4","inherited":true,"kind":"part","name":"c","origin":{"endCol":129,"endLine":1,"file":"probe.sysml","startCol":117,"startLine":1},"parent":"n0","ports":[{"direction":"in","id":"n4.0","inherited":true,"name":"x","type":"Pin"},{"direction":"out","id":"n4.1","inherited":true,"name":"y","type":"Pin"}],"type":"Leaf"},"binding":{"context":["probe.sysml@214:14","probe.sysml@116:12"],"subject":{"id":"probe.sysml@116:12","origin":{"file":"probe.sysml","length":12,"nameLength":1,"nameOffset":121,"offset":116},"qualifiedName":"P::Base::c"},"symbol":"n4"},"declaration":"part c:Leaf;"},{"symbol":"n4.0","value":{"direction":"in","id":"n4.0","inherited":true,"name":"x","type":"Pin"},"binding":{"context":["probe.sysml@214:14","probe.sysml@116:12","probe.sysml@41:14"],"subject":{"id":"probe.sysml@41:14","origin":{"file":"probe.sysml","length":14,"nameLength":1,"nameOffset":49,"offset":41},"qualifiedName":"P::Leaf::x"},"symbol":"n4.0"},"declaration":"in port x:Pin;"},{"symbol":"n4.1","value":{"direction":"out","id":"n4.1","inherited":true,"name":"y","type":"Pin"},"binding":{"context":["probe.sysml@214:14","probe.sysml@116:12","probe.sysml@56:15"],"subject":{"id":"probe.sysml@56:15","origin":{"file":"probe.sysml","length":15,"nameLength":1,"nameOffset":65,"offset":56},"qualifiedName":"P::Leaf::y"},"symbol":"n4.1"},"declaration":"out port y:Pin;"},{"symbol":"n1","value":{"id":"n1","kind":"part","name":"P::sat2","origin":{"endCol":244,"endLine":1,"file":"probe.sysml","startCol":230,"startLine":1},"type":"Top"},"binding":{"context":["probe.sysml@229:14"],"subject":{"id":"probe.sysml@229:14","origin":{"file":"probe.sysml","length":14,"nameLength":4,"nameOffset":234,"offset":229},"qualifiedName":"P::sat2"},"symbol":"n1"},"declaration":"part sat2:Top;"},{"symbol":"n5","value":{"id":"n5","inherited":true,"kind":"part","name":"a","origin":{"endCol":212,"endLine":1,"file":"probe.sysml","startCol":196,"startLine":1},"parent":"n1","ports":[{"direction":"in","id":"n5.0","inherited":true,"name":"x","type":"Pin"},{"direction":"out","id":"n5.1","inherited":true,"name":"y","type":"Pin"}],"type":"Leaf"},"binding":{"context":["probe.sysml@229:14","probe.sysml@195:16"],"subject":{"id":"probe.sysml@195:16","origin":{"file":"probe.sysml","length":16,"nameLength":1,"nameOffset":204,"offset":195},"qualifiedName":"P::Top::a"},"symbol":"n5"},"declaration":"part :>> a:Leaf;"},{"symbol":"n5.0","value":{"direction":"in","id":"n5.0","inherited":true,"name":"x","type":"Pin"},"binding":{"context":["probe.sysml@229:14","probe.sysml@195:16","probe.sysml@41:14"],"subject":{"id":"probe.sysml@41:14","origin":{"file":"probe.sysml","length":14,"nameLength":1,"nameOffset":49,"offset":41},"qualifiedName":"P::Leaf::x"},"symbol":"n5.0"},"declaration":"in port x:Pin;"},{"symbol":"n5.1","value":{"direction":"out","id":"n5.1","inherited":true,"name":"y","type":"Pin"},"binding":{"context":["probe.sysml@229:14","probe.sysml@195:16","probe.sysml@56:15"],"subject":{"id":"probe.sysml@56:15","origin":{"file":"probe.sysml","length":15,"nameLength":1,"nameOffset":65,"offset":56},"qualifiedName":"P::Leaf::y"},"symbol":"n5.1"},"declaration":"out port y:Pin;"},{"symbol":"n6","value":{"id":"n6","inherited":true,"kind":"part","name":"b","origin":{"endCol":171,"endLine":1,"file":"probe.sysml","startCol":155,"startLine":1},"parent":"n1","ports":[{"direction":"in","id":"n6.0","inherited":true,"name":"x","type":"Pin"},{"direction":"out","id":"n6.1","inherited":true,"name":"y","type":"Pin"}],"type":"Leaf"},"binding":{"context":["probe.sysml@229:14","probe.sysml@154:16"],"subject":{"id":"probe.sysml@154:16","origin":{"file":"probe.sysml","length":16,"nameLength":1,"nameOffset":163,"offset":154},"qualifiedName":"P::Mid::b"},"symbol":"n6"},"declaration":"part :>> b:Leaf;"},{"symbol":"n6.0","value":{"direction":"in","id":"n6.0","inherited":true,"name":"x","type":"Pin"},"binding":{"context":["probe.sysml@229:14","probe.sysml@154:16","probe.sysml@41:14"],"subject":{"id":"probe.sysml@41:14","origin":{"file":"probe.sysml","length":14,"nameLength":1,"nameOffset":49,"offset":41},"qualifiedName":"P::Leaf::x"},"symbol":"n6.0"},"declaration":"in port x:Pin;"},{"symbol":"n6.1","value":{"direction":"out","id":"n6.1","inherited":true,"name":"y","type":"Pin"},"binding":{"context":["probe.sysml@229:14","probe.sysml@154:16","probe.sysml@56:15"],"subject":{"id":"probe.sysml@56:15","origin":{"file":"probe.sysml","length":15,"nameLength":1,"nameOffset":65,"offset":56},"qualifiedName":"P::Leaf::y"},"symbol":"n6.1"},"declaration":"out port y:Pin;"},{"symbol":"n7","value":{"id":"n7","inherited":true,"kind":"part","name":"c","origin":{"endCol":129,"endLine":1,"file":"probe.sysml","startCol":117,"startLine":1},"parent":"n1","ports":[{"direction":"in","id":"n7.0","inherited":true,"name":"x","type":"Pin"},{"direction":"out","id":"n7.1","inherited":true,"name":"y","type":"Pin"}],"type":"Leaf"},"binding":{"context":["probe.sysml@229:14","probe.sysml@116:12"],"subject":{"id":"probe.sysml@116:12","origin":{"file":"probe.sysml","length":12,"nameLength":1,"nameOffset":121,"offset":116},"qualifiedName":"P::Base::c"},"symbol":"n7"},"declaration":"part c:Leaf;"},{"symbol":"n7.0","value":{"direction":"in","id":"n7.0","inherited":true,"name":"x","type":"Pin"},"binding":{"context":["probe.sysml@229:14","probe.sysml@116:12","probe.sysml@41:14"],"subject":{"id":"probe.sysml@41:14","origin":{"file":"probe.sysml","length":14,"nameLength":1,"nameOffset":49,"offset":41},"qualifiedName":"P::Leaf::x"},"symbol":"n7.0"},"declaration":"in port x:Pin;"},{"symbol":"n7.1","value":{"direction":"out","id":"n7.1","inherited":true,"name":"y","type":"Pin"},"binding":{"context":["probe.sysml@229:14","probe.sysml@116:12","probe.sysml@56:15"],"subject":{"id":"probe.sysml@56:15","origin":{"file":"probe.sysml","length":15,"nameLength":1,"nameOffset":65,"offset":56},"qualifiedName":"P::Leaf::y"},"symbol":"n7.1"},"declaration":"out port y:Pin;"}]`, `[{"id":"probe.sysml@214:14","origin":{"file":"probe.sysml","length":14,"nameLength":4,"nameOffset":219,"offset":214},"qualifiedName":"P::sat1"},{"id":"probe.sysml@229:14","origin":{"file":"probe.sysml","length":14,"nameLength":4,"nameOffset":234,"offset":229},"qualifiedName":"P::sat2"}]`, `{"artifact":{"edges":[],"kind":"interconnection","nodes":[{"id":"n0","kind":"part","name":"P::sat1","origin":{"endCol":229,"endLine":1,"file":"probe.sysml","startCol":215,"startLine":1},"type":"Top"}],"shown":["parameter-symbols","port-symbols","compartments","edge-labels","keywords","multiplicities","typings","qualifiers"],"stated":"Read-only exploration"},"correspondence":[{"context":["probe.sysml@214:14"],"subject":{"id":"probe.sysml@214:14","origin":{"file":"probe.sysml","length":14,"nameLength":4,"nameOffset":219,"offset":214},"qualifiedName":"P::sat1"},"symbol":"n0"}],"diagnostics":[],"modelHash":"ac1478935767b1ef71e2548573db6504b9c9094f7d8ce9f6e85d50565b74c6a5","renderStatus":"available","request":{"representation":"interconnection","scope":{"kind":"element","subject":"P::sat1"}},"schemaVersion":1,"selected":[{"id":"probe.sysml@214:14","origin":{"file":"probe.sysml","length":14,"nameLength":4,"nameOffset":219,"offset":214},"qualifiedName":"P::sat1"}],"status":"complete"}`, `[{"symbol":"n0","value":{"id":"n0","kind":"part","name":"P::sat1","origin":{"endCol":229,"endLine":1,"file":"probe.sysml","startCol":215,"startLine":1},"type":"Top"},"binding":{"context":["probe.sysml@214:14"],"subject":{"id":"probe.sysml@214:14","origin":{"file":"probe.sysml","length":14,"nameLength":4,"nameOffset":219,"offset":214},"qualifiedName":"P::sat1"},"symbol":"n0"},"declaration":"part sat1:Top;"},{"symbol":"n1","value":{"id":"n1","kind":"part","name":"P::sat2","origin":{"endCol":244,"endLine":1,"file":"probe.sysml","startCol":230,"startLine":1},"type":"Top"},"binding":{"context":["probe.sysml@229:14"],"subject":{"id":"probe.sysml@229:14","origin":{"file":"probe.sysml","length":14,"nameLength":4,"nameOffset":234,"offset":229},"qualifiedName":"P::sat2"},"symbol":"n1"},"declaration":"part sat2:Top;"}]`}, + // tree: exact source declarations, parents, types, ports, contexts and legacy ordinals. + {`tree`, `package P {part def Wheel; part def SmallWheel :> Wheel; part def Sat { part a:Wheel; part b:Wheel; part c:Wheel; } part sat1:Sat {part :>> a:SmallWheel; part :>> b:SmallWheel;} part sat2:Sat;} package V {private import StandardViewDefinitions::*; view v:GeneralView {expose P::sat1; expose P::sat2; expose P::sat1::c;} }`, `[{"symbol":"n0","value":{"id":"n0","kind":"part","name":"P::sat1","origin":{"endCol":178,"endLine":1,"file":"probe.sysml","startCol":117,"startLine":1},"type":"Sat"},"binding":{"context":["probe.sysml@116:61"],"subject":{"id":"probe.sysml@116:61","origin":{"file":"probe.sysml","length":61,"nameLength":4,"nameOffset":121,"offset":116},"qualifiedName":"P::sat1"},"symbol":"n0"},"declaration":"part sat1:Sat {part :>> a:SmallWheel; part :>> b:SmallWheel;}"},{"symbol":"n1","value":{"id":"n1","kind":"part","name":"a","origin":{"endCol":154,"endLine":1,"file":"probe.sysml","startCol":132,"startLine":1},"parent":"n0","type":"SmallWheel"},"binding":{"context":["probe.sysml@116:61","probe.sysml@131:22"],"subject":{"id":"probe.sysml@131:22","origin":{"file":"probe.sysml","length":22,"nameLength":1,"nameOffset":140,"offset":131},"qualifiedName":"P::sat1::a"},"symbol":"n1"},"declaration":"part :>> a:SmallWheel;"},{"symbol":"n2","value":{"id":"n2","kind":"part","name":"b","origin":{"endCol":177,"endLine":1,"file":"probe.sysml","startCol":155,"startLine":1},"parent":"n0","type":"SmallWheel"},"binding":{"context":["probe.sysml@116:61","probe.sysml@154:22"],"subject":{"id":"probe.sysml@154:22","origin":{"file":"probe.sysml","length":22,"nameLength":1,"nameOffset":163,"offset":154},"qualifiedName":"P::sat1::b"},"symbol":"n2"},"declaration":"part :>> b:SmallWheel;"},{"symbol":"n3","value":{"id":"n3","kind":"part","name":"P::sat2","origin":{"endCol":193,"endLine":1,"file":"probe.sysml","startCol":179,"startLine":1},"type":"Sat"},"binding":{"context":["probe.sysml@178:14"],"subject":{"id":"probe.sysml@178:14","origin":{"file":"probe.sysml","length":14,"nameLength":4,"nameOffset":183,"offset":178},"qualifiedName":"P::sat2"},"symbol":"n3"},"declaration":"part sat2:Sat;"},{"symbol":"edge:0","value":{"from":"n0","kind":"composition","label":"a","origin":{"endCol":154,"endLine":1,"file":"probe.sysml","startCol":132,"startLine":1},"to":"n1"},"binding":{"context":[],"subject":{"id":"probe.sysml@131:22","origin":{"file":"probe.sysml","length":22,"nameLength":1,"nameOffset":140,"offset":131},"qualifiedName":"P::sat1::a"},"symbol":"edge:0"},"declaration":"part :>> a:SmallWheel;"},{"symbol":"edge:1","value":{"from":"n0","kind":"composition","label":"b","origin":{"endCol":177,"endLine":1,"file":"probe.sysml","startCol":155,"startLine":1},"to":"n2"},"binding":{"context":[],"subject":{"id":"probe.sysml@154:22","origin":{"file":"probe.sysml","length":22,"nameLength":1,"nameOffset":163,"offset":154},"qualifiedName":"P::sat1::b"},"symbol":"edge:1"},"declaration":"part :>> b:SmallWheel;"}]`, `[{"id":"probe.sysml@116:61","origin":{"file":"probe.sysml","length":61,"nameLength":4,"nameOffset":121,"offset":116},"qualifiedName":"P::sat1"},{"id":"probe.sysml@178:14","origin":{"file":"probe.sysml","length":14,"nameLength":4,"nameOffset":183,"offset":178},"qualifiedName":"P::sat2"},{"id":"probe.sysml@100:13","origin":{"file":"probe.sysml","length":13,"nameLength":1,"nameOffset":105,"offset":100},"qualifiedName":"P::Sat::c"}]`, `{"artifact":{"edges":[],"kind":"interconnection","nodes":[{"id":"n0","kind":"part","name":"P::sat1","origin":{"endCol":178,"endLine":1,"file":"probe.sysml","startCol":117,"startLine":1},"type":"Sat"},{"id":"n1","kind":"part","name":"a","origin":{"endCol":154,"endLine":1,"file":"probe.sysml","startCol":132,"startLine":1},"parent":"n0","type":"SmallWheel"},{"id":"n2","kind":"part","name":"b","origin":{"endCol":177,"endLine":1,"file":"probe.sysml","startCol":155,"startLine":1},"parent":"n0","type":"SmallWheel"}],"shown":["parameter-symbols","port-symbols","compartments","edge-labels","keywords","multiplicities","typings","qualifiers"],"stated":"Read-only exploration"},"correspondence":[{"context":["probe.sysml@116:61"],"subject":{"id":"probe.sysml@116:61","origin":{"file":"probe.sysml","length":61,"nameLength":4,"nameOffset":121,"offset":116},"qualifiedName":"P::sat1"},"symbol":"n0"},{"context":["probe.sysml@116:61","probe.sysml@131:22"],"subject":{"id":"probe.sysml@131:22","origin":{"file":"probe.sysml","length":22,"nameLength":1,"nameOffset":140,"offset":131},"qualifiedName":"P::sat1::a"},"symbol":"n1"},{"context":["probe.sysml@116:61","probe.sysml@154:22"],"subject":{"id":"probe.sysml@154:22","origin":{"file":"probe.sysml","length":22,"nameLength":1,"nameOffset":163,"offset":154},"qualifiedName":"P::sat1::b"},"symbol":"n2"}],"diagnostics":[],"modelHash":"2bd2edc41965dab4babddf49d30b77f33452b88dba42fe7091347658f6be471c","renderStatus":"available","request":{"representation":"interconnection","scope":{"kind":"element","subject":"P::sat1"}},"schemaVersion":1,"selected":[{"id":"probe.sysml@116:61","origin":{"file":"probe.sysml","length":61,"nameLength":4,"nameOffset":121,"offset":116},"qualifiedName":"P::sat1"}],"status":"complete"}`, `[{"symbol":"n0","value":{"id":"n0","kind":"part","name":"P::sat1","origin":{"endCol":178,"endLine":1,"file":"probe.sysml","startCol":117,"startLine":1},"type":"Sat"},"binding":{"context":["probe.sysml@116:61"],"subject":{"id":"probe.sysml@116:61","origin":{"file":"probe.sysml","length":61,"nameLength":4,"nameOffset":121,"offset":116},"qualifiedName":"P::sat1"},"symbol":"n0"},"declaration":"part sat1:Sat {part :>> a:SmallWheel; part :>> b:SmallWheel;}"},{"symbol":"n1","value":{"id":"n1","kind":"part","name":"a","origin":{"endCol":154,"endLine":1,"file":"probe.sysml","startCol":132,"startLine":1},"parent":"n0","type":"SmallWheel"},"binding":{"context":["probe.sysml@116:61","probe.sysml@131:22"],"subject":{"id":"probe.sysml@131:22","origin":{"file":"probe.sysml","length":22,"nameLength":1,"nameOffset":140,"offset":131},"qualifiedName":"P::sat1::a"},"symbol":"n1"},"declaration":"part :>> a:SmallWheel;"},{"symbol":"n2","value":{"id":"n2","kind":"part","name":"b","origin":{"endCol":177,"endLine":1,"file":"probe.sysml","startCol":155,"startLine":1},"parent":"n0","type":"SmallWheel"},"binding":{"context":["probe.sysml@116:61","probe.sysml@154:22"],"subject":{"id":"probe.sysml@154:22","origin":{"file":"probe.sysml","length":22,"nameLength":1,"nameOffset":163,"offset":154},"qualifiedName":"P::sat1::b"},"symbol":"n2"},"declaration":"part :>> b:SmallWheel;"},{"symbol":"n3","value":{"id":"n3","kind":"part","name":"P::sat2","origin":{"endCol":193,"endLine":1,"file":"probe.sysml","startCol":179,"startLine":1},"type":"Sat"},"binding":{"context":["probe.sysml@178:14"],"subject":{"id":"probe.sysml@178:14","origin":{"file":"probe.sysml","length":14,"nameLength":4,"nameOffset":183,"offset":178},"qualifiedName":"P::sat2"},"symbol":"n3"},"declaration":"part sat2:Sat;"}]`}, + // zero: exact source declarations, parents, types, ports, contexts and legacy ordinals. + {`zero`, `package P {part def Wheel; part def SmallWheel :> Wheel; part def Sat { part a:Wheel; part b:Wheel; part c:Wheel; } part sat1:Sat {} part sat2:Sat;} package V {private import StandardViewDefinitions::*; view v:InterconnectionView {expose P::sat1; expose P::sat2; expose P::sat1::c;} }`, `[{"symbol":"n0","value":{"id":"n0","kind":"part","name":"P::sat1","origin":{"endCol":133,"endLine":1,"file":"probe.sysml","startCol":117,"startLine":1},"type":"Sat"},"binding":{"context":["probe.sysml@116:16"],"subject":{"id":"probe.sysml@116:16","origin":{"file":"probe.sysml","length":16,"nameLength":4,"nameOffset":121,"offset":116},"qualifiedName":"P::sat1"},"symbol":"n0"},"declaration":"part sat1:Sat {}"},{"symbol":"n2","value":{"id":"n2","inherited":true,"kind":"part","name":"a","origin":{"endCol":86,"endLine":1,"file":"probe.sysml","startCol":73,"startLine":1},"parent":"n0","type":"Wheel"},"binding":{"context":["probe.sysml@116:16","probe.sysml@72:13"],"subject":{"id":"probe.sysml@72:13","origin":{"file":"probe.sysml","length":13,"nameLength":1,"nameOffset":77,"offset":72},"qualifiedName":"P::Sat::a"},"symbol":"n2"},"declaration":"part a:Wheel;"},{"symbol":"n3","value":{"id":"n3","inherited":true,"kind":"part","name":"b","origin":{"endCol":100,"endLine":1,"file":"probe.sysml","startCol":87,"startLine":1},"parent":"n0","type":"Wheel"},"binding":{"context":["probe.sysml@116:16","probe.sysml@86:13"],"subject":{"id":"probe.sysml@86:13","origin":{"file":"probe.sysml","length":13,"nameLength":1,"nameOffset":91,"offset":86},"qualifiedName":"P::Sat::b"},"symbol":"n3"},"declaration":"part b:Wheel;"},{"symbol":"n4","value":{"id":"n4","inherited":true,"kind":"part","name":"c","origin":{"endCol":114,"endLine":1,"file":"probe.sysml","startCol":101,"startLine":1},"parent":"n0","type":"Wheel"},"binding":{"context":["probe.sysml@116:16","probe.sysml@100:13"],"subject":{"id":"probe.sysml@100:13","origin":{"file":"probe.sysml","length":13,"nameLength":1,"nameOffset":105,"offset":100},"qualifiedName":"P::Sat::c"},"symbol":"n4"},"declaration":"part c:Wheel;"},{"symbol":"n1","value":{"id":"n1","kind":"part","name":"P::sat2","origin":{"endCol":148,"endLine":1,"file":"probe.sysml","startCol":134,"startLine":1},"type":"Sat"},"binding":{"context":["probe.sysml@133:14"],"subject":{"id":"probe.sysml@133:14","origin":{"file":"probe.sysml","length":14,"nameLength":4,"nameOffset":138,"offset":133},"qualifiedName":"P::sat2"},"symbol":"n1"},"declaration":"part sat2:Sat;"},{"symbol":"n5","value":{"id":"n5","inherited":true,"kind":"part","name":"a","origin":{"endCol":86,"endLine":1,"file":"probe.sysml","startCol":73,"startLine":1},"parent":"n1","type":"Wheel"},"binding":{"context":["probe.sysml@133:14","probe.sysml@72:13"],"subject":{"id":"probe.sysml@72:13","origin":{"file":"probe.sysml","length":13,"nameLength":1,"nameOffset":77,"offset":72},"qualifiedName":"P::Sat::a"},"symbol":"n5"},"declaration":"part a:Wheel;"},{"symbol":"n6","value":{"id":"n6","inherited":true,"kind":"part","name":"b","origin":{"endCol":100,"endLine":1,"file":"probe.sysml","startCol":87,"startLine":1},"parent":"n1","type":"Wheel"},"binding":{"context":["probe.sysml@133:14","probe.sysml@86:13"],"subject":{"id":"probe.sysml@86:13","origin":{"file":"probe.sysml","length":13,"nameLength":1,"nameOffset":91,"offset":86},"qualifiedName":"P::Sat::b"},"symbol":"n6"},"declaration":"part b:Wheel;"},{"symbol":"n7","value":{"id":"n7","inherited":true,"kind":"part","name":"c","origin":{"endCol":114,"endLine":1,"file":"probe.sysml","startCol":101,"startLine":1},"parent":"n1","type":"Wheel"},"binding":{"context":["probe.sysml@133:14","probe.sysml@100:13"],"subject":{"id":"probe.sysml@100:13","origin":{"file":"probe.sysml","length":13,"nameLength":1,"nameOffset":105,"offset":100},"qualifiedName":"P::Sat::c"},"symbol":"n7"},"declaration":"part c:Wheel;"}]`, `[{"id":"probe.sysml@116:16","origin":{"file":"probe.sysml","length":16,"nameLength":4,"nameOffset":121,"offset":116},"qualifiedName":"P::sat1"},{"id":"probe.sysml@133:14","origin":{"file":"probe.sysml","length":14,"nameLength":4,"nameOffset":138,"offset":133},"qualifiedName":"P::sat2"},{"id":"probe.sysml@100:13","origin":{"file":"probe.sysml","length":13,"nameLength":1,"nameOffset":105,"offset":100},"qualifiedName":"P::Sat::c"}]`, `{"artifact":{"edges":[],"kind":"interconnection","nodes":[{"id":"n0","kind":"part","name":"P::sat1","origin":{"endCol":133,"endLine":1,"file":"probe.sysml","startCol":117,"startLine":1},"type":"Sat"}],"shown":["parameter-symbols","port-symbols","compartments","edge-labels","keywords","multiplicities","typings","qualifiers"],"stated":"Read-only exploration"},"correspondence":[{"context":["probe.sysml@116:16"],"subject":{"id":"probe.sysml@116:16","origin":{"file":"probe.sysml","length":16,"nameLength":4,"nameOffset":121,"offset":116},"qualifiedName":"P::sat1"},"symbol":"n0"}],"diagnostics":[],"modelHash":"bbb162884878be6c369a43fa3cb2d29712ccacc3b5df86a0c36250d0b4e3d787","renderStatus":"available","request":{"representation":"interconnection","scope":{"kind":"element","subject":"P::sat1"}},"schemaVersion":1,"selected":[{"id":"probe.sysml@116:16","origin":{"file":"probe.sysml","length":16,"nameLength":4,"nameOffset":121,"offset":116},"qualifiedName":"P::sat1"}],"status":"complete"}`, `[{"symbol":"n0","value":{"id":"n0","kind":"part","name":"P::sat1","origin":{"endCol":133,"endLine":1,"file":"probe.sysml","startCol":117,"startLine":1},"type":"Sat"},"binding":{"context":["probe.sysml@116:16"],"subject":{"id":"probe.sysml@116:16","origin":{"file":"probe.sysml","length":16,"nameLength":4,"nameOffset":121,"offset":116},"qualifiedName":"P::sat1"},"symbol":"n0"},"declaration":"part sat1:Sat {}"},{"symbol":"n1","value":{"id":"n1","kind":"part","name":"P::sat2","origin":{"endCol":148,"endLine":1,"file":"probe.sysml","startCol":134,"startLine":1},"type":"Sat"},"binding":{"context":["probe.sysml@133:14"],"subject":{"id":"probe.sysml@133:14","origin":{"file":"probe.sysml","length":14,"nameLength":4,"nameOffset":138,"offset":133},"qualifiedName":"P::sat2"},"symbol":"n1"},"declaration":"part sat2:Sat;"}]`}, +} diff --git a/examples/README.md b/examples/README.md index 4ab5468..311faf2 100644 --- a/examples/README.md +++ b/examples/README.md @@ -245,13 +245,13 @@ Current gaps: Conjugated port type labels are retained; effective payload direct - Show electrical, pneumatic, cooling and service assemblies with authored cross-subsystem connections. -Current gaps: Conjugated port type labels are retained; effective payload directions and attribute compartments are not expanded. Nested typed assemblies remain component-level nodes; deeper connection endpoints are shown at those nodes rather than expanded internal ports. +Current gaps: Conjugated port type labels are retained; effective payload directions and evaluated attribute values are not expanded. **CompressorViews::installation** — engine artifact: `interconnection`; app: **partial**. - Keep two installed compressor usages, tools and human roles distinct. -Current gaps: Conjugated port type labels are retained; effective payload directions and attribute compartments are not expanded. Nested typed assemblies remain component-level nodes; deeper connection endpoints are shown at those nodes rather than expanded internal ports. +Current gaps: Conjugated port type labels are retained; effective payload directions and evaluated attribute values are not expanded. **CompressorViews::pneumatics** — engine artifact: `interconnection`; app: **partial**. diff --git a/examples/air-compressor/README.md b/examples/air-compressor/README.md index f931a50..f45f9fd 100644 --- a/examples/air-compressor/README.md +++ b/examples/air-compressor/README.md @@ -18,7 +18,7 @@ Loading this example opens **overview** in Diagram mode. Choose **pneumatics** f ## Views and current support -- **overview**, **structure**, **installation**, **pneumatics**: structural diagrams. Nested typed assemblies can remain component-level nodes, with deeper links collapsed to those nodes. Conjugated type labels are retained, but payload expansion and effective conjugated directions remain incomplete. +- **overview**, **structure**, **installation**, **pneumatics**: structural diagrams. Authored structural views expand inherited members within native traversal bounds and attach declared links to their actual visible ports. Conjugated type labels are retained, but payload expansion and effective conjugated directions remain incomplete. - **definitions**: part definitions, specialization, typing and composition with generic relationship lines. - **requirements**: a GeneralView tree of the specification and applied subjects; dedicated requirement notation and satisfaction/verification trace links are not drawn. - **actions**, **maintenance**, **states**: bounded native action/state diagrams with declared order/transitions; no execution. diff --git a/examples/catalog.json b/examples/catalog.json index 9e52fbd..8c3c695 100644 --- a/examples/catalog.json +++ b/examples/catalog.json @@ -683,8 +683,18 @@ "nodeNames": [ "CompressorSystem::Compressor", "electrical", + "supply", + "controller", + "motor", "pneumatic", + "pump", + "tank", + "regulator", + "relief", + "pressureSensor", "cooling", + "fan", + "temperature", "serviceConsole" ], "edgeKinds": [ @@ -699,8 +709,7 @@ "Show electrical, pneumatic, cooling and service assemblies with authored cross-subsystem connections." ], "gaps": [ - "Conjugated port type labels are retained; effective payload directions and attribute compartments are not expanded.", - "Nested typed assemblies remain component-level nodes; deeper connection endpoints are shown at those nodes rather than expanded internal ports." + "Conjugated port type labels are retained; effective payload directions and evaluated attribute values are not expanded." ], "notices": [] }, @@ -711,7 +720,35 @@ "nodeNames": [ "CompressorSystem::Workshop", "compressor", + "electrical", + "supply", + "controller", + "motor", + "pneumatic", + "pump", + "tank", + "regulator", + "relief", + "pressureSensor", + "cooling", + "fan", + "temperature", + "serviceConsole", "spareCompressor", + "electrical", + "supply", + "controller", + "motor", + "pneumatic", + "pump", + "tank", + "regulator", + "relief", + "pressureSensor", + "cooling", + "fan", + "temperature", + "serviceConsole", "tool", "blowGun", "operator", @@ -722,8 +759,7 @@ "Keep two installed compressor usages, tools and human roles distinct." ], "gaps": [ - "Conjugated port type labels are retained; effective payload directions and attribute compartments are not expanded.", - "Nested typed assemblies remain component-level nodes; deeper connection endpoints are shown at those nodes rather than expanded internal ports." + "Conjugated port type labels are retained; effective payload directions and evaluated attribute values are not expanded." ], "notices": [] }, diff --git a/src/lib/workbench/diagrams/Diagram.tsx b/src/lib/workbench/diagrams/Diagram.tsx index a6aedbe..5b9e81c 100644 --- a/src/lib/workbench/diagrams/Diagram.tsx +++ b/src/lib/workbench/diagrams/Diagram.tsx @@ -542,15 +542,15 @@ export function Diagram({ edgeTypes={edgeTypes} onNodesChange={(changes) => { setNodes((current) => applyNodeChanges(changes, current)); + for (const change of changes) + if (change.type === 'select' && change.selected) + onSelect(change.id); if ( ['requirement', 'action', 'state'].includes( view?.artifact?.kind ?? '', ) || exploration ) { - for (const change of changes) - if (change.type === 'select' && change.selected) - onSelect(change.id); if ( !exploration && !disabled && diff --git a/src/lib/workbench/diagrams/structure.test.ts b/src/lib/workbench/diagrams/structure.test.ts index 0f922de..742519d 100644 --- a/src/lib/workbench/diagrams/structure.test.ts +++ b/src/lib/workbench/diagrams/structure.test.ts @@ -145,3 +145,178 @@ it('still rejects positions meaningfully beyond an owner boundary', async () => expect(restored.notice).toContain('outside the current owner'); expect(absolutePositions(restored.nodes).left).not.toEqual(saved.left); }); + +it('places newly visible children clear of unchanged saved siblings', async () => { + const original: ViewArtifact = { + kind: 'interconnection', + nodes: [ + { id: 'root', name: 'usage', kind: 'part' }, + { id: 'a', name: 'a', kind: 'part', parent: 'root' }, + { id: 'b', name: 'b', kind: 'part', parent: 'root' }, + ], + edges: [], + }; + const saved = absolutePositions((await layoutStructure(original, {})).nodes); + const expanded: ViewArtifact = { + ...original, + nodes: [ + original.nodes[0]!, + { id: 'c', name: 'c', kind: 'part', parent: 'root', inherited: true }, + ...original.nodes.slice(1), + ], + }; + const result = await layoutStructure(expanded, saved); + const positions = absolutePositions(result.nodes); + for (const [id, position] of Object.entries(saved)) + expect(positions[id]).toEqual(position); + const added = result.nodes.find((node) => node.id === 'c')!; + const root = result.nodes.find((node) => node.id === 'root')!; + for (const id of ['a', 'b']) { + const sibling = result.nodes.find((node) => node.id === id)!; + expect( + added.position.x + Number(added.style!.width) <= sibling.position.x || + sibling.position.x + Number(sibling.style!.width) <= added.position.x || + added.position.y + Number(added.style!.height) <= sibling.position.y || + sibling.position.y + Number(sibling.style!.height) <= added.position.y, + ).toBe(true); + } + expect(added.position.y + Number(added.style!.height)).toBeLessThan( + Number(root.style!.height), + ); +}); + +it('anchors an unsaved ancestor of a restored child while placing an automatic sibling', async () => { + const nested: ViewArtifact = { + kind: 'interconnection', + nodes: [ + { id: 'root', name: 'root', kind: 'part' }, + { id: 'branch', name: 'branch', kind: 'part', parent: 'root' }, + { id: 'leaf', name: 'leaf', kind: 'part', parent: 'branch' }, + { id: 'saved', name: 'saved', kind: 'part', parent: 'root' }, + { id: 'new', name: 'new', kind: 'part', parent: 'root', inherited: true }, + ], + edges: [], + }; + const automatic = await layoutStructure(nested, {}); + const original = absolutePositions(automatic.nodes); + const stored = { leaf: original.leaf!, saved: original.new! }; + const result = await layoutStructure(nested, stored); + const actual = absolutePositions(result.nodes); + expect(actual.leaf).toEqual(stored.leaf); + expect(actual.saved).toEqual(stored.saved); + expect(actual.branch).toEqual(original.branch); + const root = result.nodes.find((node) => node.id === 'root')!; + const added = result.nodes.find((node) => node.id === 'new')!; + const occupied = result.nodes.find((node) => node.id === 'saved')!; + expect( + added.position.y + Number(added.style!.height) <= occupied.position.y || + occupied.position.y + Number(occupied.style!.height) <= + added.position.y || + added.position.x + Number(added.style!.width) <= occupied.position.x || + occupied.position.x + Number(occupied.style!.width) <= added.position.x, + ).toBe(true); + expect(added.position.y + Number(added.style!.height)).toBeLessThan( + Number(root.style!.height), + ); + expect(added.extent).toEqual([ + [32, 86], + [Number(root.style!.width) - 32, Number(root.style!.height) - 32], + ]); +}); + +it('reuses a vacant slot within unchanged owner bounds and drag extents', async () => { + const simple: ViewArtifact = { + kind: 'interconnection', + nodes: [ + { id: 'root', name: 'root', kind: 'part' }, + { id: 'saved', name: 'saved', kind: 'part', parent: 'root' }, + { id: 'new', name: 'new', kind: 'part', parent: 'root', inherited: true }, + ], + edges: [], + }; + const automatic = await layoutStructure(simple, {}); + const original = absolutePositions(automatic.nodes); + const result = await layoutStructure(simple, { saved: original.new! }); + const actual = absolutePositions(result.nodes); + expect(actual.saved).toEqual(original.new); + const root = result.nodes.find((node) => node.id === 'root')!; + const added = result.nodes.find((node) => node.id === 'new')!; + expect(root.style).toEqual(automatic.nodes[0]!.style); + expect(added.position.y).toBeLessThan( + result.nodes.find((node) => node.id === 'saved')!.position.y, + ); + expect(added.position.y + Number(added.style!.height)).toBeLessThan( + Number(root.style!.height), + ); + expect(added.extent).toEqual([ + [32, 86], + [Number(root.style!.width) - 32, Number(root.style!.height) - 32], + ]); +}); + +it('reopens every position published by partial restoration without discarding geometry', async () => { + const simple: ViewArtifact = { + kind: 'interconnection', + nodes: [ + { id: 'root', name: 'root', kind: 'part' }, + { id: 'saved', name: 'saved', kind: 'part', parent: 'root' }, + { id: 'new', name: 'new', kind: 'part', parent: 'root', inherited: true }, + ], + edges: [], + }; + const automatic = await layoutStructure(simple, {}); + const original = absolutePositions(automatic.nodes); + const first = await layoutStructure(simple, { saved: original.new! }); + const published = absolutePositions(first.nodes); + const reopened = await layoutStructure(simple, published); + expect(absolutePositions(reopened.nodes)).toEqual(published); + expect(reopened.notice).not.toContain('outside the current owner'); +}); + +it('warns when fixed saved geometry leaves no bounded slot instead of publishing larger drag bounds', async () => { + const simple: ViewArtifact = { + kind: 'interconnection', + nodes: [ + { id: 'root', name: 'root', kind: 'part' }, + { id: 'saved', name: 'saved', kind: 'part', parent: 'root' }, + { id: 'new', name: 'new', kind: 'part', parent: 'root', inherited: true }, + ], + edges: [], + }; + const automatic = await layoutStructure(simple, {}); + const original = absolutePositions(automatic.nodes); + const owner = automatic.nodes[0]!; + const child = automatic.nodes.find((node) => node.id === 'saved')!; + const middle = { + x: original.saved!.x, + y: + original.root!.y + + 86 + + (Number(owner.style!.height) - 86 - 32 - Number(child.style!.height)) / 2, + }; + const result = await layoutStructure(simple, { saved: middle }); + expect(absolutePositions(result.nodes).saved).toEqual(middle); + expect(result.nodes[0]!.style).toEqual(owner.style); + expect(result.notice).toContain('automatic positions overlap saved nodes'); + const published = absolutePositions(result.nodes); + expect( + absolutePositions((await layoutStructure(simple, published)).nodes), + ).toEqual(published); +}); + +it('keeps native placement of newly authored owned nodes outside inherited-member restoration', async () => { + const owned: ViewArtifact = { + kind: 'interconnection', + nodes: [ + { id: 'root', name: 'root', kind: 'part' }, + { id: 'saved', name: 'saved', kind: 'part', parent: 'root' }, + { id: 'new', name: 'new', kind: 'part', parent: 'root' }, + ], + edges: [], + }; + const automatic = await layoutStructure(owned, {}); + const original = absolutePositions(automatic.nodes); + const result = await layoutStructure(owned, { saved: original.new! }); + expect(absolutePositions(result.nodes).new).toEqual(original.new); + expect(result.notice).toBe(''); +}); diff --git a/src/lib/workbench/diagrams/structure.ts b/src/lib/workbench/diagrams/structure.ts index a501bba..f1a7e08 100644 --- a/src/lib/workbench/diagrams/structure.ts +++ b/src/lib/workbench/diagrams/structure.ts @@ -204,6 +204,7 @@ export async function layoutStructure( }); const nodes: StructureNode[] = []; const absolute: Record = {}; + const restored = new Set(); let discarded = false; function project(graph: ElkNode, parent?: StructureNode) { const item = artifact.nodes.find((item) => item.id === graph.id)!; @@ -234,9 +235,10 @@ export async function layoutStructure( candidate.y >= min.y - boundaryEpsilon && candidate.x <= max.x + boundaryEpsilon && candidate.y <= max.y + boundaryEpsilon) - ) + ) { position = candidate; - else discarded = true; + restored.add(item.id); + } else discarded = true; } const compound = !!graph.children?.length; const node: StructureNode = { @@ -276,6 +278,72 @@ export async function layoutStructure( for (const child of graph.children ?? []) project(child, node); } for (const root of layout.children ?? []) project(root); + // Newly retained inherited occurrences may share a legacy saved child's + // automatic slot. Keep ordinary authored-node placement and canonical bounds: + // version 1 saves positions, not larger drag extents. + let placementOverlap = false; + if (!behavior && restored.size) { + function separate(parent?: StructureNode): boolean { + const siblings = nodes.filter((node) => node.parentId === parent?.id); + const anchored = new Set( + siblings.filter((node) => separate(node) || restored.has(node.id)), + ); + const automaticInherited = new Set( + siblings.filter( + (node) => !anchored.has(node) && node.data.item.inherited, + ), + ); + const occupied = siblings.filter((node) => !automaticInherited.has(node)); + if (occupied.length && automaticInherited.size) { + for (const node of automaticInherited) { + const nodeWidth = Number(node.style!.width); + const nodeHeight = Number(node.style!.height); + const min = parent + ? { x: padding, y: contentTop(parent.data.item, dense) } + : { x: 0, y: 0 }; + const max = parent + ? { + x: Number(parent.style!.width) - padding - nodeWidth, + y: Number(parent.style!.height) - padding - nodeHeight, + } + : { x: Infinity, y: Infinity }; + const xs = new Set([node.position.x, min.x]); + const ys = new Set([node.position.y, min.y]); + for (const other of occupied) { + const right = other.position.x + Number(other.style!.width); + const bottom = other.position.y + Number(other.style!.height); + xs.add(right + padding); + xs.add(right); + ys.add(bottom + padding); + ys.add(bottom); + } + const candidates = [...ys].flatMap((y) => + [...xs].map((x) => ({ x, y })), + ); + const free = candidates.find( + (point) => + point.x >= min.x && + point.y >= min.y && + point.x <= max.x + boundaryEpsilon && + point.y <= max.y + boundaryEpsilon && + occupied.every( + (other) => + point.x + nodeWidth <= other.position.x || + other.position.x + Number(other.style!.width) <= point.x || + point.y + nodeHeight <= other.position.y || + other.position.y + Number(other.style!.height) <= point.y, + ), + ); + if (free) node.position = free; + else placementOverlap = true; + occupied.push(node); + } + } + return anchored.size > 0; + } + separate(); + Object.assign(absolute, absolutePositions(nodes)); + } const edges: Edge[] = artifact.edges.map((edge, index) => ({ id: `edge:${index}`, source: edge.from, @@ -336,8 +404,15 @@ export async function layoutStructure( return { nodes, edges, - notice: discarded - ? 'Some saved positions were arranged again because they fall outside the current owner.' - : '', + notice: [ + discarded + ? 'Some saved positions were arranged again because they fall outside the current owner.' + : '', + placementOverlap + ? 'Some automatic positions overlap saved nodes. Move saved nodes to make room.' + : '', + ] + .filter(Boolean) + .join(' '), }; } diff --git a/src/lib/workbench/shell/Workbench.tsx b/src/lib/workbench/shell/Workbench.tsx index 0b5264e..8060dcd 100644 --- a/src/lib/workbench/shell/Workbench.tsx +++ b/src/lib/workbench/shell/Workbench.tsx @@ -131,7 +131,8 @@ export function Workbench({ setOrigin('Browser recovery'); setEverOpened(true); setLauncherOpen(false); - await refreshView(session, entry.activeView ?? undefined); + // Recovery is accepted; visualization failures belong to Workbench actions. + await run(() => refreshView(session, entry.activeView ?? undefined)); } const element = state.model?.elements.find((item) => item.id === selected); // Adapt only for shared rendering/selection; workspace state and saved views stay distinct. diff --git a/src/lib/workspace/views/schema.ts b/src/lib/workspace/views/schema.ts index cbc54b8..0026940 100644 --- a/src/lib/workspace/views/schema.ts +++ b/src/lib/workspace/views/schema.ts @@ -38,6 +38,7 @@ export const ArtifactNodeSchema = Type.Object({ parent: Type.Optional(Type.String()), ports: Type.Optional(Type.Array(ArtifactPortSchema)), reference: Type.Optional(Type.Boolean()), + inherited: Type.Optional(Type.Boolean()), direction: Type.Optional(DirectionSchema), x: Type.Optional(Type.Number()), y: Type.Optional(Type.Number()), diff --git a/tests/e2e/authoring.spec.ts b/tests/e2e/authoring.spec.ts index ec55b8f..7b6b0c1 100644 --- a/tests/e2e/authoring.spec.ts +++ b/tests/e2e/authoring.spec.ts @@ -11,6 +11,7 @@ import { semanticPositions, diagramSymbol, expectRenderedPositions, + diagramState, } from './support'; test('@critical creates a typed part and connects its actual inherited usage ports through reviewed source transactions', async ({ @@ -196,6 +197,10 @@ test('@critical creates a typed part and connects its actual inherited usage por ).toHaveSource(text); } const saved = await downloadProject(page); + await testInfo.attach('authored-portable-project', { + body: saved.buffer, + contentType: 'application/json', + }); await page.reload(); await startExample(page); await page.locator('input[type=file]').setInputFiles({ @@ -208,6 +213,19 @@ test('@critical creates a typed part and connects its actual inherited usage por ).toHaveSource(connected.documents[0]!.text); for (const view of ['structure', 'devices']) { await openView(page, view); + await testInfo.attach(`reopened-${view}`, { + body: JSON.stringify( + { + evaluation: evaluations.findLast( + (item) => item.view === 'AssemblyViews::' + view, + ), + rendered: await diagramState(page), + }, + null, + 2, + ), + contentType: 'application/json', + }); await expectRenderedPositions(page, saved.bundle, 'AssemblyViews::' + view); } const reopened = (await downloadProject(page)).bundle; diff --git a/tests/e2e/engine-upgrade.spec.ts b/tests/e2e/engine-upgrade.spec.ts index 219b7a3..b101b71 100644 --- a/tests/e2e/engine-upgrade.spec.ts +++ b/tests/e2e/engine-upgrade.spec.ts @@ -64,6 +64,24 @@ test('retains 0.10 declaration-to-position identities across structural, require await import('../../src/lib/engines/workers/schema'); type Correspondence = import('../../src/lib/workspace/views').EvaluatedView['correspondence']; + // #18 intentionally adds inherited descendants and resolves declared edges + // to their actual ports. Retain every original identity in the old fixture; + // assert the reviewed additions separately instead of replacing that fixture. + const inherited = JSON.parse( + await readFile( + new URL( + '../fixtures/inherited-members-compressor-correspondence.json', + import.meta.url, + ), + 'utf8', + ), + ) as Record< + string, + { + correspondence: Correspondence; + edges: Record[]; + } + >; const fixture = JSON.parse( await readFile( new URL( @@ -148,10 +166,18 @@ test('retains 0.10 declaration-to-position identities across structural, require ); expect(result.status, view.view).toBe('complete'); expect(result.artifact?.kind, view.view).toBe(view.kind); + const current = result.correspondence.filter( + (c) => !c.symbol.startsWith('edge:'), + ); + const legacySymbols = new Set(view.correspondence.map((c) => c.symbol)); expect( - result.correspondence.filter((c) => !c.symbol.startsWith('edge:')), + current.filter((c) => legacySymbols.has(c.symbol)), view.view, ).toEqual(view.correspondence); + expect( + current.filter((c) => !legacySymbols.has(c.symbol)), + view.view + ' inherited additions', + ).toEqual(inherited[view.view]?.correspondence ?? []); if (view.edges) { const pick = (edge: Record) => Object.fromEntries( @@ -168,7 +194,7 @@ test('retains 0.10 declaration-to-position identities across structural, require expect( sort(result.artifact!.edges.map((edge) => pick({ ...edge }))), view.view + ' declared endpoints', - ).toEqual(sort(view.edges.map(pick))); + ).toEqual(sort((inherited[view.view]?.edges ?? view.edges).map(pick))); } } } diff --git a/tests/e2e/inherited-members.spec.ts b/tests/e2e/inherited-members.spec.ts new file mode 100644 index 0000000..3e243f7 --- /dev/null +++ b/tests/e2e/inherited-members.spec.ts @@ -0,0 +1,286 @@ +import { readFile } from 'node:fs/promises'; +import type { Page } from '@playwright/test'; +import type { EvaluatedView } from '../../src/lib/workspace/views'; +import type { ProjectBundle } from '../../src/lib/workspace/persistence'; +import { + test, + expect, + observeEvaluations, + diagramSymbol, + moveNode, + nodePosition, + expectRenderedPositions, + expectSeparateSiblings, + semanticPositions, + copySourceSelection, + downloadProject, + openView, + fillSource, +} from './support'; + +// Captured with the real old prepared engine (WASM SHA-256 +// 9149b23d80b7fd0b6935b374f0e9102c270bc5f7d595b43a8186d01dece7b663). +// Do not regenerate this fixture with the candidate engine. +const legacyBuffer = await readFile( + new URL( + '../fixtures/inherited-members-legacy.sysml-project.json', + import.meta.url, + ), +); +const legacy = JSON.parse(legacyBuffer.toString()) as ProjectBundle; +const both = 'Views::both'; + +async function importProject(page: Page, buffer: Buffer) { + await page.goto('/app'); + // Each caller uses a new context: no live work or recovery may be discarded. + await expect( + page.getByRole('region', { name: 'Saved recovery', exact: true }), + ).toBeHidden(); + await page.locator('input[type=file]').setInputFiles({ + name: 'inherited.sysml-project.json', + mimeType: 'application/json', + buffer, + }); + await expect( + page.getByRole('combobox', { name: 'Named view', exact: true }), + ).toHaveValue(both); + await expect( + page.getByRole('button', { name: 'Download project', exact: true }), + ).toBeEnabled(); + await page.getByRole('button', { name: 'Split', exact: true }).click(); +} + +async function evaluated(evaluations: EvaluatedView[], view = both) { + await expect + .poll(() => evaluations.some((item) => item.view === view)) + .toBe(true); + const result = evaluations.findLast((item) => item.view === view)!; + expect(result.status).toBe('complete'); + expect(result.renderStatus).toBe('available'); + expect(result.artifact?.kind).toBe('interconnection'); + return result; +} + +function occurrence(view: EvaluatedView, owner: string, member: string) { + const root = view.correspondence.filter( + (item) => item.subject.qualifiedName === owner, + ); + expect(root).toHaveLength(1); + const declaration = + owner === 'Demo::sat1' && member !== 'c' + ? `${owner}::${member}` + : `Demo::Sat::${member}`; + const matches = view.correspondence.filter( + (item) => + item.subject.qualifiedName === declaration && + item.context[0] === root[0]!.subject.id, + ); + // This is the baseline-red assertion: old pruning has no c occurrence. + expect( + matches, + `${owner}/${member} must retain its exact source and usage`, + ).toHaveLength(1); + const match = matches[0]!; + expect(match.context).toEqual([root[0]!.subject.id, match.subject.id]); + const nodes = view.artifact!.nodes.filter((node) => node.id === match.symbol); + expect(nodes).toHaveLength(1); + expect(nodes[0]!.parent).toBe(root[0]!.symbol); + expect(nodes[0]!.inherited ?? false).toBe( + owner === 'Demo::sat2' || (owner === 'Demo::sat1' && member === 'c'), + ); + expect(nodes[0]!.type).toBe( + owner === 'Demo::sat1' && member !== 'c' ? 'SmallWheel' : 'Wheel', + ); + const text = + owner === 'Demo::sat1' && member !== 'c' + ? `part :>> ${member} : SmallWheel;` + : `part ${member} : Wheel;`; + const offset = legacy.documents[0]!.text.indexOf(text); + expect(match.subject.id).toBe(`model.sysml@${offset}:${text.length}`); + expect(match.subject.origin).toMatchObject({ + file: 'model.sysml', + offset, + length: text.length, + }); + return { ...match, text }; +} + +async function inspect( + page: Page, + view: EvaluatedView, + owner: string, + member: string, + sourceAvailable = true, +) { + const match = occurrence(view, owner, member); + const node = diagramSymbol(page, match.symbol); + await expect(node).toBeVisible(); + await node.focus(); + await page.keyboard.press('Enter'); + const properties = page.getByRole('complementary', { + name: 'Properties', + exact: true, + }); + await expect( + properties.getByText(match.subject.qualifiedName!, { exact: true }), + ).toBeVisible(); + await expect(properties.getByText(owner, { exact: true })).toBeVisible(); + const reveal = properties.getByRole('button', { + name: 'Open source', + exact: true, + }); + if (!sourceAvailable) { + await expect(reveal).toBeDisabled(); + return match; + } + await reveal.click(); + const source = page.getByRole('textbox', { name: 'SysML source' }); + await expect(source).toBeFocused(); + expect(await copySourceSelection(source)).toContain(match.text); + return match; +} + +test('retains inherited occurrence identity, legacy positions and exact portable drafts @critical', async ({ + page, + browser, +}) => { + test.setTimeout(180_000); + const evaluations = await observeEvaluations(page); + await importProject(page, legacyBuffer); + const view = await evaluated(evaluations); + const sat1c = occurrence(view, 'Demo::sat1', 'c'); + const sat2c = occurrence(view, 'Demo::sat2', 'c'); + expect(sat1c.subject).toEqual(sat2c.subject); + expect(sat1c.context).not.toEqual(sat2c.context); + expect(sat1c.symbol).not.toBe(sat2c.symbol); + for (const [symbol, name, context] of [ + ['n0', 'Demo::sat1', ['model.sysml@134:69']], + ['n1', 'Demo::sat1::a', ['model.sysml@134:69', 'model.sysml@152:24']], + ['n2', 'Demo::sat1::b', ['model.sysml@134:69', 'model.sysml@177:24']], + ['n3', 'Demo::sat2', ['model.sysml@206:16']], + ] as const) { + const matches = view.correspondence.filter( + (item) => item.symbol === symbol, + ); + expect(matches).toHaveLength(1); + expect(matches[0]!.subject.qualifiedName).toBe(name); + expect(matches[0]!.subject.id).toBe(context.at(-1)); + expect(matches[0]!.context).toEqual(context); + } + await expectRenderedPositions(page, legacy, both); + await expectSeparateSiblings(page, view.artifact!); + const restored = (await downloadProject(page)).bundle; + expect(restored.documents).toEqual(legacy.documents); + expect(restored.drafts).toEqual(legacy.drafts); + expect(restored.presentations[both]).toMatchObject( + legacy.presentations[both]!, + ); + for (const owner of ['Demo::sat1', 'Demo::sat2']) { + for (const member of ['a', 'b', 'c']) + await inspect(page, view, owner, member); + } + const newSymbols = ['Demo::sat1', 'Demo::sat2'] + .flatMap((owner) => + ['a', 'b', 'c'].map((member) => occurrence(view, owner, member).symbol), + ) + .filter((symbol) => !['n1', 'n2'].includes(symbol)); + expect(new Set(newSymbols).size).toBe(4); + for (const symbol of newSymbols) { + expect(Number(symbol.slice(1))).toBeGreaterThan(3); + await expect(diagramSymbol(page, symbol)).toBeVisible(); + // Automatic layout is displayed without writing new saved positions. + const arranged = await nodePosition(diagramSymbol(page, symbol)); + expect(Number.isFinite(arranged.x)).toBe(true); + expect(Number.isFinite(arranged.y)).toBe(true); + } + const secondBefore = await nodePosition(diagramSymbol(page, sat2c.symbol)); + // Stay within the free sibling gap at the fitted camera scale. Manual + // overlap prevention is outside this renderer's preservation contract. + const step = { x: 0, y: 8 }; + const firstMoved = await moveNode( + page, + diagramSymbol(page, sat1c.symbol), + step, + ); + expect(await nodePosition(diagramSymbol(page, sat2c.symbol))).toEqual( + secondBefore, + ); + await moveNode(page, diagramSymbol(page, sat2c.symbol), step); + expect(await nodePosition(diagramSymbol(page, sat1c.symbol))).toEqual( + firstMoved, + ); + const moved = (await downloadProject(page)).bundle; + await expectRenderedPositions(page, moved, both); + expect(moved.presentations[both]!.positions[sat1c.symbol]).not.toEqual( + moved.presentations[both]!.positions[sat2c.symbol], + ); + await openView(page, 'definition'); + const definition = await evaluated(evaluations, 'Views::definition'); + for (const member of ['a', 'b', 'c']) + occurrence(definition, 'Demo::Sat', member); + await moveNode( + page, + diagramSymbol(page, occurrence(definition, 'Demo::Sat', 'c').symbol), + ); + const withDefinition = (await downloadProject(page)).bundle; + await openView(page, 'both'); + await expectRenderedPositions(page, moved, both); + const beforeDraft = (await downloadProject(page)).bundle; + expect(beforeDraft.documents).toEqual(legacy.documents); + expect(beforeDraft.drafts).toEqual(legacy.drafts); + expect(beforeDraft.presentations['Views::definition']).toEqual( + withDefinition.presentations['Views::definition'], + ); + const expected = semanticPositions(beforeDraft, evaluations); + const draft = + legacy.documents[0]!.text + + '\n// retained invalid inherited-members draft\npart'; + await fillSource(page.getByRole('textbox', { name: 'SysML source' }), draft); + await page.getByRole('button', { name: 'Apply source', exact: true }).click(); + await expect( + page.getByRole('region', { name: 'Issues', exact: true }), + ).toBeVisible(); + const saved = await downloadProject(page); + expect(saved.bundle.documents).toEqual(legacy.documents); + expect(saved.bundle.drafts).toEqual([{ name: 'model.sysml', text: draft }]); + expect(saved.bundle.presentations).toEqual(beforeDraft.presentations); + expect(semanticPositions(saved.bundle, evaluations)).toEqual(expected); + + const fresh = await browser.newContext({ + baseURL: new URL(page.url()).origin, + viewport: { width: 1440, height: 900 }, + }); + try { + const reopenedPage = await fresh.newPage(); + const errors: string[] = []; + reopenedPage.on('pageerror', (error) => errors.push(error.message)); + const reopenedEvaluations = await observeEvaluations(reopenedPage); + await importProject(reopenedPage, saved.buffer); + await expect( + reopenedPage.getByRole('textbox', { name: 'SysML source' }), + ).toHaveSource(draft); + const reopenedView = await evaluated(reopenedEvaluations); + await expectSeparateSiblings(reopenedPage, reopenedView.artifact!); + for (const owner of ['Demo::sat1', 'Demo::sat2']) + occurrence(reopenedView, owner, 'c'); + for (const name of ['definition', 'both']) { + await openView(reopenedPage, name); + await evaluated(reopenedEvaluations, `Views::${name}`); + await expectRenderedPositions( + reopenedPage, + saved.bundle, + `Views::${name}`, + ); + } + const reopened = (await downloadProject(reopenedPage)).bundle; + expect(reopened.documents).toEqual(saved.bundle.documents); + expect(reopened.drafts).toEqual(saved.bundle.drafts); + expect(reopened.presentations).toEqual(saved.bundle.presentations); + expect(semanticPositions(reopened, reopenedEvaluations)).toEqual(expected); + for (const owner of ['Demo::sat1', 'Demo::sat2']) + await inspect(reopenedPage, reopenedView, owner, 'c', false); + expect(errors).toEqual([]); + } finally { + await fresh.close(); + } +}); diff --git a/tests/e2e/recovery-error-lifetime.spec.ts b/tests/e2e/recovery-error-lifetime.spec.ts new file mode 100644 index 0000000..940aade --- /dev/null +++ b/tests/e2e/recovery-error-lifetime.spec.ts @@ -0,0 +1,139 @@ +import { + test, + expect, + fillSource, + downloadProject, + waitForLauncher, +} from './support'; + +const failing = + 'package P { part def C; requirement def R { subject c : C; } }'; +const passing = 'package P { part def Motor; part m : Motor; }'; +const explorationError = + 'Exploration source correspondence is outside the applied project.'; + +test('accepted recovery exploration errors survive current failures and clear after successful exploration', async ({ + page, +}) => { + await page.goto('/app'); + await waitForLauncher(page); + const documents = [{ name: 'model.sysml', text: failing }]; + await page.locator('input[type=file]').setInputFiles({ + name: 'issue14.sysml-project.json', + mimeType: 'application/json', + buffer: Buffer.from( + JSON.stringify({ + format: 'sysmlv2-view-project', + version: 1, + name: 'Issue 14', + documents, + drafts: documents, + presentations: {}, + }), + ), + }); + const source = page.getByRole('textbox', { + name: 'SysML source', + exact: true, + }); + const alerts = page.getByRole('alert').filter({ hasText: explorationError }); + await expect(alerts.first()).toBeVisible(); + // Persist through the real recovery writer, then replay through the real restore UI. + const saved = failing + '\n// recover this exact accepted source\n'; + await fillSource(source, saved); + await page.getByRole('button', { name: 'Apply source', exact: true }).click(); + await expect( + page.getByRole('button', { name: 'Apply source', exact: true }), + ).toBeDisabled(); + await expect(alerts.first()).toBeVisible(); + await expect + .poll(() => + page.evaluate( + async () => + new Promise((resolve, reject) => { + const request = indexedDB.open('sysml-view-recovery', 1); + request.onerror = () => reject(request.error); + request.onsuccess = () => { + const database = request.result; + const transaction = database.transaction('recovery', 'readonly'); + const read = transaction.objectStore('recovery').get('latest'); + read.onsuccess = () => + resolve( + read.result + ? JSON.parse(JSON.parse(read.result).project).documents[0] + .text + : undefined, + ); + read.onerror = () => reject(read.error); + transaction.oncomplete = () => database.close(); + }; + }), + ), + ) + .toBe(saved); + page.on('dialog', (dialog) => dialog.accept()); + await page.reload(); + await waitForLauncher(page); + await page + .getByRole('region', { name: 'Saved recovery', exact: true }) + .getByRole('button', { name: 'Restore recovery', exact: true }) + .click(); + await expect(source).toHaveSource(saved); + await expect(alerts.first()).toBeVisible(); + const issues = page.getByRole('region', { name: 'Issues', exact: true }); + await expect( + issues.getByRole('alert').filter({ hasText: explorationError }), + ).toBeVisible(); + await expect( + page.getByRole('region', { name: 'Saved recovery', exact: true }), + ).not.toBeVisible(); + await expect(issues.getByRole('listitem')).toHaveCount(0); + expect((await downloadProject(page)).bundle.documents).toEqual([ + { name: 'model.sysml', text: saved }, + ]); + + // A new parse-clean revision with the same construct must still report its current failure. + const stillFailing = saved + '// another accepted revision\n'; + await fillSource(source, stillFailing); + await page.getByRole('button', { name: 'Apply source', exact: true }).click(); + await expect( + page.getByRole('button', { name: 'Apply source', exact: true }), + ).toBeDisabled(); + await expect(alerts.first()).toBeVisible(); + await expect(source).toHaveSource(stillFailing); + await expect(issues.getByRole('listitem')).toHaveCount(0); + + for (const width of [1440, 390]) { + await page.setViewportSize({ width, height: 900 }); + for (const theme of ['light', 'dark']) { + await page + .getByRole('button', { name: 'Appearance', exact: true }) + .click(); + const appearance = page.getByRole('dialog', { + name: 'Appearance', + exact: true, + }); + await appearance.getByLabel('Theme', { exact: true }).selectOption(theme); + await appearance + .getByRole('button', { name: 'Done', exact: true }) + .click(); + await expect(alerts.first()).toBeVisible(); + await page.screenshot({ + path: `sandbox/issue14-implementation/current-error-${width}-${theme}.png`, + }); + } + } + await page.setViewportSize({ width: 1440, height: 900 }); + + await fillSource(source, passing); + await page.getByRole('button', { name: 'Apply source', exact: true }).click(); + // Successful exploration, not a zero diagnostic count, establishes supersession. + await expect( + page.getByRole('group', { name: 'part def Motor', exact: true }), + ).toBeVisible(); + await expect(source).toHaveSource(passing); + await expect(alerts).toHaveCount(0); + expect((await downloadProject(page)).bundle.documents).toEqual([ + { name: 'model.sysml', text: passing }, + ]); +}); diff --git a/tests/e2e/support.ts b/tests/e2e/support.ts index fd45dbd..d36ec63 100644 --- a/tests/e2e/support.ts +++ b/tests/e2e/support.ts @@ -471,6 +471,44 @@ export async function nodePosition(node: Locator) { }); } +/** Check sibling cards separately from ancestry and serialized coordinates. */ +export async function expectSeparateSiblings( + page: Page, + artifact: ViewArtifact, +) { + await expect + .poll(async () => { + const overlaps: string[] = []; + const boxes = await Promise.all( + artifact.nodes.map(async (node) => ({ + node, + box: await diagramSymbol(page, node.id).boundingBox(), + })), + ); + for (let i = 0; i < boxes.length; i++) { + const a = boxes[i]!; + if (!a.box) { + overlaps.push(a.node.id + ' missing'); + continue; + } + for (const b of boxes.slice(i + 1)) { + if (!b.box || a.node.parent !== b.node.parent) continue; + if ( + Math.min(a.box.x + a.box.width, b.box.x + b.box.width) - + Math.max(a.box.x, b.box.x) > + 1 && + Math.min(a.box.y + a.box.height, b.box.y + b.box.height) - + Math.max(a.box.y, b.box.y) > + 1 + ) + overlaps.push(a.node.id + '/' + b.node.id); + } + } + return overlaps; + }) + .toEqual([]); +} + /** Check displayed model-space geometry, independently of serialized records. */ export async function expectRenderedPositions( page: Page, @@ -529,7 +567,11 @@ export async function diagramState(page: Page) { ); } -export async function dragNode(page: Page, node: Locator) { +export async function dragNode( + page: Page, + node: Locator, + delta = { x: 80, y: 55 }, +) { await node.scrollIntoViewIfNeeded(); // Mode changes fit the camera after ResizeObserver and animation frames. // Wait for observable heading/viewport geometry, then perform one gesture. @@ -576,13 +618,17 @@ export async function dragNode(page: Page, node: Locator) { ); await page.mouse.move(x, y); await page.mouse.down(); - await page.mouse.move(x + 80, y + 55, { steps: 8 }); + await page.mouse.move(x + delta.x, y + delta.y, { steps: 8 }); await page.mouse.up(); } -export async function moveNode(page: Page, node: Locator) { +export async function moveNode( + page: Page, + node: Locator, + delta = { x: 80, y: 55 }, +) { const before = await nodePosition(node); - await dragNode(page, node); + await dragNode(page, node, delta); await expect.poll(() => nodePosition(node)).not.toEqual(before); return nodePosition(node); } diff --git a/tests/e2e/visual.spec.ts b/tests/e2e/visual.spec.ts index cdd62c0..10e62e8 100644 --- a/tests/e2e/visual.spec.ts +++ b/tests/e2e/visual.spec.ts @@ -1,3 +1,4 @@ +import { readFile } from 'node:fs/promises'; import { noViews, assemblyExploration, documents } from './exploration-support'; import { test, @@ -13,6 +14,8 @@ import { copySourceSelection, observeEvaluations, overviewRoutingGeometry, + diagramSymbol, + expectSeparateSiblings, } from './support'; import { importRequirementsTraceability, @@ -1122,3 +1125,54 @@ for (const theme of ['light', 'dark']) { }); } } + +for (const theme of ['light', 'dark']) { + for (const width of [1440, 768]) { + test(`inherited structural occurrences ${theme} ${width}`, async ({ + page, + }) => { + await page.setViewportSize({ width, height: 900 }); + const evaluations = await observeEvaluations(page); + await page.goto('/app'); + await page.locator('input[type=file]').setInputFiles({ + name: 'inherited.sysml-project.json', + mimeType: 'application/json', + buffer: await readFile( + new URL( + '../fixtures/inherited-members-legacy.sysml-project.json', + import.meta.url, + ), + ), + }); + await expect( + page.getByRole('combobox', { name: 'Named view', exact: true }), + ).toHaveValue('Views::both'); + await expect + .poll(() => evaluations.some((view) => view.view === 'Views::both')) + .toBe(true); + const view = evaluations.findLast((view) => view.view === 'Views::both')!; + const inherited = view.correspondence.filter( + (entry) => entry.subject.qualifiedName === 'Demo::Sat::c', + ); + expect(inherited).toHaveLength(2); + expect(inherited[0]!.context).not.toEqual(inherited[1]!.context); + await page.getByRole('button', { name: 'Diagram', exact: true }).click(); + await page + .getByRole('button', { name: 'Appearance', exact: true }) + .click(); + const dialog = page.getByRole('dialog', { + name: 'Appearance', + exact: true, + }); + await dialog.getByLabel('Theme', { exact: true }).selectOption(theme); + await dialog.getByRole('button', { name: 'Done', exact: true }).click(); + await expect(dialog).toBeHidden(); + for (const entry of inherited) + await expect(diagramSymbol(page, entry.symbol)).toBeVisible(); + await expectSeparateSiblings(page, view.artifact!); + await expect( + page.getByRole('region', { name: 'Diagram', exact: true }), + ).toHaveScreenshot(`inherited-occurrences-${theme}-${width}.png`); + }); + } +} diff --git a/tests/e2e/visual.spec.ts-snapshots/inherited-occurrences-dark-1440-visual-linux.png b/tests/e2e/visual.spec.ts-snapshots/inherited-occurrences-dark-1440-visual-linux.png new file mode 100644 index 0000000..1725176 Binary files /dev/null and b/tests/e2e/visual.spec.ts-snapshots/inherited-occurrences-dark-1440-visual-linux.png differ diff --git a/tests/e2e/visual.spec.ts-snapshots/inherited-occurrences-dark-768-visual-linux.png b/tests/e2e/visual.spec.ts-snapshots/inherited-occurrences-dark-768-visual-linux.png new file mode 100644 index 0000000..48116ce Binary files /dev/null and b/tests/e2e/visual.spec.ts-snapshots/inherited-occurrences-dark-768-visual-linux.png differ diff --git a/tests/e2e/visual.spec.ts-snapshots/inherited-occurrences-light-1440-visual-linux.png b/tests/e2e/visual.spec.ts-snapshots/inherited-occurrences-light-1440-visual-linux.png new file mode 100644 index 0000000..7ca43c2 Binary files /dev/null and b/tests/e2e/visual.spec.ts-snapshots/inherited-occurrences-light-1440-visual-linux.png differ diff --git a/tests/e2e/visual.spec.ts-snapshots/inherited-occurrences-light-768-visual-linux.png b/tests/e2e/visual.spec.ts-snapshots/inherited-occurrences-light-768-visual-linux.png new file mode 100644 index 0000000..aaafb3e Binary files /dev/null and b/tests/e2e/visual.spec.ts-snapshots/inherited-occurrences-light-768-visual-linux.png differ diff --git a/tests/fixtures/inherited-members-compressor-correspondence.json b/tests/fixtures/inherited-members-compressor-correspondence.json new file mode 100644 index 0000000..c5ece40 --- /dev/null +++ b/tests/fixtures/inherited-members-compressor-correspondence.json @@ -0,0 +1,2450 @@ +{ + "CompressorViews::structure": { + "correspondence": [ + { + "symbol": "n5", + "subject": { + "id": "system.sysml@1543:26", + "qualifiedName": "CompressorSystem::ElectricalAssembly::supply", + "origin": { + "file": "system.sysml", + "offset": 1543, + "length": 26, + "nameOffset": 1548, + "nameLength": 6 + } + }, + "context": [ + "system.sysml@192:1308", + "system.sysml@407:37", + "system.sysml@1543:26" + ] + }, + { + "symbol": "n5.0", + "subject": { + "id": "components.sysml@195:30", + "qualifiedName": "CompressorComponents::PowerSupply::supply", + "origin": { + "file": "components.sysml", + "offset": 195, + "length": 30, + "nameOffset": 204, + "nameLength": 6 + } + }, + "context": [ + "system.sysml@192:1308", + "system.sysml@407:37", + "system.sysml@1543:26", + "components.sysml@195:30" + ] + }, + { + "symbol": "n6", + "subject": { + "id": "system.sysml@1578:29", + "qualifiedName": "CompressorSystem::ElectricalAssembly::controller", + "origin": { + "file": "system.sysml", + "offset": 1578, + "length": 29, + "nameOffset": 1583, + "nameLength": 10 + } + }, + "context": [ + "system.sysml@192:1308", + "system.sysml@407:37", + "system.sysml@1578:29" + ] + }, + { + "symbol": "n6.0", + "subject": { + "id": "components.sysml@324:35", + "qualifiedName": "CompressorComponents::Controller::pressure", + "origin": { + "file": "components.sysml", + "offset": 324, + "length": 35, + "nameOffset": 332, + "nameLength": 8 + } + }, + "context": [ + "system.sysml@192:1308", + "system.sysml@407:37", + "system.sysml@1578:29", + "components.sysml@324:35" + ] + }, + { + "symbol": "n6.1", + "subject": { + "id": "components.sysml@368:41", + "qualifiedName": "CompressorComponents::Controller::temperature", + "origin": { + "file": "components.sysml", + "offset": 368, + "length": 41, + "nameOffset": 376, + "nameLength": 11 + } + }, + "context": [ + "system.sysml@192:1308", + "system.sysml@407:37", + "system.sysml@1578:29", + "components.sysml@368:41" + ] + }, + { + "symbol": "n6.2", + "subject": { + "id": "components.sysml@418:33", + "qualifiedName": "CompressorComponents::Controller::service", + "origin": { + "file": "components.sysml", + "offset": 418, + "length": 33, + "nameOffset": 426, + "nameLength": 7 + } + }, + "context": [ + "system.sysml@192:1308", + "system.sysml@407:37", + "system.sysml@1578:29", + "components.sysml@418:33" + ] + }, + { + "symbol": "n6.3", + "subject": { + "id": "components.sysml@460:33", + "qualifiedName": "CompressorComponents::Controller::command", + "origin": { + "file": "components.sysml", + "offset": 460, + "length": 33, + "nameOffset": 469, + "nameLength": 7 + } + }, + "context": [ + "system.sysml@192:1308", + "system.sysml@407:37", + "system.sysml@1578:29", + "components.sysml@460:33" + ] + }, + { + "symbol": "n6.4", + "subject": { + "id": "components.sysml@502:36", + "qualifiedName": "CompressorComponents::Controller::fanCommand", + "origin": { + "file": "components.sysml", + "offset": 502, + "length": 36, + "nameOffset": 511, + "nameLength": 10 + } + }, + "context": [ + "system.sysml@192:1308", + "system.sysml@407:37", + "system.sysml@1578:29", + "components.sysml@502:36" + ] + }, + { + "symbol": "n6.5", + "subject": { + "id": "components.sysml@547:29", + "qualifiedName": "CompressorComponents::Controller::alarm", + "origin": { + "file": "components.sysml", + "offset": 547, + "length": 29, + "nameOffset": 556, + "nameLength": 5 + } + }, + "context": [ + "system.sysml@192:1308", + "system.sysml@407:37", + "system.sysml@1578:29", + "components.sysml@547:29" + ] + }, + { + "symbol": "n7", + "subject": { + "id": "system.sysml@1616:19", + "qualifiedName": "CompressorSystem::ElectricalAssembly::motor", + "origin": { + "file": "system.sysml", + "offset": 1616, + "length": 19, + "nameOffset": 1621, + "nameLength": 5 + } + }, + "context": [ + "system.sysml@192:1308", + "system.sysml@407:37", + "system.sysml@1616:19" + ] + }, + { + "symbol": "n7.0", + "subject": { + "id": "components.sysml@666:29", + "qualifiedName": "CompressorComponents::Motor::power", + "origin": { + "file": "components.sysml", + "offset": 666, + "length": 29, + "nameOffset": 674, + "nameLength": 5 + } + }, + "context": [ + "system.sysml@192:1308", + "system.sysml@407:37", + "system.sysml@1616:19", + "components.sysml@666:29" + ] + }, + { + "symbol": "n7.1", + "subject": { + "id": "components.sysml@704:33", + "qualifiedName": "CompressorComponents::Motor::command", + "origin": { + "file": "components.sysml", + "offset": 704, + "length": 33, + "nameOffset": 712, + "nameLength": 7 + } + }, + "context": [ + "system.sysml@192:1308", + "system.sysml@407:37", + "system.sysml@1616:19", + "components.sysml@704:33" + ] + }, + { + "symbol": "n8", + "subject": { + "id": "system.sysml@1828:17", + "qualifiedName": "CompressorSystem::PneumaticAssembly::pump", + "origin": { + "file": "system.sysml", + "offset": 1828, + "length": 17, + "nameOffset": 1833, + "nameLength": 4 + } + }, + "context": [ + "system.sysml@192:1308", + "system.sysml@453:35", + "system.sysml@1828:17" + ] + }, + { + "symbol": "n8.0", + "subject": { + "id": "components.sysml@772:27", + "qualifiedName": "CompressorComponents::Pump::inlet", + "origin": { + "file": "components.sysml", + "offset": 772, + "length": 27, + "nameOffset": 780, + "nameLength": 5 + } + }, + "context": [ + "system.sysml@192:1308", + "system.sysml@453:35", + "system.sysml@1828:17", + "components.sysml@772:27" + ] + }, + { + "symbol": "n8.1", + "subject": { + "id": "components.sysml@808:28", + "qualifiedName": "CompressorComponents::Pump::outlet", + "origin": { + "file": "components.sysml", + "offset": 808, + "length": 28, + "nameOffset": 817, + "nameLength": 6 + } + }, + "context": [ + "system.sysml@192:1308", + "system.sysml@453:35", + "system.sysml@1828:17", + "components.sysml@808:28" + ] + }, + { + "symbol": "n8.2", + "subject": { + "id": "components.sysml@845:41", + "qualifiedName": "CompressorComponents::Pump::temperature", + "origin": { + "file": "components.sysml", + "offset": 845, + "length": 41, + "nameOffset": 854, + "nameLength": 11 + } + }, + "context": [ + "system.sysml@192:1308", + "system.sysml@453:35", + "system.sysml@1828:17", + "components.sysml@845:41" + ] + }, + { + "symbol": "n9", + "subject": { + "id": "system.sysml@1854:17", + "qualifiedName": "CompressorSystem::PneumaticAssembly::tank", + "origin": { + "file": "system.sysml", + "offset": 1854, + "length": 17, + "nameOffset": 1859, + "nameLength": 4 + } + }, + "context": [ + "system.sysml@192:1308", + "system.sysml@453:35", + "system.sysml@1854:17" + ] + }, + { + "symbol": "n9.0", + "subject": { + "id": "components.sysml@1018:27", + "qualifiedName": "CompressorComponents::Tank::inlet", + "origin": { + "file": "components.sysml", + "offset": 1018, + "length": 27, + "nameOffset": 1026, + "nameLength": 5 + } + }, + "context": [ + "system.sysml@192:1308", + "system.sysml@453:35", + "system.sysml@1854:17", + "components.sysml@1018:27" + ] + }, + { + "symbol": "n9.1", + "subject": { + "id": "components.sysml@1054:30", + "qualifiedName": "CompressorComponents::Tank::delivery", + "origin": { + "file": "components.sysml", + "offset": 1054, + "length": 30, + "nameOffset": 1063, + "nameLength": 8 + } + }, + "context": [ + "system.sysml@192:1308", + "system.sysml@453:35", + "system.sysml@1854:17", + "components.sysml@1054:30" + ] + }, + { + "symbol": "n9.2", + "subject": { + "id": "components.sysml@1093:28", + "qualifiedName": "CompressorComponents::Tank::relief", + "origin": { + "file": "components.sysml", + "offset": 1093, + "length": 28, + "nameOffset": 1102, + "nameLength": 6 + } + }, + "context": [ + "system.sysml@192:1308", + "system.sysml@453:35", + "system.sysml@1854:17", + "components.sysml@1093:28" + ] + }, + { + "symbol": "n9.3", + "subject": { + "id": "components.sysml@1130:42", + "qualifiedName": "CompressorComponents::Tank::pressureReading", + "origin": { + "file": "components.sysml", + "offset": 1130, + "length": 42, + "nameOffset": 1139, + "nameLength": 15 + } + }, + "context": [ + "system.sysml@192:1308", + "system.sysml@453:35", + "system.sysml@1854:17", + "components.sysml@1130:42" + ] + }, + { + "symbol": "n10", + "subject": { + "id": "system.sysml@1880:28", + "qualifiedName": "CompressorSystem::PneumaticAssembly::regulator", + "origin": { + "file": "system.sysml", + "offset": 1880, + "length": 28, + "nameOffset": 1885, + "nameLength": 9 + } + }, + "context": [ + "system.sysml@192:1308", + "system.sysml@453:35", + "system.sysml@1880:28" + ] + }, + { + "symbol": "n10.0", + "subject": { + "id": "components.sysml@1612:27", + "qualifiedName": "CompressorComponents::Valve::inlet", + "origin": { + "file": "components.sysml", + "offset": 1612, + "length": 27, + "nameOffset": 1620, + "nameLength": 5 + } + }, + "context": [ + "system.sysml@192:1308", + "system.sysml@453:35", + "system.sysml@1880:28", + "components.sysml@1612:27" + ] + }, + { + "symbol": "n10.1", + "subject": { + "id": "components.sysml@1648:28", + "qualifiedName": "CompressorComponents::Valve::outlet", + "origin": { + "file": "components.sysml", + "offset": 1648, + "length": 28, + "nameOffset": 1657, + "nameLength": 6 + } + }, + "context": [ + "system.sysml@192:1308", + "system.sysml@453:35", + "system.sysml@1880:28", + "components.sysml@1648:28" + ] + }, + { + "symbol": "n11", + "subject": { + "id": "system.sysml@1917:26", + "qualifiedName": "CompressorSystem::PneumaticAssembly::relief", + "origin": { + "file": "system.sysml", + "offset": 1917, + "length": 26, + "nameOffset": 1922, + "nameLength": 6 + } + }, + "context": [ + "system.sysml@192:1308", + "system.sysml@453:35", + "system.sysml@1917:26" + ] + }, + { + "symbol": "n11.0", + "subject": { + "id": "components.sysml@1612:27", + "qualifiedName": "CompressorComponents::Valve::inlet", + "origin": { + "file": "components.sysml", + "offset": 1612, + "length": 27, + "nameOffset": 1620, + "nameLength": 5 + } + }, + "context": [ + "system.sysml@192:1308", + "system.sysml@453:35", + "system.sysml@1917:26", + "components.sysml@1612:27" + ] + }, + { + "symbol": "n11.1", + "subject": { + "id": "components.sysml@1648:28", + "qualifiedName": "CompressorComponents::Valve::outlet", + "origin": { + "file": "components.sysml", + "offset": 1648, + "length": 28, + "nameOffset": 1657, + "nameLength": 6 + } + }, + "context": [ + "system.sysml@192:1308", + "system.sysml@453:35", + "system.sysml@1917:26", + "components.sysml@1648:28" + ] + }, + { + "symbol": "n12", + "subject": { + "id": "system.sysml@1952:37", + "qualifiedName": "CompressorSystem::PneumaticAssembly::pressureSensor", + "origin": { + "file": "system.sysml", + "offset": 1952, + "length": 37, + "nameOffset": 1957, + "nameLength": 14 + } + }, + "context": [ + "system.sysml@192:1308", + "system.sysml@453:35", + "system.sysml@1952:37" + ] + }, + { + "symbol": "n12.0", + "subject": { + "id": "components.sysml@1305:33", + "qualifiedName": "CompressorComponents::PressureSensor::sample", + "origin": { + "file": "components.sysml", + "offset": 1305, + "length": 33, + "nameOffset": 1313, + "nameLength": 6 + } + }, + "context": [ + "system.sysml@192:1308", + "system.sysml@453:35", + "system.sysml@1952:37", + "components.sysml@1305:33" + ] + }, + { + "symbol": "n12.1", + "subject": { + "id": "components.sysml@1347:34", + "qualifiedName": "CompressorComponents::PressureSensor::reading", + "origin": { + "file": "components.sysml", + "offset": 1347, + "length": 34, + "nameOffset": 1356, + "nameLength": 7 + } + }, + "context": [ + "system.sysml@192:1308", + "system.sysml@453:35", + "system.sysml@1952:37", + "components.sysml@1347:34" + ] + }, + { + "symbol": "n13", + "subject": { + "id": "components.sysml@2055:22", + "qualifiedName": "CompressorComponents::CoolingAssembly::fan", + "origin": { + "file": "components.sysml", + "offset": 2055, + "length": 22, + "nameOffset": 2060, + "nameLength": 3 + } + }, + "context": [ + "system.sysml@192:1308", + "system.sysml@497:31", + "components.sysml@2055:22" + ] + }, + { + "symbol": "n13.0", + "subject": { + "id": "components.sysml@1976:33", + "qualifiedName": "CompressorComponents::CoolingFan::command", + "origin": { + "file": "components.sysml", + "offset": 1976, + "length": 33, + "nameOffset": 1984, + "nameLength": 7 + } + }, + "context": [ + "system.sysml@192:1308", + "system.sysml@497:31", + "components.sysml@2055:22", + "components.sysml@1976:33" + ] + }, + { + "symbol": "n14", + "subject": { + "id": "components.sysml@2086:37", + "qualifiedName": "CompressorComponents::CoolingAssembly::temperature", + "origin": { + "file": "components.sysml", + "offset": 2086, + "length": 37, + "nameOffset": 2091, + "nameLength": 11 + } + }, + "context": [ + "system.sysml@192:1308", + "system.sysml@497:31", + "components.sysml@2086:37" + ] + }, + { + "symbol": "n14.0", + "subject": { + "id": "components.sysml@1494:36", + "qualifiedName": "CompressorComponents::TemperatureSensor::sample", + "origin": { + "file": "components.sysml", + "offset": 1494, + "length": 36, + "nameOffset": 1502, + "nameLength": 6 + } + }, + "context": [ + "system.sysml@192:1308", + "system.sysml@497:31", + "components.sysml@2086:37", + "components.sysml@1494:36" + ] + }, + { + "symbol": "n14.1", + "subject": { + "id": "components.sysml@1539:37", + "qualifiedName": "CompressorComponents::TemperatureSensor::reading", + "origin": { + "file": "components.sysml", + "offset": 1539, + "length": 37, + "nameOffset": 1548, + "nameLength": 7 + } + }, + "context": [ + "system.sysml@192:1308", + "system.sysml@497:31", + "components.sysml@2086:37", + "components.sysml@1539:37" + ] + } + ], + "edges": [ + { + "from": "n12", + "to": "n6", + "fromPort": "n12.1", + "toPort": "n6.0", + "kind": "connection", + "label": "pressureFeedback" + }, + { + "from": "n8", + "to": "n14", + "fromPort": "n8.2", + "toPort": "n14.0", + "kind": "connection", + "label": "temperatureSample" + }, + { + "from": "n14", + "to": "n6", + "fromPort": "n14.1", + "toPort": "n6.1", + "kind": "connection", + "label": "temperatureFeedback" + }, + { + "from": "n6", + "to": "n13", + "fromPort": "n6.4", + "toPort": "n13.0", + "kind": "connection", + "label": "fanControl" + }, + { + "from": "n4", + "to": "n6", + "fromPort": "n4.0", + "toPort": "n6.2", + "kind": "connection", + "label": "maintenanceRequest" + }, + { + "from": "n6", + "to": "n4", + "fromPort": "n6.5", + "toPort": "n4.1", + "kind": "connection", + "label": "faultAlarm" + } + ] + }, + "CompressorViews::installation": { + "correspondence": [ + { + "symbol": "n7", + "subject": { + "id": "system.sysml@407:37", + "qualifiedName": "CompressorSystem::Compressor::electrical", + "origin": { + "file": "system.sysml", + "offset": 407, + "length": 37, + "nameOffset": 412, + "nameLength": 10 + } + }, + "context": [ + "system.sysml@2310:434", + "system.sysml@2338:29", + "system.sysml@407:37" + ] + }, + { + "symbol": "n8", + "subject": { + "id": "system.sysml@1543:26", + "qualifiedName": "CompressorSystem::ElectricalAssembly::supply", + "origin": { + "file": "system.sysml", + "offset": 1543, + "length": 26, + "nameOffset": 1548, + "nameLength": 6 + } + }, + "context": [ + "system.sysml@2310:434", + "system.sysml@2338:29", + "system.sysml@407:37", + "system.sysml@1543:26" + ] + }, + { + "symbol": "n8.0", + "subject": { + "id": "components.sysml@195:30", + "qualifiedName": "CompressorComponents::PowerSupply::supply", + "origin": { + "file": "components.sysml", + "offset": 195, + "length": 30, + "nameOffset": 204, + "nameLength": 6 + } + }, + "context": [ + "system.sysml@2310:434", + "system.sysml@2338:29", + "system.sysml@407:37", + "system.sysml@1543:26", + "components.sysml@195:30" + ] + }, + { + "symbol": "n9", + "subject": { + "id": "system.sysml@1578:29", + "qualifiedName": "CompressorSystem::ElectricalAssembly::controller", + "origin": { + "file": "system.sysml", + "offset": 1578, + "length": 29, + "nameOffset": 1583, + "nameLength": 10 + } + }, + "context": [ + "system.sysml@2310:434", + "system.sysml@2338:29", + "system.sysml@407:37", + "system.sysml@1578:29" + ] + }, + { + "symbol": "n9.0", + "subject": { + "id": "components.sysml@324:35", + "qualifiedName": "CompressorComponents::Controller::pressure", + "origin": { + "file": "components.sysml", + "offset": 324, + "length": 35, + "nameOffset": 332, + "nameLength": 8 + } + }, + "context": [ + "system.sysml@2310:434", + "system.sysml@2338:29", + "system.sysml@407:37", + "system.sysml@1578:29", + "components.sysml@324:35" + ] + }, + { + "symbol": "n9.1", + "subject": { + "id": "components.sysml@368:41", + "qualifiedName": "CompressorComponents::Controller::temperature", + "origin": { + "file": "components.sysml", + "offset": 368, + "length": 41, + "nameOffset": 376, + "nameLength": 11 + } + }, + "context": [ + "system.sysml@2310:434", + "system.sysml@2338:29", + "system.sysml@407:37", + "system.sysml@1578:29", + "components.sysml@368:41" + ] + }, + { + "symbol": "n9.2", + "subject": { + "id": "components.sysml@418:33", + "qualifiedName": "CompressorComponents::Controller::service", + "origin": { + "file": "components.sysml", + "offset": 418, + "length": 33, + "nameOffset": 426, + "nameLength": 7 + } + }, + "context": [ + "system.sysml@2310:434", + "system.sysml@2338:29", + "system.sysml@407:37", + "system.sysml@1578:29", + "components.sysml@418:33" + ] + }, + { + "symbol": "n9.3", + "subject": { + "id": "components.sysml@460:33", + "qualifiedName": "CompressorComponents::Controller::command", + "origin": { + "file": "components.sysml", + "offset": 460, + "length": 33, + "nameOffset": 469, + "nameLength": 7 + } + }, + "context": [ + "system.sysml@2310:434", + "system.sysml@2338:29", + "system.sysml@407:37", + "system.sysml@1578:29", + "components.sysml@460:33" + ] + }, + { + "symbol": "n9.4", + "subject": { + "id": "components.sysml@502:36", + "qualifiedName": "CompressorComponents::Controller::fanCommand", + "origin": { + "file": "components.sysml", + "offset": 502, + "length": 36, + "nameOffset": 511, + "nameLength": 10 + } + }, + "context": [ + "system.sysml@2310:434", + "system.sysml@2338:29", + "system.sysml@407:37", + "system.sysml@1578:29", + "components.sysml@502:36" + ] + }, + { + "symbol": "n9.5", + "subject": { + "id": "components.sysml@547:29", + "qualifiedName": "CompressorComponents::Controller::alarm", + "origin": { + "file": "components.sysml", + "offset": 547, + "length": 29, + "nameOffset": 556, + "nameLength": 5 + } + }, + "context": [ + "system.sysml@2310:434", + "system.sysml@2338:29", + "system.sysml@407:37", + "system.sysml@1578:29", + "components.sysml@547:29" + ] + }, + { + "symbol": "n10", + "subject": { + "id": "system.sysml@1616:19", + "qualifiedName": "CompressorSystem::ElectricalAssembly::motor", + "origin": { + "file": "system.sysml", + "offset": 1616, + "length": 19, + "nameOffset": 1621, + "nameLength": 5 + } + }, + "context": [ + "system.sysml@2310:434", + "system.sysml@2338:29", + "system.sysml@407:37", + "system.sysml@1616:19" + ] + }, + { + "symbol": "n10.0", + "subject": { + "id": "components.sysml@666:29", + "qualifiedName": "CompressorComponents::Motor::power", + "origin": { + "file": "components.sysml", + "offset": 666, + "length": 29, + "nameOffset": 674, + "nameLength": 5 + } + }, + "context": [ + "system.sysml@2310:434", + "system.sysml@2338:29", + "system.sysml@407:37", + "system.sysml@1616:19", + "components.sysml@666:29" + ] + }, + { + "symbol": "n10.1", + "subject": { + "id": "components.sysml@704:33", + "qualifiedName": "CompressorComponents::Motor::command", + "origin": { + "file": "components.sysml", + "offset": 704, + "length": 33, + "nameOffset": 712, + "nameLength": 7 + } + }, + "context": [ + "system.sysml@2310:434", + "system.sysml@2338:29", + "system.sysml@407:37", + "system.sysml@1616:19", + "components.sysml@704:33" + ] + }, + { + "symbol": "n11", + "subject": { + "id": "system.sysml@453:35", + "qualifiedName": "CompressorSystem::Compressor::pneumatic", + "origin": { + "file": "system.sysml", + "offset": 453, + "length": 35, + "nameOffset": 458, + "nameLength": 9 + } + }, + "context": [ + "system.sysml@2310:434", + "system.sysml@2338:29", + "system.sysml@453:35" + ] + }, + { + "symbol": "n12", + "subject": { + "id": "system.sysml@1828:17", + "qualifiedName": "CompressorSystem::PneumaticAssembly::pump", + "origin": { + "file": "system.sysml", + "offset": 1828, + "length": 17, + "nameOffset": 1833, + "nameLength": 4 + } + }, + "context": [ + "system.sysml@2310:434", + "system.sysml@2338:29", + "system.sysml@453:35", + "system.sysml@1828:17" + ] + }, + { + "symbol": "n12.0", + "subject": { + "id": "components.sysml@772:27", + "qualifiedName": "CompressorComponents::Pump::inlet", + "origin": { + "file": "components.sysml", + "offset": 772, + "length": 27, + "nameOffset": 780, + "nameLength": 5 + } + }, + "context": [ + "system.sysml@2310:434", + "system.sysml@2338:29", + "system.sysml@453:35", + "system.sysml@1828:17", + "components.sysml@772:27" + ] + }, + { + "symbol": "n12.1", + "subject": { + "id": "components.sysml@808:28", + "qualifiedName": "CompressorComponents::Pump::outlet", + "origin": { + "file": "components.sysml", + "offset": 808, + "length": 28, + "nameOffset": 817, + "nameLength": 6 + } + }, + "context": [ + "system.sysml@2310:434", + "system.sysml@2338:29", + "system.sysml@453:35", + "system.sysml@1828:17", + "components.sysml@808:28" + ] + }, + { + "symbol": "n12.2", + "subject": { + "id": "components.sysml@845:41", + "qualifiedName": "CompressorComponents::Pump::temperature", + "origin": { + "file": "components.sysml", + "offset": 845, + "length": 41, + "nameOffset": 854, + "nameLength": 11 + } + }, + "context": [ + "system.sysml@2310:434", + "system.sysml@2338:29", + "system.sysml@453:35", + "system.sysml@1828:17", + "components.sysml@845:41" + ] + }, + { + "symbol": "n13", + "subject": { + "id": "system.sysml@1854:17", + "qualifiedName": "CompressorSystem::PneumaticAssembly::tank", + "origin": { + "file": "system.sysml", + "offset": 1854, + "length": 17, + "nameOffset": 1859, + "nameLength": 4 + } + }, + "context": [ + "system.sysml@2310:434", + "system.sysml@2338:29", + "system.sysml@453:35", + "system.sysml@1854:17" + ] + }, + { + "symbol": "n13.0", + "subject": { + "id": "components.sysml@1018:27", + "qualifiedName": "CompressorComponents::Tank::inlet", + "origin": { + "file": "components.sysml", + "offset": 1018, + "length": 27, + "nameOffset": 1026, + "nameLength": 5 + } + }, + "context": [ + "system.sysml@2310:434", + "system.sysml@2338:29", + "system.sysml@453:35", + "system.sysml@1854:17", + "components.sysml@1018:27" + ] + }, + { + "symbol": "n13.1", + "subject": { + "id": "components.sysml@1054:30", + "qualifiedName": "CompressorComponents::Tank::delivery", + "origin": { + "file": "components.sysml", + "offset": 1054, + "length": 30, + "nameOffset": 1063, + "nameLength": 8 + } + }, + "context": [ + "system.sysml@2310:434", + "system.sysml@2338:29", + "system.sysml@453:35", + "system.sysml@1854:17", + "components.sysml@1054:30" + ] + }, + { + "symbol": "n13.2", + "subject": { + "id": "components.sysml@1093:28", + "qualifiedName": "CompressorComponents::Tank::relief", + "origin": { + "file": "components.sysml", + "offset": 1093, + "length": 28, + "nameOffset": 1102, + "nameLength": 6 + } + }, + "context": [ + "system.sysml@2310:434", + "system.sysml@2338:29", + "system.sysml@453:35", + "system.sysml@1854:17", + "components.sysml@1093:28" + ] + }, + { + "symbol": "n13.3", + "subject": { + "id": "components.sysml@1130:42", + "qualifiedName": "CompressorComponents::Tank::pressureReading", + "origin": { + "file": "components.sysml", + "offset": 1130, + "length": 42, + "nameOffset": 1139, + "nameLength": 15 + } + }, + "context": [ + "system.sysml@2310:434", + "system.sysml@2338:29", + "system.sysml@453:35", + "system.sysml@1854:17", + "components.sysml@1130:42" + ] + }, + { + "symbol": "n14", + "subject": { + "id": "system.sysml@1880:28", + "qualifiedName": "CompressorSystem::PneumaticAssembly::regulator", + "origin": { + "file": "system.sysml", + "offset": 1880, + "length": 28, + "nameOffset": 1885, + "nameLength": 9 + } + }, + "context": [ + "system.sysml@2310:434", + "system.sysml@2338:29", + "system.sysml@453:35", + "system.sysml@1880:28" + ] + }, + { + "symbol": "n14.0", + "subject": { + "id": "components.sysml@1612:27", + "qualifiedName": "CompressorComponents::Valve::inlet", + "origin": { + "file": "components.sysml", + "offset": 1612, + "length": 27, + "nameOffset": 1620, + "nameLength": 5 + } + }, + "context": [ + "system.sysml@2310:434", + "system.sysml@2338:29", + "system.sysml@453:35", + "system.sysml@1880:28", + "components.sysml@1612:27" + ] + }, + { + "symbol": "n14.1", + "subject": { + "id": "components.sysml@1648:28", + "qualifiedName": "CompressorComponents::Valve::outlet", + "origin": { + "file": "components.sysml", + "offset": 1648, + "length": 28, + "nameOffset": 1657, + "nameLength": 6 + } + }, + "context": [ + "system.sysml@2310:434", + "system.sysml@2338:29", + "system.sysml@453:35", + "system.sysml@1880:28", + "components.sysml@1648:28" + ] + }, + { + "symbol": "n15", + "subject": { + "id": "system.sysml@1917:26", + "qualifiedName": "CompressorSystem::PneumaticAssembly::relief", + "origin": { + "file": "system.sysml", + "offset": 1917, + "length": 26, + "nameOffset": 1922, + "nameLength": 6 + } + }, + "context": [ + "system.sysml@2310:434", + "system.sysml@2338:29", + "system.sysml@453:35", + "system.sysml@1917:26" + ] + }, + { + "symbol": "n15.0", + "subject": { + "id": "components.sysml@1612:27", + "qualifiedName": "CompressorComponents::Valve::inlet", + "origin": { + "file": "components.sysml", + "offset": 1612, + "length": 27, + "nameOffset": 1620, + "nameLength": 5 + } + }, + "context": [ + "system.sysml@2310:434", + "system.sysml@2338:29", + "system.sysml@453:35", + "system.sysml@1917:26", + "components.sysml@1612:27" + ] + }, + { + "symbol": "n15.1", + "subject": { + "id": "components.sysml@1648:28", + "qualifiedName": "CompressorComponents::Valve::outlet", + "origin": { + "file": "components.sysml", + "offset": 1648, + "length": 28, + "nameOffset": 1657, + "nameLength": 6 + } + }, + "context": [ + "system.sysml@2310:434", + "system.sysml@2338:29", + "system.sysml@453:35", + "system.sysml@1917:26", + "components.sysml@1648:28" + ] + }, + { + "symbol": "n16", + "subject": { + "id": "system.sysml@1952:37", + "qualifiedName": "CompressorSystem::PneumaticAssembly::pressureSensor", + "origin": { + "file": "system.sysml", + "offset": 1952, + "length": 37, + "nameOffset": 1957, + "nameLength": 14 + } + }, + "context": [ + "system.sysml@2310:434", + "system.sysml@2338:29", + "system.sysml@453:35", + "system.sysml@1952:37" + ] + }, + { + "symbol": "n16.0", + "subject": { + "id": "components.sysml@1305:33", + "qualifiedName": "CompressorComponents::PressureSensor::sample", + "origin": { + "file": "components.sysml", + "offset": 1305, + "length": 33, + "nameOffset": 1313, + "nameLength": 6 + } + }, + "context": [ + "system.sysml@2310:434", + "system.sysml@2338:29", + "system.sysml@453:35", + "system.sysml@1952:37", + "components.sysml@1305:33" + ] + }, + { + "symbol": "n16.1", + "subject": { + "id": "components.sysml@1347:34", + "qualifiedName": "CompressorComponents::PressureSensor::reading", + "origin": { + "file": "components.sysml", + "offset": 1347, + "length": 34, + "nameOffset": 1356, + "nameLength": 7 + } + }, + "context": [ + "system.sysml@2310:434", + "system.sysml@2338:29", + "system.sysml@453:35", + "system.sysml@1952:37", + "components.sysml@1347:34" + ] + }, + { + "symbol": "n17", + "subject": { + "id": "system.sysml@497:31", + "qualifiedName": "CompressorSystem::Compressor::cooling", + "origin": { + "file": "system.sysml", + "offset": 497, + "length": 31, + "nameOffset": 502, + "nameLength": 7 + } + }, + "context": [ + "system.sysml@2310:434", + "system.sysml@2338:29", + "system.sysml@497:31" + ] + }, + { + "symbol": "n18", + "subject": { + "id": "components.sysml@2055:22", + "qualifiedName": "CompressorComponents::CoolingAssembly::fan", + "origin": { + "file": "components.sysml", + "offset": 2055, + "length": 22, + "nameOffset": 2060, + "nameLength": 3 + } + }, + "context": [ + "system.sysml@2310:434", + "system.sysml@2338:29", + "system.sysml@497:31", + "components.sysml@2055:22" + ] + }, + { + "symbol": "n18.0", + "subject": { + "id": "components.sysml@1976:33", + "qualifiedName": "CompressorComponents::CoolingFan::command", + "origin": { + "file": "components.sysml", + "offset": 1976, + "length": 33, + "nameOffset": 1984, + "nameLength": 7 + } + }, + "context": [ + "system.sysml@2310:434", + "system.sysml@2338:29", + "system.sysml@497:31", + "components.sysml@2055:22", + "components.sysml@1976:33" + ] + }, + { + "symbol": "n19", + "subject": { + "id": "components.sysml@2086:37", + "qualifiedName": "CompressorComponents::CoolingAssembly::temperature", + "origin": { + "file": "components.sysml", + "offset": 2086, + "length": 37, + "nameOffset": 2091, + "nameLength": 11 + } + }, + "context": [ + "system.sysml@2310:434", + "system.sysml@2338:29", + "system.sysml@497:31", + "components.sysml@2086:37" + ] + }, + { + "symbol": "n19.0", + "subject": { + "id": "components.sysml@1494:36", + "qualifiedName": "CompressorComponents::TemperatureSensor::sample", + "origin": { + "file": "components.sysml", + "offset": 1494, + "length": 36, + "nameOffset": 1502, + "nameLength": 6 + } + }, + "context": [ + "system.sysml@2310:434", + "system.sysml@2338:29", + "system.sysml@497:31", + "components.sysml@2086:37", + "components.sysml@1494:36" + ] + }, + { + "symbol": "n19.1", + "subject": { + "id": "components.sysml@1539:37", + "qualifiedName": "CompressorComponents::TemperatureSensor::reading", + "origin": { + "file": "components.sysml", + "offset": 1539, + "length": 37, + "nameOffset": 1548, + "nameLength": 7 + } + }, + "context": [ + "system.sysml@2310:434", + "system.sysml@2338:29", + "system.sysml@497:31", + "components.sysml@2086:37", + "components.sysml@1539:37" + ] + }, + { + "symbol": "n20", + "subject": { + "id": "system.sysml@537:37", + "qualifiedName": "CompressorSystem::Compressor::serviceConsole", + "origin": { + "file": "system.sysml", + "offset": 537, + "length": 37, + "nameOffset": 542, + "nameLength": 14 + } + }, + "context": [ + "system.sysml@2310:434", + "system.sysml@2338:29", + "system.sysml@537:37" + ] + }, + { + "symbol": "n20.0", + "subject": { + "id": "components.sysml@2168:33", + "qualifiedName": "CompressorComponents::ServiceConsole::request", + "origin": { + "file": "components.sysml", + "offset": 2168, + "length": 33, + "nameOffset": 2177, + "nameLength": 7 + } + }, + "context": [ + "system.sysml@2310:434", + "system.sysml@2338:29", + "system.sysml@537:37", + "components.sysml@2168:33" + ] + }, + { + "symbol": "n20.1", + "subject": { + "id": "components.sysml@2210:29", + "qualifiedName": "CompressorComponents::ServiceConsole::alarm", + "origin": { + "file": "components.sysml", + "offset": 2210, + "length": 29, + "nameOffset": 2218, + "nameLength": 5 + } + }, + "context": [ + "system.sysml@2310:434", + "system.sysml@2338:29", + "system.sysml@537:37", + "components.sysml@2210:29" + ] + }, + { + "symbol": "n21", + "subject": { + "id": "system.sysml@407:37", + "qualifiedName": "CompressorSystem::Compressor::electrical", + "origin": { + "file": "system.sysml", + "offset": 407, + "length": 37, + "nameOffset": 412, + "nameLength": 10 + } + }, + "context": [ + "system.sysml@2310:434", + "system.sysml@2376:34", + "system.sysml@407:37" + ] + }, + { + "symbol": "n22", + "subject": { + "id": "system.sysml@1543:26", + "qualifiedName": "CompressorSystem::ElectricalAssembly::supply", + "origin": { + "file": "system.sysml", + "offset": 1543, + "length": 26, + "nameOffset": 1548, + "nameLength": 6 + } + }, + "context": [ + "system.sysml@2310:434", + "system.sysml@2376:34", + "system.sysml@407:37", + "system.sysml@1543:26" + ] + }, + { + "symbol": "n22.0", + "subject": { + "id": "components.sysml@195:30", + "qualifiedName": "CompressorComponents::PowerSupply::supply", + "origin": { + "file": "components.sysml", + "offset": 195, + "length": 30, + "nameOffset": 204, + "nameLength": 6 + } + }, + "context": [ + "system.sysml@2310:434", + "system.sysml@2376:34", + "system.sysml@407:37", + "system.sysml@1543:26", + "components.sysml@195:30" + ] + }, + { + "symbol": "n23", + "subject": { + "id": "system.sysml@1578:29", + "qualifiedName": "CompressorSystem::ElectricalAssembly::controller", + "origin": { + "file": "system.sysml", + "offset": 1578, + "length": 29, + "nameOffset": 1583, + "nameLength": 10 + } + }, + "context": [ + "system.sysml@2310:434", + "system.sysml@2376:34", + "system.sysml@407:37", + "system.sysml@1578:29" + ] + }, + { + "symbol": "n23.0", + "subject": { + "id": "components.sysml@324:35", + "qualifiedName": "CompressorComponents::Controller::pressure", + "origin": { + "file": "components.sysml", + "offset": 324, + "length": 35, + "nameOffset": 332, + "nameLength": 8 + } + }, + "context": [ + "system.sysml@2310:434", + "system.sysml@2376:34", + "system.sysml@407:37", + "system.sysml@1578:29", + "components.sysml@324:35" + ] + }, + { + "symbol": "n23.1", + "subject": { + "id": "components.sysml@368:41", + "qualifiedName": "CompressorComponents::Controller::temperature", + "origin": { + "file": "components.sysml", + "offset": 368, + "length": 41, + "nameOffset": 376, + "nameLength": 11 + } + }, + "context": [ + "system.sysml@2310:434", + "system.sysml@2376:34", + "system.sysml@407:37", + "system.sysml@1578:29", + "components.sysml@368:41" + ] + }, + { + "symbol": "n23.2", + "subject": { + "id": "components.sysml@418:33", + "qualifiedName": "CompressorComponents::Controller::service", + "origin": { + "file": "components.sysml", + "offset": 418, + "length": 33, + "nameOffset": 426, + "nameLength": 7 + } + }, + "context": [ + "system.sysml@2310:434", + "system.sysml@2376:34", + "system.sysml@407:37", + "system.sysml@1578:29", + "components.sysml@418:33" + ] + }, + { + "symbol": "n23.3", + "subject": { + "id": "components.sysml@460:33", + "qualifiedName": "CompressorComponents::Controller::command", + "origin": { + "file": "components.sysml", + "offset": 460, + "length": 33, + "nameOffset": 469, + "nameLength": 7 + } + }, + "context": [ + "system.sysml@2310:434", + "system.sysml@2376:34", + "system.sysml@407:37", + "system.sysml@1578:29", + "components.sysml@460:33" + ] + }, + { + "symbol": "n23.4", + "subject": { + "id": "components.sysml@502:36", + "qualifiedName": "CompressorComponents::Controller::fanCommand", + "origin": { + "file": "components.sysml", + "offset": 502, + "length": 36, + "nameOffset": 511, + "nameLength": 10 + } + }, + "context": [ + "system.sysml@2310:434", + "system.sysml@2376:34", + "system.sysml@407:37", + "system.sysml@1578:29", + "components.sysml@502:36" + ] + }, + { + "symbol": "n23.5", + "subject": { + "id": "components.sysml@547:29", + "qualifiedName": "CompressorComponents::Controller::alarm", + "origin": { + "file": "components.sysml", + "offset": 547, + "length": 29, + "nameOffset": 556, + "nameLength": 5 + } + }, + "context": [ + "system.sysml@2310:434", + "system.sysml@2376:34", + "system.sysml@407:37", + "system.sysml@1578:29", + "components.sysml@547:29" + ] + }, + { + "symbol": "n24", + "subject": { + "id": "system.sysml@1616:19", + "qualifiedName": "CompressorSystem::ElectricalAssembly::motor", + "origin": { + "file": "system.sysml", + "offset": 1616, + "length": 19, + "nameOffset": 1621, + "nameLength": 5 + } + }, + "context": [ + "system.sysml@2310:434", + "system.sysml@2376:34", + "system.sysml@407:37", + "system.sysml@1616:19" + ] + }, + { + "symbol": "n24.0", + "subject": { + "id": "components.sysml@666:29", + "qualifiedName": "CompressorComponents::Motor::power", + "origin": { + "file": "components.sysml", + "offset": 666, + "length": 29, + "nameOffset": 674, + "nameLength": 5 + } + }, + "context": [ + "system.sysml@2310:434", + "system.sysml@2376:34", + "system.sysml@407:37", + "system.sysml@1616:19", + "components.sysml@666:29" + ] + }, + { + "symbol": "n24.1", + "subject": { + "id": "components.sysml@704:33", + "qualifiedName": "CompressorComponents::Motor::command", + "origin": { + "file": "components.sysml", + "offset": 704, + "length": 33, + "nameOffset": 712, + "nameLength": 7 + } + }, + "context": [ + "system.sysml@2310:434", + "system.sysml@2376:34", + "system.sysml@407:37", + "system.sysml@1616:19", + "components.sysml@704:33" + ] + }, + { + "symbol": "n25", + "subject": { + "id": "system.sysml@453:35", + "qualifiedName": "CompressorSystem::Compressor::pneumatic", + "origin": { + "file": "system.sysml", + "offset": 453, + "length": 35, + "nameOffset": 458, + "nameLength": 9 + } + }, + "context": [ + "system.sysml@2310:434", + "system.sysml@2376:34", + "system.sysml@453:35" + ] + }, + { + "symbol": "n26", + "subject": { + "id": "system.sysml@1828:17", + "qualifiedName": "CompressorSystem::PneumaticAssembly::pump", + "origin": { + "file": "system.sysml", + "offset": 1828, + "length": 17, + "nameOffset": 1833, + "nameLength": 4 + } + }, + "context": [ + "system.sysml@2310:434", + "system.sysml@2376:34", + "system.sysml@453:35", + "system.sysml@1828:17" + ] + }, + { + "symbol": "n26.0", + "subject": { + "id": "components.sysml@772:27", + "qualifiedName": "CompressorComponents::Pump::inlet", + "origin": { + "file": "components.sysml", + "offset": 772, + "length": 27, + "nameOffset": 780, + "nameLength": 5 + } + }, + "context": [ + "system.sysml@2310:434", + "system.sysml@2376:34", + "system.sysml@453:35", + "system.sysml@1828:17", + "components.sysml@772:27" + ] + }, + { + "symbol": "n26.1", + "subject": { + "id": "components.sysml@808:28", + "qualifiedName": "CompressorComponents::Pump::outlet", + "origin": { + "file": "components.sysml", + "offset": 808, + "length": 28, + "nameOffset": 817, + "nameLength": 6 + } + }, + "context": [ + "system.sysml@2310:434", + "system.sysml@2376:34", + "system.sysml@453:35", + "system.sysml@1828:17", + "components.sysml@808:28" + ] + }, + { + "symbol": "n26.2", + "subject": { + "id": "components.sysml@845:41", + "qualifiedName": "CompressorComponents::Pump::temperature", + "origin": { + "file": "components.sysml", + "offset": 845, + "length": 41, + "nameOffset": 854, + "nameLength": 11 + } + }, + "context": [ + "system.sysml@2310:434", + "system.sysml@2376:34", + "system.sysml@453:35", + "system.sysml@1828:17", + "components.sysml@845:41" + ] + }, + { + "symbol": "n27", + "subject": { + "id": "system.sysml@1854:17", + "qualifiedName": "CompressorSystem::PneumaticAssembly::tank", + "origin": { + "file": "system.sysml", + "offset": 1854, + "length": 17, + "nameOffset": 1859, + "nameLength": 4 + } + }, + "context": [ + "system.sysml@2310:434", + "system.sysml@2376:34", + "system.sysml@453:35", + "system.sysml@1854:17" + ] + }, + { + "symbol": "n27.0", + "subject": { + "id": "components.sysml@1018:27", + "qualifiedName": "CompressorComponents::Tank::inlet", + "origin": { + "file": "components.sysml", + "offset": 1018, + "length": 27, + "nameOffset": 1026, + "nameLength": 5 + } + }, + "context": [ + "system.sysml@2310:434", + "system.sysml@2376:34", + "system.sysml@453:35", + "system.sysml@1854:17", + "components.sysml@1018:27" + ] + }, + { + "symbol": "n27.1", + "subject": { + "id": "components.sysml@1054:30", + "qualifiedName": "CompressorComponents::Tank::delivery", + "origin": { + "file": "components.sysml", + "offset": 1054, + "length": 30, + "nameOffset": 1063, + "nameLength": 8 + } + }, + "context": [ + "system.sysml@2310:434", + "system.sysml@2376:34", + "system.sysml@453:35", + "system.sysml@1854:17", + "components.sysml@1054:30" + ] + }, + { + "symbol": "n27.2", + "subject": { + "id": "components.sysml@1093:28", + "qualifiedName": "CompressorComponents::Tank::relief", + "origin": { + "file": "components.sysml", + "offset": 1093, + "length": 28, + "nameOffset": 1102, + "nameLength": 6 + } + }, + "context": [ + "system.sysml@2310:434", + "system.sysml@2376:34", + "system.sysml@453:35", + "system.sysml@1854:17", + "components.sysml@1093:28" + ] + }, + { + "symbol": "n27.3", + "subject": { + "id": "components.sysml@1130:42", + "qualifiedName": "CompressorComponents::Tank::pressureReading", + "origin": { + "file": "components.sysml", + "offset": 1130, + "length": 42, + "nameOffset": 1139, + "nameLength": 15 + } + }, + "context": [ + "system.sysml@2310:434", + "system.sysml@2376:34", + "system.sysml@453:35", + "system.sysml@1854:17", + "components.sysml@1130:42" + ] + }, + { + "symbol": "n28", + "subject": { + "id": "system.sysml@1880:28", + "qualifiedName": "CompressorSystem::PneumaticAssembly::regulator", + "origin": { + "file": "system.sysml", + "offset": 1880, + "length": 28, + "nameOffset": 1885, + "nameLength": 9 + } + }, + "context": [ + "system.sysml@2310:434", + "system.sysml@2376:34", + "system.sysml@453:35", + "system.sysml@1880:28" + ] + }, + { + "symbol": "n28.0", + "subject": { + "id": "components.sysml@1612:27", + "qualifiedName": "CompressorComponents::Valve::inlet", + "origin": { + "file": "components.sysml", + "offset": 1612, + "length": 27, + "nameOffset": 1620, + "nameLength": 5 + } + }, + "context": [ + "system.sysml@2310:434", + "system.sysml@2376:34", + "system.sysml@453:35", + "system.sysml@1880:28", + "components.sysml@1612:27" + ] + }, + { + "symbol": "n28.1", + "subject": { + "id": "components.sysml@1648:28", + "qualifiedName": "CompressorComponents::Valve::outlet", + "origin": { + "file": "components.sysml", + "offset": 1648, + "length": 28, + "nameOffset": 1657, + "nameLength": 6 + } + }, + "context": [ + "system.sysml@2310:434", + "system.sysml@2376:34", + "system.sysml@453:35", + "system.sysml@1880:28", + "components.sysml@1648:28" + ] + }, + { + "symbol": "n29", + "subject": { + "id": "system.sysml@1917:26", + "qualifiedName": "CompressorSystem::PneumaticAssembly::relief", + "origin": { + "file": "system.sysml", + "offset": 1917, + "length": 26, + "nameOffset": 1922, + "nameLength": 6 + } + }, + "context": [ + "system.sysml@2310:434", + "system.sysml@2376:34", + "system.sysml@453:35", + "system.sysml@1917:26" + ] + }, + { + "symbol": "n29.0", + "subject": { + "id": "components.sysml@1612:27", + "qualifiedName": "CompressorComponents::Valve::inlet", + "origin": { + "file": "components.sysml", + "offset": 1612, + "length": 27, + "nameOffset": 1620, + "nameLength": 5 + } + }, + "context": [ + "system.sysml@2310:434", + "system.sysml@2376:34", + "system.sysml@453:35", + "system.sysml@1917:26", + "components.sysml@1612:27" + ] + }, + { + "symbol": "n29.1", + "subject": { + "id": "components.sysml@1648:28", + "qualifiedName": "CompressorComponents::Valve::outlet", + "origin": { + "file": "components.sysml", + "offset": 1648, + "length": 28, + "nameOffset": 1657, + "nameLength": 6 + } + }, + "context": [ + "system.sysml@2310:434", + "system.sysml@2376:34", + "system.sysml@453:35", + "system.sysml@1917:26", + "components.sysml@1648:28" + ] + }, + { + "symbol": "n30", + "subject": { + "id": "system.sysml@1952:37", + "qualifiedName": "CompressorSystem::PneumaticAssembly::pressureSensor", + "origin": { + "file": "system.sysml", + "offset": 1952, + "length": 37, + "nameOffset": 1957, + "nameLength": 14 + } + }, + "context": [ + "system.sysml@2310:434", + "system.sysml@2376:34", + "system.sysml@453:35", + "system.sysml@1952:37" + ] + }, + { + "symbol": "n30.0", + "subject": { + "id": "components.sysml@1305:33", + "qualifiedName": "CompressorComponents::PressureSensor::sample", + "origin": { + "file": "components.sysml", + "offset": 1305, + "length": 33, + "nameOffset": 1313, + "nameLength": 6 + } + }, + "context": [ + "system.sysml@2310:434", + "system.sysml@2376:34", + "system.sysml@453:35", + "system.sysml@1952:37", + "components.sysml@1305:33" + ] + }, + { + "symbol": "n30.1", + "subject": { + "id": "components.sysml@1347:34", + "qualifiedName": "CompressorComponents::PressureSensor::reading", + "origin": { + "file": "components.sysml", + "offset": 1347, + "length": 34, + "nameOffset": 1356, + "nameLength": 7 + } + }, + "context": [ + "system.sysml@2310:434", + "system.sysml@2376:34", + "system.sysml@453:35", + "system.sysml@1952:37", + "components.sysml@1347:34" + ] + }, + { + "symbol": "n31", + "subject": { + "id": "system.sysml@497:31", + "qualifiedName": "CompressorSystem::Compressor::cooling", + "origin": { + "file": "system.sysml", + "offset": 497, + "length": 31, + "nameOffset": 502, + "nameLength": 7 + } + }, + "context": [ + "system.sysml@2310:434", + "system.sysml@2376:34", + "system.sysml@497:31" + ] + }, + { + "symbol": "n32", + "subject": { + "id": "components.sysml@2055:22", + "qualifiedName": "CompressorComponents::CoolingAssembly::fan", + "origin": { + "file": "components.sysml", + "offset": 2055, + "length": 22, + "nameOffset": 2060, + "nameLength": 3 + } + }, + "context": [ + "system.sysml@2310:434", + "system.sysml@2376:34", + "system.sysml@497:31", + "components.sysml@2055:22" + ] + }, + { + "symbol": "n32.0", + "subject": { + "id": "components.sysml@1976:33", + "qualifiedName": "CompressorComponents::CoolingFan::command", + "origin": { + "file": "components.sysml", + "offset": 1976, + "length": 33, + "nameOffset": 1984, + "nameLength": 7 + } + }, + "context": [ + "system.sysml@2310:434", + "system.sysml@2376:34", + "system.sysml@497:31", + "components.sysml@2055:22", + "components.sysml@1976:33" + ] + }, + { + "symbol": "n33", + "subject": { + "id": "components.sysml@2086:37", + "qualifiedName": "CompressorComponents::CoolingAssembly::temperature", + "origin": { + "file": "components.sysml", + "offset": 2086, + "length": 37, + "nameOffset": 2091, + "nameLength": 11 + } + }, + "context": [ + "system.sysml@2310:434", + "system.sysml@2376:34", + "system.sysml@497:31", + "components.sysml@2086:37" + ] + }, + { + "symbol": "n33.0", + "subject": { + "id": "components.sysml@1494:36", + "qualifiedName": "CompressorComponents::TemperatureSensor::sample", + "origin": { + "file": "components.sysml", + "offset": 1494, + "length": 36, + "nameOffset": 1502, + "nameLength": 6 + } + }, + "context": [ + "system.sysml@2310:434", + "system.sysml@2376:34", + "system.sysml@497:31", + "components.sysml@2086:37", + "components.sysml@1494:36" + ] + }, + { + "symbol": "n33.1", + "subject": { + "id": "components.sysml@1539:37", + "qualifiedName": "CompressorComponents::TemperatureSensor::reading", + "origin": { + "file": "components.sysml", + "offset": 1539, + "length": 37, + "nameOffset": 1548, + "nameLength": 7 + } + }, + "context": [ + "system.sysml@2310:434", + "system.sysml@2376:34", + "system.sysml@497:31", + "components.sysml@2086:37", + "components.sysml@1539:37" + ] + }, + { + "symbol": "n34", + "subject": { + "id": "system.sysml@537:37", + "qualifiedName": "CompressorSystem::Compressor::serviceConsole", + "origin": { + "file": "system.sysml", + "offset": 537, + "length": 37, + "nameOffset": 542, + "nameLength": 14 + } + }, + "context": [ + "system.sysml@2310:434", + "system.sysml@2376:34", + "system.sysml@537:37" + ] + }, + { + "symbol": "n34.0", + "subject": { + "id": "components.sysml@2168:33", + "qualifiedName": "CompressorComponents::ServiceConsole::request", + "origin": { + "file": "components.sysml", + "offset": 2168, + "length": 33, + "nameOffset": 2177, + "nameLength": 7 + } + }, + "context": [ + "system.sysml@2310:434", + "system.sysml@2376:34", + "system.sysml@537:37", + "components.sysml@2168:33" + ] + }, + { + "symbol": "n34.1", + "subject": { + "id": "components.sysml@2210:29", + "qualifiedName": "CompressorComponents::ServiceConsole::alarm", + "origin": { + "file": "components.sysml", + "offset": 2210, + "length": 29, + "nameOffset": 2218, + "nameLength": 5 + } + }, + "context": [ + "system.sysml@2310:434", + "system.sysml@2376:34", + "system.sysml@537:37", + "components.sysml@2210:29" + ] + } + ], + "edges": [ + { + "from": "n14", + "to": "n3", + "fromPort": "n14.1", + "toPort": "n3.0", + "kind": "connection", + "label": "deliveryLine" + }, + { + "from": "n28", + "to": "n4", + "fromPort": "n28.1", + "toPort": "n4.0", + "kind": "connection", + "label": "spareDelivery" + } + ] + } +} diff --git a/tests/fixtures/inherited-members-legacy.sysml-project.json b/tests/fixtures/inherited-members-legacy.sysml-project.json new file mode 100644 index 0000000..7ee841a --- /dev/null +++ b/tests/fixtures/inherited-members-legacy.sysml-project.json @@ -0,0 +1,41 @@ +{ + "format": "sysmlv2-view-project", + "version": 1, + "name": "Inherited usage compatibility", + "documents": [ + { + "name": "model.sysml", + "text": "package Demo {\n part def Wheel;\n part def SmallWheel :> Wheel;\n part def Sat { part a : Wheel; part b : Wheel; part c : Wheel; }\n part sat1 : Sat { part :>> a : SmallWheel; part :>> b : SmallWheel; }\n part sat2 : Sat;\n}\npackage Views {\n view both : StandardViewDefinitions::InterconnectionView { expose Demo::sat1; expose Demo::sat2; }\n view definition : StandardViewDefinitions::InterconnectionView { expose Demo::Sat; }\n}\n" + } + ], + "drafts": [ + { + "name": "model.sysml", + "text": "package Demo {\n part def Wheel;\n part def SmallWheel :> Wheel;\n part def Sat { part a : Wheel; part b : Wheel; part c : Wheel; }\n part sat1 : Sat { part :>> a : SmallWheel; part :>> b : SmallWheel; }\n part sat2 : Sat;\n}\npackage Views {\n view both : StandardViewDefinitions::InterconnectionView { expose Demo::sat1; expose Demo::sat2; }\n view definition : StandardViewDefinitions::InterconnectionView { expose Demo::Sat; }\n}\n" + } + ], + "presentations": { + "Views::both": { + "sourceHash": "03dd0bc8fbcd49def59153eddeb4f0576797535bac04a20666863c5c110d22ef", + "modelHash": "f2d4e8f1c17badbe742c4558ed311c2ff99ccf727f628e92726c8de318deb48b", + "positions": { + "n0": { + "x": 12, + "y": 12 + }, + "n1": { + "x": 44, + "y": 98 + }, + "n2": { + "x": 44, + "y": 264 + }, + "n3": { + "x": 134.40963855421688, + "y": 576.9847138554217 + } + } + } + } +} diff --git a/tests/fixtures/opensysml-0.12-exposure-membership_test.go b/tests/fixtures/opensysml-0.12-exposure-membership_test.go new file mode 100644 index 0000000..d7a9955 --- /dev/null +++ b/tests/fixtures/opensysml-0.12-exposure-membership_test.go @@ -0,0 +1,183 @@ +package combined + +import ( + "context" + "encoding/json" + "fmt" + "reflect" + "sort" + "strings" + "testing" +) + +func TestIssue20Origins(t *testing.T) { + s, err := New("v0.12.0") + if err != nil { + t.Fatal(err) + } + call := func(method string, p any) map[string]any { + t.Helper() + b, _ := json.Marshal(p) + raw, e := s.Call(context.Background(), method, b) + if e != nil { + t.Fatal(e) + } + var v map[string]any + if e = json.Unmarshal(raw, &v); e != nil { + t.Fatal(e) + } + return v + } + source := `package ReviewSource { public part def Visible; private part def Hidden; } + package Mid { %s import %sReviewSource::*; private part def OwnHidden; } + package Probe { private import Mid::*; } + package Issue20Views { view focused : StandardViewDefinitions::GeneralView { expose %s; } }` + cases := []struct { + name, vis, all, target string + want []string + }{ + {"private-mid", "private", "", "Mid::*", []string{"Mid::OwnHidden", "ReviewSource::Visible"}}, + {"public-mid", "public", "", "Mid::*", []string{"Mid::OwnHidden", "ReviewSource::Visible"}}, + {"all-mid", "private", "all ", "Mid::*", []string{"Mid::OwnHidden", "ReviewSource::Hidden", "ReviewSource::Visible"}}, + {"private-probe", "private", "", "Probe::*", nil}, + {"public-probe", "public", "", "Probe::*", []string{"ReviewSource::Visible"}}, + {"private-all-probe", "private", "all ", "Probe::*", nil}, + {"public-all-probe", "public", "all ", "Probe::*", []string{"ReviewSource::Hidden", "ReviewSource::Visible"}}, + {"explicit", "private", "", "Mid::OwnHidden", []string{"Mid::OwnHidden"}}, + {"named", "private", "", "Mid::*", []string{"Mid::OwnHidden", "ReviewSource::Visible"}}, + {"no-import", "private", "", "Mid::*", []string{"Mid::OwnHidden"}}, + } + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + text := fmt.Sprintf(source, c.vis, c.all, c.target) + if c.name == "named" { + text = strings.Replace(text, "import ReviewSource::*", "import ReviewSource::Visible", 1) + } + if c.name == "no-import" { + text = strings.Replace(text, "private import ReviewSource::*;", "", 1) + } + p := call("ParseSources", map[string]any{"strictConformance": true, "documents": []any{map[string]any{"name": "issue20.sysml", "content": text}}}) + params := map[string]any{"modelHash": p["modelHash"], "view": "Issue20Views::focused"} + v := call("EvaluateView", params) + d := call("GetDiagnostics", map[string]any{"modelHash": p["modelHash"]}) + if ds, ok := d["diagnostics"].([]any); ok && len(ds) != 0 { + t.Fatalf("fixture diagnostics: %v", d) + } + raw, _ := json.Marshal(map[string]any{"source": text, "evaluation": v, "diagnostics": d}) + t.Log(string(raw)) + if v["status"] != "complete" { + t.Fatalf("invalid fixture: %v", v) + } + var got []string + for _, x := range v["selected"].([]any) { + m := x.(map[string]any) + name := m["qualifiedName"].(string) + o := m["origin"].(map[string]any) + start := int(o["offset"].(float64)) + length := int(o["length"].(float64)) + leaf := name[strings.LastIndex(name, "::")+2:] + visibility := "private " + if leaf == "Visible" { + visibility = "public " + } + decl := visibility + "part def " + leaf + ";" + if o["file"] != "issue20.sysml" || text[start:start+length] != decl { + t.Errorf("wrong origin %v: %q", o, text[start:start+length]) + } + got = append(got, name) + } + sort.Strings(got) + sort.Strings(c.want) + if !reflect.DeepEqual(got, c.want) { + t.Errorf("selected origins=%v want=%v", got, c.want) + } + }) + } +} + +func TestIssue20Controls(t *testing.T) { + s, e := New("v0.12.0") + if e != nil { + t.Fatal(e) + } + call := func(method string, p any) map[string]any { + t.Helper() + b, _ := json.Marshal(p) + raw, e := s.Call(context.Background(), method, b) + if e != nil { + t.Fatal(e) + } + var v map[string]any + if e = json.Unmarshal(raw, &v); e != nil { + t.Fatal(e) + } + return v + } + for _, tc := range []struct { + name, src string + bad []string + }{ + {"outside-private", `package ReviewSource { public part def Visible; private part def Hidden; } package Mid { private import ReviewSource::*; private part def OwnHidden; } package Outside { part a : Mid::OwnHidden; part b : Mid::Visible; part c : ReviewSource::Hidden; }`, []string{"Mid::OwnHidden", "Mid::Visible", "ReviewSource::Hidden"}}, + {"qualified-type-no-import", `package ReviewSource { public part def Visible; } package Outside { part a : ReviewSource::Visible; }`, nil}, + {"outside-public", `package ReviewSource { public part def Visible; } package Mid { public import ReviewSource::*; } package Outside { part a : Mid::Visible; }`, nil}, + {"ordinary-import", `package ReviewSource { public part def Visible; private part def Hidden; } package Mid { private import ReviewSource::*; private part def OwnHidden; } package Outside { private import Mid::*; part a : OwnHidden; part b : Visible; }`, []string{"OwnHidden", "Visible"}}, + } { + t.Run(tc.name, func(t *testing.T) { + p := call("ParseSources", map[string]any{"strictConformance": true, "documents": []any{map[string]any{"name": "control.sysml", "content": tc.src}}}) + d := call("GetDiagnostics", map[string]any{"modelHash": p["modelHash"]}) + b, _ := json.Marshal(d) + t.Log(string(b)) + if tc.bad == nil && strings.Contains(string(b), "severity") { + t.Fatalf("positive control diagnostics: %s", b) + } + for _, ref := range tc.bad { + if !strings.Contains(string(b), "unresolved reference: "+ref) { + t.Errorf("missing unresolved reference %s: %s", ref, b) + } + } + }) + } + text := `package Fixture { part def Device { part child; } part p : Device; } package Issue20Views { view focused : StandardViewDefinitions::GeneralView { expose Fixture::p; } }` + p := call("ParseSources", map[string]any{"strictConformance": true, "documents": []any{map[string]any{"name": "traversal.sysml", "content": text}}}) + params := map[string]any{"modelHash": p["modelHash"], "view": "Issue20Views::focused"} + v := call("EvaluateView", params) + native := call("RenderView", params) + raw, _ := json.Marshal(map[string]any{"evaluation": v, "native": native}) + t.Log(string(raw)) + selected := v["selected"].([]any) + if len(selected) != 1 || selected[0].(map[string]any)["qualifiedName"] != "Fixture::p" { + t.Fatalf("selection changed: %v", selected) + } + o := selected[0].(map[string]any)["origin"].(map[string]any) + start := int(o["offset"].(float64)) + length := int(o["length"].(float64)) + if o["file"] != "traversal.sysml" || text[start:start+length] != "part p : Device;" { + t.Fatalf("wrong selected origin %v", o) + } + found := false + childStart := strings.Index(text, "part child;") + 1 + for _, x := range native["nodes"].([]any) { + n := x.(map[string]any) + if n["name"] == "child" { + o := n["origin"].(map[string]any) + found = o["file"] == "traversal.sysml" && int(o["startLine"].(float64)) == 1 && int(o["startCol"].(float64)) == childStart && int(o["endCol"].(float64)) == childStart+len("part child;") + } + } + if !found { + t.Fatalf("native traversal lacks exact child origin: %s", raw) + } +} + +// A namespace may be declared across documents. Discarding every index +// re-export merely because one declaration has a scope is not safe. +func TestIssue20SplitNamespace(t *testing.T) { + s,e:=New("v0.12.0");if e!=nil {t.Fatal(e)} + call:=func(method string,p any) map[string]any {t.Helper();b,_:=json.Marshal(p);raw,e:=s.Call(context.Background(),method,b);if e!=nil {t.Fatal(e)};var v map[string]any;if e=json.Unmarshal(raw,&v);e!=nil {t.Fatal(e)};return v} + p:=call("ParseSources",map[string]any{"strictConformance":true,"documents":[]any{ + map[string]any{"name":"a.sysml","content":`package SourceA { part def A; } package Mid { public import SourceA::*; }`}, + map[string]any{"name":"b.sysml","content":`package SourceB { part def B; } package Mid { public import SourceB::*; }`}, + map[string]any{"name":"v.sysml","content":`package Issue20Views { view focused : StandardViewDefinitions::GeneralView { expose Mid::*; } }`}, + }}) + v:=call("EvaluateView",map[string]any{"modelHash":p["modelHash"],"view":"Issue20Views::focused"});raw,_:=json.Marshal(v);t.Log(string(raw));if v["status"]!="complete" {t.Fatalf("invalid fixture: %s",raw)} + var got []string;for _,x:=range v["selected"].([]any) {got=append(got,x.(map[string]any)["qualifiedName"].(string))};sort.Strings(got);if !reflect.DeepEqual(got,[]string{"SourceA::A","SourceB::B"}) {t.Errorf("split namespace origins=%v want SourceA::A, SourceB::B",got)} +}