diff --git a/NOTICE b/NOTICE index e21a8ae..eca9681 100644 --- a/NOTICE +++ b/NOTICE @@ -9,7 +9,7 @@ license. Application code and legacy repository structure were not imported whol OpenSysML engine source: https://github.com/Open-MBEE/OpenSysML -Pinned source: 08ef397841767b829e492bbecf6d723483b4421e (0.10.0) +Pinned source: 760c4d1fc8edfa4e214423099065c0a7c1b24e2d (0.12.0) Apache-2.0. Local bridge overlays are maintained in engine-bridge/opensysml/. SysML Toolkit: diff --git a/README.md b/README.md index 0e4147c..c0beb19 100644 --- a/README.md +++ b/README.md @@ -39,10 +39,10 @@ OpenMBEE View Editor brings source editing, named model views and reviewed graph **Source owns model semantics. Presentation owns geometry.** Renaming a part or connecting ports produces a reviewed source transaction; moving a symbol changes its position. The application runs locally in browser workers and requires no application backend.
-
+
Smart air workshop: a pneumatic assembly with typed ports and authored connections.
+Inspection rover: connected assemblies, declared component values and typed interfaces.
## Features @@ -59,7 +59,7 @@ OpenMBEE View Editor brings source editing, named model views and reviewed graph - **Arrange your workspace.** Resize or collapse Explorer, Inspector, source/diagram panes and Issues using dividers, keyboard controls or panel options. - **Choose your appearance.** Use Light, Dark or Follow system, six diagram palettes and custom muted element colors. Theme and panel preferences stay in the browser. -Structural and native requirement workflows have real-engine browser coverage; behavior, use-case, state, sequence and geometry renderers remain unavailable. See [capabilities and limits](docs/capabilities.md) and [local verification](docs/verification.md) for the implemented scope. +Structural, native requirement and bounded action/state workflows have real-engine browser coverage. Use-case, mixed, sequence and geometry renderers remain unavailable; behavior diagrams do not execute or simulate models. See [capabilities and limits](docs/capabilities.md) and [local verification](docs/verification.md) for the implemented scope. In the source editor, use **Ctrl+F** / **Cmd+F** for find/replace and **Ctrl+Z** / **Cmd+Z** for draft editing undo; **Ctrl+Y** / **Cmd+Shift+Z** redo draft edits. **Tab** moves focus out of the editor. Toolbar **Undo source** / **Redo source** operate on accepted model revisions. Highlighting is lexical; the pinned engines still validate source when you apply it. Use **Ctrl+Space** for engine-backed completion, **F1** for symbol information and **F12** for project-file definitions; pointing at a symbol also requests hover information. Queries use the complete current draft source set. Unsupported completion edits and stale replies are rejected. Formatting and inline diagnostics remain future work. @@ -74,7 +74,7 @@ npm ci npm run dev -- --host 127.0.0.1 ``` -Open the URL printed by Vite. The first dev/build verifies and prepares checksum-pinned browser engines. It needs network access, `tar` and disk space for the Go build cache. Linux x64 can download the pinned Go 1.25.11 toolchain into ignored `sandbox/`; on other platforms, set `SYSML_GO` to that Go version's binary. Later runs verify and reuse prepared assets. See [engine preparation](engine-bridge/README.md). +Open the URL printed by Vite. The first dev/build verifies and prepares checksum-pinned browser engines. It needs network access, `tar` and disk space for the Go build cache. Linux x64 can download the pinned Go 1.27.3 toolchain into ignored `sandbox/`; on other platforms, set `SYSML_GO` to that Go version's binary. Later runs verify and reuse prepared assets. See [engine preparation](engine-bridge/README.md). Vite excludes generated `sandbox/` contents and prepared `static/vendor/` binaries from development watching; app sources remain watched. If an older dev process reported `ENOSPC` from those directories, restart it with the current configuration. @@ -82,16 +82,16 @@ Vite excludes generated `sandbox/` contents and prepared `static/vendor/` binari The landing page offers **Open app** and **Try an example**. `/app` opens a project launcher with **New project**, **Open files or project**, **Load example** and **Local recovery**. No model loads implicitly. New projects stay marked modified until downloaded. -Choose **Load example** to open **Smart air workshop** in Diagram mode. Its nine source files and fourteen named views cover reusable definitions, specialization, attributes, constraints, requirements, actions, states, use cases, sequences and geometry. +Choose **Load example** to open the **Inspection rover** in Diagram mode. Six source files describe four connected assemblies, declared component values, an inspection mission, operating states and quantitative requirements. -The starting **pneumatics** view shows the pump, tank, regulator, relief valve and pressure sensor, with thirteen ports and four connections: +Follow the in-app guide through **architecture → mission → lifecycle → requirements → definitions**. The opening diagram includes a 480 Wh battery, two distinct drives, lidar, controller, camera and radio. Declared values, successions and satisfaction links are model content; the app does not execute or simulate them. -1. Select another named view in Explorer or the diagram’s **Named view** selector to explore its contents. -2. Choose **Split** to see source beside the diagram; selecting a source file keeps the active view open. -3. Edit source and choose **Apply source**. Invalid drafts retain the previous applied model and appear in **Issues**. -4. Choose **Download project** to save your work before closing or reloading. +1. Select another named view in Explorer or the diagram’s **Named view** selector. +2. Choose **Split** to see source; selecting a file keeps the active diagram open. +3. Edit source and choose **Apply source**. Invalid drafts retain the applied model and appear in **Issues**. +4. Choose **Download project** to preserve sources and arranged view geometry. -Six example views have partial structural/definition/tree rendering. The other eight evaluate through the engine and display an explicit unavailable-renderer message. The [model guide](examples/air-compressor/README.md) lists each view's current support. +[Take the rover tour](examples/inspection-rover/README.md). The launcher offers **New project**, **Open files or project**, and the rover example. The compressor sources remain in the example library for explicit imports and regression coverage. ### 3. Try graphical authoring @@ -104,17 +104,23 @@ Use **Open** to select both SysML files from [`examples/assembly/`](examples/ass Connection creation currently requires explicit compatible directions and identical resolved port types. Conjugated and ambiguous endpoint paths are unavailable for graphical creation. [Authoring workflow and supported subset](docs/architecture/graphical-authoring.md). +### 4. Inspect requirements and source + +Choose **Requirements table** above the diagram to browse applied requirement identifiers, documentation and declared links; search or sort the rows and export CSV. **Traceability matrix** shows satisfaction or verification declarations against linked elements, including explicitly negated links. These are read-only views, not executed verification results. Click a requirement or link to reveal its source; apply or discard a changed draft in that file first. + +In the source pane, **Find references** (also Shift+F12) lists project reference locations. **Format source** formats the current draft with Toolkit. Formatting supports local editor undo; **Apply source** validates and accepts it into source history. Failed or stale queries retain your work. + ## Examples Open all `.sysml` files from one example folder together. Each project has its own named views and recorded visual targets. -| Project | Explore | -| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | -| [Smart air workshop](examples/air-compressor/README.md) | A complete nine-file example spanning structure, requirements, behavior and geometry; the built-in launcher example | -| [Nested assembly](examples/assembly/) | Nested ownership, repeated usages, inherited ports, a connection and reference notation; the focused authoring fixture | -| [Parts](examples/parts/), [ports](examples/ports/) and [packages](examples/packages/) | Shared definitions, usage contexts, conjugation, imports and exposure | -| [Requirements](examples/requirements/), [use cases](examples/use-cases/), [actions](examples/actions/), [states](examples/states/) and [sequences](examples/sequences/) | Source constructs and named views, including explicit unavailable-renderer cases | -| [Complete example library](examples/README.md) | Comments, documentation, filters, empty views and per-view support gaps | +| Project | Explore | +| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | +| [Smart air workshop](examples/air-compressor/README.md) | A complete nine-file example spanning structure, requirements, behavior and geometry; a legacy import and regression fixture | +| [Nested assembly](examples/assembly/) | Nested ownership, repeated usages, inherited ports, a connection and reference notation; the focused authoring fixture | +| [Parts](examples/parts/), [ports](examples/ports/) and [packages](examples/packages/) | Shared definitions, usage contexts, conjugation, imports and exposure | +| [Requirements](examples/requirements/), [use cases](examples/use-cases/), [actions](examples/actions/), [states](examples/states/) and [sequences](examples/sequences/) | Source constructs and named views, including explicit unavailable-renderer cases | +| [Complete example library](examples/README.md) | Comments, documentation, filters, empty views and per-view support gaps | Engine evaluation, application rendering and intended visual quality are recorded separately. Passing an example does not establish full SysML v2 conformance. @@ -137,7 +143,7 @@ React Flow draws the interactive diagrams and ELK supplies automatic layout. The Use **Projects** to return to the launcher and **Resume project** to continue. The live workbench stays mounted while you browse the launcher or landing page, retaining source drafts, accepted history, selection and diagram state. A full reload starts at the launcher; existing browser recovery is offered explicitly. Landing and app share one appearance preference controller. -Folder write-back, Flexo project/branch checkout and automatic diagrams for models without named views remain planned. Opening source imports copies; it does not connect to a folder or remote branch. +Models without named views open a temporary project overview. Choose **Explore model** in the diagram’s view selector to browse project/package scopes or an explicit part definition/usage, with separate scope and representation controls and a full qualified scope caption. Overview boxes show package/ownership hierarchy; parallel declarations have separate relationship labels. Exploration uses applied source, preserves drafts and saved named-view positions, and does not persist its scope or allow graphical edits. Typed usages do not automatically expand their type’s internals. Folder write-back and Flexo project/branch checkout remain planned. Opening source imports copies; it does not connect to a folder or remote branch. **Download project** saves one `.sysml-project.json`; **Open** accepts that bundle or multiple `.sysml` files. Downloads do not write back to the imported folder. **Local recovery** keeps one recovery copy in this browser using IndexedDB, including applied source, unapplied drafts, diagram positions and the active file/view. On reload, choose **Restore recovery** or **Discard recovery** before new work can replace that copy. The dialog can disable local saving or retry storage failures. Browser storage can be cleared; retain downloaded projects as portable backups. Restored work remains modified until downloaded. With saving enabled, returning to a clean project clears this session’s older recovery; a competing tab cannot silently overwrite a newer copy. @@ -178,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 thirteen 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 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. 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/README.md b/docs/README.md index 9a93239..34ca682 100644 --- a/docs/README.md +++ b/docs/README.md @@ -27,9 +27,12 @@ OpenMBEE View Editor is a sandbox browser workspace for SysML v2 source and name | [Identity and layout preservation](architecture/identity-reconciliation.md) | Identity domains, verified edits and conservative invalidation | | [Project format](architecture/project-format.md) | Portable version-1 data and compatibility rules | +- [Read-only requirements data](architecture/requirements-data.md): source-bound tables, matrices, CSV and preservation rules. +- [OpenSysML 0.12 compatibility](research/opensysml-0.12-compatibility.md): ported overlay seams and real-worker evidence. + ## Planned work and research -- [Model exploration](architecture/model-exploration.md): agreed behavior for browsing models without authored views; not implemented. +- [Model exploration](architecture/model-exploration.md): bounded read-only browsing without authored views and its remaining opening/persistence work. - [Exploration engine investigation](research/automatic-exploration-contract.md): pinned renderer/WASM evidence and the next contract boundary. - [Flexo MMS integration](research/openmbee-flexo-mms.md): dated source research and unresolved interchange/concurrency gates; not implemented. diff --git a/docs/architecture/README.md b/docs/architecture/README.md index c8cdc02..65f813a 100644 --- a/docs/architecture/README.md +++ b/docs/architecture/README.md @@ -34,7 +34,7 @@ The [identity investigation](identity-reconciliation.md) compares real worker ou ## Views -The [agreed exploration direction](model-exploration.md) separates temporary scope/representation browsing from authored named views. It is planned; the current implementation described below requires a named view. The [domain glossary](../../CONTEXT.md) defines these terms. +The [agreed exploration direction](model-exploration.md) separates temporary scope/representation browsing from authored named views. The bounded read-only slice supports project/package overviews and explicit part subjects without a named view; automatic system-root selection and browsing-context persistence remain planned. The [domain glossary](../../CONTEXT.md) defines these terms. Named views are actual SysML usages. Open existing views without authoring another declaration. New view takes a subject, representation and name; package ownership is advanced configuration. Definitions, exposure and filters remain source content. The canvas is a derived artifact and does not serialize the model. diff --git a/docs/architecture/application-shell.md b/docs/architecture/application-shell.md index 431559c..90d08ef 100644 --- a/docs/architecture/application-shell.md +++ b/docs/architecture/application-shell.md @@ -5,10 +5,10 @@ Current implementation, updated 2026-11-01. The application uses the shared Geis ## Entry and opening - `/` presents the product, documentation/feedback/license links, **Open app** and **Try an example**. -- `/app` starts an empty launcher with **Load example**, **New project**, **Open files or project** and **Local recovery**. An existing live session adds **Resume project**. +- `/app` starts an empty launcher with **New project**, **Open files or project**, the inspection-rover **Load example** choice and **Local recovery**. An existing live session adds **Resume project**. - `/app/?example=1` requests the example once and is canonicalized to `/app` before opening; reload does not repeat the example action. Static hosting must serve the SPA fallback for `/app` while preserving asset paths. -No model loads implicitly. The built-in example opens its pneumatic named view in Diagram mode. New projects open source and remain modified until downloaded. Imports are copies; Download exports a portable bundle. Recovery is a separate browser copy, not a disk save. +No model loads implicitly. The default inspection-rover example opens its architecture named view in Diagram mode and provides a five-view guide. Compressor sources are not bundled in the app; regression tests import them explicitly. New projects open source with a temporary project overview and remain modified until downloaded. Imports without named views also open an overview; imports with named views retain their existing selection. Imports are copies; Download exports a portable bundle. Recovery is a separate browser copy, not a disk save. Applied source is validated before replacing the project. Replacing dirty work requires confirmation. Cancelled or failed replacement retains current source, drafts, model, history and compatible presentations. Failed operations remain visible and can be retried. @@ -20,7 +20,7 @@ One appearance provider owns preferences across all surfaces. Dialogs close when ## Workbench grouping -The app bar groups product/home navigation, Projects, project name/origin and persistence/settings actions. Source transaction history, apply/discard and Code/Split/Diagram controls use the measured toolbar layout. Sidebar toggles belong to their tools. The diagram header selects an actual **Named view**, displays its supported representation and applied/draft status, and keeps authoring actions nearby. Source-file selection does not replace the diagram's named view. +The app bar groups product/home navigation, Projects, project name/origin and persistence/settings actions. Source transaction history, apply/discard and Code/Split/Diagram controls use the measured toolbar layout. Sidebar toggles belong to their tools. The diagram header selects an actual **Named view** or **Explore model**. Exploration has separate scope and representation controls, uses applied source, and disables graphical authoring/movement. Named views retain their supported representation, applied/draft status and authoring actions. Source-file selection does not replace the diagram scope. See [layout contracts](workbench-layout.md), [source/recovery policies](source-assistance-recovery.md) and [project format](project-format.md). [Testing](../testing.md) and [verification](../verification.md) own checks and observed results. diff --git a/docs/architecture/model-exploration.md b/docs/architecture/model-exploration.md index 090920c..dcd4642 100644 --- a/docs/architecture/model-exploration.md +++ b/docs/architecture/model-exploration.md @@ -1,6 +1,22 @@ # Model exploration and named views -Agreed direction, 2026-11-01. This document describes planned behavior, not an implemented capability. Current diagrams still require named SysML view usages. +Agreed direction, 2026-11-01; first bounded implementation, 2026-11-05. Project/package overview trees and explicit part-definition/usage tree or structural projections are implemented on `feature/automatic-exploration`. The broader opening policy and follow-ups below remain planned. + +## Current bounded slice + +Imports without named views open a project overview; imports with named views retain their existing opening behavior. **Explore model** in the named-view selector opens temporary browsing. Separate **Exploration scope** and **Exploration representation** controls select project roots, a package or an explicit part declaration. Project/package scopes support overview trees; explicit part definitions/usages also support structural projection. Typed usages are not automatically unfolded and show that limit explicitly. No automatic system-root inference is claimed. + +Ownership overviews now draw nested packages and declarations from the native artifact's parent identities. Relationship endpoints do not determine ownership: for example, `Q` owns `Motor`, while `P::System` owns its `primary` and `standby` usages. Parallel declarations retain independent correspondence and keyboard/source inspection. Generated ownership layouts reserve right-side routing gutters within the nearest shared owner, with separate rectilinear tracks and label rows clear of declaration bodies and headers. Cross-root routes use the gaps between roots or space below intervening roots. Camera fitting includes route and label bounds. This routing depends on the generated vertical ownership layout; it is not a general router for arbitrary manually positioned diagrams. + +Exploration uses compact 52 px leaf cards without empty bodies, reduced containment padding and side-by-side leaf siblings in structural scopes. Named-view dimensions and stored geometry are unchanged. Visible Scope/Representation labels and a wrapping full qualified scope caption distinguish Part definition from Part usage, including long names at narrow widths. Selecting a diagram declaration highlights its source range without moving keyboard focus into the editor; explicit Open source still focuses the editor. + +`workspace/exploration` owns closed request/result schemas bound to the applied model hash. The semantic worker reparses the complete applied source set and calls the validated combined `Explore` bridge, which uses native `RenderExposed` without adding source. Named views and exploration share artifact/source normalization, but the exploration envelope has no authored `view` field. Source origins, topology, representation and request identity are checked before publication. + +`WorkspaceState.exploration` is independent of `view` and portable presentations. Success switches the active diagram without changing project data, dirty state, revision or semantic history. Failure, cancellation and superseded requests retain current work; draft changes invalidate pending work. Graphical movement and semantic authoring are disabled in exploration. Selection can reveal a source declaration, and source-file navigation does not change scope. Opening a named view exits exploration and restores its own saved geometry. Accepted source revisions clear the revision-bound exploration and use the opening fallback on refresh. + +Before an exploration succeeds, the empty diagram offers an explicit **Explore model** action. It retries the applied project overview independently of the already-selected navigation option, stays disabled while busy and focuses Scope on success. This preserves recovery from an initial engine failure without reopening or changing source. + +Scope, camera and exploration layout are transient and are not saved in portable bundles or local recovery. Reopening falls back to the existing named-view preference or the project overview. Reviewed named-view creation from exploration, browsing-context persistence, automatic root detection and additional representation families remain follow-ups. Opening is exploration; creating a named view is authorship. Users should be able to inspect an imported project without first changing its source. The [domain glossary](../../CONTEXT.md) distinguishes source files, the project model, scopes, representations, explorations, named views, diagram presentations and cameras. @@ -41,4 +57,4 @@ Follow-up slices are explicit creation of a named view from exploration, storage ## Engine evidence -The [pinned-engine investigation](../research/automatic-exploration-contract.md) executed the current WASM boundary and native renderer. Declaration-free project/package rendering has a promising underlying seam, but the browser contract still requires named views. A typed system usage did not automatically unfold its definition’s internals. The first delivered exploration must therefore use an overview until structural subject/usage expansion is proven, rather than promise an inferred full-system diagram. +The [pinned-engine investigation](../research/automatic-exploration-contract.md) executed the current WASM boundary and native renderer. The investigation originally identified a declaration-free project/package seam; the bounded implementation now exposes it through a separate browser contract. A typed system usage did not automatically unfold its definition’s internals. The first delivered exploration must therefore use an overview until structural subject/usage expansion is proven, rather than promise an inferred full-system diagram. diff --git a/docs/architecture/requirements-data.md b/docs/architecture/requirements-data.md new file mode 100644 index 0000000..ea2b97a --- /dev/null +++ b/docs/architecture/requirements-data.md @@ -0,0 +1,13 @@ +# Read-only requirements data + +The requirements table and traceability matrix inspect the complete **applied** source set, independently of named diagram exposure. They do not create a view declaration, modify source, evaluate requirements or write presentation data. Unapplied drafts remain unchanged and are called out explicitly. + +`workspace/requirements` owns the TypeBox response schema and source-bound validation. The browser semantic worker parses the exact applied documents, checks the model hash and calls the retained OpenSysML `EvaluateRelationships` bridge. The combined server validates that cached model before inspection. The bridge returns requirement definitions/usages (excluding verification objectives), declared identifiers and documentation, and resolved allocation/satisfaction/verification endpoints. Assertion and negation are retained separately. Library-only/unlocated endpoints cannot pass the project-origin validator. Incomplete extraction returns no rows or links; an unknown result is never presented as an empty matrix. + +The workspace checks the applied model and source references again after the asynchronous request. Switching projects, applying source or disposing the workspace rejects a late result. Component lifetime cancellation terminates the disposable worker. Read-only inspection does not take over transaction history or the active diagram. + +The table supports name/identifier/documentation filtering, name sorting and CSV export. The matrix supports satisfaction and verification, with requirements on rows and **linked** elements on columns. Matrices above 10,000 cells require narrowing the requirement search before display/export. Unlinked requirements remain rows; this first version does not enumerate all possible satisfiers, edit relationships, compute transitive links or infer compliance. Repeated local names remain separate by exact declaration identity. Multiple declarations in one cell remain separate, including negated assertions. + +Selecting a row, column or link reveals its exact recorded declaration. Source reveal is refused when that file's draft differs from applied source, rather than applying an old byte range to new text. The diagram stays mounted while data is shown, retaining its camera and presentation. CSV and read-only display settings do not alter the portable version-1 bundle. + +Selecting a named view returns to the diagram. Successfully opening or restoring a project also starts in diagram mode; failed operations retain the current representation. diff --git a/docs/architecture/source-assistance-recovery.md b/docs/architecture/source-assistance-recovery.md index 9b0ac6b..a127b2d 100644 --- a/docs/architecture/source-assistance-recovery.md +++ b/docs/architecture/source-assistance-recovery.md @@ -4,6 +4,8 @@ The workbench composes a separate Toolkit language adapter in `shell/session.ts` Completion is explicit with Control+Space. Only supported single plain-text edits are offered; snippets, additional edits and commands are excluded. Hover and F1 display plain text. F12 navigates to one exact project-file definition; unavailable, ambiguous and library-only targets explain their limits. The editor rejects results after draft, session, file or busy-state changes and rechecks completion validity when applying it. Queries do not apply source, change model history or replace views. +Diagram selection can highlight a declaration passively without moving focus away from the diagram. Explicit Open source, issue navigation and definition navigation focus the editor. This distinction prevents the Enter key used to select a diagram relationship from being inserted into the source editor during the same key event. + ## Issue navigation OpenSysML's pinned ParseSources response supplies diagnostic `span.file` and one-based `span.startLine`. The existing semantic worker normalizes those to optional `file` and `line`. The workbench selects a whole reported line, retaining raw UTF-16 offsets and CRLF/CR/LF boundaries; it does not claim a column or token range that this transport does not carry. @@ -27,3 +29,9 @@ A startup record blocks replacement until an explicit restore/discard choice. Re 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; retry reads and protects that copy for an explicit choice. 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. Only one recovery copy is kept, without a time-based expiration. Disabling saving retains the existing record until discarded. Browser storage is local to the origin and may be cleared or unavailable; downloaded project files remain the portable backup mechanism. + +## References and formatting + +The same draft-bound Toolkit language worker also handles `textDocument/references` (without the declaration) and `textDocument/formatting`. Reference ranges are normalized from UTF-16 LSP positions, limited to project documents and displayed with file/line locations; omitted library locations are counted explicitly. Navigating a result rechecks the originating source-set lifetime. + +Formatting accepts only in-bounds, nonoverlapping text edits. It retains the file's line-separator convention and updates the editor as one undoable draft transaction. Formatting does not accept a semantic model, replace other files or bypass Apply source. Draft edits, file changes, busy state and session replacement invalidate outstanding language results; failure keeps source and accepted history unchanged. diff --git a/docs/architecture/structural-views.md b/docs/architecture/structural-views.md index da66762..e7a9ac6 100644 --- a/docs/architecture/structural-views.md +++ b/docs/architecture/structural-views.md @@ -37,7 +37,7 @@ This slice establishes the fixture's nested interconnection workflow. GeneralVie ## Planned edge interactions and routing -The broader structural/tree interaction and routing follow-up remains planned. Native requirement graphs now support pointer/keyboard relationship selection and exact endpoint/source inspection. Context7 MCP documentation was checked against the installed React Flow implementation on 2026-11-01. +The broader structural/tree interaction and routing follow-up remains planned. Native requirement graphs now support pointer/keyboard relationship selection and exact endpoint/source inspection. Read-only exploration also has ownership grouping, distinct parallel relationship lanes and exact edge inspection; its compact geometry is separate from named-view presentations. Context7 MCP documentation was checked against the installed React Flow implementation on 2026-11-01. Current projections use ELK node placement and React Flow `smoothstep` edges; they do not consume ELK edge sections. The structural layout does not pass actual port geometry to ELK, and saved node positions can replace the initially calculated coordinates. Adopting initial ELK routes without reconciling those positions would leave stale paths. @@ -46,3 +46,17 @@ Extend controlled edge selection, keyboard focus/selection styling and descripti Built-in smoothstep edges already pass the [path helper's label coordinates](https://reactflow.dev/api-reference/utils/get-smooth-step-path) to SVG labels, and [BaseEdge](https://reactflow.dev/api-reference/components/base-edge) already supplies an invisible 20 px hit area. Measure selection at representative zoom levels before choosing a larger hit width. Keep plain labels in SVG; use [EdgeLabelRenderer](https://reactflow.dev/api-reference/components/edge-label-renderer) only for richer or interactive HTML, with explicit pointer events and `nodrag nopan`. Complete [edge descriptions and focus behavior](https://reactflow.dev/learn/advanced-use/accessibility) without implying full accessibility coverage. Obstacle-aware routing requires a separate contract: accurate port coordinates/directions, nested coordinate transforms, parallel edges, labels on sufficiently long clear segments and rerouting after saved-position restoration or movement. Rerouting must retain manually positioned nodes. Test exact endpoint attachment, source/history preservation, pointer/keyboard movement and save/reopen before adopting those routes. Rounded corners alone do not provide obstacle avoidance. + +## Declared values and behavior projection + +Structural artifacts may carry `attributeDeclarations`, exact source slices of effective attribute members returned by the pinned semantic model. The bridge excludes library declarations and retains authored text; it does not parse rendered detail strings, evaluate defaults or infer measured values. Named structural diagrams with at least ten artifact nodes use 240-unit cards, 32-unit attribute rows and reduced layout gaps; smaller diagrams and exploration retain their existing sizing. Simple scalar defaults display as `capacityWh: Real = 480`, removing declaration syntax and redundant fractional zeros. Complex declarations retain their source spelling. Exact declaration text remains available in each row title; this formatting never evaluates expressions or changes source. + +Native `action` and `state` artifacts use the existing compound layout and portable absolute positions. The shared graph validator rejects missing/cyclic parents and unresolved node/port endpoints. Initial/final nodes have explicit terminal notation; state/action bodies remain distinct from the structural authoring commands. Directed successions/transitions retain native identities, source correspondence and labels. Behavior ports have fixed sides; ELK's container-relative orthogonal sections and label positions are translated into canvas coordinates. Initial/final nodes constrain first/last layers. Anonymous successions and the initial-state arrow have no invented labels. Routing follows the generated layout; arbitrary obstacle avoidance after manual movement is not established. They are declared graphs, without execution or simulation. Other representation families remain separately gated. + +## Named structural connection routing + +Named interconnection diagrams now supply ELK with native port identities, physical sides, fixed port-row coordinates and label-size reservations before placement/routing. Leaf rows share their body's spare height; logical ownership does not imply DOM nesting. Compound-owned rows retain their fixed heights. The workbench renders the resulting orthogonal sections and label positions through the same `RoutedEdge` adapter used by behavior diagrams, translating each route from its layout container's coordinates. It no longer discards those routes in favor of endpoint-only smooth-step paths. + +Physical handle tips remain the visible endpoints. Direct links retain orthogonal bends when those endpoints differ from layout points. Routes and labels follow container translation during assembly movement; compatible version-1 node positions and semantic endpoints remain independent of generated routes. No source or project-format field is added. Complete obstacle rerouting after arbitrary individual-node movement remains a separate follow-up; this change establishes the generated layout and translated-assembly cases. + +The rover regression samples actual SVG geometry against every leaf card and header, plus the entire rectangle of each compound unrelated to the connection's native endpoints. This explicitly prevents unrelated power/command lines passing through inspection's empty interior. It checks ten named labels, overlap/viewport bounds and orthogonal sections, then verifies exact source and positions through assembly movement and portable reopen. Desktop/narrow captures cover both themes. diff --git a/docs/capabilities.md b/docs/capabilities.md index 54b20af..8ea12a5 100644 --- a/docs/capabilities.md +++ b/docs/capabilities.md @@ -2,23 +2,24 @@ This catalogue describes the current implementation. Check [verification](verification.md) for checks actually performed; a feature description alone is not test evidence. -| Workflow | Support | Limit | -| -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| Entry and shell | OpenMBEE landing, explicit project launcher, example/new/import/recovery actions; live-session resume and browser navigation | Folder write-back, remote checkout and automatic declaration-free exploration remain planned | -| Project sources | Multiple imported SysML files resolve together | 20 files / 5 MiB per source set; one active source editor | -| Source editing | CodeMirror lexical highlighting, line numbers, bracket matching/closing, indentation, find/replace, draft history, apply/discard and accepted source history | No formatting, inline diagnostics or syntax-aware folding; unsafe declaration/context matches lose affected positions | -| Source assistance | Toolkit completion, hover and project-file definitions bound to the full current draft source set | Explicit plain-text completion only; snippets/additional edits, ambiguous and library-only definition targets are unavailable | -| Named views | Open declarations; create GeneralView/InterconnectionView usages in a package | Custom filters/definitions remain editable in source | -| Diagram rendering | Native requirement graphs with identifiers, documentation, typing, direct/type-based ownership and declared positive/negated satisfaction; flat tree views; nested interconnection parts, ports and resolved endpoints | Requirement constraints/subject compartments, generalized typed-instance trace expansion, owned-port boundary notation, effective conjugated directions, modeled layout/routes and behavior/sequence/geometry renderers remain future work | -| Semantic editing | Reviewed part definition/usage rename, native view creation, typed part creation and directed same-type port connections | Simple identifiers and qualified names; explicit out/inout to in/inout with identical resolved types; conjugated, ambiguous or undirected endpoints, reconnect, delete, duplicate and behavior editing are unavailable | -| Model navigation | Expandable ownership tree, declared members, type/kind labels, keyboard navigation and diagram-selection reveal | View declarations use the separate Views list; inherited members are not cloned beneath usages; no full-model search or virtualized rendering | -| Source navigation | Recorded origin range from diagram/port selection, snapshot-bound Issues file/line navigation and explicit file-only handling | Explorer-only selection does not yet resolve a source range; stale, unknown and out-of-range diagnostic locations are explained without guessing | -| Diagram presentation | Move nested groups, retain descendant positions, reconcile compatible positions across reviewed edits and bounded source changes, save/reopen and refit panes | Changed bindings, headers, owners or ambiguous correspondence invalidate affected positions; manual overlap avoidance, parent resizing, camera persistence, route edits and independent layout undo remain future work | -| Workbench panels | Resizable Explorer, Inspector, source/diagram split and Issues; collapse/restore, keyboard and button resizing, reset and browser layout preferences | Full docking/floating windows are unavailable; preferences do not recover source work | -| Save/reopen | Version-1 bundle of source, drafts and positions; local recovery with explicit restore/discard and active file/view | One IndexedDB recovery copy per browser origin; storage can be cleared or unavailable; no camera persistence or folder write-back | -| Runtime validation | TypeBox project/command/message/engine schemas | Schema validity is separate from SysML semantic validity | -| Appearance | Shared shadcn/Radix controls; Light/Dark/System, six palettes and custom muted package/part fills | Browser preferences, separate from project files; detailed port color controls remain unavailable | -| Execution | No simulation controls | Rendered behavior or declared verify links would not imply execution results | +| Workflow | Support | Limit | +| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Entry and shell | OpenMBEE landing, explicit project launcher, example/new/import/recovery actions; live-session resume and browser navigation | Folder write-back and remote checkout remain planned; exploration scope/camera are transient | +| Model exploration | Read-only nested project/package ownership overview and explicit part-definition/usage tree or compact structural projection without view declarations; independent source-file navigation | No inferred system root, automatic typed-instance expansion, exploration persistence or graphical editing | +| Project sources | Multiple imported SysML files resolve together | 20 files / 5 MiB per source set; one active source editor | +| Source editing | CodeMirror lexical highlighting, line numbers, bracket matching/closing, indentation, find/replace, Toolkit draft formatting, draft history, apply/discard and accepted source history | Toolkit formatting remains a draft until apply; no inline diagnostics or syntax-aware folding; unsafe declaration/context matches lose affected positions | +| Source assistance | Toolkit completion, hover, project-file definitions and references bound to the full current draft source set | Explicit plain-text completion only; snippets/additional edits, ambiguous and library-only definition targets are unavailable | +| Named views | Open declarations; create GeneralView/InterconnectionView usages in a package | Custom filters/definitions remain editable in source | +| Diagram rendering | Native requirement graphs with identifiers, documentation, typing, direct/type-based ownership and declared positive/negated satisfaction; flat tree views; nested interconnection parts, declared attribute compartments, ports and resolved endpoints; bounded native action/state diagrams with initial/final nodes and directed relationships | Requirement constraints/subject compartments, generalized typed-instance trace expansion, owned-port boundary notation, effective conjugated directions, modeled layout/routes and case/mixed/sequence/geometry renderers remain future work | +| Semantic editing | Reviewed part definition/usage rename, native view creation, typed part creation and directed same-type port connections | Simple identifiers and qualified names; explicit out/inout to in/inout with identical resolved types; conjugated, ambiguous or undirected endpoints, reconnect, delete, duplicate and behavior editing are unavailable | +| Model navigation | Expandable ownership tree, declared members, type/kind labels, keyboard navigation and diagram-selection reveal | View declarations use the separate Views list; inherited members are not cloned beneath usages; no full-model search or virtualized rendering | +| Source navigation | Recorded origin range from diagram/port selection, snapshot-bound Issues file/line navigation and explicit file-only handling | Explorer-only selection does not yet resolve a source range; stale, unknown and out-of-range diagnostic locations are explained without guessing | +| Diagram presentation | Move nested groups, retain descendant positions, reconcile compatible positions across reviewed edits and bounded source changes, save/reopen and refit panes | Changed bindings, headers, owners or ambiguous correspondence invalidate affected positions; manual overlap avoidance, parent resizing, camera persistence, route edits and independent layout undo remain future work | +| Workbench panels | Resizable Explorer, Inspector, source/diagram split and Issues; collapse/restore, keyboard and button resizing, reset and browser layout preferences | Full docking/floating windows are unavailable; preferences do not recover source work | +| Save/reopen | Version-1 bundle of source, drafts and positions; local recovery with explicit restore/discard and active file/view | One IndexedDB recovery copy per browser origin; storage can be cleared or unavailable; no camera persistence or folder write-back | +| Runtime validation | TypeBox project/command/message/engine schemas | Schema validity is separate from SysML semantic validity | +| Appearance | Shared shadcn/Radix controls; Light/Dark/System, six palettes and custom muted package/part fills | Browser preferences, separate from project files; detailed port color controls remain unavailable | +| Execution | No simulation controls | Rendered behavior or declared verify links would not imply execution results | This repository does not claim full SysML v2 language, notation, execution or interchange conformance. @@ -28,6 +29,8 @@ Use **New part** and **Connect ports** in supported structural diagrams, or the Layout reconciliation checks every saved view, including inactive views. Verified commands use source-bound UTF-8 splices; free-form edits use conservative replacement ranges and require unchanged engine-recorded declaration headers, complete usage contexts and compatible resolved bindings. Unknown or ambiguous mappings are invalidated explicitly. This is bounded preservation, not a general persistent-identity guarantee. See [the implemented policy](architecture/identity-reconciliation.md) and [authoring contracts](architecture/graphical-authoring.md). -The explicit **Load example** project is [Smart air workshop](../examples/air-compressor/README.md): nine source files and fourteen named views, opening its pneumatic assembly in Diagram mode. Six views render partial structural/definition/tree representations; eight behavior/sequence/geometry views evaluate but report unavailable renderers. The simpler Workshop remains a separate example. +The explicit **Load example** project is [Inspection rover](../examples/inspection-rover/README.md): six source files and five guided views covering architecture, mission, lifecycle, requirements and component definitions. All five render bounded projections; their catalog entries retain limits. Inspection rover is the only bundled example; the legacy [Smart air workshop](../examples/air-compressor/README.md) remains available for explicit source import and regression coverage. Declared attributes show effective source members, with shorter labels for simple scalar defaults and exact declaration text on hover. Larger named structural diagrams use narrower cards and tighter spacing. Values are not evaluated. Action/state graphs show authored behavior, not execution. The [visualization example library](../examples/README.md) records representative models, named views, intended visual quality and current gaps. Its checks distinguish successful engine evaluation from application rendering; use-case, package-graph and other unsupported renderer fixtures remain explicit development targets. + +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). diff --git a/docs/next-steps.md b/docs/next-steps.md index 2b98205..1a2f8c2 100644 --- a/docs/next-steps.md +++ b/docs/next-steps.md @@ -1,20 +1,20 @@ # Next steps -Updated 2026-11-04. This is the forward plan; [capabilities](capabilities.md) describes shipped behavior and [verification](verification.md) records actual checks. No delivery dates are assigned. +Updated 2026-11-05. This is the forward plan; [capabilities](capabilities.md) describes shipped behavior and [verification](verification.md) records actual checks. No delivery dates are assigned. ## Current position -The editor supports reviewed rename/view/part/connection commands, bounded layout preservation, CodeMirror source editing and draft language queries, issue navigation, resizable panels and explicit browser recovery. The OpenMBEE landing → launcher → workbench flow is implemented. Opening the app does not load an example implicitly. Current diagrams require authored named views. +The editor supports reviewed rename/view/part/connection commands, bounded layout preservation, CodeMirror source editing and draft language queries, issue navigation, resizable panels and explicit browser recovery. The OpenMBEE landing → launcher → workbench flow is implemented. Opening the app does not load an example implicitly. The in-progress `feature/automatic-exploration` slice also opens project/package overviews and explicit part subjects without authored views. ## 1. Requirements and satisfaction diagrams -The active feature branch `feature/requirements-traceability` implements a bounded [native requirements renderer](architecture/requirements-views.md), with identifiers/documentation, typing/ownership, declared satisfaction polarity, source reveal and presentation preservation. The capacity and traceability fixtures establish this subset; the compressor requirements view remains a generic tree. Complete review and validation before integrating. Declared satisfaction is not a test result. Verification coverage and graphical relationship authoring remain separate follow-ups. +Merged into `develop` through PR #10 on 2026-11-05: a bounded [native requirements renderer](architecture/requirements-views.md), with identifiers/documentation, typing/ownership, declared satisfaction polarity, source reveal and presentation preservation. The capacity and traceability fixtures establish this subset; the compressor requirements view remains a generic tree. Declared satisfaction is not a test result. Verification coverage and graphical relationship authoring remain separate follow-ups. ## 2. Read-only automatic exploration -Separate planned track: inspect an imported model without adding view declarations. Use a project/package overview when a system subject is ambiguous; offer structural exploration only where the engine demonstrates the intended definition or usage expansion. +The first bounded slice inspects an imported model without adding view declarations. Use a project/package overview when a system subject is ambiguous; offer structural exploration only where the engine demonstrates the intended definition or usage expansion. -The [engine investigation](research/automatic-exploration-contract.md) is complete for its bounded probes: native `RenderExposed` is promising, but the browser worker currently exposes only named-view evaluation. The tested typed usage did not unfold its definition's internals. Implement and validate that contract before promising a consolidated system diagram. +The [engine investigation](research/automatic-exploration-contract.md) is complete for its bounded probes: native `RenderExposed` now has a separate validated browser-worker operation for overview and explicit structural subjects. The tested typed usage did not unfold its definition's internals. Automatic system-root selection and generalized usage expansion still need proof before promising a consolidated system diagram. Acceptance is owned by the [exploration plan](architecture/model-exploration.md): @@ -40,10 +40,12 @@ Publication follows only after one reviewed command preserves repository UUIDs a ## Follow-on usability and rendering -The next diagram-polish slice is [edge interaction and routing](architecture/structural-views.md#planned-edge-interactions-and-routing): verify controlled selection, accessible endpoint descriptions, readable labels and hit targets before adopting obstacle-aware routes. Existing smoothstep labels and hit areas already provide part of that behavior. +Named structural and bounded action/state diagrams now retain generated orthogonal routes and labels. Follow-on [edge interaction and routing](architecture/structural-views.md#planned-edge-interactions-and-routing) work includes accessible endpoint descriptions, hit targets and obstacle rerouting after arbitrary individual-node movement. Read-only exploration retains its separate routing geometry. - Consistent selection and source/canvas reveal, including Explorer-only selection; model-wide search and discoverable language shortcuts. - Broader structural notation, effective conjugated directions and new representation families, each backed by actual engine/renderer evidence. - Accessibility and large-model performance work based on measured workflows. Keep arbitrary source reconciliation conservative; general persistent identity, independent layout undo, camera persistence, reconnect/delete/duplicate and behavior editing remain gaps. Use the [authoring contract](architecture/graphical-authoring.md), [identity policy](architecture/identity-reconciliation.md) and [example targets](../examples/README.md) when extending them. Completed assembly milestones and earlier delivery sequences are retained in Git history, not repeated as pending work. + +The inspection-rover showcase now has bounded named action/state rendering and effective declared-attribute compartments. Remaining representation work includes case/mixed/sequence/geometry families and richer notation, each requiring independent engine and renderer evidence; execution and behavior editing remain separate work. diff --git a/docs/research/automatic-exploration-contract.md b/docs/research/automatic-exploration-contract.md index 9ccdae5..1db4c03 100644 --- a/docs/research/automatic-exploration-contract.md +++ b/docs/research/automatic-exploration-contract.md @@ -2,13 +2,15 @@ Recorded 2026-11-01. This is a bounded investigation for the [exploration plan](../architecture/model-exploration.md), not an implemented application capability. No engine pins, production dependencies, worker protocols or project formats changed during this investigation. +Implementation update, 2026-11-05: the separate validated `Explore` bridge and workspace contract now implement the bounded seam described here. See [current scope](../architecture/model-exploration.md#current-bounded-slice). The investigation and probe results below describe the earlier boundary. + ## Finding The pinned OpenSysML renderer already has a source-preserving `RenderExposed` entry point for rendering selected model elements without a view declaration. It is the preferred initial seam for a validated read-only exploration adapter because it uses the same parser, origins and rendering machinery as named views. It is not currently exposed through the application's combined WASM/worker contract. The actual Toolkit WASM also produces a whole-project graph without view declarations. That establishes an alternative projection capability, but its UUID graph identities and source locations do not satisfy the existing OpenSysML correspondence contract automatically. Switching renderers would require a separately verified identity join. -## What the application exposes today +## Application boundary at investigation time - `SemanticEngine` in `src/lib/workspace/editing/index.ts` exposes `parse` and `evaluate(documents, modelHash, view, signal)`. - `src/lib/engines/workers/schema.ts` allows only parse/evaluate semantic operations. The adapter in `src/lib/engines/opensysml/index.ts` validates an evaluation against the requested model hash and named-view identity. diff --git a/docs/research/opensysml-0.12-compatibility.md b/docs/research/opensysml-0.12-compatibility.md new file mode 100644 index 0000000..0a1360e --- /dev/null +++ b/docs/research/opensysml-0.12-compatibility.md @@ -0,0 +1,34 @@ +# OpenSysML 0.12 compatibility evaluation + +Candidate: stable [v0.12.0](https://github.com/Open-MBEE/OpenSysML/releases/tag/v0.12.0), commit `760c4d1fc8edfa4e214423099065c0a7c1b24e2d`. Evaluation started 2026-11-06 against this editor's pinned 0.10.0 bridge. This document records local evidence; upstream release assertions are not application test results. + +## Build and provenance + +The source archive SHA-256 is `49939c5f4c3008cdcf79d39a976719f67aab9d38bb38b246832eb1fdd90de08e`. The build uses Go 1.27.3 (Linux amd64 archive SHA-256 `65efbc80fdc1fa2cd2631cad5b481777a68e8725f34e89f50fc6ac714e379b40`) in ignored `sandbox/opensysml-012/`. The preparation script retains archive, overlay, toolchain, builder, binary and JS runtime provenance and runs the combined/engine Go packages before compiling WASM. Toolkit stays at 0.10.10. + +## Compatibility changes discovered + +- `Rendering.DataFor` became `DataWith`; native `marshalRenderView` now takes the cached model. +- The new renderer hides optional typing/labels by default. The bridge requests the full semantic notation needed by this app; presentation remains owned by the browser renderer. +- Native tree/interconnection rendering expands inherited usages and synthesizes satisfy-subject members. The bridge retains the existing explicit ownership projection, excluding inherited occurrences and AST-identified synthetic subjects. Named-view selected roots/exposure remain native. +- Interconnection renderings expand inherited attributes and newly include constraint nodes. The bridge retains the existing structural subset and its source-backed effective attribute compartments. Explicit owned attributes keep their prior projection. +- Native JSON omits empty node/edge arrays and anonymous node/port names. The transport canonicalizes these as arrays and empty strings without inventing declarations. +- Composition member labels and satisfaction keywords changed. The bridge retains the composed member's label and carries satisfaction kind/polarity separately from the authored relationship name. +- Extra native nodes shift ordinal IDs even though the model hash is unchanged. After the compatibility projection, node/port IDs and all dependent endpoints are renumbered in retained declaration order. Declared structural connectors whose native endpoints are inside pruned inherited children retain their nearest recorded visible component boundaries; hidden internal pins are not invented as boundary ports, and inherited internal connectors remain excluded. The old-engine correspondence fixture checks exact source origins, complete usage contexts and IDs across four example projects. +- Native tree edge ordering changed. The exploration regression now binds its source-identity assertion to the actual declaration rather than a fixed edge index; its exact source assertions remain. + +## Compatibility evidence + +Initial native bridge tests passed after the API port. A deliberate fault setting satisfaction negation to false failed the new `TestStudioRelationships` assertion; restoring the cached source passed the same command. Logs are `polarity-mutation.log` and `polarity-restored.log`. Initial browser runs exposed the changes above: identity and UTF-8 edit probes passed, while exploration, requirements and rover regressions found transport/projection differences. Failures were diagnosed from retained Playwright traces before changes were made. + +A positioned version-1 project was captured by serving the retained 0.10 WASM through the running application: `tests/fixtures/opensysml-0.10-positioned.sysml-project.json`. Its source hash, model hash and geometry are actual old-engine output, not invented fixture values. `engine-upgrade.spec.ts` imports it through the current worker and compares exact source, declaration correspondence, rendered positions and the downloaded bundle. + +Ignored working evidence is under `sandbox/opensysml-012/`: build/check/browser logs, the old module and runtime, comparison scripts and captures. The first full local functional pass was 102/110. Failures exposed richer tree relationships, lost bounded structural connections, a camera-fit/drag readiness race and an existing raw-double-versus-CSS-precision assertion. The structural connection loss is an application adaptation defect and was fixed; a deliberate boundary-projection fault failed `TestStudioRetainsDeclaredBoundaryConnections`, then passed after restoration (`boundary-mutation.log`, `boundary-restored.log`). Tree changes were checked against actual source/resolved endpoints before catalog expectations changed; the new fixture adds endpoint/declaration assertions. Geometry tests retain exact bundle doubles and use the existing browser-normalized rendering comparison. + +## Final local checks + +Code checks passed: 160 Vitest tests, ten Node checks, formatting, architecture and TypeScript. The production build passed. The full functional run passed 109/110; the failing new navigation/preservation case exposed a representation reset race, fixed before all eleven focused requirements/upgrade/source-assistance cases passed against a fresh build. All retained legacy identities/endpoints and positioned-project checks passed. The full suite was not rerun after this bounded fix. + +Normal visual comparison passed 75/77; two narrow diagram baselines were reviewed and corrected after three pixel-identical follow-up captures. Four requirement-diagram comparisons then passed. Final fresh-build workbench, requirement-diagram and table/matrix comparisons passed all fifteen. No tolerance or semantic assertion was weakened. [Full verification record](../verification.md) distinguishes initial failures, corrections, intermediate server failures and final results. + +No hosted CI result is established by these local runs. diff --git a/docs/testing.md b/docs/testing.md index 283cfff..96f6a9a 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 | Thirteen `@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 and exact requirements/satisfaction browsing | -| 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 | 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 | 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. @@ -54,7 +54,7 @@ The [example catalog](../examples/README.md) supplies independent source project ## Test outcomes -`default-example.spec.ts` explicitly loads the built-in project and checks: Diagram mode, the pneumatic assembly’s exact parts/ports/connections, fourteen named views, all nine unchanged source files and independent file/view selection. `ready()` enters `/app`, explicitly chooses **Load example**, then waits for the real-engine model; `waitForStartup()` is only a readiness assertion, and reload scenarios choose their fixture deliberately; focused history and palette tests explicitly import the smaller Workshop fixture through `openWorkshop()`. The example-library scenario exercises all fourteen compressor views and exact source download/reopen, including unavailable renderer messages. +`default-example.spec.ts` explicitly imports the retained air-workshop fixture and checks: Diagram mode, the workshop context’s exact parts/ports/connections and explicit navigation into Pneumatics, fourteen named views, all nine unchanged source files and independent file/view selection. `ready()` enters `/app`, imports the legacy compressor bundle from its source files, waits for the real-engine overview, then explicitly opens Pneumatics as the fixture for existing diagram regressions; legacy import tests use `startExample()` directly; rover startup tests load the actual built-in example; `waitForStartup()` is only a readiness assertion, and reload scenarios choose their fixture deliberately; focused history and palette tests explicitly import the smaller Workshop fixture through `openWorkshop()`. The example-library scenario exercises all fourteen compressor views and exact source download/reopen, including unavailable renderer messages. The [identity investigation](architecture/identity-reconciliation.md) runs the compiled production workers directly to compare model/view identities across source revisions. Run `npm run test:e2e -- --project=functional identity.spec.ts` for the source-change matrix and Toolkit UTF-8 splice/correspondence probe. These are worker/SDK contract checks; they do not assert that the UI retains layout after applying source. Full source inputs, evaluations, edit reports and candidate mappings are retained as JSON attachments in each scenario's output directory. @@ -84,6 +84,8 @@ Use real engines for supported semantic command acceptance, source application, Visual checks complement source, identity and geometry assertions. Current baselines cover the default palette in light/dark workbenches, expanded Appearance dialogs and nested assembly diagrams, plus a 768 px workbench and dialog. Resized panels with the Issues dock have desktop and narrow captures in both themes. Part and connection forms have desktop and narrow snapshots in both light and dark themes; the connection form repeats complete selected endpoints in wrapping text below its selectors. Functional checks exercise all palettes and custom colors, structural containment, physical connector endpoints, keyboard port selection and group/child movement through save/reopen. Full visual parity across every palette and browser is not established. Keyboard checks cover Appearance, inherited-port/source interaction, authoring-dialog focus containment/return and a stable workspace focus fallback when an accepted edit removes its original trigger. They do not cover the complete editor. Automated accessibility scanning and Firefox/WebKit coverage are not currently configured. +`exploration.spec.ts` covers no-view multi-file imports, project/package roots, repeated usage context, Unicode/CRLF declaration reveal and explicit definition-versus-typed-usage structure through the pinned worker. It compares exact source/drafts/presentations before and after browsing, including an invalid draft and named-view positions through portable reopen. Asset failure injection proves the old scope and project survive and a fresh query can succeed. Controlled workspace tests cover stale/cancelled results independently. Exploration checks also assert actual nested package/usage bounds, nonoverlapping parallel relationship labels, exact keyboard edge source reveal without source mutation, compact sibling layout and a full wrapping qualified scope at narrow width. Four focused exploration visual scenarios cover overview/structure at desktop/narrow sizes in both themes and keyboard traversal between scope and representation controls. + ## Investigate failures and review baselines ```sh @@ -102,3 +104,21 @@ npm run test:e2e -- --project=visual --grep 'workbench and appearance' --update- Inspect each generated image and available diff before retaining it. Keep expected PNGs alongside the visual spec; transient captures and experiments belong in `sandbox/`. Rerun the affected tests without `--update-snapshots`. Never update snapshots automatically in CI or treat successful baseline generation as a passing comparison. For a critical regression, demonstrate that the assertion detects a deliberate, bounded fault, restore the original behavior, and rerun. Keep fault experiments isolated from unrelated user edits. Report the injected fault, expected failure and restored passing result without claiming broader coverage. + +`overview-routing.spec.ts` checks the assembly overview against a retained native-worker artifact, samples actual SVG routes against card bodies/headers, checks thirteen label rectangles, reveals every exact declaration by keyboard and preserves the portable bundle. Four assembly visual scenarios cover overview and structure at desktop/narrow widths in light/dark. Renderer geometry reads stay in `support.ts`. + +The initial-exploration failure regression lets the actual parse worker load, fails the following exploration WASM request, then retries with the asset restored. It checks unchanged source/presentations/history and keyboard focus on Scope. Four visual cases retain the retry state at desktop/narrow widths in both themes. + +`rover.spec.ts` checks the actual default example, inherited source-declared attribute values, exact architecture connections, all six source files, the five-view guide, declared requirement links and source/geometry preservation for both native behavior families through portable reopen. `startExample()` explicitly chooses the retained air-workshop fixture for existing regressions; `waitForRoverStartup()` asserts the product default independently. Behavior captures cover desktop/narrow widths and both themes. + +## OpenSysML 0.12 and requirements data + +`engine-upgrade.spec.ts` reopens the retained 0.10 positioned project through the current real worker and compares its exact bundle, correspondence and rendered geometry. Its broader fixture retains node/port identities, origins and complete contexts for sixteen supported named views across four old-engine example projects. This is bounded compatibility evidence, not a universal cross-version identity promise. + +`requirements-data.spec.ts` checks table content, repeated qualified satisfiers, positive/negative declarations, unlinked rows, CSV, source reveal, actual verification through a cross-file alias, retained invalid drafts/positions and portable reopen. A real asset failure must occur before retry assertions. Controlled workspace tests separately reject stale/cancelled inspection replies without replacing work. Matrices over 10,000 cells require narrowing the search. + +The language-assistance suite additionally checks real Toolkit references and exact source navigation, formatting as a draft, local undo, accepted source undo/redo and formatting failure preservation. The visual project includes requirements tables/matrices at desktop/narrow widths in both themes. + +Drag helpers wait for stable heading and viewport geometry across animation frames before issuing one pointer gesture. This avoids reading a screen coordinate while a mode change is still fitting the camera; it does not retry a failed drag. The legacy workspace reopen scenario uses the same browser-normalized rendered-position helper as the newer preservation tests, while separately requiring the moved symbols and exact portable position doubles to survive reopen. + +The 0.12 assembly overview fixture records thirteen source-backed edges (ownership/reference plus typing) rather than the old six type-collapsed edges. Its routing regression still requires every route/label to avoid cards and reveals each exact declaration or type reference by keyboard. Changed example tree expectations retain explicit source targets/gaps and exact endpoint/declaration fixtures; structural boundary connection counts are not relaxed. diff --git a/docs/verification.md b/docs/verification.md index 31008c9..c00cad4 100644 --- a/docs/verification.md +++ b/docs/verification.md @@ -1,5 +1,172 @@ # Verification record +## OpenSysML 0.12 and requirements/source assistance + +Recorded 2026-11-06. OpenSysML is now pinned to 0.12.0 with the local overlays ported, verified source/toolchain checksums and retained provenance. SysML Toolkit remains at 0.10.10. Requirements table/matrix inspection is read-only; references navigate project source and formatting creates an undoable draft requiring explicit Apply. No production dependencies or saved-format changes. + +- `npm run check` passed formatting, architecture, TypeScript, 160 tests in 23 Vitest files and ten Node checks (`sandbox/opensysml-012/check-complete.log`). `npm run build -- --outDir sandbox/opensysml-012/final-dist` passed, including engine preparation/Go tests, with the existing bundle-size warning (`build-final.log` in that directory). +- The complete functional run passed 109/110 (`functional-final.log`, output `sandbox/playwright/opensysml012-functional-final`). Its new named-view/matrix regression exposed a late navigation callback resetting the user's matrix selection. Navigation now switches representation at the start, restores it on failure and disables representation controls while busy. Fresh-build `requirements-data.spec.ts engine-upgrade.spec.ts language-assistance.spec.ts` passed all eleven (`fixed-functional.log`, output `sandbox/playwright/opensysml012-fixed-functional`). This includes exact old-engine identities/endpoints and positioned-project reopen, references, formatting undo/redo, declared verification through an alias, failure/retry and unchanged source/drafts/positions. The entire 110-case suite was not rerun after this bounded fix. +- All 77 visual baselines were generated and visually reviewed at desktop/narrow widths in both themes. Normal comparison passed 75/77 (`visual-final.log`); two narrow requirement-diagram captures differed only within the zoomed canvas. Follow-up captures were pixel-identical across three runs; both images and their bounds were inspected before correcting the initial regenerated baselines. The centering helper now waits for settled viewport geometry before reading bounds. No tolerances or semantic assertions were weakened. All four requirement-diagram comparisons then passed (`requirements-visual-confirmed.log`). Against the final fresh build, all fifteen workbench, requirement-diagram and table/matrix comparisons passed without snapshot updates (`fixed-visual-confirmed.log`, output `sandbox/playwright/opensysml012-fixed-visual-confirmed`). An intermediate shared-server run lost its preview process and had eight connection-refused failures; the final run owns its server. +- Deliberately dropping negation failed the new relationship assertion; restoring it passed. Deliberately removing boundary endpoint lifting failed the declared-connector regression; restoring it passed. Retained logs: `polarity-mutation.log`, `polarity-restored.log`, `boundary-mutation.log`, `boundary-restored.log` under `sandbox/opensysml-012/`. The real navigation race also demonstrated the new browser preservation assertion failing before the fix. + +Screenshot contact sheets and pixel-bound manifest are under `sandbox/opensysml-012/visual-review/`. [Compatibility report](research/opensysml-0.12-compatibility.md) records the native behavior changes and fixture provenance. These are local Chromium results, not hosted CI, Firefox/WebKit, complete accessibility auditing or performance evidence. The matrix shows declared links to linked elements, retains negation and unlinked rows, and limits display/export to 10,000 cells; it does not execute verification or infer compliance. Cross-version compatibility is bounded to the retained fixtures. + +## Consolidated project launcher + +Recorded 2026-11-06. The empty launcher now has three ordered choices: New project, Open files or project, and Load example (Inspection rover). It removes the air-workshop choice and its raw source imports from the production app, condenses save guidance and removes the planned-workflow disclosure. Existing project resume, replacement protection, import failure handling and local recovery remain available. Compressor sources remain unchanged as explicit import/regression fixtures; the fixture helper imports their portable bundle rather than relying on a launcher-only entry point. + +- Before changing the UI, the current launcher passed its existing desktop visual comparison (`sandbox/launcher-before.log`). The new no-air-button regression then failed against the old implementation (`sandbox/launcher-consolidation-red.log`). +- `npm run check` passed formatting, architecture, TypeScript, 154 Vitest tests and ten Node checks (`sandbox/launcher-check.log`). Fresh production builds passed through the browser runs, with the existing bundle-size warning. Production JavaScript no longer contains the compressor project's name or namespace. +- Focused functional run (`sandbox/launcher-functional.log`): six passed, including empty startup without engine loading, three keyboard-accessible choices, exact compressor import, rover startup, new-project cancellation/focus return, source import and failed/cancelled replacement preservation. The mobile scenario failed because the migrated fixture helper inspected a hidden Explorer view button. The helper now verifies the same exact view ID through the visible named-view selector. No identity assertion was removed. +- Follow-up `shell-entry.spec.ts toolbar.spec.ts rover.spec.ts --grep 'launcher and browser navigation|unsupported project bundle|wrapped mobile|opens the inspection rover'`: four passed in 28.4 seconds (`sandbox/launcher-preservation.log`), covering exact drafts/source DOM/camera/layout/history through resume, invalid bundle retry, mobile controls and all six rover sources through its guided tour. +- Reviewed six entry-surface cases at 1440/768/390 in light/dark (`sandbox/launcher-visual-review.log`, six passed in 12.9 seconds), plus five fixture/header/keyboard cases (`sandbox/launcher-fixture-visual-review.log`, five passed in 28.1 seconds). Six launcher images intentionally change; two desktop panel images change only the imported fixture caption within `(300,35)–(379,45)`. Narrow fixture captures are unchanged. Captures and exact bounds were inspected: `sandbox/launcher-consolidation-review.jpg`, `sandbox/launcher-fixture-review.jpg`, `sandbox/launcher-visual-diff.json`. Baseline generation is separate from normal comparison. + +Final normal comparison with `--project=visual --grep 'OpenMBEE entry surfaces|resized workspace panels|workbench and keyboard'` passed all eleven in 35.6 seconds without updates, retries or skips (`sandbox/launcher-visual-final.log`). The final repository-wide `npm run check` stopped at formatting of unrelated, newly present `docs/research/syside.md` (`sandbox/launcher-final-check.log`); that file was left untouched. Formatting checks over the changed files passed (`sandbox/launcher-changed-format.log`). Architecture, TypeScript, 154 Vitest tests and ten Node checks passed independently (`sandbox/launcher-final-code-check.log`), distinguishing that formatting blocker from this change. The user subsequently requested removal of the untracked Syside research document; it was removed before committing. The complete `npm run check` then passed (`sandbox/launcher-commit-check.log`), including formatting, architecture, TypeScript, 154 Vitest tests and ten Node checks. + +Full functional/visual suites, hosted CI, deployment, Firefox/WebKit and complete accessibility auditing are outside this focused checkpoint. No semantic engine, source model, dependency or saved format changes. + +## Recovery dialog capture isolation + +Recorded 2026-11-06 after publishing rover commit `04f6d41`. Hosted run [42165758615](https://github.com/Open-MBEE/sysml_view_editor/actions/runs/42165758615) passed code checks, audit and all 15 critical browser workflows in both attempts. Both attempts passed 68/69 visual comparisons; the remaining light recovery-dialog crop differed at pixel `(399, 295)`, its last row. The original baseline retained a dark pixel at the crop edge, consistent with the underlying diagram leaking into an enclosing pixel-rounded screenshot. The focused local reproduction also failed at exactly that pixel (`sandbox/recovery-pixel-repro.log`); captured trace and diff evidence distinguish it from a dialog-content or semantic failure. + +This scoped dialog comparison now applies `tests/e2e/recovery-dialog.css` only during capture, hiding the unrelated React Flow background while preserving layout. Full-page diagram comparisons remain unchanged. The first isolated run passed three cases and retained the expected failure against the old background-bearing baseline (`sandbox/recovery-pixel-isolated.log`). Only the affected light baseline is regenerated for the intentionally isolated capture. No screenshot tolerance, semantic assertion, application styling or production code changes. The changed baseline was visually inspected. Normal comparisons then passed all four theme/width cases in 23.3 seconds (`sandbox/recovery-pixel-final.log`), including keyboard focus return. `npm run check` passed again with 154 Vitest tests and ten Node checks (`sandbox/recovery-pixel-check.log`). The final hosted run remains separate from these local results. + +## Compact rover presentation + +Recorded 2026-11-06. Named structural graphs with at least ten nodes now use narrower cards, tighter gaps and 32-unit attribute rows. Simple scalar defaults display without declaration syntax or redundant fractional zeros; complex declarations retain exact spelling, and all rows retain exact declarations in their titles. Source and engine attribute payloads are unchanged. + +`npm run check` passed formatting, architecture, TypeScript, 154 Vitest tests and ten Node checks (`sandbox/rover-polish-check.log`). The fresh production build and actual-worker rover default/assembly movement-and-reopen checks passed two cases in 30.4 seconds (`sandbox/rover-polish.log`). These assert resolved native endpoints, all ten routes against unrelated compound interiors, orthogonal SVG geometry, exact source and compatible saved positions. The small formatter unit verifies both concise labels and expression fallback. + +The targeted 19-case visual baseline review covers desktop/narrow layouts in both themes, including keyboard-operated appearance, panels, structural selection, nested assemblies, default rover and guided behavior. Captures were visually inspected, including `sandbox/rover-polish-review.jpg`. The older PR #11 CI failure was limited to diagnostics-light/dark diagram-position screenshots; its actual/diff captures are retained under `sandbox/previous-pr-evidence`. Baseline review passed 19 cases in 2.3 minutes (`sandbox/rover-polish-visual.log`). The subsequent normal comparison added the four diagnostics cases and passed all 23 in 2.8 minutes without updates, retries or skips (`sandbox/rover-polish-visual-final.log`, output `sandbox/playwright/rover-polish-visual-final`). This is local evidence, not hosted CI. No Firefox/WebKit, full accessibility audit, simulation or arbitrary moved-node obstacle rerouting is established. + +## Structural connection routing around unrelated containers + +Recorded 2026-11-05 on `feature/inspection-rover-showcase`. The rover screenshot exposed a mismatch: named structural diagrams used ELK node placement but discarded its routes, drawing endpoint-only smooth-step connections across inspection and its children. Named interconnection diagrams now pass actual native port IDs, sides, rendered row positions and label reservations into ELK and render its orthogonal sections/label positions through the shared `RoutedEdge` adapter. The adapter translates routes with their owning container during assembly movement and retains orthogonal direct links at physical handle tips. No source semantics, persistence format, dependencies or engine pins changed. + +The actual-worker regression failed against the previous built app with camera/lidar/radio crossings and overlapping labels (`sandbox/structural-routing-red.log`, `sandbox/playwright/structural-routing-red`). The geometry helper now additionally treats a compound's entire body as an obstacle unless it owns one of that connection's native endpoints. Thus a line cannot pass through inspection's empty interior merely because it misses camera/radio. All ten rover labels, card/header/group bounds, label overlap/viewport bounds and orthogonal SVG sections are checked. Existing overview/behavior checks retain their original scope by default. + +Checks and retained evidence: + +- Focused layout units: `npx vitest run src/lib/workbench/diagrams/structure.test.ts`: ten passed (`sandbox/structural-routing-unit.log`), including compatible absolute positions, added connections and fractional owner boundaries. +- `npm run test:e2e -- --project=functional assembly.spec.ts rover.spec.ts --output=sandbox/playwright/structural-routing-functional`: six passed; mission drag failed because its fixed screen-space grab point missed a small fitted card. The drag helper now chooses a point inside the actual card header, retaining the same drag displacement and outcome assertion. The unchanged mission preservation assertion passed in the targeted rerun (`sandbox/structural-routing-mission-drag.log`, one passed in 14.0 seconds). No application failure was hidden or assertion weakened. +- Final port alignment: visual inspection found a slight diagonal in an otherwise straight assembly connection. React Flow's logical parent IDs do not create DOM ancestors, so nested leaf port rows also share their spare body height. The fixed coordinates now account for this, borders and attribute rows. Direct links remain orthogonal when actual tips differ from planning points. `npm run test:e2e -- --project=functional assembly.spec.ts rover.spec.ts --grep 'renders nested ownership|opens the inspection rover|moved rover assembly' --output=sandbox/playwright/structural-routing-aligned`: three passed in 23.8 seconds, establishing actual port endpoints, the complete rover graph and geometry/source preservation after moving the assembly and portable reopen. Earlier group-preservation evidence also passed two cases in 17.4 seconds (`sandbox/structural-routing-preservation.log`). +- `npm run check` passed formatting, architecture, TypeScript, 153 Vitest tests and ten Node checks (`sandbox/structural-routing-check-confirmed.log`). An earlier check stopped at test formatting; corrected runs passed. Fresh production builds passed through browser runs, with the existing bundle-size warning. +- Final targeted visual review: `npm run test:e2e -- --project=visual --grep 'workbench and appearance dialog|workbench and keyboard|nested assembly|resized workspace panels|selected structural nodes|default rover architecture|rover guided behavior' --update-snapshots --output=sandbox/playwright/structural-routing-aligned-visual-review`: 19 passed in 1.7 minutes. Earlier intermediate alignment captures/comparisons remain under `sandbox/playwright/structural-routing-visual-review` and `structural-routing-visual-final`; those preceded the final row-alignment correction. +- Normal comparison with the same selector and `--output=sandbox/playwright/structural-routing-aligned-visual-final` passed all 19 in 1.7 minutes without baseline updates, retries or skips. This includes actual SVG geometry assertions for the default rover at desktop/narrow widths in both themes. Behavior captures remain covered alongside structural changes. + +Fifteen structural images intentionally change, with 85 images byte-for-byte unchanged against `sandbox/structural-routing-baselines-before/`. Exact files are in `sandbox/structural-routing-visual-manifest.json`. Changed captures were inspected on four sheets under `sandbox/structural-routing-inspection/`, with final assembly endpoint alignment and rover captures also inspected at full size in both themes and widths. Full visual/functional suites, hosted CI, deployment, Firefox/WebKit and comprehensive accessibility were not repeated. Arbitrary obstacle rerouting after individual-node movement remains outside this generated-layout/container-translation contract. Source selection and compatible saved positions remain independently tested; routes are transient presentation geometry. + +## Inspection rover showcase and behavior rendering + +Recorded 2026-11-05 on `feature/inspection-rover-showcase`, using Node 26.3.1 and the existing pinned engines. The product default is now a six-file Inspection rover with four nested assemblies, ten resolved port connections, effective source-declared attributes, component specialization, a mission, operating states and four quantified requirement/satisfaction traces. Four source allocations connect mission steps to real rover usages. The built-in guide connects the five authored views. The air workshop remains separately available; its existing sources are unchanged. New rover sources are authored here under Apache-2.0. + +The bridge adds exact source slices for effective project attribute members via `MembersOf`, excluding library declarations; it does not evaluate defaults or infer measured performance. A retained Go test verifies the same inherited declaration on two distinct typed usages. Native action/state graphs now use compound rendering, terminal shapes, directed relationships and existing source/portable-position contracts. Their transport uses the existing graph validator, with two added unresolved-endpoint regressions. No dependency versions, engine pins or portable/recovery format changed. The generated engine overlay/provenance changes through the existing preparation process. + +Evidence and checks: + +- `npm run check` passed formatting, architecture, TypeScript, 153 Vitest tests and ten Node checks (`sandbox/rover-check-confirmed.log`). A first check stopped at catalog formatting; corrected runs passed. Fresh production builds passed throughout the browser workflow, including retained Go bridge tests; the existing bundle-size warning remains. +- Hiding the actual attribute compartment deliberately made the real-engine showcase assertion fail with **Received: hidden** (`sandbox/rover-attributes-red.log`, `sandbox/playwright/rover-attributes-red`). Restored rendering passed the critical path. This is UI fault injection, not a substituted semantic engine. +- Initial local probes rejected reserved identifiers `frame` and `message`; final source uses `scanData` and `report`. The retained native capture (`sandbox/rover-native.json`) supplied the reviewed catalog expectations. `npm run test:e2e -- --project=functional examples.spec.ts --grep 'example library: (inspection-rover|actions|states|air-compressor)$' --output=sandbox/playwright/rover-catalog`: four passed in 1.3 minutes. This exercises all five rover views, both small behavior fixtures, all fourteen air-workshop views and exact source download/reopen. It preceded the final behavior routing refinement below. +- `npm run test:e2e -- --project=functional rover.spec.ts examples.spec.ts shell-entry.spec.ts --grep 'inspection rover|mission source|lifecycle source|example library (inspection-rover|actions|states|air-compressor)|landing and direct example|newer example opening' --output=sandbox/playwright/rover-native-final`: five passed in 35.9 seconds. The grep omitted the colon needed to select catalog cases, so this run establishes rover/entry coverage only; catalog coverage is recorded separately. +- `rover.spec.ts` verifies exact architecture identities/connections, effective declared values, all six source texts, requirement links and independent view browsing. Both behavior families reveal exact node/edge source ranges by keyboard, retain source on Enter, and preserve independently moved positions and exact portable data after a fresh reopen. `sandbox/rover-source-geometry-final.log`: three passed in 33.9 seconds before final routing; final routing results follow. + +### Behavior routing refinement + +Inspecting the initial captures showed generic smooth-step return paths crossing state cards and overlapping labels. The actual-SVG regression against that built version failed on card/header crossings and label overlap (`sandbox/rover-behavior-routing-red.log`). Behavior layout now requests fixed-side ports and labels from ELK, translates its container-relative orthogonal sections into canvas coordinates, retains labels only where native declarations supply them, and constrains initial/final layers. These are generated presentation routes, not modeled source routes. Arbitrary obstacle avoidance after manual movement remains outside scope. + +`npm run test:e2e -- --project=functional rover.spec.ts --output=sandbox/playwright/rover-routing-confirmed`: three passed in 35.0 seconds, with actual SVG/card/header intersections, named label overlap/counts, exact node/relationship source selection and portable geometry preservation. An intermediate attempt flagged anonymous arrows as missing labels: the helper now takes explicit unlabelled native edge IDs, still checks every path and rejects unexpected labels on those edges. Existing overview assertions retain their default requirement that every relationship has a label; no crossing or overlap assertion was weakened. + +Final compatibility after routing and the final drag-event guard: `npm run test:e2e -- --project=functional rover.spec.ts examples.spec.ts --grep 'mission source|lifecycle source|example library: (actions|states|air-compressor)$' --output=sandbox/playwright/rover-compatibility-final`: five passed in 1.6 minutes (`sandbox/rover-compatibility-final.log`). This includes the smaller action/state fixtures, all fourteen air-workshop views and both rover behavior source/geometry regressions. + +The final default-tour smoke against the freshly built local preview also asserts each selected native view's actual rendered root text: `npx playwright test --config=sandbox/rover-local.config.ts --project=functional rover.spec.ts --grep 'opens the inspection rover' --output=sandbox/playwright/rover-default-confirmed`: one passed in 6.8 seconds (`sandbox/rover-default-confirmed.log`). This uses the real local production preview without another build. + +### Visual evidence + +The full visual baseline review passed all 69 cases in 6.7 minutes (`sandbox/rover-visual-review-final.log`). A preliminary run was intentionally cancelled after inspection found unthemed behavior terminals; source colors and requirement descriptions were corrected before the full completed review. Against `sandbox/rover-baselines-before/`, 25 existing images intentionally change for rover startup, the additional launcher choice, declared attribute compartments and contextual authoring controls; 14 rover images are new and 61 images remain byte-for-byte unchanged. `sandbox/rover-visual-manifest.json` records exact files/cases. All changed/new captures were inspected in seven sheets under `sandbox/rover-visual-inspection/`, with rover architecture, mission, lifecycle and requirement captures also inspected at full size in desktop/narrow and light/dark variants. + +Normal comparison of all 31 changed/new visual cases passed in 2.5 minutes without updates, retries or skips (`sandbox/rover-visual-final.log`, executable selector in `sandbox/run-rover-normal.py`). This preceded only the behavior routing refinement. The four affected guided behavior cases were subsequently regenerated and inspected: four passed in 39.4 seconds (`sandbox/rover-routing-visual-review.log`); normal comparison with `--project=visual --grep 'rover guided behavior' --output=sandbox/playwright/rover-routing-visual-final` passed all four in 34.7 seconds, again without updates, retries or skips. Their SVG geometry checks run at both widths and themes. No existing structural/requirement assertion or baseline was changed merely to conceal a failure. + +Full functional suites, hosted CI, publication, Firefox/WebKit, comprehensive accessibility, execution and simulation were not established by this local checkpoint. Appearance dialog focus return and keyboard source selection are covered, not complete accessibility conformance. Local captures under ignored `sandbox/` may be absent from another checkout. An importable source-only bundle is retained at `sandbox/inspection-rover.sysml-project.json`. + +## Workshop overview startup + +Recorded 2026-11-05 on `feature/workshop-startup`. The built-in example's explicit preferred named view is now `CompressorViews::overview`, showing `WorkshopContext` with mains, compressor interface, tool and service console. Pneumatics remains a separately selectable detail view. The nine source documents and all fourteen view declarations are unchanged. + +The startup regression was first run against the old preference and failed with expected `CompressorViews::overview`, received `CompressorViews::pneumatics` (`sandbox/workshop-startup-red.log`). The final regression verifies the real worker's exact context nodes, six ports and two connection labels, all nine source texts, independent source navigation and explicit transition into Pneumatics. Existing diagram regressions deliberately select their Pneumatics fixture after the actual overview startup; startup and direct-link tests assert the real default independently. + +- `npm run check`: passed formatting, architecture, TypeScript, 151 Vitest tests and ten Node checks (`sandbox/workshop-startup-check.log`). +- `npm run test:e2e -- --project=functional default-example.spec.ts toolbar.spec.ts shell-entry.spec.ts --grep 'complete Smart air|wrapped mobile|landing and direct example' --output=sandbox/playwright/workshop-startup-functional`: three passed in 19.3 seconds, including landing/direct example links, reload to launcher and mobile header layout. Production builds passed through browser runs with the existing bundle-size warning. +- Visual baseline generation for `workbench and appearance dialog|default workshop overview`: four passed in 19.7 seconds (`sandbox/workshop-startup-review.log`). The two existing workbench images intentionally show overview instead of Pneumatics; two new narrow images cover both themes. All four were inspected. Appearance-dialog images remain byte-for-byte unchanged against copies in `sandbox/workshop-startup-before/`. + +Normal comparison: `npm run test:e2e -- --project=visual --grep 'workbench and appearance dialog|default workshop overview' --output=sandbox/playwright/workshop-startup-visual-final`: four passed in 16.8 seconds without baseline updates, retries or skips (`sandbox/workshop-startup-visual-final.log`). + +Full browser suites, hosted CI, deployment and comprehensive accessibility were not run for this local change. No engine pins, dependencies or saved formats changed. + +## Initial exploration failure recovery + +Recorded 2026-11-05 for PR #11. The Codex review correctly identified that a no-view project's initial exploration failure left the only selector option already selected, with no change event available to retry. The empty diagram now exposes **Explore model**, disabled while busy, using the existing read-only command and focusing Scope after success. + +The focused regression lets the pinned parse worker succeed and fails only the following exploration WASM request. Before the fix it failed because the retry button did not exist (`sandbox/exploration-retry-red.log`). After the fix both initial and later asset-failure cases passed: `npm run test:e2e -- --project=functional exploration.spec.ts --grep 'retries an initial|asset failure preserves' --output=sandbox/playwright/exploration-retry-green` (two passed, 15.1 seconds). It verifies the exact portable bundle, applied source, disabled source undo/apply controls and successful native diagram after keyboard retry. The final initial-failure case also asserts Scope focus. + +`npm run check` passed 151 Vitest tests and ten Node checks, formatting, architecture and TypeScript (`sandbox/exploration-retry-check.log`). A first attempt stopped at test formatting; the corrected run passed. Fresh production builds passed through the browser runs. The combined targeted functional/visual baseline generation passed five cases in 27.6 seconds (`sandbox/exploration-retry-review.log`). All four new retry-state captures were inspected at desktop/narrow sizes in light/dark; existing baselines were not updated. + +The first normal visual comparison exposed a delayed sidebar tooltip at narrow width. Dismissing it with Escape also restores its trigger's focus, so the capture now dismisses the tooltip, explicitly focuses the retry button afterward and asserts that focus before capture. Intermediate comparisons recorded these test-setup differences under `sandbox/playwright/exploration-retry-visual-final`, `exploration-retry-visual-confirmed` and `exploration-retry-visual-final-normal`; existing visual assertions were not weakened. Final captures intentionally include the retry button's keyboard focus ring. + +Final baseline generation passed four cases in 22.7 seconds (`sandbox/exploration-retry-focus-final-review.log`). The final normal comparison `npm run test:e2e -- --project=visual --grep 'initial exploration retry' --output=sandbox/playwright/exploration-retry-confirmed` passed all four in 21.6 seconds without updates, retries or skips. Final captures were inspected. + +Full browser suites, hosted CI and comprehensive accessibility were not established by these local runs. No dependencies, engine pins or saved formats changed. + +## Assembly overview routing + +Recorded 2026-11-05 on `feature/automatic-exploration`, using Node 26.3.1 and the existing pinned browser engine. Ownership layouts reserve shared-owner gutters and separate rectilinear tracks/label rows. Fit includes route and label bounds. Structural layouts, semantic endpoints, native correspondence and portable data remain unchanged. + +The fixture `tests/fixtures/exploration-assembly-artifact.json` was captured from the actual pinned worker for `examples/assembly/model.sysml`; the browser regression compares the returned artifact exactly. The independent route/card geometry assertion failed against the old curves (`sandbox/assembly-routing-red.log`), then passed with the new routing. A separate unit case covers forward/reverse routes across an intervening root and their camera bounds. + +Executed checks: + +- `npm run check`: passed formatting, architecture, TypeScript, 151 Vitest tests and ten Node checks (`sandbox/assembly-routing-check.log`). An earlier attempt stopped on helper formatting, corrected before this successful run. +- `npm run test:e2e -- --project=functional overview-routing.spec.ts --output=sandbox/playwright/assembly-routing-functional-final`: one passed in 12.1 seconds. Actual SVG paths avoid card bodies/headers, all six labels avoid each other and remain in the viewport. Every edge retains keyboard focus and reveals its exact source declaration without changing source or the portable bundle; the structural handoff still renders. An initial discovery attempt required a JSON import attribute and did not exercise the application. +- Baseline generation with `--project=visual --grep 'assembly exploration routing|read-only model exploration' --update-snapshots --output=sandbox/playwright/assembly-routing-visual-review`: eight passed in 57.8 seconds. Eight new assembly captures and four intentionally changed P/Q overview captures were inspected at desktop/narrow widths in both themes. The four existing exploration structure captures are byte-for-byte identical to their retained copies in `sandbox/assembly-routing-baselines-before/`. + +Normal comparison: `npm run test:e2e -- --project=visual --grep 'assembly exploration routing|read-only model exploration|nested assembly' --output=sandbox/playwright/assembly-routing-visual-final`: ten passed without baseline updates, retries or skips (`sandbox/assembly-routing-visual-final.log`). This includes existing named assembly views. + +Full browser suites, Firefox/WebKit, hosted CI and general arbitrary-layout obstacle avoidance were not run. Fresh production builds run through the browser workflow; the existing bundle-size warning remains. No dependency, saved format or engine pin changed. Local evidence is retained under ignored `sandbox/`. + +## Exploration hierarchy, relationships and layout polish + +Recorded 2026-11-05 on `feature/automatic-exploration`. The earlier flat overview was insufficient for useful model exploration. The replacement honors native parent identities with nested package/declaration boxes; relationship targets never become owners. The real-worker regression verifies `P` → `System` → `primary`/`standby` and `Q` → `Motor`, actual rendered containment, separate native relationship identities and nonoverlapping labels. Compact exploration leaf cards are 208 × 52 model-space units; the two-part structural container is 468 × 136, with side-by-side siblings and no empty leaf body. Existing named-view dimensions and presentation data retain their original contract. + +Visible Scope/Representation fields and the full wrapping qualified scope caption distinguish Part definition from Part usage. The narrow real-engine scenario verifies the complete `Engineering::CompressorControlSubsystem::RedundantDriveAssembly` scope and its separate `installedDrive` usage without clipping. The existing inherited-port keyboard/source workflow also passed, establishing that explicit Open source still focuses the editor. + +Keyboard edge selection in Split mode exposed a source-preservation bug: selecting a declaration synchronously focused CodeMirror during Enter keydown, allowing an `insertLineBreak` to replace the highlighted declaration. `sandbox/exploration-keyboard-red.log` records the focus transfer and `Source preserved: false`; the source-range update is now passive for diagram selection, while explicit Open source/issue/definition navigation still focuses the editor. The focused real-engine regression verifies that both edges retain focus, leave Apply source disabled, reveal the exact primary/standby declarations on request and preserve the complete portable bundle. The restored probe reports `Source preserved: true` (`sandbox/exploration-keyboard-green.log`). No debug instrumentation remains in application/test source. + +Executed checks: + +- `npm run check` passed formatting, architecture, TypeScript, 149 Vitest tests and ten Node checks (`sandbox/exploration-polish-check.log`). This preceded the final leaf-border and spacing adjustment; the final fresh production builds and focused unit results below include it. +- Final focused units: `npx vitest run src/lib/workbench/diagrams/exploration.test.ts src/lib/workspace/exploration/exploration.test.ts src/lib/workbench/properties/diagram-selection.test.ts`: 11 passed (`sandbox/exploration-polish-unit-final.log`). +- `npm run test:e2e -- --project=functional exploration.spec.ts --output=sandbox/playwright/exploration-polish-functional`: three passed, one failed on the subsequently fixed Enter/source mutation. The corrected critical path passed with `--grep 'opens a no-view' --output=sandbox/playwright/exploration-polish-focus-final` (8.9-second scenario, 14.4-second run). An intermediate run reached an option-disabled assertion despite a native disabled attribute; explicit label/field associations replaced wrapping labels, and the unchanged disabled-option assertion passed in the final run. Logs: `sandbox/exploration-polish-functional.log`, `sandbox/exploration-polish-focus-fixed.log`, `sandbox/exploration-polish-focus-final.log`. +- `npm run test:e2e -- --project=functional exploration.spec.ts assembly.spec.ts --grep 'entire qualified scope|inherited ports by keyboard' --output=sandbox/playwright/exploration-polish-navigation`: two passed in 17.9 seconds (`sandbox/exploration-polish-navigation.log`). +- Only the eight exploration overview/structure baselines intentionally changed. Final generation: `npm run test:e2e -- --project=visual --grep 'read-only model exploration' --update-snapshots --output=sandbox/playwright/exploration-polish-visual-final-review`: four passed in 33.2 seconds. All eight captures were inspected at desktop/narrow widths in light/dark, including complete leaf borders and readable separated labels. +- Normal comparison: `npm run test:e2e -- --project=visual --grep 'read-only model exploration|workbench and appearance dialog|nested assembly' --output=sandbox/playwright/exploration-polish-visual-confirmed`: eight passed in 47.1 seconds, without baseline updates, retries or skips. This includes four existing named-view scenarios with unchanged baselines. Log: `sandbox/exploration-polish-visual-confirmed.log`. + +Fresh production builds passed through these runs with the existing bundle-size warning. Full functional/visual suites, hosted CI, audit, Firefox/WebKit, comprehensive accessibility and large-model/obstacle-routing coverage were not repeated. Parallel lanes establish distinct paths and label positions for the demonstrated declarations; they do not establish general obstacle avoidance. No project/recovery format, dependency version or engine pin changed. Evidence is local and ignored under `sandbox/`. + +## Read-only exploration checkpoint + +Recorded 2026-11-05 on `feature/automatic-exploration`, with Node 26.3.1 and the existing pinned engines. The new `Explore` operation renders project/package trees and explicit part-definition/usage tree or structural subjects without adding declarations. Its separate envelope and workspace state do not become named-view presentation keys. No dependency versions, upstream pins/checksums or portable/recovery formats changed. + +The retained Go bridge tests passed during engine preparation, including multi-file project/package roots, explicit definition versus typed usage, exact source correspondence, unchanged model hash and rejection of invalid/library-only subjects or invalid semantic source. Fresh production builds passed through the focused browser runs; the existing bundle-size warning remains. `npm run check` passed formatting, architecture, TypeScript, 145 Vitest tests and ten Node checks (`sandbox/exploration-check-final.log`). Four exploration unit cases exercise malformed/stale transport, applied-source queries with invalid drafts, presentation/history preservation, engine failure and superseded requests. Removing the exploration publication generation check made the replacement-project regression fail; the restored implementation passed the final check. Fault evidence: `sandbox/exploration-stale-fault.log`. + +`npm run test:e2e -- --project=functional exploration.spec.ts --output=sandbox/playwright/exploration-functional` initially passed the named-view/draft/reopen preservation and injected asset-failure/retry scenarios. Its no-view scenario stopped because the assertion expected a name rather than the existing full-declaration reveal contract: the trace selected exactly `part primary : Q::Motor;` after Unicode/CRLF source. The corrected no-view scenario passed, then passed again after adding explicit query-control focus return: `npm run test:e2e -- --project=functional exploration.spec.ts --grep 'opens a no-view' --output=sandbox/playwright/exploration-focus-final` (10.2-second scenario, 15.8-second run). All three scenarios passed across these focused runs; they were not repeated as a complete functional suite. Logs: `sandbox/exploration-browser.log`, `sandbox/exploration-no-view-final.log`, `sandbox/exploration-focus-final.log`. + +Four visual scenarios retain eight new overview/structure captures at 1440/768 px in light/dark. Reviewed baseline generation passed under `sandbox/playwright/exploration-visual-reviewed` (32.2 seconds); all eight images were inspected. An earlier narrow keyboard check exposed an asynchronous panel focus return; the test now observes the reopen-button focus before traversing the exploration controls. A later normal run stopped on an incorrect readiness locator for a nonexistent standalone text leaf; the trace showed the combined `part def System` card, and readiness now asserts that exact rendered group. No existing baseline or tolerance was changed. The final normal comparison, `npm run test:e2e -- --project=visual --grep 'read-only model exploration' --output=sandbox/playwright/exploration-visual-confirmed`, passed all four cases in 30.4 seconds without updates, retries or skips. Logs: `sandbox/exploration-visual-reviewed.log`, `sandbox/exploration-visual-final.log`, `sandbox/exploration-visual-confirmed.log`. + +These are local results. The full functional/visual suites, dependency audit, Firefox/WebKit and complete accessibility/performance audits were not repeated for this slice. Automatic system-root inference, generalized typed-instance expansion, exploration persistence and reviewed named-view creation from an exploration remain outside the delivered subset. Applying or undoing/redoing accepted source clears revision-bound exploration and uses the opening fallback when the workbench refreshes. Ignored evidence may be absent from other checkouts. + +## Requirements integration + +On 2026-11-05, PR #10 merged into `develop` as `7b37ebe40f350f316be8ad951c095a7fec35fd92`. GitHub reported a clean merge state and no unresolved review threads. The required `checks` job passed for head `ac7ee9f` in [Validate run 42040187761](https://github.com/Open-MBEE/sysml_view_editor/actions/runs/42040187761). That head adds only a routing-documentation correction to the previously validated requirements implementation. No exploration work was included in this merge. + ## Native requirements and satisfaction checkpoint Implementation recorded 2026-11-03; final validation continued 2026-11-04 on `feature/requirements-traceability`, using Node 26.3.1, npm 11.16.0 and the pinned engines. Native requirement graphs now retain declared short identifiers, full available AST documentation, selected/contextual membership, satisfaction assertion/negation and direct versus type-based ownership. The app adds bounded requirement/model-element cards, labeled relationships, pointer/keyboard selection, exact source reveal and existing version-1 presentation persistence. No production dependencies, upstream engine pins, archive checksums or saved-format version changed. diff --git a/engine-bridge/README.md b/engine-bridge/README.md index 2f83332..7fd98d5 100644 --- a/engine-bridge/README.md +++ b/engine-bridge/README.md @@ -4,10 +4,10 @@ The browser app requires OpenSysML and SysML Toolkit WASM workers. Preparation s | Engine | Pin | Purpose | | ------------- | ---------------------------------------------------- | ----------------------------------------------------------- | -| OpenSysML | 0.10.0 / `08ef397841767b829e492bbecf6d723483b4421e` | Parsing, model inspection, validation and native evaluation | +| OpenSysML | 0.12.0 / `760c4d1fc8edfa4e214423099065c0a7c1b24e2d` | Parsing, model inspection, validation and native evaluation | | SysML Toolkit | 0.10.10 / `339ef0e073d06e553e81c079755ddfcd224e4b92` | Source transformations and draft language queries | -`scripts/prepare-opensysml.mjs` verifies the source archive, applies the retained overlays under `engine-bridge/opensysml/`, runs bridge tests and builds with Go 1.25.11. The derived module exposes `studio_evaluate_view_v1` and `studio_relationships_v1`. It records source/archive, overlay, builder, toolchain, binary and runtime identities. Set `SYSML_GO` to a local Go 1.25.11 binary on platforms without the pinned Linux x64 download path. +`scripts/prepare-opensysml.mjs` verifies the source archive, applies the retained overlays under `engine-bridge/opensysml/`, runs bridge tests and builds with Go 1.27.3. The derived module exposes `studio_evaluate_view_v1`, `studio_relationships_v1` and `studio_exploration_v1`. It records source/archive, overlay, builder, toolchain, binary and runtime identities. Set `SYSML_GO` to a local Go 1.27.3 binary on platforms without the pinned Linux x64 download path. `scripts/prepare-sysml-toolkit.mjs` verifies its release archive and every prepared asset. It retains the Apache and EPL licenses, library assets, build information and third-party notices. Neither preparation script installs a toolchain globally. @@ -21,4 +21,10 @@ Native requirement graphs retain the engine's selected subjects and related cont `insertMember` supports typed part and connection creation. Creation may insert within the recorded owner or promote only its terminal semicolon to a body; existing surrounding source is retained. Toolkit accepts some directionally incompatible connections, so the workspace separately checks explicit out/inout-to-in/inout direction, identical resolved type and exact evaluated usage/port endpoints. Conjugated or ambiguous endpoint paths remain unavailable for graphical creation. [Authoring scope and evidence](../docs/architecture/graphical-authoring.md). -Toolkit also supplies read-only completion, hover and definition queries through a separate disposable language worker. Queries use the complete draft source set, negotiate UTF-16 positions and normalize only supported results. The workbench rejects stale replies; completion edits remain unapplied draft changes until the normal source transaction validates them. Engine pins, prepared assets and provenance are unchanged. See [source assistance](../docs/architecture/source-assistance-recovery.md). +Toolkit also supplies completion, hover, definition, references and formatting queries through a separate disposable language worker. Queries use the complete draft source set, negotiate UTF-16 positions and normalize only supported results. The workbench rejects stale replies; completion and formatting edits remain unapplied draft changes until the normal source transaction validates them. Engine pins, prepared assets and provenance are unchanged. See [source assistance](../docs/architecture/source-assistance-recovery.md). + +Read-only `Explore` uses the same validated applied-source revision as named-view evaluation, selects project-owned roots or one exact project declaration, and calls native `RenderExposed`. Project/package scopes support trees; explicit part definitions/usages additionally support bounded structural rendering. Its separate envelope carries the request instead of an authored view identity; it does not write source or saved presentation keys. + +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). diff --git a/engine-bridge/opensysml/evaluate_view.go b/engine-bridge/opensysml/evaluate_view.go index 6e27bfd..69b2bcb 100644 --- a/engine-bridge/opensysml/evaluate_view.go +++ b/engine-bridge/opensysml/evaluate_view.go @@ -85,6 +85,32 @@ func (e *Engine) evaluateView(req *JRenderViewRequest) ([]byte, error) { return marshal(out) } out.Status = "complete" + out.Selected = cached.selectedSubjects(selected) + renderer := view.NewRenderer(typed, cached.resolver, cached.sourceText()) + rendering, err := renderer.Render(matches[0]) + if err != nil { + var unsupported *view.UnsupportedKindError + if errors.As(err, &unsupported) && unsupported.Kind == view.KindGeometry { + return e.studioGeometry(req, selected, typed, out) + } + out.RenderStatus = "unsupported" + out.Diagnostics = append(out.Diagnostics, err.Error()) + return marshal(out) + } + if len(failures) > 0 { + out.Status = "indeterminate" + out.Selected = []ViewSubject{} + for reason := range failures { + out.Diagnostics = append(out.Diagnostics, reason) + } + sort.Strings(out.Diagnostics) + return marshal(out) + } + return cached.normalizedRendering(out, selected, rendering) +} + +func (cached *cachedModel) normalizedRendering(out EvaluatedView, selected []*symbols.Symbol, rendering *view.Rendering) ([]byte, error) { + var err error byOrigin := map[string]string{} declarations := map[string]*symbols.Symbol{} cached.Index.Registered(func(fqn string, syms []*symbols.Symbol) { @@ -96,6 +122,30 @@ func (e *Engine) evaluateView(req *JRenderViewRequest) ([]byte, error) { } } }) + // The semantic index also owns anonymous members (including synthesized + // satisfy subjects). Inspect their AST kind, never their rendered text. + seenDeclarations := map[*symbols.Symbol]bool{} + var visitDeclaration func(*symbols.Symbol) + visitDeclaration = func(sym *symbols.Symbol) { + if sym == nil || seenDeclarations[sym] || cached.Index.IsLibraryDocument(sym.DocName) { + return + } + seenDeclarations[sym] = true + key := originKey(sym.Origin()) + if key != "" && declarations[key] == nil { + declarations[key] = sym + } + if sym.Scope != nil { + for _, member := range sym.Scope.AllMembers() { + visitDeclaration(member) + } + } + } + cached.Index.Registered(func(_ string, syms []*symbols.Symbol) { + for _, sym := range syms { + visitDeclaration(sym) + } + }) subject := func(origin symbols.Origin) ViewSubject { id := originKey(origin) ref := ViewSubject{ID: id, QualifiedName: byOrigin[id]} @@ -104,43 +154,136 @@ func (e *Engine) evaluateView(req *JRenderViewRequest) ([]byte, error) { } return ref } - for _, sym := range selected { - ref := subject(sym.Origin()) - if ref.QualifiedName == "" { - ref.QualifiedName = cached.Index.GetFQN(sym) + out.Selected = cached.selectedSubjects(selected) + // Keep complete semantic labels in the bridge; our renderer owns presentation. + shown := view.Shown{} + for _, element := range view.Elements() { + 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. + if data.Kind == view.KindTree || data.Kind == view.KindInterconnection { + kept := map[string]bool{} + redirect := map[string]string{} + original := map[string]view.NodeData{} + for _, node := range data.Nodes { + original[node.ID] = node } - if ref.ID == "" { - ref.ID = "unlocated:" + ref.QualifiedName + exposed := map[string]bool{} + for _, sym := range selected { + exposed[originKey(sym.Origin())] = true + } + nodes := data.Nodes[:0] + for _, node := range data.Nodes { + syntheticSubject := false + unsupportedConstraint := false + if sym := declarations[originKey(node.Origin)]; sym != nil { + if rel, ok := sym.Decl.(*ast.Relationship); ok && rel.Kind == ast.RelSubject { + syntheticSubject = true + } + unsupportedConstraint = data.Kind == view.KindInterconnection && (sym.Kind == symbols.SymbolConstraintDef || sym.Kind == symbols.SymbolConstraintUsage) + } + if node.Inherited || syntheticSubject || unsupportedConstraint || (node.Parent != "" && !kept[node.Parent]) { + redirect[node.ID] = redirect[node.Parent] + continue + } + redirect[node.ID] = node.ID + kept[node.ID] = true + nodes = append(nodes, node) } - out.Selected = append(out.Selected, ref) + data.Nodes = nodes + edges := data.Edges[:0] + for _, edge := range data.Edges { + if data.Kind == view.KindInterconnection { + // Keep connectors declared in a visible occurrence, or explicitly + // exposed. Do not leak a type's inherited internal connections. + if declaration := declarations[originKey(edge.Origin)]; declaration != nil && !exposed[originKey(edge.Origin)] { + owner := declaration.Owner() + visibleOwner := false + for _, endpoint := range []string{edge.From, edge.To} { + for id := endpoint; id != ""; id = original[id].Parent { + if owner != nil && kept[id] && originKey(original[id].Origin) == originKey(owner.Origin()) { + visibleOwner = true + break + } + } + } + if !visibleOwner { + continue + } + } + // Native hierarchy records the nearest retained boundary. A + // hidden internal pin cannot become a pin on that boundary. + if from := redirect[edge.From]; from != "" && from != edge.From { + edge.From, edge.FromPort = from, "" + } + if to := redirect[edge.To]; to != "" && to != edge.To { + edge.To, edge.ToPort = to, "" + } + } + if kept[edge.From] && kept[edge.To] { + edges = append(edges, edge) + } + } + data.Edges = edges } - renderer := view.NewRenderer(typed, cached.resolver, cached.sourceText()) - rendering, err := renderer.Render(matches[0]) - if err != nil { - var unsupported *view.UnsupportedKindError - if errors.As(err, &unsupported) && unsupported.Kind == view.KindGeometry { - return e.studioGeometry(req, selected, typed, out) + // 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. + 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) + ids[node.ID] = id + for j, port := range node.Ports { + ids[port.ID] = fmt.Sprintf("%s.%d", id, j) + } + } + for i := range data.Nodes { + node := &data.Nodes[i] + node.ID = ids[node.ID] + node.Parent = ids[node.Parent] + for j := range node.Ports { + node.Ports[j].ID = ids[node.Ports[j].ID] + } + } + for i := range data.Edges { + edge := &data.Edges[i] + edge.From = ids[edge.From] + edge.To = ids[edge.To] + edge.FromPort = ids[edge.FromPort] + edge.ToPort = ids[edge.ToPort] } - out.RenderStatus = "unsupported" - out.Diagnostics = append(out.Diagnostics, err.Error()) - return marshal(out) } - if len(failures) > 0 { - out.Status = "indeterminate" - out.Selected = []ViewSubject{} - for reason := range failures { - out.Diagnostics = append(out.Diagnostics, reason) + // Requirement edge keywords are presentation; polarity and kind travel + // separately and the browser supplies their accessible labels. + for i := range data.Edges { + if data.Edges[i].Kind == view.EdgeSatisfy { + data.Edges[i].Label = data.Edges[i].Name + } + } + // 0.12 separates association end labels from the edge label. The + // browser's tree contract exposes the composed member as its label. + if data.Kind == view.KindTree { + names := map[string]string{} + for _, node := range data.Nodes { + names[node.ID] = node.Name + } + for i := range data.Edges { + edge := &data.Edges[i] + if (edge.Kind == view.EdgeComposition || edge.Kind == view.EdgeReference) && edge.Label == "" { + edge.Label = names[edge.To] + } } - sort.Strings(out.Diagnostics) - return marshal(out) } - data := rendering.DataFor(view.PortsFull) - out.Artifact, err = marshalRenderView(data) + out.Artifact, err = marshalRenderView(cached, data) if err != nil { return nil, err } if data.Kind == view.KindInterconnection { - out.Artifact, err = structuralNotation(out.Artifact, data, declarations) + out.Artifact, err = cached.structuralNotation(out.Artifact, data, declarations) if err != nil { return nil, err } @@ -151,6 +294,48 @@ func (e *Engine) evaluateView(req *JRenderViewRequest) ([]byte, error) { return nil, err } } + // Native JSON omits empty collections; available empty graphs keep arrays. + var artifact map[string]json.RawMessage + if err = json.Unmarshal(out.Artifact, &artifact); err != nil { + return nil, err + } + for _, key := range []string{"nodes", "edges"} { + if len(artifact[key]) == 0 || string(artifact[key]) == "null" { + artifact[key] = json.RawMessage(`[]`) + } + } + var nodeObjects []map[string]json.RawMessage + if err = json.Unmarshal(artifact["nodes"], &nodeObjects); err != nil { + return nil, err + } + for _, node := range nodeObjects { + if len(node["name"]) == 0 { + node["name"] = json.RawMessage(`""`) + } + if len(node["ports"]) > 0 { + var ports []map[string]json.RawMessage + if err = json.Unmarshal(node["ports"], &ports); err != nil { + return nil, err + } + for _, port := range ports { + if len(port["name"]) == 0 { + port["name"] = json.RawMessage(`""`) + } + } + node["ports"], err = json.Marshal(ports) + if err != nil { + return nil, err + } + } + } + artifact["nodes"], err = json.Marshal(nodeObjects) + if err != nil { + return nil, err + } + out.Artifact, err = json.Marshal(artifact) + if err != nil { + return nil, err + } out.RenderStatus = "available" contexts := map[string][]string{} for _, node := range data.Nodes { @@ -171,18 +356,46 @@ func (e *Engine) evaluateView(req *JRenderViewRequest) ([]byte, error) { return marshal(out) } +func (cached *cachedModel) selectedSubjects(selected []*symbols.Symbol) []ViewSubject { + byOrigin := map[string]string{} + cached.Index.Registered(func(_ string, syms []*symbols.Symbol) { + for _, sym := range syms { + if sym.Origin().Located() { + byOrigin[originKey(sym.Origin())] = cached.Index.GetFQN(sym) + } + } + }) + out := []ViewSubject{} + for _, sym := range selected { + origin := sym.Origin() + ref := ViewSubject{ID: originKey(origin), QualifiedName: byOrigin[originKey(origin)]} + if ref.QualifiedName == "" { + ref.QualifiedName = cached.Index.GetFQN(sym) + } + if origin.Located() { + ref.Origin = &ViewOrigin{File: origin.Doc, Offset: origin.Span.Offset, Length: origin.Span.Len, NameOffset: origin.Name.Offset, NameLength: origin.Name.Len} + } + if ref.ID == "" { + ref.ID = "unlocated:" + ref.QualifiedName + } + out = append(out, ref) + } + return out +} + // The pinned renderer leaves structural pins undirected and omits reference // modifiers. Carry only explicit declaration modifiers, using recorded origins; // do not infer flow direction from port types, conjugation or connector order. -func structuralNotation(raw []byte, data view.Data, declarations map[string]*symbols.Symbol) ([]byte, error) { +func (cached *cachedModel) structuralNotation(raw []byte, data view.Data, declarations map[string]*symbols.Symbol) ([]byte, error) { var base JRenderViewResponse if err := json.Unmarshal(raw, &base); err != nil { return nil, err } type node struct { JRenderNode - Reference bool `json:"reference,omitempty"` - Direction string `json:"direction,omitempty"` + Reference bool `json:"reference,omitempty"` + Direction string `json:"direction,omitempty"` + AttributeDeclarations []string `json:"attributeDeclarations,omitempty"` } out := struct { *JRenderViewResponse @@ -197,6 +410,14 @@ func structuralNotation(raw []byte, data view.Data, declarations map[string]*sym } for i, original := range data.Nodes { item := node{JRenderNode: base.Nodes[i]} + if sym := declarations[originKey(original.Origin)]; sym != nil { + text := cached.sourceText() + for _, member := range cached.typedModel().MembersOf(sym) { + if member.Kind == symbols.SymbolAttributeUsage && member.Decl != nil && !cached.Index.IsLibraryDocument(member.DocName) { + item.AttributeDeclarations = append(item.AttributeDeclarations, text(member.DocName, member.Decl.Span())) + } + } + } if decl := usage(original.Origin); decl != nil { item.Reference = decl.IsReference if decl.Direction != ast.DirNone { diff --git a/engine-bridge/opensysml/exploration.go b/engine-bridge/opensysml/exploration.go new file mode 100644 index 0000000..45659cf --- /dev/null +++ b/engine-bridge/opensysml/exploration.go @@ -0,0 +1,114 @@ +package engine + +import ( + "encoding/json" + "fmt" + "sort" + + "github.com/Open-MBEE/OpenSysML/internal/ir/view" + "github.com/Open-MBEE/OpenSysML/internal/semantic/symbols" +) + +type ExplorationScope struct { + Kind string `json:"kind"` + Subject string `json:"subject,omitempty"` +} +type ExplorationRequest struct { + Scope ExplorationScope `json:"scope"` + Representation string `json:"representation"` +} +type JExploreRequest struct { + ModelHash string `json:"modelHash"` + Request ExplorationRequest `json:"request"` +} + +// Exploration has no authored view identity. Only normalized artifact data is +// shared with named views; the wire envelope cannot become a presentation key. +func (e *Engine) explore(req *JExploreRequest) ([]byte, error) { + cached, ok := e.get(req.ModelHash) + if !ok { + return nil, statusErrorf(codeNotFound, msgModelNotFound, req.ModelHash) + } + out := EvaluatedView{SchemaVersion: 1, ModelHash: req.ModelHash, Status: "complete", Selected: []ViewSubject{}, Diagnostics: []string{}, RenderStatus: "unavailable", Correspondence: []ViewCorrespondence{}} + invalid := func(message string) ([]byte, error) { + out.Status = "invalid" + out.Diagnostics = append(out.Diagnostics, message) + return marshalExploration(out, req.Request) + } + scope := req.Request.Scope + if req.Request.Representation != "tree" && req.Request.Representation != "interconnection" { + return invalid("Unsupported exploration representation.") + } + if scope.Kind != "element" && req.Request.Representation != "tree" { + return invalid("Project and package exploration require an overview tree.") + } + selected := []*symbols.Symbol{} + if scope.Kind == "project" && scope.Subject == "" { + seen := map[*symbols.Symbol]bool{} + for _, doc := range cached.Documents { + root := cached.Index.DocumentRoot(doc.Source.Name()) + if root == nil { + continue + } + for _, sym := range root.AllMembers() { + if sym.Origin().Located() && !cached.Index.IsLibraryDocument(sym.DocName) && !seen[sym] { + seen[sym] = true + selected = append(selected, sym) + } + } + } + sort.Slice(selected, func(i, j int) bool { + return originKey(selected[i].Origin()) < originKey(selected[j].Origin()) + }) + } else if (scope.Kind == "package" || scope.Kind == "element") && scope.Subject != "" { + matches := lookupNamed(cached.Index, scope.Subject) + if len(matches) != 1 || !matches[0].Origin().Located() || cached.Index.IsLibraryDocument(matches[0].DocName) { + return invalid("Choose one unambiguous project declaration.") + } + sym := matches[0] + if scope.Kind == "package" && sym.Kind != symbols.SymbolPackage { + return invalid("Package exploration requires a package declaration.") + } + if req.Request.Representation == "interconnection" && sym.Kind != symbols.SymbolPartDef && sym.Kind != symbols.SymbolPartUsage { + return invalid("Structural exploration requires an explicit part definition or usage.") + } + selected = append(selected, sym) + } else { + return invalid("Invalid exploration scope.") + } + cached.typed = nil + typed := cached.typedModel() + renderer := view.NewRenderer(typed, cached.resolver, cached.sourceText()) + rendering, err := renderer.RenderExposed(selected, view.Kind(req.Request.Representation), "Read-only exploration") + if err != nil { + out.RenderStatus = "unsupported" + out.Diagnostics = append(out.Diagnostics, err.Error()) + return marshalExploration(out, req.Request) + } + raw, err := cached.normalizedRendering(out, selected, rendering) + if err != nil { + return nil, err + } + if err = json.Unmarshal(raw, &out); err != nil { + return nil, fmt.Errorf("normalize exploration: %w", err) + } + return marshalExploration(out, req.Request) +} + +func marshalExploration(out EvaluatedView, request ExplorationRequest) ([]byte, error) { + // Drop the named-view field rather than serialize an invented view identity. + raw, err := json.Marshal(out) + if err != nil { + return nil, err + } + var result map[string]json.RawMessage + if err = json.Unmarshal(raw, &result); err != nil { + return nil, err + } + delete(result, "view") + result["request"], err = json.Marshal(request) + if err != nil { + return nil, err + } + return json.Marshal(result) +} diff --git a/engine-bridge/opensysml/exploration_test.go b/engine-bridge/opensysml/exploration_test.go new file mode 100644 index 0000000..c8aef47 --- /dev/null +++ b/engine-bridge/opensysml/exploration_test.go @@ -0,0 +1,76 @@ +package combined + +import ( + "context" + "encoding/json" + "testing" +) + +func TestStudioExploration(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() + raw, _ := json.Marshal(params) + body, err := server.Call(context.Background(), method, raw) + if err != nil { + t.Fatal(err) + } + var out map[string]any + if err := json.Unmarshal(body, &out); err != nil { + t.Fatal(err) + } + return out + } + documents := []any{ + map[string]any{"name": "model.sysml", "content": "package P { /* 🔧 */ part def Motor; part def System { part primary : Motor; part standby : Motor; } part system : System; }"}, + map[string]any{"name": "other.sysml", "content": "package Q { part def Other; part other : Other; }"}, + } + parsed := call("ParseSources", map[string]any{"documents": documents, "strictConformance": true}) + for _, tc := range []struct { + kind, subject, representation string + count int + }{ + {"project", "", "tree", 9}, {"package", "P", "tree", 6}, + {"element", "P::system", "tree", 1}, {"element", "P::System", "interconnection", 3}, {"element", "P::system", "interconnection", 1}, + } { + scope := map[string]any{"kind": tc.kind} + if tc.subject != "" { + scope["subject"] = tc.subject + } + result := call("Explore", map[string]any{"modelHash": parsed["modelHash"], "request": map[string]any{"scope": scope, "representation": tc.representation}}) + if result["status"] != "complete" || result["renderStatus"] != "available" || result["view"] != nil { + t.Fatalf("%v", result) + } + artifact := result["artifact"].(map[string]any) + if artifact["kind"] != tc.representation || len(artifact["nodes"].([]any)) != tc.count { + t.Fatalf("%v", artifact) + } + if _, ok := artifact["edges"].([]any); !ok { + t.Fatalf("Missing empty edge array: %v", artifact) + } + for _, raw := range result["correspondence"].([]any) { + item := raw.(map[string]any) + if item["subject"].(map[string]any)["origin"] == nil { + t.Fatalf("Missing origin: %v", item) + } + } + } + for _, scope := range []map[string]any{{"kind": "package", "subject": "P::Motor"}, {"kind": "element", "subject": "Missing"}, {"kind": "element", "subject": "Base::Anything"}} { + result := call("Explore", map[string]any{"modelHash": parsed["modelHash"], "request": map[string]any{"scope": scope, "representation": "tree"}}) + if result["status"] != "invalid" || result["artifact"] != nil { + t.Fatalf("%v", result) + } + } + after := call("ParseSources", map[string]any{"documents": documents, "strictConformance": true}) + if after["modelHash"] != parsed["modelHash"] { + t.Fatal("Exploration changed source") + } + invalid := call("ParseSources", map[string]any{"documents": []any{map[string]any{"name": "bad.sysml", "content": "package Bad { part p : Missing; }"}}, "strictConformance": true}) + rejected := call("Explore", map[string]any{"modelHash": invalid["modelHash"], "request": map[string]any{"scope": map[string]any{"kind": "project"}, "representation": "tree"}}) + if rejected["status"] != "invalid" || rejected["artifact"] != nil { + t.Fatalf("%v", rejected) + } +} diff --git a/engine-bridge/opensysml/relationships.go b/engine-bridge/opensysml/relationships.go index 510b735..588e302 100644 --- a/engine-bridge/opensysml/relationships.go +++ b/engine-bridge/opensysml/relationships.go @@ -7,15 +7,25 @@ import ( "github.com/Open-MBEE/OpenSysML/internal/semantic/semantics" "github.com/Open-MBEE/OpenSysML/internal/semantic/symbols" "github.com/Open-MBEE/OpenSysML/internal/syntax/ast" + "github.com/Open-MBEE/OpenSysML/internal/syntax/source" ) type StudioRelationship struct { Kind string `json:"kind"` + Negated bool `json:"negated"` + Asserted bool `json:"asserted"` Declaration ViewSubject `json:"declaration"` Source ViewSubject `json:"source"` Target ViewSubject `json:"target"` } +type StudioRequirement struct { + Subject ViewSubject `json:"subject"` + Kind string `json:"kind"` + ShortName string `json:"shortName"` + Documentation []string `json:"documentation"` +} + // These are declared, resolved relationships, not satisfaction or verification verdicts. func (e *Engine) studioRelationships(req *JRenderViewRequest) ([]byte, error) { cached, ok := e.get(req.ModelHash) @@ -35,6 +45,7 @@ func (e *Engine) studioRelationships(req *JRenderViewRequest) ([]byte, error) { return ref } edges := []StudioRelationship{} + requirements := []StudioRequirement{} diagnostics := []string{} seen := map[*symbols.Symbol]bool{} var visit func(*symbols.Symbol) @@ -43,11 +54,34 @@ func (e *Engine) studioRelationships(req *JRenderViewRequest) ([]byte, error) { return } seen[sym] = true + isRequirement := sym.Kind == symbols.SymbolRequirementDef + if usage, ok := sym.Decl.(*ast.Usage); ok && usage.Kind == ast.UsageRequirement { + isRequirement = true + } + if isRequirement { + row := StudioRequirement{Subject: subject(sym), Kind: sym.Kind.String(), ShortName: sym.ShortName, Documentation: []string{}} + if sym.Scope != nil { + sym.Scope.ForEachMember(func(member *symbols.Symbol) bool { + if doc, ok := member.Decl.(*ast.Documentation); ok { + if body := source.CommentProse(cached.sourceText()(member.DocName, doc.BodySpan)); body != "" { + row.Documentation = append(row.Documentation, body) + } + } + return true + }) + } + requirements = append(requirements, row) + } before := len(edges) for _, kind := range []semantics.RelationshipKind{semantics.RelationshipAllocation, semantics.RelationshipSatisfaction, semantics.RelationshipVerification} { for _, edge := range typed.RelationshipEdgesOf(sym, kind) { if edge.Source != nil && edge.Target != nil { - edges = append(edges, StudioRelationship{Kind: string(kind), Declaration: subject(sym), Source: subject(edge.Source), Target: subject(edge.Target)}) + link := StudioRelationship{Kind: string(kind), Declaration: subject(sym), Source: subject(edge.Source), Target: subject(edge.Target)} + if usage, ok := sym.Decl.(*ast.Usage); ok { + link.Negated = usage.IsNegated + link.Asserted = usage.PrefixKeyword == "assert" + } + edges = append(edges, link) } } } @@ -78,11 +112,13 @@ func (e *Engine) studioRelationships(req *JRenderViewRequest) ([]byte, error) { } return a.Target.ID < b.Target.ID }) + sort.Slice(requirements, func(i, j int) bool { return requirements[i].Subject.ID < requirements[j].Subject.ID }) status := "complete" if len(diagnostics) > 0 { status = "indeterminate" edges = []StudioRelationship{} + requirements = []StudioRequirement{} sort.Strings(diagnostics) } - return marshal(map[string]any{"schemaVersion": 1, "modelHash": req.ModelHash, "status": status, "relationships": edges, "diagnostics": diagnostics}) + return marshal(map[string]any{"schemaVersion": 1, "modelHash": req.ModelHash, "status": status, "relationships": edges, "requirements": requirements, "diagnostics": diagnostics}) } diff --git a/engine-bridge/opensysml/relationships_test.go b/engine-bridge/opensysml/relationships_test.go index 20a2f2c..6139ea7 100644 --- a/engine-bridge/opensysml/relationships_test.go +++ b/engine-bridge/opensysml/relationships_test.go @@ -8,7 +8,7 @@ import ( ) func TestStudioRelationships(t *testing.T) { - server, err := New("v0.10.0") + server, err := New("v0.12.0") if err != nil { t.Fatal(err) } @@ -26,7 +26,7 @@ func TestStudioRelationships(t *testing.T) { return out } documents := []any{ - map[string]any{"name": "requirements.sysml", "content": `package Requirements { requirement safety; alias Safe for safety; }`}, + map[string]any{"name": "requirements.sysml", "content": `package Requirements { requirement <'R-1'> safety { doc /* Keep the system safe. */ } alias Safe for safety; }`}, map[string]any{"name": "system.sysml", "content": `package System { private import Requirements::*; part pump { satisfy Safe; } @@ -41,6 +41,14 @@ func TestStudioRelationships(t *testing.T) { if result["status"] != "complete" { t.Fatalf("%v", result) } + rows := result["requirements"].([]any) + if len(rows) != 1 { + t.Fatalf("Expected one requirement, got %v", rows) + } + row := rows[0].(map[string]any) + if row["shortName"] != "R-1" || row["kind"] != "requirementUsage" || !reflect.DeepEqual(row["documentation"], []any{"Keep the system safe."}) { + t.Fatalf("Lost requirement content: %v", row) + } pairs := map[string][][2]string{} for _, raw := range result["relationships"].([]any) { edge := raw.(map[string]any) @@ -84,6 +92,15 @@ func TestStudioRelationships(t *testing.T) { if noEnds["status"] == "complete" || len(noEnds["relationships"].([]any)) != 0 { t.Fatalf("Missing endpoints were called complete: %v", noEnds) } + negative := call("ParseSources", map[string]any{"documents": []any{map[string]any{"name": "negative.sysml", "content": "package N { requirement r; part p; assert not satisfy r by p; }"}}, "strictConformance": true}) + negLinks := call("EvaluateRelationships", map[string]any{"modelHash": negative["modelHash"]}) + if negLinks["status"] != "complete" { + t.Fatalf("%v", negLinks) + } + negativeEdges := negLinks["relationships"].([]any) + if len(negativeEdges) != 1 || negativeEdges[0].(map[string]any)["negated"] != true || negativeEdges[0].(map[string]any)["asserted"] != true { + t.Fatalf("Lost negative assertion: %v", negativeEdges) + } if _, err := server.Call(context.Background(), "EvaluateRelationships", []byte(`{"modelHash":"absent"}`)); err == nil { t.Fatal("Missing revision was accepted") } diff --git a/engine-bridge/opensysml/requirements_view_test.go b/engine-bridge/opensysml/requirements_view_test.go index 279b512..38e49d1 100644 --- a/engine-bridge/opensysml/requirements_view_test.go +++ b/engine-bridge/opensysml/requirements_view_test.go @@ -8,7 +8,7 @@ import ( ) func TestStudioRequirementsNotation(t *testing.T) { - server, err := New("v0.10.0") + server, err := New("v0.12.0") if err != nil { t.Fatal(err) } diff --git a/engine-bridge/opensysml/validated_view.go b/engine-bridge/opensysml/validated_view.go index 6058540..1f2d2c5 100644 --- a/engine-bridge/opensysml/validated_view.go +++ b/engine-bridge/opensysml/validated_view.go @@ -60,7 +60,7 @@ func (s *Server) evaluateRelationships(ctx context.Context, params []byte) ([]by } for _, diagnostic := range validation.Diagnostics { if diagnostic.Severity == "error" { - return json.Marshal(map[string]any{"schemaVersion": 1, "modelHash": req.ModelHash, "status": "invalid", "relationships": []any{}, "diagnostics": []string{"Model validation failed; relationship endpoints are unavailable."}}) + return json.Marshal(map[string]any{"schemaVersion": 1, "modelHash": req.ModelHash, "status": "invalid", "relationships": []any{}, "requirements": []any{}, "diagnostics": []string{"Model validation failed; relationship endpoints are unavailable."}}) } } body, err = s.engine.Call(ctx, "EvaluateRelationships", params) @@ -69,3 +69,32 @@ func (s *Server) evaluateRelationships(ctx context.Context, params []byte) ([]by } return body, err } + +func (s *Server) explore(ctx context.Context, params []byte) ([]byte, error) { + var req engine.JExploreRequest + if err := json.Unmarshal(params, &req); err != nil { + return nil, err + } + body, err := s.core.Call(ctx, "GetDiagnostics", params) + if err != nil { + return nil, err + } + var validation struct { + Diagnostics []struct { + Severity string `json:"severity"` + } `json:"diagnostics"` + } + if err = json.Unmarshal(body, &validation); err != nil { + return nil, err + } + for _, diagnostic := range validation.Diagnostics { + if diagnostic.Severity == "error" { + return json.Marshal(map[string]any{"schemaVersion": 1, "modelHash": req.ModelHash, "request": req.Request, "status": "invalid", "selected": []any{}, "diagnostics": []string{"Model validation failed; correct source diagnostics before exploring."}, "renderStatus": "unavailable", "correspondence": []any{}}) + } + } + body, err = s.engine.Call(ctx, "Explore", params) + if err == nil { + s.touchRequest(params) + } + return body, err +} diff --git a/engine-bridge/opensysml/validated_view_test.go b/engine-bridge/opensysml/validated_view_test.go index 7b50e05..ee67402 100644 --- a/engine-bridge/opensysml/validated_view_test.go +++ b/engine-bridge/opensysml/validated_view_test.go @@ -9,7 +9,7 @@ import ( ) func TestStudioStructuralAssembly(t *testing.T) { - server, err := New("v0.10.0") + server, err := New("v0.12.0") if err != nil { t.Fatal(err) } @@ -102,7 +102,7 @@ func TestStudioStructuralAssembly(t *testing.T) { } func TestStudioEvaluatedView(t *testing.T) { - server, err := New("v0.10.0") + server, err := New("v0.12.0") if err != nil { t.Fatal(err) } @@ -169,7 +169,7 @@ func TestStudioEvaluatedView(t *testing.T) { } func TestStudioGeometryDimensions(t *testing.T) { - server, err := New("v0.10.0") + server, err := New("v0.12.0") if err != nil { t.Fatal(err) } @@ -220,7 +220,7 @@ func TestStudioGeometryDimensions(t *testing.T) { } func TestStudioGeometryPlacement(t *testing.T) { - server, err := New("v0.10.0") + server, err := New("v0.12.0") if err != nil { t.Fatal(err) } @@ -284,3 +284,91 @@ func TestStudioGeometryPlacement(t *testing.T) { } } } + +func TestStudioDeclaredAttributeValues(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() + p, _ := json.Marshal(params) + raw, err := server.Call(context.Background(), method, p) + if err != nil { + t.Fatal(err) + } + var out map[string]any + if err := json.Unmarshal(raw, &out); err != nil { + t.Fatal(err) + } + return out + } + source := "package P { private import ScalarValues::*; part def Battery { attribute capacityWh : Real default = 480.0; } part def Rover { part left : Battery; part right : Battery; } } package V { private import StandardViewDefinitions::*; view architecture : InterconnectionView { expose P::Rover; } }" + parsed := call("ParseSources", map[string]any{"strictConformance": true, "documents": []any{map[string]any{"name": "model.sysml", "content": source}}}) + result := call("EvaluateView", map[string]any{"modelHash": parsed["modelHash"], "view": "V::architecture"}) + if result["status"] != "complete" { + t.Fatalf("%v", result) + } + found := 0 + for _, raw := range result["artifact"].(map[string]any)["nodes"].([]any) { + n := raw.(map[string]any) + if n["name"] == "left" || n["name"] == "right" { + values := n["attributeDeclarations"].([]any) + if len(values) != 1 || values[0] != "attribute capacityWh : Real default = 480.0;" { + t.Fatalf("%v", n) + } + found++ + } + } + if found != 2 { + t.Fatalf("Expected two independently typed usages, found %d", found) + } +} + +func TestStudioRetainsDeclaredBoundaryConnections(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, _ := json.Marshal(params) + 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": "model.sysml", "content": `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; } +}`}}}) + result := call("EvaluateView", map[string]any{"modelHash": parsed["modelHash"], "view": "P::v"}) + if result["status"] != "complete" || result["renderStatus"] != "available" { + t.Fatalf("%v", result) + } + 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) + } + edge := edges[0].(map[string]any) + if edge["from"] != "n1" || edge["to"] != "n2" || edge["fromPort"] != nil || edge["toPort"] != nil || edge["label"] != "bridge" { + t.Fatalf("Incorrect retained usage boundaries: %v", edge) + } +} diff --git a/examples/README.md b/examples/README.md index 129f793..4ab5468 100644 --- a/examples/README.md +++ b/examples/README.md @@ -6,22 +6,23 @@ The “blocks” examples use SysML v2 `part def` and `part` declarations. Lexic Status is per view: **rendered** covers the stated fixture scope; **partial** shows a useful subset with recorded gaps; **unavailable** means the engine evaluates the view but the app reports that its renderer is unavailable. No examples are silently skipped for missing renderers. Targets describe intended visual quality and need visual review; passing source/engine checks alone does not satisfy every target. -| Project | Topics | Named views and current app status | -| -------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| [Comments and documentation](comments/model.sysml) | comments, documentation, annotations, unicode, long-labels | `overview`: partial; `maintenance`: partial | -| [Packages and imports](packages/model.sysml) | packages, imports, ownership, multiple-files | `outline`: partial; `dependencies`: unavailable | -| [Parts and definitions (blocks)](parts/model.sysml) | parts, blocks, definitions, usages, specialization, references, repeated-names | `structure`: rendered; `definitions`: partial | -| [Ports and conjugation](ports/model.sysml) | ports, interfaces, connections, conjugation | `link`: partial | -| [Use cases and actors](use-cases/model.sysml) | use-cases, actors, subjects, include, objectives | `scenarios`: unavailable; `definitions`: unavailable | -| [Requirements and satisfaction](requirements/model.sysml) | requirements, documentation, constraints, satisfaction | `requirements`: partial | -| [Requirement hierarchy and satisfaction](requirements-traceability/system.sysml) | requirements, hierarchy, typing, satisfaction, negation, aliases, multiple-files, repeated-names | `requirements`: partial; `overview`: partial | -| [Actions and successions](actions/model.sysml) | actions, behavior, succession, parameters | `process`: unavailable | -| [States and transitions](states/model.sysml) | states, nested-states, transitions, initial-state | `states`: unavailable | -| [Messages and sequence ordering](sequences/model.sysml) | sequences, messages, occurrences, ordering | `exchange`: unavailable | -| [Filtered and empty views](filters/model.sysml) | filters, metadata, recursive-exposure, empty-view | `reviewed`: rendered; `empty`: rendered | -| [Workshop foundation](workshop/model.sysml) | multiple-files, shared-types, rename, tree | `overview`: rendered; `compressor`: rendered | -| [Nested assembly](assembly/model.sysml) | assembly, nested-parts, directed-ports, connections, references, usage-context | `structure`: rendered; `devices`: partial | -| [Smart air workshop](air-compressor/system.sysml) | multiple-files, specialization, directed-ports, conjugation, connections, attributes, constraints, requirements, actions, states, use-cases, sequences, geometry | `overview`: partial; `structure`: partial; `installation`: partial; `pneumatics`: partial; `definitions`: partial; `requirements`: partial; `actions`: unavailable; `maintenance`: unavailable; `useCases`: unavailable; `states`: unavailable; `startupSequence`: unavailable; `demandSequence`: unavailable; `faultSequence`: unavailable; `envelopeGeometry`: unavailable | +| Project | Topics | Named views and current app status | +| -------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| [Comments and documentation](comments/model.sysml) | comments, documentation, annotations, unicode, long-labels | `overview`: partial; `maintenance`: partial | +| [Packages and imports](packages/model.sysml) | packages, imports, ownership, multiple-files | `outline`: partial; `dependencies`: unavailable | +| [Parts and definitions (blocks)](parts/model.sysml) | parts, blocks, definitions, usages, specialization, references, repeated-names | `structure`: rendered; `definitions`: partial | +| [Ports and conjugation](ports/model.sysml) | ports, interfaces, connections, conjugation | `link`: partial | +| [Use cases and actors](use-cases/model.sysml) | use-cases, actors, subjects, include, objectives | `scenarios`: unavailable; `definitions`: unavailable | +| [Requirements and satisfaction](requirements/model.sysml) | requirements, documentation, constraints, satisfaction | `requirements`: partial | +| [Requirement hierarchy and satisfaction](requirements-traceability/system.sysml) | requirements, hierarchy, typing, satisfaction, negation, aliases, multiple-files, repeated-names | `requirements`: partial; `overview`: partial | +| [Actions and successions](actions/model.sysml) | actions, behavior, succession, parameters | `process`: partial | +| [States and transitions](states/model.sysml) | states, nested-states, transitions, initial-state | `states`: partial | +| [Messages and sequence ordering](sequences/model.sysml) | sequences, messages, occurrences, ordering | `exchange`: unavailable | +| [Filtered and empty views](filters/model.sysml) | filters, metadata, recursive-exposure, empty-view | `reviewed`: rendered; `empty`: rendered | +| [Workshop foundation](workshop/model.sysml) | multiple-files, shared-types, rename, tree | `overview`: rendered; `compressor`: rendered | +| [Nested assembly](assembly/model.sysml) | assembly, nested-parts, directed-ports, connections, references, usage-context | `structure`: rendered; `devices`: partial | +| [Smart air workshop](air-compressor/system.sysml) | multiple-files, specialization, directed-ports, conjugation, connections, attributes, constraints, requirements, actions, states, use-cases, sequences, geometry | `overview`: partial; `structure`: partial; `installation`: partial; `pneumatics`: partial; `definitions`: partial; `requirements`: partial; `actions`: partial; `maintenance`: partial; `useCases`: unavailable; `states`: partial; `startupSequence`: unavailable; `demandSequence`: unavailable; `faultSequence`: unavailable; `envelopeGeometry`: unavailable | +| [Inspection rover](inspection-rover/system.sysml) | nested-parts, directed-ports, connections, attributes, specialization, requirements, actions, states | `architecture`: partial; `definitions`: partial; `mission`: partial; `lifecycle`: partial; `requirements`: partial | ## Visual targets and known gaps @@ -35,6 +36,7 @@ Files: [model.sysml](comments/model.sysml), [views.sysml](comments/views.sysml). - Show documentation and semantic comment callouts anchored to the correct element. - Preserve Unicode punctuation and text through download/reopen. - Fit qualified names inside their symbols at a readable scale. +- Retain resolved typing and distinguish declared ownership, references and specialization kinds. Current gaps: Documentation and semantic comment callouts are not drawn or transported as note bodies/anchors by the current bridge. Long qualified labels can extend past the fixed-width tree nodes. @@ -53,6 +55,7 @@ Files: [model.sysml](packages/model.sysml), [views.sysml](packages/views.sysml). - Show nested package/type ownership without merging similarly named elements. - Preserve resolution of Controller and Sensor across private imports. +- Retain resolved typing and distinguish declared ownership, references and specialization kinds. Current gaps: The outline is a flat tree; package containers and import arrows require the package renderer. @@ -140,6 +143,7 @@ Current gaps: Requirement identifiers, documentation and relationships are shown - Retain the same source model as an independent generic overview. - Keep requirement-view and overview positions separate through save and reopen. +- Retain resolved typing and distinguish declared ownership, references and specialization kinds. Current gaps: This GeneralView produces a generic flat tree rather than dedicated requirement notation. @@ -147,25 +151,25 @@ Current gaps: This GeneralView produces a generic flat tree rather than dedicate Files: [model.sysml](actions/model.sysml), [views.sysml](actions/views.sysml). -**WorkflowViews::process** — engine artifact: `action`; app: **unavailable**. +**WorkflowViews::process** — engine artifact: `action`; app: **partial**. - Show the collect, inspect and archive order from start to done. - Retain typed inspection parameters and distinguish them from control succession. - Do not imply that rendering the workflow executes it. -Current gaps: The engine emits an action artifact; the application has no action-flow renderer. +Current gaps: Declared behavior is rendered without execution, simulation or live token/state animation. ### States and transitions Files: [model.sysml](states/model.sysml), [views.sysml](states/views.sysml). -**LifecycleViews::states** — engine artifact: `state`; app: **unavailable**. +**LifecycleViews::states** — engine artifact: `state`; app: **partial**. - Keep processing nested inside active. - Distinguish initial-state markers from named states. - Show startWork and finishWork transition endpoints clearly. -Current gaps: The engine emits a state artifact; the application has no state-transition renderer. +Current gaps: Declared behavior is rendered without execution, simulation or live token/state animation. ### Messages and sequence ordering @@ -201,6 +205,7 @@ Files: [model.sysml](workshop/model.sysml), [views.sysml](workshop/views.sysml). - Show both Motor usages and their shared definition. - Keep the active view stable when changing source files. +- Retain resolved typing and distinguish declared ownership, references and specialization kinds. **WorkshopViews::compressor** — engine artifact: `interconnection`; app: **rendered**. @@ -266,17 +271,17 @@ Current gaps: Definition graphs use generic relationship lines and fixed-width l Current gaps: This GeneralView produces a flat tree; dedicated requirement notation and satisfaction/verification trace links are not drawn. -**CompressorViews::actions** — engine artifact: `action`; app: **unavailable**. +**CompressorViews::actions** — engine artifact: `action`; app: **partial**. - Show normal air supply steps, performer allocations and authored control/item-flow ordering. -Current gaps: The engine emits an action artifact; this application has no action renderer. +Current gaps: Declared behavior is rendered without execution, simulation or live token/state animation. -**CompressorViews::maintenance** — engine artifact: `action`; app: **unavailable**. +**CompressorViews::maintenance** — engine artifact: `action`; app: **partial**. - Show isolation and depressurization before inspection, filter replacement and restoration. -Current gaps: The engine emits an action artifact; this application has no action renderer. +Current gaps: Declared behavior is rendered without execution, simulation or live token/state animation. **CompressorViews::useCases** — engine artifact: `case`; app: **unavailable**. @@ -284,11 +289,11 @@ Current gaps: The engine emits an action artifact; this application has no actio Current gaps: The engine emits a case artifact; this application has no case renderer. -**CompressorViews::states** — engine artifact: `state`; app: **unavailable**. +**CompressorViews::states** — engine artifact: `state`; app: **partial**. - Show operating states and event-triggered transitions without implying execution. -Current gaps: The engine emits a state artifact; this application has no state renderer. +Current gaps: Declared behavior is rendered without execution, simulation or live token/state animation. **CompressorViews::startupSequence** — engine artifact: `sequence`; app: **unavailable**. @@ -316,6 +321,40 @@ Current gaps: The engine emits a geometry artifact; this application has no geom Expected engine notices include: “Dimensions and coordinate-frame poses evaluated in metres. Boxes are centered on their frame origins. Solids are inspected separately; meshes and void subtraction are unsupported.”. +### Inspection rover + +Files: [system.sysml](inspection-rover/system.sysml), [components.sysml](inspection-rover/components.sysml), [types.sysml](inspection-rover/types.sysml), [behavior.sysml](inspection-rover/behavior.sysml), [requirements.sysml](inspection-rover/requirements.sysml), [views.sysml](inspection-rover/views.sysml). + +**RoverViews::architecture** — engine artifact: `interconnection`; app: **partial**. + +- Show the four nested assemblies, shared drive types, declared component values and exact port connections. + +Current gaps: Values are source declarations, not evaluated or simulated results. Effective conjugated directions and execution remain outside scope. + +**RoverViews::definitions** — engine artifact: `definition`; app: **partial**. + +- Show reusable part definitions and explicit actuator/sensor specialization. + +Current gaps: Generic definition graph notation; no full attribute compartments in this view. + +**RoverViews::mission** — engine artifact: `action`; app: **partial**. + +- Show the ordered inspection mission from battery check through patrol, capture, reporting and recharge. + +Current gaps: Declared behavior is rendered without execution, simulation or live token/state animation. + +**RoverViews::lifecycle** — engine artifact: `state`; app: **partial**. + +- Show operating states, initial state, safe stop and return-to-charge transitions. + +Current gaps: Declared behavior is rendered without execution, simulation or live token/state animation. + +**RoverViews::requirements** — engine artifact: `requirement`; app: **partial**. + +- Show four quantified requirements and their exact declared satisfaction targets. + +Current gaps: Satisfaction is declared traceability, not evidence that a test passed. + ## Validate and extend the library Use the repository Node version and existing dependencies. Browser checks build current source and use the real pinned workers: diff --git a/examples/air-compressor/README.md b/examples/air-compressor/README.md index f7a26f4..f931a50 100644 --- a/examples/air-compressor/README.md +++ b/examples/air-compressor/README.md @@ -1,8 +1,8 @@ # Smart air workshop -This is the built-in project opened by **Load example** in the launcher or **Try an example** on the landing page. Its nine SysML files and 14 named views model the compressor's electrical and pneumatic assemblies, cooling, two installed compressor usages, tools, operator/technician roles, requirements, behaviour and enclosure geometry. +This legacy project is retained for explicit imports and regression tests. Import all nine `.sysml` files together; it is not bundled in the launcher. The built-in example is the inspection rover. Its nine SysML files and 14 named views model the compressor's electrical and pneumatic assemblies, cooling, two installed compressor usages, tools, operator/technician roles, requirements, behaviour and enclosure geometry. -Loading the example opens **pneumatics** in Diagram mode: pump, tank, regulator, relief valve and pressure sensor, with 13 displayed ports and four authored connections. **Split** reveals the source; other named views remain available in the sidebar. +Loading this example opens **overview** in Diagram mode. Choose **pneumatics** for the pump, tank, regulator, relief valve and pressure sensor, with 13 displayed ports and four authored connections. **Split** reveals the source; other named views remain available in the sidebar. | File | Features | | -------------------- | -------------------------------------------------------------------------------------------------------- | @@ -21,9 +21,10 @@ Loading the example opens **pneumatics** in Diagram mode: pump, tank, regulator, - **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. - **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**, **useCases**, **states**, **startupSequence**, **demandSequence**, **faultSequence**, **envelopeGeometry**: evaluated by the pinned engine, with explicit unavailable-renderer messages in this application. +- **actions**, **maintenance**, **states**: bounded native action/state diagrams with declared order/transitions; no execution. +- **useCases**, **startupSequence**, **demandSequence**, **faultSequence**, **envelopeGeometry**: evaluated by the pinned engine, with explicit unavailable-renderer messages in this application. -Attributes and constraints remain authored source; the diagram does not draw full attribute compartments or run a solver. Satisfaction and verification declarations are not evidence of a completed test. Conjugated endpoints cannot be used by the bounded graphical connection command. Use the smaller [assembly fixture](../assembly/model.sysml) to exercise that command's explicit same-type endpoint subset. +Structural diagrams show source declarations for attributes, including inherited type members. Constraints remain source; the app does not run a solver. Satisfaction and verification declarations are not evidence of a completed test. Conjugated endpoints cannot be used by the bounded graphical connection command. Use the smaller [assembly fixture](../assembly/model.sysml) to exercise that command's explicit same-type endpoint subset. The [catalog](../catalog.json) retains exact engine node/relationship expectations, visual targets and gaps for every view. [Verification](../../docs/verification.md) distinguishes engine results, application behaviour and local test evidence. diff --git a/examples/catalog.json b/examples/catalog.json index 415868d..9e52fbd 100644 --- a/examples/catalog.json +++ b/examples/catalog.json @@ -22,12 +22,13 @@ "CommentExample::ServiceLoop", "pump" ], - "edgeKinds": ["composition"], + "edgeKinds": ["composition", "typing"], "targets": [ "Keep line and block comments intact in source.", "Show documentation and semantic comment callouts anchored to the correct element.", "Preserve Unicode punctuation and text through download/reopen.", - "Fit qualified names inside their symbols at a readable scale." + "Fit qualified names inside their symbols at a readable scale.", + "Retain resolved typing and distinguish declared ownership, references and specialization kinds." ], "gaps": [ "Documentation and semantic comment callouts are not drawn or transported as note bodies/anchors by the current bridge.", @@ -72,10 +73,11 @@ "Installation", "controller" ], - "edgeKinds": ["composition", "composition"], + "edgeKinds": ["composition", "typing", "composition", "typing"], "targets": [ "Show nested package/type ownership without merging similarly named elements.", - "Preserve resolution of Controller and Sensor across private imports." + "Preserve resolution of Controller and Sensor across private imports.", + "Retain resolved typing and distinguish declared ownership, references and specialization kinds." ], "gaps": [ "The outline is a flat tree; package containers and import arrows require the package renderer." @@ -380,18 +382,27 @@ "typing", "typing", "typing", - "specialization", - "specialization", - "specialization", + "subsetting", + "subsetting", + "subsetting", "composition", - "specialization", + "reference", "composition", - "specialization", - "specialization" + "typing", + "reference", + "redefinition", + "composition", + "typing", + "reference", + "redefinition", + "typing", + "reference", + "redefinition" ], "targets": [ "Retain the same source model as an independent generic overview.", - "Keep requirement-view and overview positions separate through save and reopen." + "Keep requirement-view and overview positions separate through save and reopen.", + "Retain resolved typing and distinguish declared ownership, references and specialization kinds." ], "gaps": [ "This GeneralView produces a generic flat tree rather than dedicated requirement notation." @@ -409,7 +420,7 @@ { "id": "WorkflowViews::process", "engineKind": "action", - "app": "unavailable", + "app": "partial", "nodeNames": [ "Workflow::Process", "start", @@ -425,7 +436,7 @@ "Do not imply that rendering the workflow executes it." ], "gaps": [ - "The engine emits an action artifact; the application has no action-flow renderer." + "Declared behavior is rendered without execution, simulation or live token/state animation." ], "notices": [] } @@ -440,7 +451,7 @@ { "id": "LifecycleViews::states", "engineKind": "state", - "app": "unavailable", + "app": "partial", "nodeNames": [ "Lifecycle::StationStates", "", @@ -456,7 +467,7 @@ "Show startWork and finishWork transition endpoints clearly." ], "gaps": [ - "The engine emits a state artifact; the application has no state-transition renderer." + "Declared behavior is rendered without execution, simulation or live token/state animation." ], "notices": [] } @@ -539,10 +550,11 @@ "primary", "standby" ], - "edgeKinds": ["composition", "composition"], + "edgeKinds": ["composition", "composition", "typing", "typing"], "targets": [ "Show both Motor usages and their shared definition.", - "Keep the active view stable when changing source files." + "Keep the active view stable when changing source files.", + "Retain resolved typing and distinguish declared ownership, references and specialization kinds." ], "gaps": [], "notices": [] @@ -812,7 +824,7 @@ { "id": "CompressorViews::actions", "engineKind": "action", - "app": "unavailable", + "app": "partial", "nodeNames": [ "CompressorBehavior::CompressAir", "sense", @@ -847,14 +859,14 @@ "Show normal air supply steps, performer allocations and authored control/item-flow ordering." ], "gaps": [ - "The engine emits an action artifact; this application has no action renderer." + "Declared behavior is rendered without execution, simulation or live token/state animation." ], "notices": [] }, { "id": "CompressorViews::maintenance", "engineKind": "action", - "app": "unavailable", + "app": "partial", "nodeNames": [ "CompressorBehavior::ServiceCompressor", "isolate", @@ -877,7 +889,7 @@ "Show isolation and depressurization before inspection, filter replacement and restoration." ], "gaps": [ - "The engine emits an action artifact; this application has no action renderer." + "Declared behavior is rendered without execution, simulation or live token/state animation." ], "notices": [] }, @@ -949,7 +961,7 @@ { "id": "CompressorViews::states", "engineKind": "state", - "app": "unavailable", + "app": "partial", "nodeNames": [ "CompressorBehavior::OperatingMode", "", @@ -983,7 +995,7 @@ "Show operating states and event-triggered transitions without implying execution." ], "gaps": [ - "The engine emits a state artifact; this application has no state renderer." + "Declared behavior is rendered without execution, simulation or live token/state animation." ], "notices": [] }, @@ -1046,6 +1058,183 @@ ] } ] + }, + { + "id": "inspection-rover", + "title": "Inspection rover", + "topics": [ + "nested-parts", + "directed-ports", + "connections", + "attributes", + "specialization", + "requirements", + "actions", + "states" + ], + "files": [ + "system.sysml", + "components.sysml", + "types.sysml", + "behavior.sysml", + "requirements.sysml", + "views.sysml" + ], + "views": [ + { + "id": "RoverViews::architecture", + "engineKind": "interconnection", + "app": "partial", + "nodeNames": [ + "RoverSystem::InspectionRover", + "energy", + "battery", + "distribution", + "mobility", + "leftDrive", + "rightDrive", + "autonomy", + "controller", + "lidar", + "inspection", + "camera", + "radio" + ], + "edgeKinds": [ + "connection", + "connection", + "connection", + "connection", + "connection", + "connection", + "connection", + "connection", + "connection", + "connection" + ], + "targets": [ + "Show the four nested assemblies, shared drive types, declared component values and exact port connections." + ], + "gaps": [ + "Values are source declarations, not evaluated or simulated results. Effective conjugated directions and execution remain outside scope." + ], + "notices": [] + }, + { + "id": "RoverViews::definitions", + "engineKind": "definition", + "app": "partial", + "nodeNames": [ + "RoverComponents::Battery", + "RoverComponents::Distribution", + "RoverComponents::Actuator", + "RoverComponents::Sensor", + "RoverComponents::Drive", + "RoverComponents::Controller", + "RoverComponents::Lidar", + "RoverComponents::Camera", + "RoverComponents::Radio" + ], + "edgeKinds": ["specialization", "specialization", "specialization"], + "targets": [ + "Show reusable part definitions and explicit actuator/sensor specialization." + ], + "gaps": [ + "Generic definition graph notation; no full attribute compartments in this view." + ], + "notices": [] + }, + { + "id": "RoverViews::mission", + "engineKind": "action", + "app": "partial", + "nodeNames": [ + "RoverBehavior::InspectRoute", + "start", + "checkBattery", + "patrol", + "detectObstacle", + "captureInspection", + "transmitReport", + "returnToDock", + "recharge", + "done" + ], + "edgeKinds": [ + "succession", + "succession", + "succession", + "succession", + "succession", + "succession", + "succession", + "succession" + ], + "targets": [ + "Show the ordered inspection mission from battery check through patrol, capture, reporting and recharge." + ], + "gaps": [ + "Declared behavior is rendered without execution, simulation or live token/state animation." + ], + "notices": [] + }, + { + "id": "RoverViews::lifecycle", + "engineKind": "state", + "app": "partial", + "nodeNames": [ + "RoverBehavior::OperatingMode", + "", + "docked", + "patrolling", + "inspecting", + "returning", + "charging", + "safeStop" + ], + "edgeKinds": [ + "transition", + "transition", + "transition", + "transition", + "transition", + "transition", + "transition", + "transition", + "transition" + ], + "targets": [ + "Show operating states, initial state, safe stop and return-to-charge transitions." + ], + "gaps": [ + "Declared behavior is rendered without execution, simulation or live token/state animation." + ], + "notices": [] + }, + { + "id": "RoverViews::requirements", + "engineKind": "requirement", + "app": "partial", + "nodeNames": [ + "RoverRequirements::reserve", + "RoverRequirements::speed", + "RoverRequirements::clearance", + "RoverRequirements::imaging", + "RoverSystem::InspectionRover::energy::battery", + "RoverSystem::InspectionRover::mobility::leftDrive", + "RoverSystem::InspectionRover::autonomy::controller", + "RoverSystem::InspectionRover::inspection::camera" + ], + "edgeKinds": ["satisfy", "satisfy", "satisfy", "satisfy"], + "targets": [ + "Show four quantified requirements and their exact declared satisfaction targets." + ], + "gaps": [ + "Satisfaction is declared traceability, not evidence that a test passed." + ], + "notices": [] + } + ] } ] } diff --git a/examples/inspection-rover/README.md b/examples/inspection-rover/README.md new file mode 100644 index 0000000..efead98 --- /dev/null +++ b/examples/inspection-rover/README.md @@ -0,0 +1,24 @@ +# Inspection rover + +The default **Load example** and **Try an example** project. Six source files and five named views describe one indoor inspection rover, rather than unrelated syntax demonstrations. This is the only bundled example in the project launcher. + +## Guided tour + +1. **architecture** opens first. Follow a 480 Wh battery through distribution to two drive usages, the autonomy controller and an inspection camera. Lidar feeds obstacle perception; images return to the controller, and radio carries mission telemetry. Four explicit assembly containers retain ownership. Attribute compartments show the exact declared values, including inherited type members; they do not evaluate performance. +2. **mission** follows battery check, patrol, obstacle detection, image capture, reporting, return and recharge. Initial/final nodes and directed succession arrows show the declared order. Four explicit allocations link mission steps to actual controller, camera, radio and left-drive usages. The action diagram shows the sequence; allocation declarations remain available in source. This simple sequence does not branch or execute. +3. **lifecycle** connects docked, patrolling, inspecting, returning, charging and safeStop. The low-reserve and obstacle paths make the mission's recovery concerns explicit. Transition names are labels, not evaluated guards or live events. +4. **requirements** traces four quantitative declarations to their exact usage targets: 20% energy reserve, 1.5 m/s maximum drive speed, 0.8 m obstacle clearance and 12 MP image resolution. Satisfaction assertions are modeled claims, not verification results. Only the left drive is explicitly linked to the speed requirement; no right-drive link is inferred. +5. **definitions** shows reusable component types and actuator/sensor specialization. The two drives in architecture share the same Drive definition but remain distinct usages. + +Select any ordinary diagram node and choose **Open source** to inspect its declaration. Download the project to preserve sources and independently arranged view geometry. Model edits remain drafts until **Apply source** succeeds. + +## Files and scope + +- `system.sysml`: rover composition and ten port connections. +- `components.sysml`: component definitions, declared values and specialization. +- `types.sysml`: power, command, sensor, image and telemetry interfaces. +- `behavior.sysml`: mission successions and operating-state transitions. +- `requirements.sysml`: quantitative bounds and four explicit satisfaction assertions. +- `views.sysml`: the five authored browsing selections. + +The example is authored here under Apache-2.0. Numeric attributes use scalar names with explicit units in their names/documentation; this is not a dimensional-analysis or simulation model. No physical test results, live state, automatic routing behavior or solver results are claimed. Declared behavior rendering is bounded to the native action/state graphs; sequence, case and mixed views still need independent renderers. diff --git a/examples/inspection-rover/behavior.sysml b/examples/inspection-rover/behavior.sysml new file mode 100644 index 0000000..0cbf3d5 --- /dev/null +++ b/examples/inspection-rover/behavior.sysml @@ -0,0 +1,43 @@ +package RoverBehavior { + action def InspectRoute { + doc /* Declared mission order; this diagram does not execute the rover. */ + first start; + action checkBattery; + action patrol; + action detectObstacle; + action captureInspection; + action transmitReport; + action returnToDock; + action recharge; + done; + succession first start then checkBattery; + succession first checkBattery then patrol; + succession first patrol then detectObstacle; + succession first detectObstacle then captureInspection; + succession first captureInspection then transmitReport; + succession first transmitReport then returnToDock; + succession first returnToDock then recharge; + succession first recharge then done; + allocation perceptionResponsibility allocate detectObstacle to RoverSystem::rover.autonomy.controller; + allocation imagingResponsibility allocate captureInspection to RoverSystem::rover.inspection.camera; + allocation reportingResponsibility allocate transmitReport to RoverSystem::rover.inspection.radio; + allocation tractionResponsibility allocate patrol to RoverSystem::rover.mobility.leftDrive; + } + state def OperatingMode { + entry; then docked; + state docked; + state patrolling; + state inspecting; + state returning; + state charging; + state safeStop; + transition dispatch first docked then patrolling; + transition targetFound first patrolling then inspecting; + transition inspectionComplete first inspecting then patrolling; + transition lowReserve first patrolling then returning; + transition dockReached first returning then charging; + transition chargeComplete first charging then docked; + transition obstacleDetected first patrolling then safeStop; + transition pathClear first safeStop then patrolling; + } +} diff --git a/examples/inspection-rover/components.sysml b/examples/inspection-rover/components.sysml new file mode 100644 index 0000000..8549794 --- /dev/null +++ b/examples/inspection-rover/components.sysml @@ -0,0 +1,44 @@ +package RoverComponents { + private import ScalarValues::*; + private import RoverTypes::*; + part def Battery { + doc /* Rechargeable battery. Capacity and reserve are design targets. */ + attribute capacityWh : Real default = 480.0; + attribute reservePercent : Real default = 20.0; + out port supply : PowerPort; + } + part def Distribution { + in port input : PowerPort; + out port drive : PowerPort; + out port compute : PowerPort; + out port payload : PowerPort; + } + part def Actuator; + part def Sensor; + part def Drive :> Actuator { + attribute maximumSpeedMps : Real default = 1.5; + in port power : PowerPort; + in port command : CommandPort; + } + part def Controller { + attribute obstacleStopDistanceM : Real default = 0.8; + in port power : PowerPort; + in port perception : SensorPort; + in port images : ImagePort; + out port drive : CommandPort; + out port status : TelemetryPort; + } + part def Lidar :> Sensor { + attribute rangeM : Real default = 12.0; + out port scan : SensorPort; + } + part def Camera :> Sensor { + attribute resolutionMP : Real default = 12.0; + in port power : PowerPort; + out port image : ImagePort; + } + part def Radio { + attribute rangeM : Real default = 300.0; + in port telemetry : TelemetryPort; + } +} diff --git a/examples/inspection-rover/requirements.sysml b/examples/inspection-rover/requirements.sysml new file mode 100644 index 0000000..339e80c --- /dev/null +++ b/examples/inspection-rover/requirements.sysml @@ -0,0 +1,44 @@ +package RoverRequirements { + private import ScalarValues::*; + private import RoverComponents::*; + requirement def <'RVR-001'> EnergyReserve { + doc /* Keep at least 20 percent battery reserve for the return journey. */ + subject battery : Battery; + require constraint { battery.reservePercent >= 20.0 } + } + requirement def <'RVR-002'> SafeSpeed { + doc /* Each drive shall limit nominal indoor speed to 1.5 m/s. */ + subject drive : Drive; + require constraint { drive.maximumSpeedMps <= 1.5 } + } + requirement def <'RVR-003'> ObstacleClearance { + doc /* Stop at least 0.8 m before a detected obstacle. */ + subject controller : Controller; + require constraint { controller.obstacleStopDistanceM >= 0.8 } + } + requirement def <'RVR-004'> InspectionQuality { + doc /* Capture inspection images at a resolution of at least 12 MP. */ + subject camera : Camera; + require constraint { camera.resolutionMP >= 12.0 } + } + requirement <'RVR-RESERVE'> reserve : EnergyReserve { + doc /* Keep at least 20 percent battery reserve for the return journey. */ + subject battery = RoverSystem::rover.energy.battery; + } + requirement <'RVR-SPEED'> speed : SafeSpeed { + doc /* Limit the left drive nominal indoor speed to 1.5 m/s. */ + subject drive = RoverSystem::rover.mobility.leftDrive; + } + requirement <'RVR-CLEAR'> clearance : ObstacleClearance { + doc /* Stop at least 0.8 m before a detected obstacle. */ + subject controller = RoverSystem::rover.autonomy.controller; + } + requirement <'RVR-IMAGE'> imaging : InspectionQuality { + doc /* Capture inspection images at a resolution of at least 12 MP. */ + subject camera = RoverSystem::rover.inspection.camera; + } + assert satisfy reserve by RoverSystem::rover.energy.battery; + assert satisfy speed by RoverSystem::rover.mobility.leftDrive; + assert satisfy clearance by RoverSystem::rover.autonomy.controller; + assert satisfy imaging by RoverSystem::rover.inspection.camera; +} diff --git a/examples/inspection-rover/system.sysml b/examples/inspection-rover/system.sysml new file mode 100644 index 0000000..74599af --- /dev/null +++ b/examples/inspection-rover/system.sysml @@ -0,0 +1,34 @@ +package RoverSystem { + private import RoverComponents::*; + part def InspectionRover { + doc /* A slow indoor inspection rover. Explore architecture, mission, + operating states and declared requirement traceability. */ + part energy { + part battery : Battery; + part distribution : Distribution; + connection batterySupply connect battery.supply to distribution.input; + } + part mobility { + part leftDrive : Drive; + part rightDrive : Drive; + } + part autonomy { + part controller : Controller; + part lidar : Lidar; + connection obstacleScan connect lidar.scan to controller.perception; + } + part inspection { + part camera : Camera; + part radio : Radio; + } + connection leftPower connect energy.distribution.drive to mobility.leftDrive.power; + connection rightPower connect energy.distribution.drive to mobility.rightDrive.power; + connection computePower connect energy.distribution.compute to autonomy.controller.power; + connection cameraPower connect energy.distribution.payload to inspection.camera.power; + connection leftCommand connect autonomy.controller.drive to mobility.leftDrive.command; + connection rightCommand connect autonomy.controller.drive to mobility.rightDrive.command; + connection inspectionImages connect inspection.camera.image to autonomy.controller.images; + connection missionTelemetry connect autonomy.controller.status to inspection.radio.telemetry; + } + part rover : InspectionRover; +} diff --git a/examples/inspection-rover/types.sysml b/examples/inspection-rover/types.sysml new file mode 100644 index 0000000..5873f30 --- /dev/null +++ b/examples/inspection-rover/types.sysml @@ -0,0 +1,12 @@ +package RoverTypes { + item def ElectricalPower; + item def DriveCommand; + item def SensorFrame; + item def InspectionImage; + item def Telemetry; + port def PowerPort { out item power : ElectricalPower; } + port def CommandPort { out item command : DriveCommand; } + port def SensorPort { out item scanData : SensorFrame; } + port def ImagePort { out item image : InspectionImage; } + port def TelemetryPort { out item report : Telemetry; } +} diff --git a/examples/inspection-rover/views.sysml b/examples/inspection-rover/views.sysml new file mode 100644 index 0000000..61b227c --- /dev/null +++ b/examples/inspection-rover/views.sysml @@ -0,0 +1,17 @@ +package RoverViews { + private import StandardViewDefinitions::*; + view architecture : InterconnectionView { expose RoverSystem::InspectionRover; } + view definitions : GeneralView { + filter @SysML::PartDefinition or @SysML::PartUsage; + expose RoverComponents::**; + } + view mission : ActionFlowView { expose RoverBehavior::InspectRoute; } + view lifecycle : StateTransitionView { expose RoverBehavior::OperatingMode; } + view requirements : GeneralView { + filter @SysML::RequirementDefinition or @SysML::RequirementUsage; + expose RoverRequirements::reserve; + expose RoverRequirements::speed; + expose RoverRequirements::clearance; + expose RoverRequirements::imaging; + } +} diff --git a/scripts/prepare-opensysml.mjs b/scripts/prepare-opensysml.mjs index b4f1ff6..ca4dc05 100644 --- a/scripts/prepare-opensysml.mjs +++ b/scripts/prepare-opensysml.mjs @@ -5,12 +5,14 @@ import { fileURLToPath } from 'node:url'; import { resolve } from 'node:path'; const root = fileURLToPath(new URL('../', import.meta.url)); -const commit = '08ef397841767b829e492bbecf6d723483b4421e'; +const commit = '760c4d1fc8edfa4e214423099065c0a7c1b24e2d'; const archiveHash = - '896650da511eab806177f6f2a5d60f7ef57b44dfe617b2844a978f9adadb6d9d'; + '49939c5f4c3008cdcf79d39a976719f67aab9d38bb38b246832eb1fdd90de08e'; const digest = (bytes) => createHash('sha256').update(bytes).digest('hex'); const overlays = [ 'evaluate_view.go', + 'exploration.go', + 'exploration_test.go', 'validated_view.go', 'validated_view_test.go', 'geometry_pose.go', @@ -34,7 +36,7 @@ const patchHash = digest( ), ); const destination = resolve(root, 'static/vendor/opensysml'); -const cache = resolve(root, 'sandbox/opensysml-build'); +const cache = resolve(root, 'sandbox/opensysml-012'); try { const stamp = JSON.parse( await readFile(resolve(destination, 'provenance.json'), 'utf8'), @@ -97,6 +99,14 @@ for (const [name, folder] of [ name.endsWith('_test.go') ? 'studio_view_test.go' : 'studio_view.go', ), ); +for (const [name, folder] of [ + ['exploration.go', 'engine'], + ['exploration_test.go', 'combined'], +]) + await cp( + resolve(root, 'engine-bridge/opensysml', name), + resolve(source, 'internal/frontend', folder, 'studio_' + name), + ); await cp( resolve(root, 'engine-bridge/opensysml/requirements_view.go'), resolve(source, 'internal/frontend/engine/studio_requirements_view.go'), @@ -131,6 +141,11 @@ async function replace(file, before, after) { throw new Error(`Pinned source seam changed: ${file}`); await writeFile(name, text.replace(before, after)); } +await replace( + 'internal/frontend/engine/engine.go', + 'case "RenderView":', + 'case "Explore":\n var req JExploreRequest\n if err := decode(params, &req); err != nil { return nil, err }\n return e.explore(&req)\n case "RenderView":', +); await replace( 'internal/frontend/engine/engine.go', 'case "RenderView":', @@ -144,12 +159,12 @@ await replace( await replace( 'internal/frontend/combined/combined.go', 'case "GetServerInfo":', - 'case "EvaluateRelationships":\n return s.evaluateRelationships(ctx,params)\n case "EvaluateView":\n return s.evaluateView(ctx,params)\n case "GetServerInfo":', + 'case "Explore":\n return s.explore(ctx,params)\n case "EvaluateRelationships":\n return s.evaluateRelationships(ctx,params)\n case "EvaluateView":\n return s.evaluateView(ctx,params)\n case "GetServerInfo":', ); await replace( 'internal/frontend/combined/combined.go', 'var capabilities = []string{', - 'var capabilities = []string{\n "studio_evaluate_view_v1",\n "studio_relationships_v1",', + 'var capabilities = []string{\n "studio_exploration_v1",\n "studio_evaluate_view_v1",\n "studio_relationships_v1",', ); let go = process.env.SYSML_GO; if (!go) { @@ -161,19 +176,19 @@ if (!go) { } if (!go) { if (process.platform !== 'linux' || process.arch !== 'x64') - throw new Error('Set SYSML_GO to a local Go 1.25.11 binary.'); - const toolArchive = resolve(cache, 'go1.25.11.linux-amd64.tar.gz'); + throw new Error('Set SYSML_GO to a local Go 1.27.3 binary.'); + const toolArchive = resolve(cache, 'go1.27.3.linux-amd64.tar.gz'); await download( - 'https://go.dev/dl/go1.25.11.linux-amd64.tar.gz', + 'https://go.dev/dl/go1.27.3.linux-amd64.tar.gz', toolArchive, - '34f14304e856893f4ba30c2cacfe93906e9de7915c5f6aaaf3a81cdccd7ba30b', + '65efbc80fdc1fa2cd2631cad5b481777a68e8725f34e89f50fc6ac714e379b40', ); execFileSync('tar', ['-xzf', toolArchive, '-C', cache]); go = resolve(cache, 'go/bin/go'); } const toolchain = execFileSync(go, ['version'], { encoding: 'utf8' }).trim(); -if (!toolchain.startsWith('go version go1.25.11 ')) - throw new Error('The native view bridge requires Go 1.25.11.'); +if (!toolchain.startsWith('go version go1.27.3 ')) + throw new Error('The native view bridge requires Go 1.27.3.'); await mkdir(destination, { recursive: true }); const env = { ...process.env, @@ -195,7 +210,7 @@ execFileSync( '-buildvcs=false', '-trimpath', '-ldflags', - `-X main.Version=v0.10.0 -X main.Commit=${commit}`, + `-X main.Version=v0.12.0 -X main.Commit=${commit}`, '-o', resolve(destination, 'sysml-wasm.wasm'), './cmd/sysml-wasm', @@ -224,6 +239,7 @@ await writeFile( patchHash, toolchain, bridge: 'studio_evaluate_view_v1', + exploration: 'studio_exploration_v1', builderHash: digest(await readFile(fileURLToPath(import.meta.url))), binaryHash: digest( await readFile(resolve(destination, 'sysml-wasm.wasm')), diff --git a/src/lib/engines/opensysml/index.ts b/src/lib/engines/opensysml/index.ts index 6df73f1..65d3fc4 100644 --- a/src/lib/engines/opensysml/index.ts +++ b/src/lib/engines/opensysml/index.ts @@ -1,9 +1,52 @@ +import { + validateRequirementsResult, + type RequirementsEngine, +} from '$lib/workspace/requirements'; +import { + validateExploration, + type ExplorationEngine, +} from '$lib/workspace/exploration'; import type { SemanticEngine } from '$lib/workspace/editing'; import { readModelSnapshot } from '$lib/workspace/project'; import { validateEvaluation } from '$lib/workspace/views'; import { requestWorker } from '../workers/request'; -export function createSemanticEngine(base: string): SemanticEngine { +export function createSemanticEngine( + base: string, +): SemanticEngine & ExplorationEngine & RequirementsEngine { return { + requirements(documents, modelHash, signal) { + return requestWorker( + new Worker(new URL('./semantic.worker.ts', import.meta.url), { + type: 'module', + }), + { + protocolVersion: 1, + operation: 'requirements', + documents, + modelHash, + base, + }, + (value) => validateRequirementsResult(value, modelHash, documents), + signal, + ); + }, + explore(documents, modelHash, request, signal) { + return requestWorker( + new Worker(new URL('./semantic.worker.ts', import.meta.url), { + type: 'module', + }), + { + protocolVersion: 1, + operation: 'explore', + documents, + modelHash, + request, + base, + }, + (value) => validateExploration(value, modelHash, request, documents), + signal, + ); + }, parse(documents, signal) { return requestWorker( new Worker(new URL('./semantic.worker.ts', import.meta.url), { diff --git a/src/lib/engines/opensysml/semantic.worker.ts b/src/lib/engines/opensysml/semantic.worker.ts index b0f748d..1d97ef8 100644 --- a/src/lib/engines/opensysml/semantic.worker.ts +++ b/src/lib/engines/opensysml/semantic.worker.ts @@ -62,13 +62,29 @@ self.onmessage = async (event: MessageEvent