From 2b2d2bc18bc11ca71ef8c4c5d26610939c1b1f42 Mon Sep 17 00:00:00 2001 From: Codex Date: Sun, 6 Sep 2026 16:31:55 -0700 Subject: [PATCH] Integrate release recovery, semantic review, coverage and Windows packaging --- README.md | 28 +- build/windows.manifest | 8 + build/windows.rc | 1 + build/windows_manifest.rs | 35 + crates/oa-core/build.rs | 1 + crates/oa-core/src/language_nesting_tests.rs | 51 +- crates/oa-core/src/projection/state.rs | 7 +- crates/oa-core/src/semantic/mutate.rs | 31 +- crates/oa-core/src/validation.rs | 16 +- crates/oa-core/src/validation/coverage.rs | 283 ++++++ crates/oa-core/tests/coverage_diagnostics.rs | 582 ++++++++++++ crates/oa-core/tests/r3_semantic.rs | 74 ++ crates/oa-desktop/Cargo.toml | 2 +- crates/oa-desktop/build.rs | 1 + crates/oa-desktop/src/app.rs | 148 ++- crates/oa-desktop/src/app/checkpoints.rs | 479 ++++++++++ crates/oa-desktop/src/app/save_as_tests.rs | 114 +++ crates/oa-desktop/src/app/state_diagram.rs | 35 + .../oa-desktop/src/app/state_diagram_tests.rs | 158 +++- crates/oa-desktop/src/behavior.rs | 206 ++++ crates/oa-desktop/src/behavior/scenario.rs | 77 +- .../oa-desktop/src/behavior/state_canvas.rs | 195 +++- crates/oa-desktop/src/behavior/states.rs | 17 +- crates/oa-desktop/src/behavior/topology.rs | 41 +- crates/oa-desktop/src/document.rs | 133 ++- .../oa-desktop/src/document_save_as_tests.rs | 154 +++ crates/oa-desktop/src/forms.rs | 80 ++ crates/oa-desktop/src/guide.rs | 43 +- crates/oa-desktop/src/home.rs | 457 +++++++++ crates/oa-desktop/src/home/tests.rs | 177 ++++ crates/oa-desktop/src/hosted/client_tests.rs | 17 +- crates/oa-desktop/src/main.rs | 2 + crates/oa-desktop/src/model_menu.rs | 8 +- crates/oa-desktop/src/port_workflow.rs | 101 +- crates/oa-desktop/src/recovery.rs | 419 ++++++++ crates/oa-desktop/src/semantic_review.rs | 120 ++- .../oa-desktop/src/semantic_review/keyed.rs | 91 +- .../src/semantic_review/keyed_rows.rs | 345 +++++++ .../src/semantic_review/keyed_rows_tests.rs | 736 ++++++++++++++ .../oa-desktop/src/semantic_review/ordered.rs | 254 +++++ .../src/semantic_review/ordered_tests.rs | 567 +++++++++++ .../src/semantic_review/recovery.rs | 347 +++++++ crates/oa-desktop/src/semantic_review/view.rs | 254 ++++- crates/oa-desktop/src/source_workspace.rs | 79 ++ crates/oa-desktop/src/views.rs | 21 +- crates/oa-desktop/src/workflow.rs | 7 + crates/oa-desktop/src/workflow/recovery.rs | 206 ++++ crates/oa-server/build.rs | 1 + crates/oa-server/tests/http.rs | 25 +- deployment/Dockerfile | 1 + docs/language.rst | 51 +- docs/scope.md | 18 +- docs/windows-packaging.md | 69 ++ harness/DECISIONS.md | 71 ++ harness/PLAN.md | 4 +- harness/STATE.md | 34 + harness/TASK_LEDGER.md | 3 + harness/fixtures/release1-native/README.md | 9 + .../semantic-keyed.after-repair.arcadia | 482 ++++++++++ .../semantic-keyed.after-repair.json | 895 ++++++++++++++++++ .../semantic-keyed.after-take.arcadia | 482 ++++++++++ .../semantic-keyed.after-take.json | 895 ++++++++++++++++++ .../release1-native/semantic-keyed.arcadia | 482 ++++++++++ .../semantic-keyed.invalid-request.json | 80 ++ .../semantic-keyed.invalid.txt | 1 + .../release1-native/semantic-keyed.json | 895 ++++++++++++++++++ .../semantic-keyed.manifest.json | 35 + .../semantic-keyed.repair-plan.json | 543 +++++++++++ .../semantic-keyed.repair-request.json | 80 ++ .../release1-native/semantic-keyed.steps.md | 66 ++ .../semantic-keyed.take-plan.json | 670 +++++++++++++ .../semantic-keyed.take-request.json | 56 ++ ...c-ordered-branch-steps.after-order.arcadia | 231 +++++ ...ntic-ordered-branch-steps.after-order.json | 409 ++++++++ ...rdered-branch-steps.ambiguous-request.json | 50 + ...emantic-ordered-branch-steps.ambiguous.txt | 1 + .../semantic-ordered-branch-steps.arcadia | 218 +++++ ...antic-ordered-branch-steps.final-plan.json | 710 ++++++++++++++ ...ic-ordered-branch-steps.final-request.json | 87 ++ .../semantic-ordered-branch-steps.json | 386 ++++++++ ...emantic-ordered-branch-steps.manifest.json | 36 + ...antic-ordered-fragment.after-order.arcadia | 205 ++++ ...semantic-ordered-fragment.after-order.json | 359 +++++++ ...ic-ordered-fragment.ambiguous-request.json | 50 + .../semantic-ordered-fragment.ambiguous.txt | 1 + .../semantic-ordered-fragment.arcadia | 192 ++++ .../semantic-ordered-fragment.final-plan.json | 692 ++++++++++++++ ...mantic-ordered-fragment.final-request.json | 87 ++ .../semantic-ordered-fragment.json | 336 +++++++ .../semantic-ordered-fragment.manifest.json | 36 + ...mantic-ordered-reverse.after-order.arcadia | 163 ++++ .../semantic-ordered-reverse.after-order.json | 289 ++++++ ...tic-ordered-reverse.ambiguous-request.json | 50 + .../semantic-ordered-reverse.ambiguous.txt | 1 + .../semantic-ordered-reverse.arcadia | 150 +++ .../semantic-ordered-reverse.final-plan.json | 620 ++++++++++++ ...emantic-ordered-reverse.final-request.json | 78 ++ .../semantic-ordered-reverse.json | 266 ++++++ .../semantic-ordered-reverse.manifest.json | 36 + ...antic-ordered-scenario.after-order.arcadia | 179 ++++ ...semantic-ordered-scenario.after-order.json | 309 ++++++ ...ic-ordered-scenario.ambiguous-request.json | 50 + .../semantic-ordered-scenario.ambiguous.txt | 1 + .../semantic-ordered-scenario.arcadia | 166 ++++ .../semantic-ordered-scenario.final-plan.json | 704 ++++++++++++++ ...mantic-ordered-scenario.final-request.json | 84 ++ .../semantic-ordered-scenario.json | 286 ++++++ .../semantic-ordered-scenario.manifest.json | 36 + .../semantic-ordered.after-order.arcadia | 163 ++++ .../semantic-ordered.after-order.json | 289 ++++++ .../semantic-ordered.ambiguous-request.json | 50 + .../semantic-ordered.ambiguous.txt | 1 + .../release1-native/semantic-ordered.arcadia | 150 +++ .../semantic-ordered.final-plan.json | 620 ++++++++++++ .../semantic-ordered.final-request.json | 78 ++ .../release1-native/semantic-ordered.json | 266 ++++++ .../semantic-ordered.manifest.json | 36 + .../release1-native/semantic-ordered.steps.md | 73 ++ .../release1-native/state-actions.arcadia | 104 ++ harness/fixtures/rest-coverage/README.md | 32 + .../rest-coverage/coverage-probe.json | 58 ++ .../rest-coverage/run-coverage-probe.ps1 | 137 +++ harness/fixtures/run-timed-bundle-oracle.ps1 | 99 ++ .../release-branch-integration-20260906.md | 53 ++ .../release-coverage-diagnostics-design.md | 55 ++ harness/release-current-coverage-20260906.md | 161 ++++ harness/release-current-native-plan.md | 134 +++ .../release-current-native-verification.md | 168 ++++ .../release-home-recovery-review-20260906.md | 63 ++ harness/release-home.md | 76 ++ ...release-http-completion-review-20260906.md | 34 + harness/release-linux-validation-options.md | 133 +++ harness/release-msrv-readiness-20260906.md | 64 ++ ...ease-oa-communication-critique-20260906.md | 109 +++ harness/release-oa-communication-design.md | 284 ++++++ harness/release-recovery-design.md | 57 ++ harness/release-rest-coverage-probe-plan.md | 85 ++ ...ase-rest-coverage-verification-20260906.md | 37 + harness/release-save-as-runtime-packaging.md | 38 + ...lease-selective-views-critique-20260906.md | 83 ++ harness/release-selective-views-design.md | 180 ++++ ...elease-semantic-batch-foundation-design.md | 197 ++++ harness/release-semantic-keyed2.md | 104 ++ harness/release-semantic-ordered.md | 96 ++ harness/release-semantic-remaining-plan.md | 87 ++ .../release-timed-bundle-oracle-20260906.md | 67 ++ harness/release-windows-package-review.md | 38 + scripts/package-windows.ps1 | 250 +++++ scripts/test-windows-notices.ps1 | 93 ++ scripts/windows-notices.ps1 | 160 ++++ scripts/windows-notices/CC0-1.0.txt | 121 +++ ...umanAsh-clipboard-win-3b27cf2bfd1a-LICENSE | 23 + scripts/windows-notices/Fira-OFL.txt | 93 ++ .../FiraSans-Regular-NOTICE.txt | 6 + scripts/windows-notices/Iced-Icons-NOTICE.txt | 33 + ...ysma-profiling-8271551172eb-LICENSE-APACHE | 201 ++++ ...aclysma-profiling-8271551172eb-LICENSE-MIT | 21 + .../brendanzab-gl-rs-ea503e8d5fb6-LICENSE | 202 ++++ .../brendanzab-gl-rs-f150967b1c44-LICENSE | 202 ++++ .../gfx-rs-rspirv-e91a5debc10c-LICENSE | 202 ++++ .../hexf-parse-0.2.1-Cargo.toml | 11 + .../iced-rs-iced-3997291f318a-LICENSE | 18 + .../iced-rs-iced-54abf81d13fe-LICENSE | 18 + .../llvm-ea7d852a70e8-libunwind-LICENSE.TXT | 311 ++++++ scripts/windows-notices/manifest.json | 417 ++++++++ .../nical-lyon-56219c95c658-LICENSE-APACHE | 13 + .../nical-lyon-56219c95c658-LICENSE-MIT | 20 + .../nical-lyon-994526f2766c-LICENSE-APACHE | 13 + .../nical-lyon-994526f2766c-LICENSE-MIT | 20 + .../nical-rust_debug-93f414cd572b-LICENSE | 21 + .../zakarumych-gpu-alloc-61020bdcb4eb-COPYING | 6 + .../zakarumych-gpu-alloc-7ddd1fe576fc-COPYING | 6 + ...rumych-gpu-descriptor-9f2fb9546591-COPYING | 6 + ...rumych-gpu-descriptor-bb28b4e55b74-COPYING | 6 + 174 files changed, 27635 insertions(+), 127 deletions(-) create mode 100644 build/windows.manifest create mode 100644 build/windows.rc create mode 100644 build/windows_manifest.rs create mode 100644 crates/oa-core/build.rs create mode 100644 crates/oa-core/src/validation/coverage.rs create mode 100644 crates/oa-core/tests/coverage_diagnostics.rs create mode 100644 crates/oa-desktop/build.rs create mode 100644 crates/oa-desktop/src/app/checkpoints.rs create mode 100644 crates/oa-desktop/src/app/save_as_tests.rs create mode 100644 crates/oa-desktop/src/document_save_as_tests.rs create mode 100644 crates/oa-desktop/src/home.rs create mode 100644 crates/oa-desktop/src/home/tests.rs create mode 100644 crates/oa-desktop/src/recovery.rs create mode 100644 crates/oa-desktop/src/semantic_review/keyed_rows.rs create mode 100644 crates/oa-desktop/src/semantic_review/keyed_rows_tests.rs create mode 100644 crates/oa-desktop/src/semantic_review/ordered.rs create mode 100644 crates/oa-desktop/src/semantic_review/ordered_tests.rs create mode 100644 crates/oa-desktop/src/semantic_review/recovery.rs create mode 100644 crates/oa-desktop/src/workflow/recovery.rs create mode 100644 crates/oa-server/build.rs create mode 100644 docs/windows-packaging.md create mode 100644 harness/fixtures/release1-native/README.md create mode 100644 harness/fixtures/release1-native/semantic-keyed.after-repair.arcadia create mode 100644 harness/fixtures/release1-native/semantic-keyed.after-repair.json create mode 100644 harness/fixtures/release1-native/semantic-keyed.after-take.arcadia create mode 100644 harness/fixtures/release1-native/semantic-keyed.after-take.json create mode 100644 harness/fixtures/release1-native/semantic-keyed.arcadia create mode 100644 harness/fixtures/release1-native/semantic-keyed.invalid-request.json create mode 100644 harness/fixtures/release1-native/semantic-keyed.invalid.txt create mode 100644 harness/fixtures/release1-native/semantic-keyed.json create mode 100644 harness/fixtures/release1-native/semantic-keyed.manifest.json create mode 100644 harness/fixtures/release1-native/semantic-keyed.repair-plan.json create mode 100644 harness/fixtures/release1-native/semantic-keyed.repair-request.json create mode 100644 harness/fixtures/release1-native/semantic-keyed.steps.md create mode 100644 harness/fixtures/release1-native/semantic-keyed.take-plan.json create mode 100644 harness/fixtures/release1-native/semantic-keyed.take-request.json create mode 100644 harness/fixtures/release1-native/semantic-ordered-branch-steps.after-order.arcadia create mode 100644 harness/fixtures/release1-native/semantic-ordered-branch-steps.after-order.json create mode 100644 harness/fixtures/release1-native/semantic-ordered-branch-steps.ambiguous-request.json create mode 100644 harness/fixtures/release1-native/semantic-ordered-branch-steps.ambiguous.txt create mode 100644 harness/fixtures/release1-native/semantic-ordered-branch-steps.arcadia create mode 100644 harness/fixtures/release1-native/semantic-ordered-branch-steps.final-plan.json create mode 100644 harness/fixtures/release1-native/semantic-ordered-branch-steps.final-request.json create mode 100644 harness/fixtures/release1-native/semantic-ordered-branch-steps.json create mode 100644 harness/fixtures/release1-native/semantic-ordered-branch-steps.manifest.json create mode 100644 harness/fixtures/release1-native/semantic-ordered-fragment.after-order.arcadia create mode 100644 harness/fixtures/release1-native/semantic-ordered-fragment.after-order.json create mode 100644 harness/fixtures/release1-native/semantic-ordered-fragment.ambiguous-request.json create mode 100644 harness/fixtures/release1-native/semantic-ordered-fragment.ambiguous.txt create mode 100644 harness/fixtures/release1-native/semantic-ordered-fragment.arcadia create mode 100644 harness/fixtures/release1-native/semantic-ordered-fragment.final-plan.json create mode 100644 harness/fixtures/release1-native/semantic-ordered-fragment.final-request.json create mode 100644 harness/fixtures/release1-native/semantic-ordered-fragment.json create mode 100644 harness/fixtures/release1-native/semantic-ordered-fragment.manifest.json create mode 100644 harness/fixtures/release1-native/semantic-ordered-reverse.after-order.arcadia create mode 100644 harness/fixtures/release1-native/semantic-ordered-reverse.after-order.json create mode 100644 harness/fixtures/release1-native/semantic-ordered-reverse.ambiguous-request.json create mode 100644 harness/fixtures/release1-native/semantic-ordered-reverse.ambiguous.txt create mode 100644 harness/fixtures/release1-native/semantic-ordered-reverse.arcadia create mode 100644 harness/fixtures/release1-native/semantic-ordered-reverse.final-plan.json create mode 100644 harness/fixtures/release1-native/semantic-ordered-reverse.final-request.json create mode 100644 harness/fixtures/release1-native/semantic-ordered-reverse.json create mode 100644 harness/fixtures/release1-native/semantic-ordered-reverse.manifest.json create mode 100644 harness/fixtures/release1-native/semantic-ordered-scenario.after-order.arcadia create mode 100644 harness/fixtures/release1-native/semantic-ordered-scenario.after-order.json create mode 100644 harness/fixtures/release1-native/semantic-ordered-scenario.ambiguous-request.json create mode 100644 harness/fixtures/release1-native/semantic-ordered-scenario.ambiguous.txt create mode 100644 harness/fixtures/release1-native/semantic-ordered-scenario.arcadia create mode 100644 harness/fixtures/release1-native/semantic-ordered-scenario.final-plan.json create mode 100644 harness/fixtures/release1-native/semantic-ordered-scenario.final-request.json create mode 100644 harness/fixtures/release1-native/semantic-ordered-scenario.json create mode 100644 harness/fixtures/release1-native/semantic-ordered-scenario.manifest.json create mode 100644 harness/fixtures/release1-native/semantic-ordered.after-order.arcadia create mode 100644 harness/fixtures/release1-native/semantic-ordered.after-order.json create mode 100644 harness/fixtures/release1-native/semantic-ordered.ambiguous-request.json create mode 100644 harness/fixtures/release1-native/semantic-ordered.ambiguous.txt create mode 100644 harness/fixtures/release1-native/semantic-ordered.arcadia create mode 100644 harness/fixtures/release1-native/semantic-ordered.final-plan.json create mode 100644 harness/fixtures/release1-native/semantic-ordered.final-request.json create mode 100644 harness/fixtures/release1-native/semantic-ordered.json create mode 100644 harness/fixtures/release1-native/semantic-ordered.manifest.json create mode 100644 harness/fixtures/release1-native/semantic-ordered.steps.md create mode 100644 harness/fixtures/release1-native/state-actions.arcadia create mode 100644 harness/fixtures/rest-coverage/README.md create mode 100644 harness/fixtures/rest-coverage/coverage-probe.json create mode 100644 harness/fixtures/rest-coverage/run-coverage-probe.ps1 create mode 100644 harness/fixtures/run-timed-bundle-oracle.ps1 create mode 100644 harness/release-branch-integration-20260906.md create mode 100644 harness/release-coverage-diagnostics-design.md create mode 100644 harness/release-current-coverage-20260906.md create mode 100644 harness/release-current-native-plan.md create mode 100644 harness/release-current-native-verification.md create mode 100644 harness/release-home-recovery-review-20260906.md create mode 100644 harness/release-home.md create mode 100644 harness/release-http-completion-review-20260906.md create mode 100644 harness/release-linux-validation-options.md create mode 100644 harness/release-msrv-readiness-20260906.md create mode 100644 harness/release-oa-communication-critique-20260906.md create mode 100644 harness/release-oa-communication-design.md create mode 100644 harness/release-recovery-design.md create mode 100644 harness/release-rest-coverage-probe-plan.md create mode 100644 harness/release-rest-coverage-verification-20260906.md create mode 100644 harness/release-save-as-runtime-packaging.md create mode 100644 harness/release-selective-views-critique-20260906.md create mode 100644 harness/release-selective-views-design.md create mode 100644 harness/release-semantic-batch-foundation-design.md create mode 100644 harness/release-semantic-keyed2.md create mode 100644 harness/release-semantic-ordered.md create mode 100644 harness/release-semantic-remaining-plan.md create mode 100644 harness/release-timed-bundle-oracle-20260906.md create mode 100644 harness/release-windows-package-review.md create mode 100644 scripts/package-windows.ps1 create mode 100644 scripts/test-windows-notices.ps1 create mode 100644 scripts/windows-notices.ps1 create mode 100644 scripts/windows-notices/CC0-1.0.txt create mode 100644 scripts/windows-notices/DoumanAsh-clipboard-win-3b27cf2bfd1a-LICENSE create mode 100644 scripts/windows-notices/Fira-OFL.txt create mode 100644 scripts/windows-notices/FiraSans-Regular-NOTICE.txt create mode 100644 scripts/windows-notices/Iced-Icons-NOTICE.txt create mode 100644 scripts/windows-notices/aclysma-profiling-8271551172eb-LICENSE-APACHE create mode 100644 scripts/windows-notices/aclysma-profiling-8271551172eb-LICENSE-MIT create mode 100644 scripts/windows-notices/brendanzab-gl-rs-ea503e8d5fb6-LICENSE create mode 100644 scripts/windows-notices/brendanzab-gl-rs-f150967b1c44-LICENSE create mode 100644 scripts/windows-notices/gfx-rs-rspirv-e91a5debc10c-LICENSE create mode 100644 scripts/windows-notices/hexf-parse-0.2.1-Cargo.toml create mode 100644 scripts/windows-notices/iced-rs-iced-3997291f318a-LICENSE create mode 100644 scripts/windows-notices/iced-rs-iced-54abf81d13fe-LICENSE create mode 100644 scripts/windows-notices/llvm-ea7d852a70e8-libunwind-LICENSE.TXT create mode 100644 scripts/windows-notices/manifest.json create mode 100644 scripts/windows-notices/nical-lyon-56219c95c658-LICENSE-APACHE create mode 100644 scripts/windows-notices/nical-lyon-56219c95c658-LICENSE-MIT create mode 100644 scripts/windows-notices/nical-lyon-994526f2766c-LICENSE-APACHE create mode 100644 scripts/windows-notices/nical-lyon-994526f2766c-LICENSE-MIT create mode 100644 scripts/windows-notices/nical-rust_debug-93f414cd572b-LICENSE create mode 100644 scripts/windows-notices/zakarumych-gpu-alloc-61020bdcb4eb-COPYING create mode 100644 scripts/windows-notices/zakarumych-gpu-alloc-7ddd1fe576fc-COPYING create mode 100644 scripts/windows-notices/zakarumych-gpu-descriptor-9f2fb9546591-COPYING create mode 100644 scripts/windows-notices/zakarumych-gpu-descriptor-bb28b4e55b74-COPYING diff --git a/README.md b/README.md index 51265d2c..9a142008 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,11 @@ Arcadia Studio is a native Rust/Iced modeling workbench for system engineers. V2 covers Operational Analysis (OA) and System Analysis (SA), with graphical and textual editing of one typed model and a deployable Rust REST service. It is an independent implementation inspired by Arcadia and Eclipse Capella, not a complete Capella or SysML implementation. -The normative [language and editing contract](docs/language.rst) defines supported semantics, both representations, validation and migration. [Scope](docs/scope.md) states the boundaries. The [acceptance](docs/acceptance.md), [validation](docs/validation.md) and [current state](harness/STATE.md) records document V2 tests and native Windows/Linux Computer Use verification. +The normative [language and editing contract](docs/language.rst) defines supported semantics, both representations, validation and migration. [Scope](docs/scope.md) states the boundaries. The [acceptance](docs/acceptance.md) and [validation](docs/validation.md) records document the earlier V2 baseline. [Current state](harness/STATE.md) and the [2026-09-06 coverage audit](harness/release-current-coverage-20260906.md) track subsequent implementation and outstanding release gates; the current first release is not yet accepted. ## Build and run -Install stable Rust and your platform's native compiler. The workspace declares Rust 1.90; the recorded development builds use Rust 1.98.1. Windows requires Visual Studio Build Tools with **Desktop development with C++**. Ubuntu/Debian development dependencies are: +Install stable Rust and your platform's native compiler. The workspace declares Rust 1.90; the recorded development builds use Rust 1.98.1. The standard Windows MSVC path uses Visual Studio Build Tools with **Desktop development with C++**. Ubuntu/Debian development dependencies are: ```sh sudo apt-get install build-essential pkg-config libxkbcommon-dev libxkbcommon-x11-0 libwayland-dev libvulkan-dev libx11-dev libxrandr-dev libxi-dev @@ -26,6 +26,16 @@ cargo build --release --workspace --locked Run `target/release/arcadia-studio.exe` on Windows or `./target/release/arcadia-studio` on Linux. No task-specific cache directory is required. A graphical desktop and compatible graphics driver are required; the application uses Iced 0.14 with WGPU and X11/Wayland support. It does not require Java, Eclipse, a browser, an account or a running server. +An optional Windows path uses the `stable-x86_64-pc-windows-gnullvm` Rust toolchain with LLVM-MinGW. Put LLVM-MinGW's `bin` directory on `PATH`, including `clang.exe` and `llvm-windres.exe`, then run: + +```powershell +rustup toolchain install stable-x86_64-pc-windows-gnullvm +$env:CARGO_TARGET_X86_64_PC_WINDOWS_GNULLVM_LINKER = 'clang.exe' +cargo +stable-x86_64-pc-windows-gnullvm build --release --workspace --locked +``` + +GNU/gnullvm builds require a resource compiler to embed the normal-user (`asInvoker`) manifest in executables and tests. The build selects `llvm-windres` for gnullvm or `windres` for GNU; set `RC` to its executable path if it is not on `PATH`. This avoids Windows treating installer-like test filenames as elevation requests. The current machine uses user-local Rust 1.98.1 and LLVM-MinGW 20260826 UCRT; compilation and test progress are recorded in [current state](harness/STATE.md). That evidence is separate from native workflow and release-package acceptance. + On the tested Ubuntu 24.04/WSLg environment, Mesa's Vulkan software adapter is required: ```sh @@ -35,11 +45,13 @@ WGPU_BACKEND=vulkan VK_DRIVER_FILES=/usr/share/vulkan/icd.d/lvp_icd.x86_64.json A compatible native Linux graphics adapter can use the ordinary command. This software-adapter workaround requires the indicated driver file. See the [platform verification record](docs/validation.md) for tested environments. -V2 release bundles are `dist/ArcadiaStudio-Windows.zip` and `dist/ArcadiaStudio-Linux.tar.gz`, with archive checksums in `dist/SHA256SUMS.txt`. Extract the bundle and run its `arcadia-studio.exe` or `./arcadia-studio`; Rust is not needed to run a packaged executable. Both bundles include the headless server, examples, specifications and deployment instructions. Linux bundles target the documented Ubuntu 24.04 runtime. Packaged executable and archive evidence is recorded in [package verification](harness/verification-v2-packages.md). +The earlier V2 package record describes `dist/ArcadiaStudio-Windows.zip` and `dist/ArcadiaStudio-Linux.tar.gz`, with archive checksums in `dist/SHA256SUMS.txt`. Extract a verified bundle and run its `arcadia-studio.exe` or `./arcadia-studio`; Rust is not needed to run a packaged executable. Those bundles include the headless server, examples, specifications and deployment instructions; Linux targets the documented Ubuntu 24.04 runtime. [Package verification](harness/verification-v2-packages.md) applies to that recorded revision. Current changes still require fresh packages, checksums and acceptance. + +The current [Windows packaging instructions](docs/windows-packaging.md) stage both executables, verify their runtime DLL dependencies, and produce an archive with checksums and provenance. Native acceptance fixtures and independent expected results are retained in [the release fixture directory](harness/fixtures/release1-native/README.md). ## Model operational needs and system responsibilities -The coastal rescue example combines people, an organization, a System of Interest, information and physical exchange items, normal/degraded behavior and code/CAD evidence. Choose **New** to start an empty project. +The desktop starts at **Home**, where you can create a model, open a local project, continue recent files or recover retained work. Missing recent files stay visible with **Locate**; **Remove** only forgets their history entry. Choose **Explore rescue example** for a model combining people, an organization, a System of Interest, information and physical exchange items, normal/degraded behavior and code/CAD evidence. The example opens in an architecture view with work nested inside its performers. Each layer also has dedicated functional dataflow and mission/capability views. Regenerate both example formats with matching identities using `cargo run --locked -p oa-core --example generate_demo`; the command verifies both saved models. Historical migration fixtures remain separate. @@ -48,7 +60,7 @@ The example opens in an architecture view with work nested inside its performers 3. Select a System or System Actor and choose **Add port** in the browser or Properties. Choose Standard or Flow, then **Create port**. Use **Boundary exchange** in the diagram palette, or **Draw boundary exchange** on a selected visible source port, to connect compatible component ports directly. Create the other participant's matching port first if none exists. Functions expose separate input/output port actions; the **Functional exchange** palette tool connects them. **Actions** exposes other engineering relationships and off-diagram configuration; **Data & interfaces** holds reusable payloads and contracts. 4. Create focused views. **Containment** nests semantic children; **Architecture · allocated work** nests work within performers; **Functional dataflow** shows work decomposition and exchanges; **Missions & capabilities** shows objectives and participation. Dashed, italic parent work denotes an allocation summary inferred from its leaves. Ports appear on their owner's boundary. Placement dragging changes geometry. **Hide** removes a placement; **Delete from model** removes semantics and dependent references. Use **Placement** and **View properties** for exact values. 5. In **Behavior & scenarios**, author processes/chains, scenario lifelines and ordered occurrences, including repeated uses of the same exchange and explicit fragments/branches. Select a State machine and choose **Create diagram** to place its existing regions and states/modes in a saved view. State machines also support availability and state-action links. Conditions are descriptive, not executable simulation code. Behavior and Data authoring preserve your other diagram layouts. -6. Select an OA or SA element and choose **Create / update counterpart**. Review dependency scope and counterpart choices, then apply one undoable transaction. Existing counterparts retain their structural choices; reviewed updates compare names/descriptions. Manual traces link other engineering correspondences. **Traceability** also edits artifact URI, revision and fragment metadata. +6. Select an OA or SA element and choose **Create / update counterpart**. Review dependency scope and counterpart choices, then apply one undoable transaction. For an existing trace, **Review semantics…** compares typed content and prior observations, with explicit keep, take or final-value decisions for supported structural, scalar, constructor and individual-row changes. Review does not automatically synchronize the perspectives. Ordered, attachment, artifact and multi-source review controls remain unfinished. Manual traces link other engineering correspondences. **Traceability** also edits artifact URI, revision and fragment metadata. 7. Review **Model checks**, then save `.arcadia` source or `.json`. Warnings allow incomplete authoring; structural errors preserve the committed model and rejected draft. **Model explorer** groups roots by engineering purpose and opens one container's direct children. Breadcrumbs navigate ownership; **Allocated work** is listed separately. Search names, kinds or `@aliases` to find elements with ancestor context. Results are paged in groups of 100. **Collapse all** folds the outline; **Locate** clears the filter and reveals the selected element's ancestry. @@ -72,7 +84,7 @@ In the state view, **Select / move** drags vertices and containers without repar **Textual model** has a source-file folder tree, per-file drafts, syntax highlighting, live linting and explicit **Apply all**. Collapse/Locate navigate the hierarchy; collapsed folders retain draft and diagnostic indicators. Right-click a file for contextual actions. Projects can use relative includes to organize source into files. For example: ```text -arcadia 4; +arcadia 5; project harbor "Harbor assistance" { oa_entity control "Harbor control" {} oa_activity respond "Respond to requests" { @@ -104,7 +116,9 @@ Reparenting a nested element through Properties moves its declaration, descendan Ctrl+S saves to the current local or hosted destination, Ctrl+Shift+S opens Save as, and Ctrl+O opens a local file. An `.arcadia` suffix selects text; other suffixes select JSON. Opening detects content rather than relying on the filename. Windows uses native dialogs; Linux uses a path-entry dialog with overwrite confirmation and no portal requirement. -**Save all** validates a snapshot and saves the source bundle, comparing previously read dependency bytes to reject detected external changes. Each file is replaced atomically; a staged transaction journal allows explicit recovery of interrupted multi-file saves. This is not an atomic multi-file filesystem operation or distributed locking. If recovery is needed, **Recover interrupted save** appears in Textual model. Dirty New/Open/Close operations offer save/discard/cancel. There is no periodic autosave of unsaved edits. Share or commit the entry file and all included source files together. +**Save all** validates a snapshot and saves the source bundle, comparing previously read dependency bytes to reject detected external changes. Each file is replaced atomically; a staged transaction journal allows explicit recovery of interrupted multi-file saves. This is not an atomic multi-file filesystem operation or distributed locking. If recovery is needed, **Recover interrupted save** appears in Textual model. Dirty New/Open/Close operations offer save/discard/cancel. Share or commit the entry file and all included source files together. + +While work is unsaved, the desktop attempts a private recovery checkpoint every 10 seconds. It retains the accepted source bundle, raw source buffers, supported pending property/behavior forms and workflow drafts, including rejected input and semantic-review choices. **Home → Recover** opens an unsaved copy with no local or hosted save destination; use **Save as** to choose one. Undo/redo history is not restored. Checkpoints never write model files or save to the server, and credentials are excluded. A failed checkpoint is reported on Home; recent edits since the last completed checkpoint may be unavailable after a crash. Local history and checkpoints live under `%LOCALAPPDATA%/ArcadiaStudio` on Windows or `$XDG_DATA_HOME/arcadia-studio` (otherwise `~/.local/share/arcadia-studio`) on Linux. Explicit saves and version control remain the durable record. Home/recovery is implemented; current native acceptance remains pending in the [coverage audit](harness/release-current-coverage-20260906.md). ## REST service and external tools diff --git a/build/windows.manifest b/build/windows.manifest new file mode 100644 index 00000000..1cf7cdca --- /dev/null +++ b/build/windows.manifest @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/build/windows.rc b/build/windows.rc new file mode 100644 index 00000000..bcafbdfe --- /dev/null +++ b/build/windows.rc @@ -0,0 +1 @@ +1 24 "windows.manifest" diff --git a/build/windows_manifest.rs b/build/windows_manifest.rs new file mode 100644 index 00000000..1e39a4b0 --- /dev/null +++ b/build/windows_manifest.rs @@ -0,0 +1,35 @@ +// GNU Windows linkers need an explicit normal-user manifest, including tests +// named "updates", to avoid Windows installer detection. No elevation is needed. +fn main() { + println!("cargo:rerun-if-changed=../../build/windows_manifest.rs"); + println!("cargo:rerun-if-changed=../../build/windows.manifest"); + println!("cargo:rerun-if-changed=../../build/windows.rc"); + println!("cargo:rerun-if-env-changed=RC"); + let target = std::env::var("TARGET").unwrap_or_default(); + if !target.ends_with("windows-gnu") && !target.ends_with("windows-gnullvm") { + return; + } + let root = std::path::PathBuf::from(std::env::var_os("CARGO_MANIFEST_DIR").unwrap()) + .join("../../build"); + let object = std::path::PathBuf::from(std::env::var_os("OUT_DIR").unwrap()) + .join("normal-user-manifest.o"); + let compiler = std::env::var_os("RC").unwrap_or_else(|| { + if target.ends_with("gnullvm") { + "llvm-windres" + } else { + "windres" + } + .into() + }); + let result = std::process::Command::new(compiler) + .current_dir(root) + .args(["-i", "windows.rc", "-O", "coff", "-o"]) + .arg(&object) + .status() + .expect("Windows GNU resource compiler is required (set RC if not on PATH)"); + assert!( + result.success(), + "Cannot compile the normal-user Windows manifest" + ); + println!("cargo:rustc-link-arg={}", object.display()); +} diff --git a/crates/oa-core/build.rs b/crates/oa-core/build.rs new file mode 100644 index 00000000..f182d7d0 --- /dev/null +++ b/crates/oa-core/build.rs @@ -0,0 +1 @@ +include!("../../build/windows_manifest.rs"); diff --git a/crates/oa-core/src/language_nesting_tests.rs b/crates/oa-core/src/language_nesting_tests.rs index cac29fe6..11df0b3b 100644 --- a/crates/oa-core/src/language_nesting_tests.rs +++ b/crates/oa-core/src/language_nesting_tests.rs @@ -402,6 +402,37 @@ fn checked_in_source_project() -> SourceBundle { } } +fn assert_architecture_example_warnings(compiled: &crate::source_project::CompiledSourceProject) { + // This architecture-only example intentionally has no process/scenario + // context. Preserve its explicit incompleteness instead of inventing work. + let expected: std::collections::BTreeSet<_> = [ + ("unspecified_exchange_mechanism", "oa_report_data"), + ("unspecified_exchange_mechanism", "sa_report_data"), + ("work_without_behavior_context", "report_distress"), + ("work_without_behavior_context", "assess_incident"), + ("work_without_behavior_context", "transmit_report"), + ("work_without_behavior_context", "assess_report"), + ("work_without_behavior_context", "review_response"), + ("exchange_without_behavior_context", "distress_signal"), + ("exchange_without_behavior_context", "validated_report"), + ("capability_without_behavior_context", "protect_life"), + ("capability_without_behavior_context", "coordinate_response"), + ] + .into_iter() + .map(|(code, alias)| (code, Some(identity(&compiled.decoded.project, alias)))) + .collect(); + let actual: std::collections::BTreeSet<_> = compiled + .diagnostics + .iter() + .map(|issue| { + assert_eq!(issue.diagnostic.severity, validation::Severity::Warning); + (issue.diagnostic.code.as_str(), issue.diagnostic.object_id) + }) + .collect(); + assert_eq!(actual, expected); + assert_eq!(compiled.diagnostics.len(), expected.len()); +} + #[test] fn checked_in_nested_example_has_stable_ids_typed_exchanges_and_three_clear_views() { let input = checked_in_source_project(); @@ -428,15 +459,7 @@ fn checked_in_nested_example_has_stable_ids_typed_exchanges_and_three_clear_view let reopened = compile_bundle(&compiled.bundle, ProjectLimits::default()).unwrap(); assert_eq!(reopened.bundle, compiled.bundle); assert!(reopened.decoded.migration.is_none()); - assert_eq!(compiled.diagnostics.len(), 2); - assert!( - compiled - .diagnostics - .iter() - .all(|issue| issue.diagnostic.code == "unspecified_exchange_mechanism"), - "{:?}", - compiled.diagnostics - ); + assert_architecture_example_warnings(&compiled); let project = &compiled.decoded.project; assert_eq!(project.diagrams.len(), 3); let assess = identity(project, "assess_report"); @@ -495,15 +518,7 @@ fn checked_in_example_supports_cross_file_reparent_without_changing_allocations( ProjectLimits::default(), ) .unwrap(); - assert_eq!(result.diagnostics.len(), 2); - assert!( - result - .diagnostics - .iter() - .all(|issue| issue.diagnostic.code == "unspecified_exchange_mechanism"), - "{:?}", - result.diagnostics - ); + assert_architecture_example_warnings(&result); assert_eq!( result.index.declarations[&assess].path, "sa/assurance.arcadia" diff --git a/crates/oa-core/src/projection/state.rs b/crates/oa-core/src/projection/state.rs index cc9e55b7..1419b033 100644 --- a/crates/oa-core/src/projection/state.rs +++ b/crates/oa-core/src/projection/state.rs @@ -53,7 +53,12 @@ pub fn state_header_height(p: &Project, element: Id) -> f32 { if *source == element) }) .count(); - 64. + if actions { 28. } else { 0. } + internal as f32 * 56. + let typed_actions = p + .relations + .values() + .filter(|r| r.kind() == RelationKind::StateAction && r.source() == element) + .count(); + 64. + if actions { 28. } else { 0. } + typed_actions as f32 * 28. + internal as f32 * 56. } pub(crate) fn node_size(p: &Project, e: &Element, size: Size, collapsed: bool) -> Size { diff --git a/crates/oa-core/src/semantic/mutate.rs b/crates/oa-core/src/semantic/mutate.rs index bc31f624..9f8ff260 100644 --- a/crates/oa-core/src/semantic/mutate.rs +++ b/crates/oa-core/src/semantic/mutate.rs @@ -350,10 +350,7 @@ pub(crate) fn write_fact( else { return Err("Target has no message annotations".into()); }; - values.retain(|v| AnnotationKey::from(v) != *key); - if let Some(row) = row { - values.push(row); - } + replace_keyed_row(values, row, |v| AnnotationKey::from(v) == *key); Ok(()) } FacetReview::Binding { keys, review } => { @@ -374,10 +371,7 @@ pub(crate) fn write_fact( else { return Err("Target has no scenario bindings".into()); }; - bindings.retain(|v| v.referenced != key); - if let Some(row) = row { - bindings.push(row); - } + replace_keyed_row(bindings, row, |v| v.referenced == key); Ok(()) } FacetReview::Child { keys, review } => { @@ -467,6 +461,27 @@ pub(crate) fn write_fact( } } +// Reviewing an unordered row does not authorize changing its authored position. +// Preserve surviving entries in place; only genuinely new rows append. +fn replace_keyed_row( + rows: &mut Vec, + row: Option, + matches: impl FnMut(&T) -> bool, +) { + match (rows.iter().position(matches), row) { + (Some(index), Some(row)) => { + if rows[index] != row { + rows[index] = row; + } + } + (Some(index), None) => { + rows.remove(index); + } + (None, Some(row)) => rows.push(row), + (None, None) => {} + } +} + pub(crate) fn constructor_contains(group: &FacetSelector, facet: &FacetSelector) -> bool { use FacetSelector as F; match group { diff --git a/crates/oa-core/src/validation.rs b/crates/oa-core/src/validation.rs index f6d24f1a..8987acc1 100644 --- a/crates/oa-core/src/validation.rs +++ b/crates/oa-core/src/validation.rs @@ -2,6 +2,8 @@ use crate::*; use serde::{Deserialize, Serialize}; use std::collections::{BTreeMap, BTreeSet}; +mod coverage; + #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub enum Severity { Error, @@ -179,6 +181,7 @@ fn check_ids(out: &mut Vec, owner: Id, field: &str, ids: &[Id]) { pub fn validate(p: &Project) -> Vec { let mut out = Vec::new(); + let coverage = coverage::Index::new(p); if p.schema_version != SCHEMA_VERSION { error( &mut out, @@ -346,19 +349,15 @@ pub fn validate(p: &Project) -> Vec { ); } if matches!(e.kind(), Kind::Capability | Kind::SystemCapability) - && !p.relations.values().any(|r| { - matches!( - r.kind(), - RelationKind::CapabilityInvolvement | RelationKind::ChainInvolvement - ) && r.target() == e.id - }) + && !coverage.grouping.contains(&e.id) + && !coverage.supported_capabilities.contains(&e.id) { warning( &mut out, e.id, "unsupported_capability", "involvement", - "Connect the capability to involved participants, work, or a process/chain.", + "Connect the capability to involved participants, work, a process/chain, or an explicit scenario context.", ); } if let Some(steps) = e.steps() { @@ -921,6 +920,9 @@ pub fn validate(p: &Project) -> Vec { ); } } + if !out.iter().any(|issue| issue.severity == Severity::Error) { + coverage.validate(p, &mut out); + } out } fn validate_sequence(p: &Project, e: &Element, steps: &[Id], out: &mut Vec) { diff --git a/crates/oa-core/src/validation/coverage.rs b/crates/oa-core/src/validation/coverage.rs new file mode 100644 index 00000000..1e9a2003 --- /dev/null +++ b/crates/oa-core/src/validation/coverage.rs @@ -0,0 +1,283 @@ +//! Advisory presence of explicit behavior evidence, never inferred execution. +use super::*; + +#[derive(Default)] +pub(super) struct Index { + pub grouping: BTreeSet, + pub supported_capabilities: BTreeSet, + contextual_capabilities: BTreeSet, + supported_missions: BTreeSet, +} + +impl Index { + pub fn new(p: &Project) -> Self { + let mut index = Self::default(); + for element in p.elements.values() { + if let Some(parent) = element.parent.and_then(|id| p.elements.get(&id)) + && parent.kind() == element.kind() + && parent.layer() == element.layer() + { + index.grouping.insert(parent.id); + } + if let Some(SharedBody::Scenario { + context: Some(id), .. + }) = element.shared() + && p.elements.get(id).is_some_and(|context| { + matches!(context.kind(), Kind::Capability | Kind::SystemCapability) + && context.layer() == element.layer() + }) + { + index.contextual_capabilities.insert(*id); + index.supported_capabilities.insert(*id); + } + } + for relation in p.relations.values() { + let (Some(source), Some(target)) = ( + p.elements.get(&relation.source()), + p.elements.get(&relation.target()), + ) else { + continue; + }; + if source.layer() != target.layer() + || !relation.kind().permits(source.kind(), target.kind()) + { + continue; + } + match relation.kind() { + RelationKind::CapabilityInvolvement => { + index.supported_capabilities.insert(target.id); + } + RelationKind::ChainInvolvement => { + index.supported_capabilities.insert(target.id); + index.contextual_capabilities.insert(target.id); + } + RelationKind::MissionCapability => { + index.supported_missions.insert(source.id); + } + _ => {} + } + } + index + } + + pub fn validate(&self, p: &Project, out: &mut Vec) { + let (work, exchanges) = included_evidence(p); + for element in p.elements.values() { + if element.kind().is_work() + && !self.grouping.contains(&element.id) + && !work.contains(&element.id) + { + warning( + out, + element.id, + "work_without_behavior_context", + "", + "Include this leaf activity/function in a process/chain or scenario through a work action or exchange occurrence.", + ); + } + if matches!(element.kind(), Kind::OperationalMission | Kind::Mission) + && !self.supported_missions.contains(&element.id) + { + warning( + out, + element.id, + "mission_without_capability", + "", + "Connect this mission to a capability that delivers it.", + ); + } + if matches!(element.kind(), Kind::Capability | Kind::SystemCapability) + && !self.grouping.contains(&element.id) + && self.supported_capabilities.contains(&element.id) + && !self.contextual_capabilities.contains(&element.id) + { + warning( + out, + element.id, + "capability_without_behavior_context", + "", + "Add an explicit behavior context: link a process/chain with Chain involvement, or set a scenario's Context to this capability.", + ); + } + } + for relation in p.relations.values().filter(|r| work_exchange(r.kind())) { + let mut non_leaf = false; + for (field, endpoint) in [("source", relation.source()), ("target", relation.target())] + { + if self.grouping.contains(&endpoint) { + non_leaf = true; + warning( + out, + relation.id, + "non_leaf_work_exchange", + field, + "Choose the leaf activity/function responsible for this exchange; keep any typed port owned by that function.", + ); + } + } + if !non_leaf && !exchanges.contains(&relation.id) { + warning( + out, + relation.id, + "exchange_without_behavior_context", + "", + "Use this exchange in a process/chain or scenario.", + ); + } + } + } +} + +fn work_exchange(kind: RelationKind) -> bool { + matches!( + kind, + RelationKind::Interaction | RelationKind::FunctionalExchange + ) +} + +/// Only ordered, owned occurrences contribute. Each identity is visited once, +/// including reused containers; this remains bounded even on malformed drafts. +fn included_evidence(p: &Project) -> (BTreeSet, BTreeSet) { + let mut pending: Vec<_> = p + .elements + .values() + .filter(|e| { + matches!( + e.kind(), + Kind::Process | Kind::FunctionalChain | Kind::Scenario + ) + }) + .map(|e| e.id) + .collect(); + let mut seen = BTreeSet::new(); + let mut work = BTreeSet::new(); + let mut exchanges = BTreeSet::new(); + while let Some(id) = pending.pop() { + if !seen.insert(id) { + continue; + } + let Some(element) = p.elements.get(&id) else { + continue; + }; + if let Some(steps) = element.steps() { + pending.extend( + steps + .iter() + .filter(|step| { + p.elements.get(step).is_some_and(|child| { + child.kind() == Kind::Occurrence + && child.parent == Some(id) + && child.layer() == element.layer() + }) + }) + .copied(), + ); + } + let Some(SharedBody::Occurrence { body, .. }) = element.shared() else { + continue; + }; + match body { + OccurrenceBody::Action { + action: Some(action), + .. + } => { + if p.elements + .get(action) + .is_some_and(|e| e.kind().is_work() && e.layer() == element.layer()) + { + work.insert(*action); + } + } + OccurrenceBody::Exchange { + exchange: Some(exchange), + .. + } => { + if let Some(relation) = p.relations.get(exchange) + && work_exchange(relation.kind()) + && [relation.source(), relation.target()].iter().all(|id| { + p.elements + .get(id) + .is_some_and(|e| e.kind().is_work() && e.layer() == element.layer()) + }) + { + exchanges.insert(*exchange); + work.extend([relation.source(), relation.target()]); + } + } + OccurrenceBody::Fragment { branches, .. } => { + pending.extend( + branches + .iter() + .filter(|branch| { + p.elements.get(branch).is_some_and(|child| { + child.parent == Some(id) + && child.layer() == element.layer() + && matches!( + child.shared(), + Some(SharedBody::Occurrence { + body: OccurrenceBody::Branch { .. }, + .. + }) + ) + }) + }) + .copied(), + ); + } + OccurrenceBody::SequenceReference { + sequence: Some(sequence), + } => { + if let Some(owner) = element.parent.and_then(|id| p.elements.get(&id)) + && matches!(owner.kind(), Kind::Process | Kind::FunctionalChain) + && p.elements.get(sequence).is_some_and(|target| { + target.kind() == owner.kind() && target.layer() == element.layer() + }) + { + pending.push(*sequence); + } + } + OccurrenceBody::ScenarioUse { + scenario: Some(scenario), + .. + } if p.elements.get(scenario).is_some_and(|target| { + target.kind() == Kind::Scenario && target.layer() == element.layer() + }) => + { + pending.push(*scenario); + } + _ => {} + } + } + (work, exchanges) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn malformed_self_reuse_is_bounded_even_without_structural_gate() { + let mut p = Project::empty(); + let scenario = p + .add_element(Kind::Scenario, Layer::Oa, "Self reuse", None) + .unwrap(); + let occurrence = p + .add_element(Kind::Occurrence, Layer::Oa, "Cycle", Some(scenario)) + .unwrap(); + if let Some(SharedBody::Occurrence { body, .. }) = + p.elements.get_mut(&occurrence).unwrap().shared_mut() + { + *body = OccurrenceBody::ScenarioUse { + scenario: Some(scenario), + bindings: vec![], + }; + } + p.elements + .get_mut(&scenario) + .unwrap() + .steps_mut() + .unwrap() + .push(occurrence); + assert_eq!(included_evidence(&p), (BTreeSet::new(), BTreeSet::new())); + } +} diff --git a/crates/oa-core/tests/coverage_diagnostics.rs b/crates/oa-core/tests/coverage_diagnostics.rs new file mode 100644 index 00000000..6dc1ea18 --- /dev/null +++ b/crates/oa-core/tests/coverage_diagnostics.rs @@ -0,0 +1,582 @@ +use oa_core::{ + language, + source_project::*, + validation::{self, Severity}, + *, +}; +use std::collections::{BTreeMap, BTreeSet}; + +const CODES: [&str; 5] = [ + "non_leaf_work_exchange", + "work_without_behavior_context", + "exchange_without_behavior_context", + "mission_without_capability", + "capability_without_behavior_context", +]; +type Located = (String, Id, Option); +fn coverage(p: &Project) -> BTreeSet { + let issues = validation::validate(p); + assert!( + !issues.iter().any(|i| i.severity == Severity::Error), + "{issues:?}" + ); + issues + .into_iter() + .filter(|i| CODES.contains(&i.code.as_str())) + .map(|i| { + assert_eq!(i.severity, Severity::Warning); + (i.code, i.element.unwrap(), i.field) + }) + .collect() +} +fn located(code: &str, id: Id, field: &str) -> Located { + (code.into(), id, (!field.is_empty()).then(|| field.into())) +} +fn add(p: &mut Project, kind: Kind, layer: Layer, name: &str) -> Id { + p.add_element(kind, layer, name, None).unwrap() +} +fn occurrence(p: &mut Project, owner: Id, body: OccurrenceBody) -> Id { + let id = p + .add_element( + Kind::Occurrence, + p.elements[&owner].layer(), + "Contribution", + Some(owner), + ) + .unwrap(); + *p.elements.get_mut(&id).unwrap().shared_mut().unwrap() = SharedBody::Occurrence { + body, + precondition: String::new(), + postcondition: String::new(), + }; + if let Some(steps) = p.elements.get_mut(&owner).unwrap().steps_mut() { + steps.push(id); + } + id +} +fn exchange(id: Id) -> OccurrenceBody { + OccurrenceBody::Exchange { + exchange: Some(id), + sender: None, + receiver: None, + message_kind: MessageKind::Asynchronous, + reply_to: None, + values: vec![], + } +} +fn action(id: Id) -> OccurrenceBody { + OccurrenceBody::Action { + action: Some(id), + lifeline: None, + } +} +fn line(p: &mut Project, scenario: Id, work: Id) -> Id { + let id = p + .add_element( + Kind::Lifeline, + p.elements[&scenario].layer(), + "Work line", + Some(scenario), + ) + .unwrap(); + *p.elements.get_mut(&id).unwrap().shared_mut().unwrap() = SharedBody::Lifeline { + represented: Some(work), + }; + id +} +fn set_context(p: &mut Project, scenario: Id, capability: Id) { + if let Some(SharedBody::Scenario { context, .. }) = + p.elements.get_mut(&scenario).unwrap().shared_mut() + { + *context = Some(capability); + } +} + +#[test] +fn included_process_and_nested_scenario_evidence_is_identity_based_in_both_layers() { + for layer in [Layer::Oa, Layer::Sa] { + let mut p = Project::empty(); + let ( + work_kind, + sequence_kind, + capability_kind, + mission_kind, + performer_kind, + exchange_kind, + ) = if layer == Layer::Oa { + ( + Kind::Activity, + Kind::Process, + Kind::Capability, + Kind::OperationalMission, + Kind::Entity, + RelationKind::Interaction, + ) + } else { + ( + Kind::Function, + Kind::FunctionalChain, + Kind::SystemCapability, + Kind::Mission, + Kind::System, + RelationKind::FunctionalExchange, + ) + }; + let performer = add(&mut p, performer_kind, layer, "Service provider"); + let a = add(&mut p, work_kind, layer, "Request service"); + let b = add(&mut p, work_kind, layer, "Provide service"); + for work in [a, b] { + p.relate(RelationKind::Allocation, "Performed work", performer, work); + } + let e = p.relate(exchange_kind, "Request", a, b); + let inner = add(&mut p, sequence_kind, layer, "Service path"); + let outer = add(&mut p, sequence_kind, layer, "Reusable path"); + occurrence(&mut p, inner, exchange(e)); + occurrence( + &mut p, + outer, + OccurrenceBody::SequenceReference { + sequence: Some(inner), + }, + ); + let capability = add(&mut p, capability_kind, layer, "Deliver service"); + p.relate( + RelationKind::ChainInvolvement, + "Service context", + outer, + capability, + ); + let mission = add(&mut p, mission_kind, layer, "Serve users"); + p.relate( + RelationKind::MissionCapability, + "Delivered capability", + mission, + capability, + ); + let scenario = add(&mut p, Kind::Scenario, layer, "Conversation"); + let sender = line(&mut p, scenario, a); + let receiver = line(&mut p, scenario, b); + let fragment = occurrence( + &mut p, + scenario, + OccurrenceBody::Fragment { + operator: FragmentOperator::Optional, + branches: vec![], + }, + ); + let branch = occurrence( + &mut p, + fragment, + OccurrenceBody::Branch { + guard: "service_requested".into(), + occurrences: vec![], + }, + ); + if let Some(SharedBody::Occurrence { + body: OccurrenceBody::Fragment { branches, .. }, + .. + }) = p.elements.get_mut(&fragment).unwrap().shared_mut() + { + branches.push(branch); + } + let call = occurrence( + &mut p, + branch, + OccurrenceBody::Exchange { + exchange: Some(e), + sender: Some(sender), + receiver: Some(receiver), + message_kind: MessageKind::Call, + reply_to: None, + values: vec![], + }, + ); + occurrence( + &mut p, + branch, + OccurrenceBody::Exchange { + exchange: Some(e), + sender: Some(receiver), + receiver: Some(sender), + message_kind: MessageKind::Reply, + reply_to: Some(call), + values: vec![], + }, + ); + let reuse = add(&mut p, Kind::Scenario, layer, "Reused conversation"); + let outer_sender = line(&mut p, reuse, a); + let outer_receiver = line(&mut p, reuse, b); + occurrence( + &mut p, + reuse, + OccurrenceBody::ScenarioUse { + scenario: Some(scenario), + bindings: vec![ + LifelineBinding { + referenced: sender, + local: outer_sender, + }, + LifelineBinding { + referenced: receiver, + local: outer_receiver, + }, + ], + }, + ); + assert!(coverage(&p).is_empty()); + let inner_steps = std::mem::take(p.elements.get_mut(&inner).unwrap().steps_mut().unwrap()); + p.elements + .get_mut(&scenario) + .unwrap() + .steps_mut() + .unwrap() + .clear(); + assert_eq!( + coverage(&p), + BTreeSet::from([ + located("work_without_behavior_context", a, ""), + located("work_without_behavior_context", b, ""), + located("exchange_without_behavior_context", e, ""), + ]), + "Unordered fragments and reused containers cannot activate omitted messages" + ); + *p.elements.get_mut(&inner).unwrap().steps_mut().unwrap() = inner_steps; + p.elements + .get_mut(&scenario) + .unwrap() + .steps_mut() + .unwrap() + .push(fragment); + assert!(coverage(&p).is_empty()); + // Removing every message leaves lifelines and reuse intact: neither is + // an execution claim. Empty sequences remain saveable, with old warnings. + for element in p.elements.values_mut() { + if let Some(SharedBody::Occurrence { + body: OccurrenceBody::Exchange { .. }, + .. + }) = element.shared() + { + *element.shared_mut().unwrap() = SharedBody::Occurrence { + body: OccurrenceBody::Action { + action: None, + lifeline: None, + }, + precondition: String::new(), + postcondition: String::new(), + }; + } + } + assert_eq!( + coverage(&p), + BTreeSet::from([ + located("work_without_behavior_context", a, ""), + located("work_without_behavior_context", b, ""), + located("exchange_without_behavior_context", e, "") + ]) + ); + occurrence(&mut p, scenario, action(a)); // known work, unfinished lifeline + assert_eq!( + coverage(&p), + BTreeSet::from([ + located("work_without_behavior_context", b, ""), + located("exchange_without_behavior_context", e, "") + ]) + ); + assert_eq!(language::parse(&language::format(&p)).unwrap(), p); + } +} + +#[test] +fn incomplete_oa_repairs_exact_records_and_ordered_membership_without_inference() { + let mut p = Project::empty(); + let performer = add(&mut p, Kind::Entity, Layer::Oa, "Crew"); + let parent = add(&mut p, Kind::Activity, Layer::Oa, "Manage service"); + let a = p + .add_element(Kind::Activity, Layer::Oa, "Prepare", Some(parent)) + .unwrap(); + let b = p + .add_element(Kind::Activity, Layer::Oa, "Deliver", Some(parent)) + .unwrap(); + let c = add(&mut p, Kind::Activity, Layer::Oa, "Verify"); + for work in [a, b, c] { + p.relate(RelationKind::Allocation, "Responsibility", performer, work); + } + let ab = p.relate(RelationKind::Interaction, "Prepared service", a, b); + let pc = p.relate(RelationKind::Interaction, "Grouped service", parent, c); + let ca = p.relate(RelationKind::Interaction, "Verified service", c, a); + let process = add(&mut p, Kind::Process, Layer::Oa, "Delivery path"); + occurrence(&mut p, process, exchange(ab)); + let mission = add( + &mut p, + Kind::OperationalMission, + Layer::Oa, + "Deliver reliably", + ); + let capability = add(&mut p, Kind::Capability, Layer::Oa, "Reliable delivery"); + p.relate( + RelationKind::CapabilityInvolvement, + "Crew support", + performer, + capability, + ); + p.relate( + RelationKind::MissionInvolvement, + "Mission responsibility", + performer, + mission, + ); + let machine = p + .add_element(Kind::StateMachine, Layer::Oa, "Crew modes", Some(performer)) + .unwrap(); + let region = p + .add_element(Kind::Region, Layer::Oa, "Crew region", Some(machine)) + .unwrap(); + let state = p + .add_element(Kind::State, Layer::Oa, "Available", Some(region)) + .unwrap(); + p.relate(RelationKind::StateAction, "Verification entry", state, c); + let observation = add(&mut p, Kind::Scenario, Layer::Oa, "Observation only"); + let observed = line(&mut p, observation, c); + occurrence( + &mut p, + observation, + OccurrenceBody::State { + state: Some(state), + lifeline: Some(observed), + }, + ); + let expected = BTreeSet::from([ + located("non_leaf_work_exchange", pc, "source"), + located("work_without_behavior_context", c, ""), + located("exchange_without_behavior_context", ca, ""), + located("mission_without_capability", mission, ""), + located("capability_without_behavior_context", capability, ""), + ]); + assert_eq!(coverage(&p), expected); + let unordered = occurrence(&mut p, process, exchange(ca)); + p.elements + .get_mut(&process) + .unwrap() + .steps_mut() + .unwrap() + .retain(|id| *id != unordered); + assert_eq!(coverage(&p), expected); + assert!( + validation::validate(&p) + .iter() + .any(|i| i.element == Some(unordered) && i.code == "unordered_occurrence") + ); + let before = p.clone(); + p.elements + .get_mut(&process) + .unwrap() + .steps_mut() + .unwrap() + .push(unordered); + if let RelationBody::Interaction { source, .. } = &mut p.relations.get_mut(&pc).unwrap().body { + *source = a; + } + occurrence(&mut p, process, exchange(pc)); + p.relate( + RelationKind::MissionCapability, + "Mission delivery", + mission, + capability, + ); + p.relate( + RelationKind::ChainInvolvement, + "Capability path", + process, + capability, + ); + assert!(coverage(&p).is_empty()); + // Public session admission and undo retain the previous exact warning set. + let mut session = Session::new(before.clone()); + session + .execute(Command::Batch(diff_commands(&before, &p))) + .unwrap(); + assert!(coverage(session.project()).is_empty()); + session.undo(); + assert_eq!(session.project(), &before); + assert_eq!(coverage(session.project()), expected); +} + +#[test] +fn non_leaf_exchange_locations_use_work_endpoints_including_typed_sa_ports() { + for layer in [Layer::Oa, Layer::Sa] { + let mut p = Project::empty(); + let (kind, relation_kind) = if layer == Layer::Oa { + (Kind::Activity, RelationKind::Interaction) + } else { + (Kind::Function, RelationKind::FunctionalExchange) + }; + let source = add(&mut p, kind, layer, "Source group"); + let target = add(&mut p, kind, layer, "Target group"); + let a = p + .add_element(kind, layer, "Source leaf", Some(source)) + .unwrap(); + let b = p + .add_element(kind, layer, "Target leaf", Some(target)) + .unwrap(); + let edge = p.relate(relation_kind, "Decompose exchange", source, target); + assert_eq!( + coverage(&p), + BTreeSet::from([ + located("non_leaf_work_exchange", edge, "source"), + located("non_leaf_work_exchange", edge, "target"), + located("work_without_behavior_context", a, ""), + located("work_without_behavior_context", b, ""), + ]) + ); + if let RelationBody::FunctionalExchange { + source_port, + target_port, + .. + } = p.relations[&edge].body + { + assert_eq!(p.elements[&source_port].parent, Some(source)); + assert_eq!(p.elements[&target_port].parent, Some(target)); + assert!( + coverage(&p) + .iter() + .all(|(_, id, _)| *id != source_port && *id != target_port) + ); + } + } +} + +#[test] +fn grouping_and_scenario_only_capabilities_do_not_duplicate_support_warnings() { + let mut p = Project::empty(); + let parent = add(&mut p, Kind::Capability, Layer::Oa, "Capability group"); + let leaf = p + .add_element(Kind::Capability, Layer::Oa, "Leaf capability", Some(parent)) + .unwrap(); + let issues = validation::validate(&p); + assert!( + issues + .iter() + .any(|i| i.element == Some(leaf) && i.code == "unsupported_capability") + ); + assert!( + !issues + .iter() + .any(|i| i.element == Some(parent) && i.code.contains("capability")) + ); + assert!(coverage(&p).is_empty()); + let scenario = add(&mut p, Kind::Scenario, Layer::Oa, "Capability scenario"); + set_context(&mut p, scenario, leaf); + let issues = validation::validate(&p); + assert!(!issues.iter().any(|i| i.code == "unsupported_capability")); + assert!( + issues + .iter() + .any(|i| i.element == Some(scenario) && i.code == "empty_sequence") + ); + assert!(coverage(&p).is_empty()); +} + +#[test] +fn structural_errors_suppress_completeness_cascades_and_rejection_keeps_the_model() { + let mut p = Project::empty(); + let a = add(&mut p, Kind::Activity, Layer::Oa, "Request"); + let b = add(&mut p, Kind::Activity, Layer::Oa, "Respond"); + let e = p.relate(RelationKind::Interaction, "Request data", a, b); + let process = add(&mut p, Kind::Process, Layer::Oa, "Path"); + let step = occurrence(&mut p, process, exchange(e)); + assert!(coverage(&p).is_empty()); + let mut bad = p.clone(); + if let RelationBody::Interaction { source, .. } = &mut bad.relations.get_mut(&e).unwrap().body { + *source = Id::new_v4(); + } + let mut wrong_owner = p.clone(); + wrong_owner.elements.get_mut(&step).unwrap().parent = Some(a); + let mut recursive = p.clone(); + let scenario = add(&mut recursive, Kind::Scenario, Layer::Oa, "Cycle"); + occurrence( + &mut recursive, + scenario, + OccurrenceBody::ScenarioUse { + scenario: Some(scenario), + bindings: vec![], + }, + ); + for (candidate, expected_error) in [ + (bad, "invalid_endpoints"), + (wrong_owner, "invalid_occurrence"), + (recursive, "recursive_scenario_use"), + ] { + let issues = validation::validate(&candidate); + assert!( + issues + .iter() + .any(|i| i.severity == Severity::Error && i.code == expected_error), + "{issues:?}" + ); + assert!( + !issues.iter().any(|i| CODES.contains(&i.code.as_str())), + "{issues:?}" + ); + let mut session = Session::new(p.clone()); + assert!( + session + .execute(Command::Batch(diff_commands(&p, &candidate))) + .is_err() + ); + assert_eq!(session.project(), &p); + assert_eq!(session.revision(), 0); + } +} + +#[test] +fn multifile_lint_preserves_core_codes_ids_and_actual_owning_source_paths() { + let source = SourceBundle { entry: "project.arcadia".into(), files: BTreeMap::from([ + ("project.arcadia".into(), "arcadia 5; project p \"Coverage\" { include \"work.arcadia\"; include \"exchanges.arcadia\"; }".into()), + ("work.arcadia".into(), "oa_activity a \"Request\" {}\noa_activity b \"Respond\" {}".into()), + ("exchanges.arcadia".into(), "interaction e \"Data\" { source @a; target @b; }".into()), + ]) }; + let compiled = compile_bundle(&source, ProjectLimits::default()).unwrap(); + let expected = coverage(&compiled.decoded.project); + assert_eq!(expected.len(), 3); + let diagnostics: Vec<_> = lint_bundle(&compiled.bundle, ProjectLimits::default()) + .into_iter() + .filter(|i| CODES.contains(&i.diagnostic.code.as_str())) + .collect(); + let actual: BTreeSet<_> = diagnostics + .iter() + .map(|i| { + assert_eq!(i.diagnostic.severity, Severity::Warning); + let id = i.diagnostic.object_id.unwrap(); + assert_eq!(i.path, compiled.index.declarations[&id].path); + assert!(i.diagnostic.span.end > i.diagnostic.span.start); + (i.diagnostic.code.clone(), id, i.diagnostic.field.clone()) + }) + .collect(); + assert_eq!(actual, expected); + assert_eq!(diagnostics.len(), expected.len()); +} + +// Authoring-equivalent complete record edits keep the test independent of a +// private completeness helper and exercise actual transaction/undo boundaries. +fn diff_commands(before: &Project, after: &Project) -> Vec { + let mut commands = Vec::new(); + for (id, element) in &after.elements { + match before.elements.get(id) { + None => commands.push(Command::CreateElement(element.clone())), + Some(old) if old != element => { + commands.push(Command::EditElement(*id, element.clone())) + } + _ => {} + } + } + for (id, relation) in &after.relations { + match before.relations.get(id) { + None => commands.push(Command::CreateRelation(relation.clone())), + Some(old) if old != relation => { + commands.push(Command::EditRelation(*id, relation.clone())) + } + _ => {} + } + } + commands +} diff --git a/crates/oa-core/tests/r3_semantic.rs b/crates/oa-core/tests/r3_semantic.rs index 1e0ef9fe..dbe6f548 100644 --- a/crates/oa-core/tests/r3_semantic.rs +++ b/crates/oa-core/tests/r3_semantic.rs @@ -642,6 +642,11 @@ fn atomic_annotation_full_path_updates_only_selected_row() { let rows = values_mut(&mut q, b.message); assert_eq!(rows.len(), 2); assert!(rows.contains(&retained)); + assert_eq!(AnnotationKey::from(&rows[0]), b.first); + assert_eq!( + rows[1], retained, + "Unselected rows retain their authored position" + ); assert!( rows.iter() .any(|v| AnnotationKey::from(v) == b.first && v.expression == "new first") @@ -651,6 +656,73 @@ fn atomic_annotation_full_path_updates_only_selected_row() { .any(|v| AnnotationKey::from(v) == b.second && v.expression == retained.expression) ); } + +#[test] +fn keeping_keyed_rows_with_an_unrelated_header_candidate_preserves_exact_authored_order() { + for binding in [false, true] { + for reverse in [false, true] { + let mut p = Project::empty(); + let (oa, sa, selector, references) = if binding { + let a = scenario_use(&mut p, Layer::Oa, "oa"); + let b = scenario_use(&mut p, Layer::Sa, "sa"); + ( + a.use_id, + b.use_id, + FacetSelector::Binding(KeyPair { + oa: Some(a.referenced), + sa: Some(b.referenced), + }), + BTreeMap::from([(a.referenced, b.referenced)]), + ) + } else { + let a = payload(&mut p, Layer::Oa, "oa"); + let b = payload(&mut p, Layer::Sa, "sa"); + let mut references: BTreeMap<_, _> = a + .first + .path + .iter() + .copied() + .zip(b.first.path.iter().copied()) + .collect(); + references.insert(a.leaf, b.leaf); + ( + a.message, + b.message, + FacetSelector::Annotation(KeyPair { + oa: Some(a.first), + sa: Some(b.first), + }), + references, + ) + }; + trace(&mut p, oa, sa); + let (source, target) = if reverse { (sa, oa) } else { (oa, sa) }; + let mut candidate = p.elements[&target].clone(); + candidate.description = "Review the header while retaining authored rows".into(); + let mut review = request( + source, + target, + vec![ + (selector, SemanticDecision::KeepTarget), + ( + FacetSelector::Header(HeaderFacet::Description), + SemanticDecision::Candidate, + ), + ], + ); + if reverse { + review.transition.direction = TransitionDirection::SaToOa; + } + review.references = references; + review + .candidates + .push(SemanticRecord::Element(candidate.clone())); + let accepted = applied(&p, review); + assert_eq!(accepted.elements[&target], candidate); + assert_eq!(accepted.elements[&source], p.elements[&source]); + } + } +} #[test] fn atomic_binding_updates_local_and_rejects_pair_collision_and_remapping_without_reset() { let mut p = Project::empty(); @@ -671,6 +743,8 @@ fn atomic_binding_updates_local_and_rejects_pair_collision_and_remapping_without r.references = BTreeMap::from([(a.referenced, b.referenced), (a.alternate, b.alternate)]); let mut q = applied(&p, r.clone()); assert!(bindings_mut(&mut q, b.use_id).contains(&retained)); + assert_eq!(bindings_mut(&mut q, b.use_id)[0].referenced, b.referenced); + assert_eq!(bindings_mut(&mut q, b.use_id)[1], retained); assert!( bindings_mut(&mut q, b.use_id) .iter() diff --git a/crates/oa-desktop/Cargo.toml b/crates/oa-desktop/Cargo.toml index d7fa0fdb..2eed9a7c 100644 --- a/crates/oa-desktop/Cargo.toml +++ b/crates/oa-desktop/Cargo.toml @@ -17,10 +17,10 @@ serde.workspace = true serde_json.workspace = true reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] } tokio = { version = "1", features = ["rt", "time"] } +tempfile = "3" [dev-dependencies] iced_runtime = "0.14" oa-server = { path = "../oa-server" } axum = { version = "0.8", default-features = false, features = ["http1", "json", "tokio"] } -tempfile = "3" tokio = { version = "1", features = ["macros", "rt-multi-thread", "net", "sync"] } diff --git a/crates/oa-desktop/build.rs b/crates/oa-desktop/build.rs new file mode 100644 index 00000000..f182d7d0 --- /dev/null +++ b/crates/oa-desktop/build.rs @@ -0,0 +1 @@ +include!("../../build/windows_manifest.rs"); diff --git a/crates/oa-desktop/src/app.rs b/crates/oa-desktop/src/app.rs index 2564a670..acafdf04 100644 --- a/crates/oa-desktop/src/app.rs +++ b/crates/oa-desktop/src/app.rs @@ -1,3 +1,4 @@ +mod checkpoints; mod hosted; use crate::forms::Draft; use crate::{ @@ -260,6 +261,7 @@ pub struct RecoverySnapshot { #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Page { + Home, Diagram, Catalog, Behavior, @@ -281,6 +283,8 @@ impl std::fmt::Display for Choice { } #[derive(Debug, Clone, Copy)] pub enum Pending { + Recent, + Recover(Id), HostedOpen, New, Demo, @@ -301,6 +305,10 @@ pub struct FilePrompt { #[derive(Debug, Clone)] pub enum Message { + Home(crate::home::Event), + CheckpointTick, + CheckpointWritten(Id, Result<(), String>), + CheckpointLoaded(Result, String>), Hosted(crate::hosted::Event), PortAnchor(crate::port_edit::Event), Connection(crate::connection_edit::Event), @@ -413,6 +421,10 @@ pub enum Message { } pub struct App { + pub home: crate::home::State, + pub recovery: crate::recovery::State, + recent_target: Option, + checkpoint_close: Option, pub hosted: crate::hosted::State, pub connection: Option, pub model_menu: Option, @@ -424,6 +436,8 @@ pub struct App { pub exact_strings: bool, pub document: Document, pub page: Page, + /// Home resumes this page; explicit Architecture navigation remains separate. + last_workspace_page: Page, pub guide: crate::guide::State, pub behavior: crate::behavior::State, pub data_state: crate::data_view::State, @@ -473,7 +487,11 @@ pub struct App { impl App { pub fn new() -> Self { - let mut app = Self::with_project(Project::demo()); + let mut app = Self::with_project(Project::empty()); + app.home = crate::home::State::load_default(); + app.recovery = crate::recovery::State::load_default(); + app.page = Page::Home; + app.status = "Create a model, open a project or explore the example.".into(); app.fit(); app } @@ -502,7 +520,11 @@ impl App { let draft = Draft::from_project(&project, selected); let explorer_index = crate::explorer::Index::new(&project, layer); let explorer_collapsed = project.elements.values().filter_map(|e| e.parent).collect(); - Self { + let mut app = Self { + home: Default::default(), + recovery: Default::default(), + recent_target: None, + checkpoint_close: None, connection: None, model_menu: None, source_tree: Default::default(), @@ -513,6 +535,7 @@ impl App { exact_strings: false, document, page: Page::Diagram, + last_workspace_page: Page::Diagram, guide: crate::guide::State::default(), behavior: Default::default(), data_state: Default::default(), @@ -562,7 +585,10 @@ impl App { explorer_group: None, explorer_page: 0, workflow: None, - } + }; + app.route_state_diagram_workspace(); + app.last_workspace_page = app.page; + app } pub fn project(&self) -> &Project { self.document.project() @@ -586,6 +612,7 @@ impl App { } pub fn dirty(&self) -> bool { self.document.dirty() + || self.workflow.is_some() || self.draft_dirty() || self.source_dirty() || self.behavior.has_pending() @@ -844,6 +871,9 @@ impl App { } } fn reset(&mut self, project: Project, path: Option) { + self.retire_checkpoint(); + let home = std::mem::take(&mut self.home); + let recovery = std::mem::take(&mut self.recovery); let window_size = self.window_size; let canvas_size = self.canvas_size; let source_version = self.source_version + 1; @@ -852,6 +882,8 @@ impl App { let mut hosted = std::mem::take(&mut self.hosted); hosted.leave_document(); *self = Self::with_project(project); + self.home = home; + self.recovery = recovery; self.hosted = hosted; self.window_size = window_size; self.canvas_size = canvas_size; @@ -923,6 +955,7 @@ impl App { } pub fn subscription(&self) -> Subscription { Subscription::batch([ + iced::time::every(std::time::Duration::from_secs(10)).map(|_| Message::CheckpointTick), window::close_requests().map(|id| Message::Request(Pending::Close(id))), window::resize_events().map(|(_, size)| Message::WindowResized(size)), iced::event::listen_with(|event, status, _| { @@ -992,6 +1025,9 @@ impl App { && !matches!( message, Message::Menu(_) + | Message::CheckpointWritten(..) + | Message::CheckpointLoaded(..) + | Message::CheckpointTick | Message::WindowResized(_) | Message::CanvasResized(_) | Message::Lint(_) @@ -1030,7 +1066,9 @@ impl App { if self.busy && !matches!( message, - Message::Saved(_) + Message::CheckpointWritten(..) + | Message::CheckpointLoaded(..) + | Message::Saved(_) | Message::Hosted(crate::hosted::Event::Completed(..)) | Message::Loaded(_) | Message::IncludesLoaded(_, _) @@ -1073,7 +1111,10 @@ impl App { } let modal_event = matches!( message, - Message::WindowResized(_) + Message::CheckpointWritten(..) + | Message::CheckpointTick + | Message::CheckpointLoaded(..) + | Message::WindowResized(_) | Message::CanvasResized(_) | Message::SourceViewport | Message::Lint(_) @@ -1145,7 +1186,14 @@ impl App { let previous_layer = self.layer; let entering_catalog = self.page != Page::Catalog && matches!(message, Message::Page(Page::Catalog)); + if self.page != Page::Home { + self.last_workspace_page = self.page; + } let mut task = self.handle(message); + self.route_state_diagram_workspace(); + if self.page != Page::Home { + self.last_workspace_page = self.page; + } if let Some(context) = self.topology_context() { self.behavior .topology @@ -1206,6 +1254,16 @@ impl App { } fn handle(&mut self, message: Message) -> Task { match message { + Message::Home(event) => return self.home_event(event), + Message::CheckpointTick => return self.checkpoint_tick(), + Message::CheckpointWritten(id, result) => { + self.checkpoint_written(id, result); + if let Some(window) = self.checkpoint_close.take() { + self.retire_checkpoint(); + return window::close(window); + } + } + Message::CheckpointLoaded(result) => return self.checkpoint_loaded(result), Message::Hosted(event) => return self.hosted_event(event), Message::ScenarioCanvas(event) => { use crate::behavior::scenario::{self, CanvasEvent}; @@ -1600,6 +1658,27 @@ impl App { } Message::Page(page) => { if page != Page::Text || !self.draft_dirty() || self.apply() { + if page == Page::Diagram + && self.active_diagram.is_some_and(|id| { + self.project() + .diagrams + .get(&id) + .is_some_and(|d| d.projection.state_machine().is_some()) + }) + { + // Architecture remains reachable from a state workspace. + // Its picker can still open a saved state view explicitly. + self.active_diagram = self + .project() + .diagrams + .values() + .filter(|d| { + d.layer == self.layer && d.projection.state_machine().is_none() + }) + .min_by_key(|d| (d.projection != ProjectionKind::Allocation, d.id)) + .map(|d| d.id); + self.fit(); + } if page == Page::Behavior && self.page != Page::Behavior { let previous_state_view = self.state_view_identity(); if let Some(owner) = @@ -2545,7 +2624,9 @@ impl App { self.pending_recovery = None; } if receipt.snapshot.bundle.entry != self.document.bundle().entry { - if receipt.revision != self.document.revision() { + if receipt.revision != self.document.revision() + || self.source_dirty() + { self.status = format!( "Snapshot saved to {}. Newer edits retain the current source location.", receipt.path.display() @@ -2553,8 +2634,9 @@ impl App { self.continue_after_save = false; return Task::none(); } - if let Err(error) = - self.document.apply_bundle(receipt.snapshot.bundle.clone()) + if let Err(error) = self + .document + .adopt_saved_entry(&receipt.snapshot, receipt.revision) { self.status = error.to_string(); self.continue_after_save = false; @@ -2568,6 +2650,10 @@ impl App { self.hosted.review = None; self.hosted.uncertain = None; self.document.mark_saved(receipt.snapshot); + self.remember_local(); + if !self.dirty() { + self.retire_checkpoint(); + } self.prune_closed_files(!self.document.dirty()); self.status = "All source files saved".into(); } else { @@ -2608,6 +2694,10 @@ impl App { match result { Ok(Some(LoadedSnapshot::Legacy(path, decoded, _bytes))) => { self.reset(decoded.project, None); + let name = self.project().name.clone(); + if let Err(error) = self.home.remember(&path, &name) { + self.home.notice = Some(error); + } self.migration = decoded.migration; if self.migration.is_some() { self.status = "Imported earlier schema · review migration details in Model checks before saving".into(); @@ -2905,6 +2995,11 @@ impl App { fn proceed(&mut self, pending: Pending) -> Task { self.pending = None; match pending { + Pending::Recent => self + .recent_target + .take() + .map_or_else(Task::none, |path| self.load_from(path)), + Pending::Recover(id) => self.load_checkpoint(id), Pending::HostedOpen => self.open_hosted(), Pending::New => { self.reset(Project::empty(), None); @@ -2915,7 +3010,16 @@ impl App { Task::none() } Pending::Open => self.choose_file(FileOperation::Open), - Pending::Close(id) => window::close(id), + Pending::Close(id) => { + self.retire_checkpoint(); + if self.recovery.writing { + self.checkpoint_close = Some(id); + self.busy = true; + Task::none() + } else { + window::close(id) + } + } } } fn choose_file(&mut self, operation: FileOperation) -> Task { @@ -2990,6 +3094,25 @@ impl App { ) } fn save_to(&mut self, path: PathBuf) -> Task { + if !path + .extension() + .is_some_and(|ext| ext.eq_ignore_ascii_case("json")) + { + let result = path + .file_name() + .and_then(|name| name.to_str()) + .ok_or_else(|| "Choose a portable .arcadia entry file name.".to_owned()) + .and_then(|entry| { + self.document + .check_save_entry(entry) + .map_err(|e| e.to_string()) + }); + if let Err(error) = result { + self.status = error; + self.continue_after_save = false; + return Task::none(); + } + } self.busy = true; let snapshot = self.document.snapshot(); let document = self.canvas_document; @@ -3084,6 +3207,9 @@ impl App { } self.pending_recovery = pending_recovery; self.disk_project = Some(disk); + if self.source_workspace.applied { + self.remember_local(); + } } fn prune_closed_files(&mut self, prune_expectations: bool) { @@ -3731,6 +3857,10 @@ fn read_snapshot(path: PathBuf) -> Result { )) } +#[cfg(test)] +#[path = "app/save_as_tests.rs"] +mod save_as_tests; + #[cfg(test)] mod source_project_tests { use super::*; diff --git a/crates/oa-desktop/src/app/checkpoints.rs b/crates/oa-desktop/src/app/checkpoints.rs new file mode 100644 index 00000000..73b39ac0 --- /dev/null +++ b/crates/oa-desktop/src/app/checkpoints.rs @@ -0,0 +1,479 @@ +//! Home navigation and asynchronous private checkpoints share the normal guards. +use super::*; +use crate::{home, recovery}; + +impl App { + pub(super) fn remember_local(&mut self) { + if let Some(path) = self.path.clone() { + let name = self.project().name.clone(); + if let Err(error) = self.home.remember(&path, &name) { + self.home.notice = Some(error); + } + } + } + pub(super) fn home_event(&mut self, event: home::Event) -> Task { + match event { + home::Event::New => self.handle(Message::Request(Pending::New)), + home::Event::Open => self.handle(Message::Request(Pending::Open)), + home::Event::Example => self.handle(Message::Request(Pending::Demo)), + home::Event::Guide => self.update(Message::Page(Page::Guide)), + home::Event::Resume => self.update(Message::Page(self.last_workspace_page)), + home::Event::OpenRecent(path) => { + self.recent_target = Some(path); + self.handle(Message::Request(Pending::Recent)) + } + home::Event::Recover(id) => self.handle(Message::Request(Pending::Recover(id))), + home::Event::Forget(path) => { + if let Err(error) = self.home.forget(&path) { + self.home.notice = Some(error); + } + Task::none() + } + home::Event::Refresh => { + self.home.refresh(); + self.recovery.refresh(); + self.recovery.entries.retain(|entry| { + entry.id != self.canvas_document && Some(entry.id) != self.recovery.restored + }); + Task::none() + } + } + } + pub(super) fn checkpoint_tick(&mut self) -> Task { + if self.recovery.writing || self.busy { + return Task::none(); + } + if !self.dirty() { + self.retire_checkpoint(); + return Task::none(); + } + let Some(directory) = self.recovery.directory.clone() else { + return Task::none(); + }; + let id = self.canvas_document; + let snapshot = match recovery::Snapshot::capture( + id, + &self.document, + &self.source_workspace, + self.draft.as_ref(), + &self.behavior, + self.path.clone(), + self.selected, + ) + .and_then(|snapshot| snapshot.with_workflow(self.workflow.as_ref())) + { + Ok(snapshot) => snapshot, + Err(error) => { + self.recovery.warning = Some(error); + return Task::none(); + } + }; + self.recovery.writing = true; + Task::perform( + async move { + tokio::task::spawn_blocking(move || recovery::write(&directory, &snapshot)) + .await + .map_err(|e| e.to_string())? + }, + move |result| Message::CheckpointWritten(id, result), + ) + } + pub(super) fn checkpoint_written(&mut self, id: Id, result: Result<(), String>) { + self.recovery.writing = false; + match result { + Ok(()) if id == self.canvas_document && self.dirty() => { + self.recovery.active = Some(id); + } + Ok(()) => { + if let Some(directory) = &self.recovery.directory + && let Err(error) = recovery::remove(directory, id) + { + self.recovery.warning = Some(error); + } + } + Err(error) => { + self.recovery.warning = Some(format!( + "Recovery checkpoint failed: {error}. Save your work explicitly." + )) + } + } + } + pub(super) fn retire_checkpoint(&mut self) { + let Some(directory) = &self.recovery.directory else { + return; + }; + for id in [self.recovery.active.take(), self.recovery.restored.take()] + .into_iter() + .flatten() + { + if let Err(error) = recovery::remove(directory, id) { + self.recovery.warning = Some(error); + } else { + self.recovery.entries.retain(|e| e.id != id); + } + } + } + pub(super) fn load_checkpoint(&mut self, id: Id) -> Task { + let Some(directory) = self.recovery.directory.clone() else { + return Task::none(); + }; + self.busy = true; + Task::perform( + async move { + tokio::task::spawn_blocking(move || recovery::read(&directory, id).map(Arc::new)) + .await + .map_err(|e| e.to_string())? + }, + Message::CheckpointLoaded, + ) + } + pub(super) fn checkpoint_loaded( + &mut self, + result: Result, String>, + ) -> Task { + self.busy = false; + let snapshot = match result { + Ok(s) => s, + Err(error) => { + self.status = error; + return Task::none(); + } + }; + let id = snapshot.id; + let restored = match (*snapshot).clone().restore() { + Ok(r) => r, + Err(error) => { + self.status = error; + return Task::none(); + } + }; + // Fully prepare recovery before touching the currently accepted document. + self.reset(restored.document.project().clone(), None); + self.hosted.leave_document(); + self.document = restored.document; + self.selected = if self.project().layer_of(restored.selected).is_some() + || restored.selected == self.project().id + { + restored.selected + } else { + self.project().id + }; + // Installing the raw workspace must follow all accepted-source synchronization. + self.source_workspace = restored.workspace; + self.show_source_file(false); + self.refresh_draft(); + if let Some(draft) = restored.property { + self.selected = draft.original; + self.exact_strings = draft.exact_strings; + self.draft = Some(draft); + } + self.behavior = restored.behavior; + self.workflow = restored.workflow; + let focus = self.behavior.pending_owner().unwrap_or(self.selected); + if let Some(layer) = self.project().layer_of(focus) { + self.layer = layer; + self.explorer_index = crate::explorer::Index::new(self.project(), layer); + self.active_diagram = self.diagram_for(layer, focus); + } + self.recovery.restored = Some(id); + self.recovery.entries.retain(|entry| entry.id != id); + self.page = if self.behavior.has_pending() { + Page::Behavior + } else if self.source_dirty() { + Page::Text + } else { + Page::Diagram + }; + self.status="Recovered an unsaved local copy. Choose Save as to save it; the original file and hosted model are unchanged.".into(); + self.request_lint() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn home_resume_returns_to_each_actual_workspace_without_model_edits() { + let mut app = App::with_project(Project::demo()); + let accepted = app.document.snapshot(); + for page in [ + Page::Diagram, + Page::Catalog, + Page::Behavior, + Page::Data, + Page::Traces, + Page::Text, + Page::Validation, + Page::Guide, + ] { + let _ = app.update(Message::Page(page)); + assert_eq!(app.page, page); + let selected = app.selected; + let _ = app.update(Message::Page(Page::Home)); + assert_eq!(app.last_workspace_page, page); + // Repeated Home events must not replace the remembered workspace. + let _ = app.update(Message::Page(Page::Home)); + let _ = app.update(Message::Home(home::Event::Resume)); + assert_eq!(app.page, page); + assert_eq!(app.selected, selected); + assert!(Arc::ptr_eq(&accepted, &app.document.snapshot())); + } + } + + #[test] + fn home_resume_keeps_state_view_while_explicit_architecture_remains_reachable() { + let mut project = Project::empty(); + let owner = project + .add_element(Kind::System, Layer::Sa, "System", None) + .unwrap(); + let machine = project + .add_element(Kind::StateMachine, Layer::Sa, "Modes", Some(owner)) + .unwrap(); + let plan = crate::behavior::state_edit::create_view(&project, machine).unwrap(); + project = prepare_command(&project, &plan.command).unwrap().project; + let mut app = App::with_project(project); + assert_eq!(app.page, Page::Behavior); + let accepted = app.document.snapshot(); + let _ = app.update(Message::Page(Page::Home)); + let _ = app.update(Message::Home(home::Event::Resume)); + assert_eq!(app.page, Page::Behavior); + assert_eq!(app.state_context().unwrap().diagram, plan.diagram); + assert_eq!(app.behavior.active_owner, Some(machine)); + let _ = app.update(Message::Page(Page::Diagram)); + assert_eq!(app.page, Page::Diagram); + assert!(app.active_diagram.is_none()); + let _ = app.update(Message::Page(Page::Home)); + let _ = app.update(Message::Home(home::Event::Resume)); + assert_eq!(app.page, Page::Diagram); + assert!(Arc::ptr_eq(&accepted, &app.document.snapshot())); + } + + #[test] + fn home_resume_retains_invalid_source_and_property_drafts() { + let mut app = App::with_project(Project::demo()); + let accepted = app.document.snapshot(); + let _ = app.update(Message::Page(Page::Text)); + let raw = format!("{}\nunfinished draft", app.source_workspace.text()); + app.source_workspace + .edit(raw.clone(), app.source_editor.cursor()); + let _ = app.update(Message::Page(Page::Home)); + let _ = app.update(Message::Home(home::Event::Resume)); + assert_eq!(app.page, Page::Text); + assert_eq!(app.source_workspace.text(), raw); + assert!(Arc::ptr_eq(&accepted, &app.document.snapshot())); + + let mut app = App::with_project(Project::demo()); + let accepted = app.document.snapshot(); + let _ = app.update(Message::Page(Page::Data)); + let _ = app.update(Message::Field("/id".into(), "-".into())); + let raw = serde_json::to_value(app.draft.as_ref().unwrap().recovery_snapshot()).unwrap(); + assert!(app.draft_dirty()); + let _ = app.update(Message::Page(Page::Home)); + let _ = app.update(Message::Home(home::Event::Resume)); + assert_eq!(app.page, Page::Data); + assert_eq!( + serde_json::to_value(app.draft.as_ref().unwrap().recovery_snapshot()).unwrap(), + raw + ); + assert!(Arc::ptr_eq(&accepted, &app.document.snapshot())); + } + + fn retained_snapshot(app: &App) -> recovery::Snapshot { + recovery::Snapshot::capture( + app.canvas_document, + &app.document, + &app.source_workspace, + app.draft.as_ref(), + &app.behavior, + app.path.clone(), + app.selected, + ) + .unwrap() + } + + #[test] + fn saving_an_older_snapshot_keeps_recovery_for_newer_unsaved_work() { + let directory = tempfile::tempdir().unwrap(); + let mut app = App::with_project(Project::demo()); + app.recovery.directory = Some(directory.path().join("recovery")); + let id = app.project().id; + app.execute(Command::Rename(id, "Saved milestone".into())); + let path = directory.path().join(&app.document.bundle().entry); + let receipt = write_snapshot( + app.canvas_document, + app.document.revision(), + app.document.snapshot(), + path, + None, + ) + .unwrap(); + app.execute(Command::Rename(id, "Later unsaved engineering".into())); + let snapshot = retained_snapshot(&app); + let recovery_directory = app.recovery.directory.clone().unwrap(); + recovery::write(&recovery_directory, &snapshot).unwrap(); + app.recovery.active = Some(app.canvas_document); + let _ = app.update(Message::Saved(Ok(Some(receipt)))); + assert!(app.dirty()); + assert_eq!(app.project().name, "Later unsaved engineering"); + assert_eq!(app.recovery.active, Some(app.canvas_document)); + let restored = recovery::read(&recovery_directory, app.canvas_document) + .unwrap() + .restore() + .unwrap(); + assert_eq!( + restored.document.project().name, + "Later unsaved engineering" + ); + } + + #[test] + fn checkpoint_completion_releases_the_writer_while_a_context_menu_is_open() { + let directory = tempfile::tempdir().unwrap(); + let mut app = App::with_project(Project::demo()); + app.recovery.directory = Some(directory.path().into()); + let id = app.project().id; + app.execute(Command::Rename(id, "Unsaved engineering".into())); + let snapshot = retained_snapshot(&app); + recovery::write(directory.path(), &snapshot).unwrap(); + app.recovery.writing = true; + let _ = app.update(Message::Menu(crate::model_menu::Event::Open( + crate::model_menu::Request::object(id, iced::Point::ORIGIN), + ))); + assert!(app.model_menu.is_some()); + let _ = app.update(Message::CheckpointWritten(app.canvas_document, Ok(()))); + assert!( + !app.recovery.writing, + "A passive completion cannot be swallowed by an open menu" + ); + assert_eq!(app.recovery.active, Some(app.canvas_document)); + assert!( + app.model_menu.is_some(), + "Saving a checkpoint does not dismiss the menu" + ); + assert_eq!(app.project().name, "Unsaved engineering"); + } + + #[test] + fn discard_and_close_waits_for_the_late_checkpoint_then_retires_it() { + let directory = tempfile::tempdir().unwrap(); + let mut app = App::with_project(Project::demo()); + app.recovery.directory = Some(directory.path().into()); + app.execute(Command::Rename( + app.project().id, + "Discarded on close".into(), + )); + let snapshot = retained_snapshot(&app); + let document = app.canvas_document; + app.recovery.writing = true; + let window = window::Id::unique(); + let _ = app.update(Message::Request(Pending::Close(window))); + assert!(matches!(app.pending, Some(Pending::Close(id)) if id == window)); + let closing = app.update(Message::Discard); + assert!( + iced_runtime::task::into_stream(closing).is_none(), + "The window must remain until its outstanding writer finishes" + ); + assert_eq!(app.checkpoint_close, Some(window)); + assert!(app.busy); + recovery::write(directory.path(), &snapshot).unwrap(); + assert!(recovery::read(directory.path(), document).is_ok()); + let close = app.update(Message::CheckpointWritten(document, Ok(()))); + assert!( + iced_runtime::task::into_stream(close).is_some(), + "Close is emitted after checkpoint cleanup" + ); + assert!(app.checkpoint_close.is_none()); + assert!(recovery::read(directory.path(), document).is_err()); + assert!(app.recovery.active.is_none()); + } + #[test] + fn recent_and_recovery_navigation_respect_unsaved_decision() { + let mut app = App::with_project(Project::demo()); + let id = app.project().id; + app.execute(Command::Rename(id, "Retained work".into())); + let _ = app.update(Message::Home(home::Event::OpenRecent(PathBuf::from( + "missing.arcadia", + )))); + assert!(matches!(app.pending, Some(Pending::Recent))); + assert!(!app.busy); + assert_eq!(app.project().name, "Retained work"); + let _ = app.update(Message::Cancel); + let recovery_id = Id::new_v4(); + let _ = app.update(Message::Home(home::Event::Recover(recovery_id))); + assert!(matches!(app.pending,Some(Pending::Recover(id)) if id==recovery_id)); + assert!(!app.busy); + } + #[test] + fn restored_copy_keeps_raw_property_and_requires_save_without_local_or_hosted_binding() { + let mut app = App::with_project(Project::demo()); + let selected = app.project().id; + app.draft + .as_mut() + .unwrap() + .input("/name", "Not yet applied".into()); + let snapshot = recovery::Snapshot::capture( + Id::new_v4(), + &app.document, + &app.source_workspace, + app.draft.as_ref(), + &app.behavior, + Some(PathBuf::from("original.arcadia")), + selected, + ) + .unwrap(); + let checkpoint_id = snapshot.id; + app.recovery.entries.push(recovery::Entry { + id: checkpoint_id, + name: "Retained work".into(), + origin: None, + modified: snapshot.modified, + }); + let _ = app.checkpoint_loaded(Ok(Arc::new(snapshot))); + assert!(app.path.is_none()); + assert!(app.disk_project.is_none()); + assert!(app.hosted.session.is_none()); + assert!(app.document.dirty()); + assert!(app.draft_dirty()); + assert_eq!(app.recovery.restored, Some(checkpoint_id)); + assert!( + app.recovery.entries.is_empty(), + "Home must not offer the copy currently being recovered again" + ); + assert_eq!( + app.draft.as_ref().unwrap().inputs["/name"].raw, + "Not yet applied" + ); + assert_ne!(app.project().name, "Not yet applied"); + } + #[test] + fn stale_write_completion_cannot_recover_a_discarded_document() { + let directory = tempfile::tempdir().unwrap(); + let mut app = App::with_project(Project::demo()); + app.recovery.directory = Some(directory.path().into()); + let old = app.canvas_document; + let s = recovery::Snapshot::capture( + old, + &app.document, + &app.source_workspace, + None, + &app.behavior, + None, + app.selected, + ) + .unwrap(); + app.reset(Project::empty(), None); + recovery::write(directory.path(), &s).unwrap(); + app.checkpoint_written(old, Ok(())); + assert!(recovery::read(directory.path(), old).is_err()); + assert!(app.recovery.active.is_none()); + } + #[test] + fn malformed_checkpoint_does_not_replace_the_current_model() { + let mut app = App::with_project(Project::demo()); + let original = app.document.bundle().clone(); + let _ = app.checkpoint_loaded(Err("Damaged checkpoint".into())); + assert_eq!(app.document.bundle(), &original); + assert!(app.status.contains("Damaged")); + } +} diff --git a/crates/oa-desktop/src/app/save_as_tests.rs b/crates/oa-desktop/src/app/save_as_tests.rs new file mode 100644 index 00000000..c6ddab7d --- /dev/null +++ b/crates/oa-desktop/src/app/save_as_tests.rs @@ -0,0 +1,114 @@ +use super::*; + +fn complete_save(app: &mut App, path: PathBuf) { + let receipt = write_snapshot( + app.canvas_document, + app.document.revision(), + app.document.snapshot(), + path, + app.disk_project.clone(), + ) + .unwrap(); + let _ = app.update(Message::Saved(Ok(Some(receipt)))); +} + +#[test] +fn save_as_receipt_then_one_undo_restores_meaning_and_saving_preserves_redo() { + let directory = tempfile::tempdir().unwrap(); + let path = directory.path().join("semantic-accepted.arcadia"); + let mut app = App::with_project(Project::empty()); + app.document.mark_recovered(); + let original = app.document.bundle().files[&app.document.bundle().entry].clone(); + app.execute(Command::Rename( + app.project().id, + "Accepted semantic change".into(), + )); + let after = app.document.bundle().files[&app.document.bundle().entry].clone(); + complete_save(&mut app, path.clone()); + assert!(!app.dirty()); + assert_eq!(app.path.as_ref(), Some(&path)); + assert_eq!(std::fs::read_to_string(&path).unwrap(), after); + let _ = app.update(Message::Undo); + assert_eq!(app.document.bundle().entry, "semantic-accepted.arcadia"); + assert_eq!( + app.document.bundle().files[&app.document.bundle().entry], + original + ); + assert!( + !app.document.can_undo(), + "Save as did not insert a history step" + ); + assert!(app.document.can_redo()); + complete_save(&mut app, path.clone()); + assert_eq!(std::fs::read_to_string(&path).unwrap(), original); + assert!( + app.document.can_redo(), + "Saving an undone state preserves the future" + ); + let _ = app.update(Message::Redo); + assert_eq!( + app.document.bundle().files[&app.document.bundle().entry], + after + ); + assert!(app.dirty()); + assert_eq!( + source_project::compile_bundle(app.document.bundle(), LIMITS) + .unwrap() + .decoded + .project, + *app.project() + ); +} + +#[test] +fn save_as_preflight_rejects_historical_collision_before_starting_io() { + let directory = tempfile::tempdir().unwrap(); + let path = directory.path().join("taken.arcadia"); + let mut app = App::with_project(Project::empty()); + let base = app.document.bundle().clone(); + let mut included = base.clone(); + let text = included.files.get_mut(&included.entry).unwrap(); + text.insert_str(text.rfind('}').unwrap(), "\n include \"taken.arcadia\";\n"); + included + .files + .insert("taken.arcadia".into(), "// retained\n".into()); + app.document.apply_bundle(included).unwrap(); + app.document.apply_bundle(base).unwrap(); + app.sync_source(); + let before = app.document.snapshot(); + let task = app.save_to(path.clone()); + assert!(iced_runtime::task::into_stream(task).is_none()); + assert!(!path.exists()); + assert!(!app.busy); + assert!(app.status.contains("Undo/Redo")); + assert!(Arc::ptr_eq(&before, &app.document.snapshot())); + let json = app.save_to(directory.path().join("taken.json")); + assert!( + iced_runtime::task::into_stream(json).is_some(), + "JSON exports do not relocate source entries" + ); +} + +#[test] +fn save_as_late_receipt_preserves_newer_raw_source_draft_and_destination() { + let directory = tempfile::tempdir().unwrap(); + let mut app = App::with_project(Project::empty()); + let entry = app.document.bundle().entry.clone(); + let receipt = write_snapshot( + app.canvas_document, + app.document.revision(), + app.document.snapshot(), + directory.path().join("accepted.arcadia"), + None, + ) + .unwrap(); + let raw = format!("{}\nunfinished {{", app.source_workspace.text()); + let cursor = app.source_workspace.files[&entry].cursor; + app.source_workspace.edit(raw.clone(), cursor); + let _ = app.update(Message::Saved(Ok(Some(receipt)))); + assert_eq!(app.source_workspace.text(), raw); + assert_eq!(app.document.bundle().entry, entry); + assert!(app.path.is_none()); + assert!(app.dirty()); + assert!(app.status.contains("Newer edits")); +} diff --git a/crates/oa-desktop/src/app/state_diagram.rs b/crates/oa-desktop/src/app/state_diagram.rs index 63e9cfe3..bf6cd7bc 100644 --- a/crates/oa-desktop/src/app/state_diagram.rs +++ b/crates/oa-desktop/src/app/state_diagram.rs @@ -6,6 +6,41 @@ use crate::behavior::{ }; impl App { + /// A saved state view has one renderer and tool set: the behavior workspace. + /// Run after navigation/adoption, never as a model or property edit. Keeping + /// selection and its Draft intact also preserves unrelated raw properties. + pub(super) fn route_state_diagram_workspace(&mut self) { + if self.page != Page::Diagram { + return; + } + let Some((diagram, machine, layer)) = self.active_diagram.and_then(|id| { + let view = self.project().diagrams.get(&id)?; + Some((id, view.projection.state_machine()?, view.layer)) + }) else { + return; + }; + // Existing navigation guards own pending behavior forms. Never replace + // their owner/camera if an exceptional caller leaves one on another page. + if self.behavior.has_pending() { + return; + } + self.layer = layer; + self.page = Page::Behavior; + self.behavior.active_owner = Some(machine); + self.behavior.diagram = behavior::state_view::State { + diagram: Some(diagram), + placement: self.project().diagrams[&diagram] + .nodes + .values() + .find(|node| node.id == self.selected || node.element == self.selected) + .map(|node| node.id), + ..Default::default() + }; + self.selected_node = None; + self.canvas_epoch = self.canvas_epoch.wrapping_add(1); + self.fit_state_diagram(); + } + /// The behavior view resolved independently of the current page. Capture /// this before selection/layer changes, while it still identifies the view /// whose camera and tool are retained during navigation. diff --git a/crates/oa-desktop/src/app/state_diagram_tests.rs b/crates/oa-desktop/src/app/state_diagram_tests.rs index 47a69842..0be1b787 100644 --- a/crates/oa-desktop/src/app/state_diagram_tests.rs +++ b/crates/oa-desktop/src/app/state_diagram_tests.rs @@ -89,6 +89,153 @@ fn assert_source_model_agree(app: &App) { assert!(!app.source_dirty()); } +#[test] +fn state_diagram_routes_constructor_and_source_open_to_the_existing_workspace_without_edits() { + let mut f = fixture(); + let diagram = create_view(&mut f).diagram; + let project = f.app.project().clone(); + let app = App::with_project(project.clone()); + assert_eq!(app.page, Page::Behavior); + assert_eq!(app.state_context().unwrap().diagram, diagram); + assert_eq!(behavior::selected_owner(&app), Some(f.machine)); + assert_eq!(app.behavior.diagram.tool, Tool::Select); + assert_eq!(app.selected, project.id); + assert_eq!(app.project(), &project); + assert!(!app.document.can_undo()); + + let directory = tempfile::tempdir().unwrap(); + let path = directory.path().join("state-view.arcadia"); + let text = format!( + "// Exact authored source must remain unchanged.\n{}", + language::format(&project) + ); + std::fs::write(&path, &text).unwrap(); + let mut reopened = App::with_project(Project::empty()); + send( + &mut reopened, + Message::Loaded(Ok(Some(read_snapshot(path.clone()).unwrap()))), + ); + assert_eq!(reopened.page, Page::Behavior); + assert_eq!(reopened.state_context().unwrap().diagram, diagram); + assert_eq!(reopened.project(), &project); + assert_eq!( + reopened.document.bundle().files[&reopened.document.bundle().entry], + text + ); + assert_eq!(std::fs::read_to_string(path).unwrap(), text); + assert!(!reopened.document.can_undo()); + assert_source_model_agree(&reopened); +} + +#[test] +fn state_diagram_routes_picker_and_reveal_but_explicit_architecture_does_not_bounce() { + let mut f = fixture(); + let diagram = create_view(&mut f).diagram; + let mut project = f.app.project().clone(); + let second = state_edit::create_view(&project, f.machine).unwrap(); + project = prepare_command(&project, &second.command).unwrap().project; + let transition = project.relate(RelationKind::StateTransition, "Switch", f.a, f.b); + let mut app = App::with_project(project.clone()); + let before = app.document.snapshot(); + send(&mut app, Message::Page(Page::Diagram)); + assert_eq!(app.page, Page::Diagram); + assert_eq!(app.active_diagram, None); + send( + &mut app, + Message::Diagram(Choice { + id: second.diagram, + label: "Second state view".into(), + }), + ); + assert_eq!(app.page, Page::Behavior); + assert_eq!(app.state_context().unwrap().diagram, second.diagram); + send(&mut app, Message::RevealInDiagram(diagram, transition)); + assert_eq!(app.page, Page::Behavior); + assert_eq!(app.state_context().unwrap().diagram, diagram); + assert_eq!(app.selected, transition); + assert_eq!(app.draft.as_ref().unwrap().original, transition); + assert!(Arc::ptr_eq(&before, &app.document.snapshot())); + assert_source_model_agree(&app); + + let architecture = project.ensure_diagram(Layer::Sa); + let mut app = App::with_project(project); + send( + &mut app, + Message::Diagram(Choice { + id: diagram, + label: "State view".into(), + }), + ); + assert_eq!(app.page, Page::Behavior); + send(&mut app, Message::Page(Page::Diagram)); + assert_eq!(app.page, Page::Diagram); + assert_eq!(app.active_diagram, Some(architecture)); + assert!(app.state_context().is_none()); + assert!(!app.document.can_undo()); +} + +#[test] +fn state_diagram_routes_preserve_selected_raw_properties_and_pending_behavior_guards() { + let mut f = fixture(); + let diagram = create_view(&mut f).diagram; + let mut app = App::with_project(f.app.project().clone()); + send(&mut app, Message::Select(f.a)); + send( + &mut app, + Message::Field("/alias".into(), "invalid alias!".into()), + ); + let raw = serde_json::to_value(app.draft.as_ref().unwrap().recovery_snapshot()).unwrap(); + assert!(!app.apply()); + let before = app.document.snapshot(); + // Exercise normalization itself with an existing raw draft, including callers + // that have just adopted a view without a successful property Apply. + app.page = Page::Diagram; + app.route_state_diagram_workspace(); + assert_eq!(app.page, Page::Behavior); + assert_eq!(app.selected, f.a); + assert_eq!( + serde_json::to_value(app.draft.as_ref().unwrap().recovery_snapshot()).unwrap(), + raw + ); + assert!(Arc::ptr_eq(&before, &app.document.snapshot())); + send( + &mut app, + Message::Diagram(Choice { + id: diagram, + label: "State view".into(), + }), + ); + assert_eq!( + serde_json::to_value(app.draft.as_ref().unwrap().recovery_snapshot()).unwrap(), + raw + ); + assert!(Arc::ptr_eq(&before, &app.document.snapshot())); + + let mut app = App::with_project(f.app.project().clone()); + form( + &mut app, + states::Event::Start(f.machine, states::Tool::Transition, f.a), + ); + form( + &mut app, + states::Event::Name("Pending behavior stays".into()), + ); + let pending = app.behavior.recovery_snapshot().unwrap(); + let context = app.state_context().unwrap(); + send(&mut app, Message::Page(Page::Diagram)); + send( + &mut app, + Message::Diagram(Choice { + id: diagram, + label: "State view".into(), + }), + ); + assert_eq!(app.page, Page::Behavior); + assert_eq!(app.state_context().unwrap().diagram, context.diagram); + assert_eq!(app.behavior.recovery_snapshot().unwrap(), pending); + assert!(!app.document.can_undo()); +} + struct NavigationFixture { app: App, machine_a: Id, @@ -789,12 +936,15 @@ fn fallback_owner_first_selection_keeps_the_captured_context_for_its_first_move( let f = fixture(); let mut project = f.app.project().clone(); // One eligible machine makes the fallback deterministic. Start from a loaded - // project with no selected behavior element and no explicitly chosen owner. + // architecture view with no selected behavior element or chosen owner. A + // state-only project now opens its saved view with an explicit owner. project .elements .retain(|id, element| f.members.contains(id) || element.kind() == Kind::System); let plan = state_edit::create_view(&project, f.machine).unwrap(); - let project = prepare_command(&project, &plan.command).unwrap().project; + let mut project = prepare_command(&project, &plan.command).unwrap().project; + let architecture = project.ensure_diagram(Layer::Sa); + project.diagrams.get_mut(&architecture).unwrap().projection = ProjectionKind::Allocation; let mut app = App::with_project(project); assert_eq!(app.selected, app.project().id); assert!(app.behavior.active_owner.is_none()); @@ -917,7 +1067,11 @@ fn shared_semantic_states_keep_architecture_and_behavior_placements_independent( id: architecture, label: app.project().named(architecture).into(), }; + // The initial UUID-ordered view may be a state view, which correctly opens + // Behavior. Enter Architecture explicitly before testing its selection. + send(&mut app, Message::Page(Page::Diagram)); send(&mut app, Message::Diagram(architecture_choice)); + assert_eq!(app.page, Page::Diagram); send(&mut app, Message::Select(f.b)); assert_eq!(app.active_diagram, Some(architecture)); assert_eq!(app.selected_node, Some(arch_b)); diff --git a/crates/oa-desktop/src/behavior.rs b/crates/oa-desktop/src/behavior.rs index b122640b..8f63e764 100644 --- a/crates/oa-desktop/src/behavior.rs +++ b/crates/oa-desktop/src/behavior.rs @@ -556,7 +556,72 @@ pub struct State { states: states::State, } const PENDING_DRAFT: &str = "Apply or cancel the current behavior form before starting another."; +#[derive(serde::Serialize, serde::Deserialize)] +#[serde(deny_unknown_fields)] +struct RecoveryDraft { + version: u32, + kind: RecoveryKind, + draft: serde_json::Value, +} +#[derive(serde::Serialize, serde::Deserialize)] +enum RecoveryKind { + State, + Scenario, + Process, +} impl State { + /// Capture only the pending authored form. Pointer gestures, cached choices, + /// viewport geometry and document/revision tokens never cross a restart. + pub fn recovery_snapshot(&self) -> Result, String> { + let drafts = [ + (RecoveryKind::State, self.states.recovery_snapshot()?), + (RecoveryKind::Scenario, self.scenario.recovery_snapshot()?), + (RecoveryKind::Process, self.topology.recovery_snapshot()?), + ]; + let mut drafts = drafts.into_iter().filter_map(|(kind, draft)| { + draft.map(|draft| RecoveryDraft { + version: 1, + kind, + draft, + }) + }); + let first = drafts.next(); + if drafts.next().is_some() { + return Err( + "Several behavior forms are pending; recovery cannot choose one to discard.".into(), + ); + } + first + .map(serde_json::to_value) + .transpose() + .map_err(|e| e.to_string()) + } + + /// Restore raw fields without planning/executing them. Rejected values and + /// incomplete endpoints must remain available for repair after a crash. + pub fn restore_recovery(&mut self, value: serde_json::Value) -> Result<(), String> { + let saved: RecoveryDraft = serde_json::from_value(value).map_err(|e| e.to_string())?; + if saved.version != 1 { + return Err(format!( + "Unsupported behavior recovery version {}", + saved.version + )); + } + let mut restored = Self::default(); + match saved.kind { + RecoveryKind::State => restored.states = states::State::from_recovery(saved.draft)?, + RecoveryKind::Scenario => { + restored.scenario = scenario::State::from_recovery(saved.draft)? + } + RecoveryKind::Process => { + restored.topology = topology::State::from_recovery(saved.draft)? + } + } + restored.active_owner = restored.pending_owner(); + *self = restored; + Ok(()) + } + pub fn has_pending(&self) -> bool { self.pending_owner().is_some() } @@ -2352,6 +2417,147 @@ mod direct_canvas_tests { #[cfg(test)] mod tests { use super::*; + + #[test] + fn recovery_retains_each_incomplete_behavior_form_without_executing_it() { + let p = Project::demo(); + let before = p.clone(); + let owner = |kind| p.elements.values().find(|e| e.kind() == kind).unwrap().id; + let machine = owner(Kind::StateMachine); + let scenario = owner(Kind::Scenario); + let process = owner(Kind::Process); + let cases = [ + ( + machine, + vec![ + Event::States(states::Event::Start( + machine, + states::Tool::Transition, + machine, + )), + Event::States(states::Event::Name("Unfinished timeout".into())), + Event::States(states::Event::Timed(true)), + Event::States(states::Event::Expression("-".into())), + Event::States(states::Event::Guard("temperature >".into())), + ], + ), + ( + scenario, + vec![ + Event::Scenario(scenario::Event::Start(scenario, scenario::Tool::Message)), + Event::Scenario(scenario::Event::Name("Unfinished request".into())), + Event::Scenario(scenario::Event::Expression("-".into())), + ], + ), + ( + process, + vec![ + Event::Topology(topology::Event::Start( + process, + topology::Tool::Connect, + None, + )), + Event::Topology(topology::Event::Name("Unfinished path".into())), + Event::Topology(topology::Event::Guard("available &&".into())), + ], + ), + ]; + for (owner, events) in cases { + let mut original = State::default(); + for event in events { + original.update(&p, event).unwrap(); + } + assert!(original.prepare_pending(&p).is_err()); + let snapshot = original.recovery_snapshot().unwrap().unwrap(); + let bytes = serde_json::to_vec(&snapshot).unwrap(); + let mut recovered = State::default(); + recovered + .restore_recovery(serde_json::from_slice(&bytes).unwrap()) + .unwrap(); + assert_eq!(recovered.pending_owner(), Some(owner)); + assert_eq!(recovered.active_owner, Some(owner)); + assert!( + recovered.prepare_pending(&p).is_err(), + "An incomplete recovered form is not applied or silently repaired" + ); + assert_eq!(recovered.recovery_snapshot().unwrap(), Some(snapshot)); + assert_eq!(p, before); + recovered.acknowledge_commit(); + assert!(recovered.recovery_snapshot().unwrap().is_none()); + } + } + + #[test] + fn recovery_preserves_distinct_qualified_value_paths_and_rejects_invalid_payload_atomically() { + let p = Project::demo(); + let scenario = p + .elements + .values() + .find(|e| e.kind() == Kind::Scenario) + .unwrap() + .id; + let field = Id::new_v4(); + let paths = [Id::new_v4(), Id::new_v4()]; + let mut state = State::default(); + state + .update( + &p, + Event::Scenario(scenario::Event::Start(scenario, scenario::Tool::Message)), + ) + .unwrap(); + for (path, value) in paths.into_iter().zip(["-", "3.4e"]) { + state + .update( + &p, + Event::Scenario(scenario::Event::Value( + crate::data_values::FieldPathKey { + path: vec![path], + field, + }, + value.into(), + )), + ) + .unwrap(); + } + let original = state.recovery_snapshot().unwrap().unwrap(); + let mut recovered = State::default(); + recovered.restore_recovery(original.clone()).unwrap(); + assert_eq!( + recovered.recovery_snapshot().unwrap(), + Some(original.clone()) + ); + let values = original["draft"]["values"].as_array().unwrap(); + assert_eq!(values.len(), 2); + assert_ne!(values[0][0], values[1][0]); + assert_eq!(values[0][1], values[1][1]); + for bad in [ + { + let mut value = original.clone(); + value["version"] = 99.into(); + value + }, + { + let mut value = original.clone(); + value["draft"]["unknown"] = true.into(); + value + }, + { + let mut value = original.clone(); + value["draft"]["values"] + .as_array_mut() + .unwrap() + .push(values[0].clone()); + value + }, + ] { + assert!(recovered.restore_recovery(bad).is_err()); + assert_eq!( + recovered.recovery_snapshot().unwrap(), + Some(original.clone()), + "Malformed recovery cannot discard the currently retained form" + ); + } + } use crate::document::{Document, DocumentError}; use oa_core::source_project::{self, ProjectLimits}; diff --git a/crates/oa-desktop/src/behavior/scenario.rs b/crates/oa-desktop/src/behavior/scenario.rs index d3deb3ba..5a0ff1cc 100644 --- a/crates/oa-desktop/src/behavior/scenario.rs +++ b/crates/oa-desktop/src/behavior/scenario.rs @@ -5,7 +5,7 @@ use super::*; use iced::widget::{Column, column, text_input}; use std::{cell::RefCell, collections::BTreeSet}; -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub enum Tool { Lifeline, Message, @@ -26,7 +26,7 @@ impl Tool { } } } -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub enum MessageStyle { Asynchronous, Call, @@ -160,7 +160,8 @@ struct CallChoiceContext { before: Option, endpoints: Option<(Option, Option)>, } -#[derive(Clone)] +#[derive(Clone, serde::Serialize, serde::Deserialize)] +#[serde(deny_unknown_fields)] struct Draft { owner: Id, parent: Id, @@ -178,8 +179,11 @@ struct Draft { second_guard: String, operator: FragmentOperator, bindings: BTreeMap, + #[serde(with = "recovery_values")] values: BTreeMap, + #[serde(with = "recovery_field_key")] value_focus: Option, + #[serde(with = "recovery_field_key")] value_browse: Option, value_browse_page: usize, value_authored_page: usize, @@ -187,6 +191,22 @@ struct Draft { choice_page: usize, } impl State { + pub(super) fn recovery_snapshot(&self) -> Result, String> { + self.draft + .as_ref() + .map(serde_json::to_value) + .transpose() + .map_err(|e| e.to_string()) + } + + pub(super) fn from_recovery(value: serde_json::Value) -> Result { + let draft = serde_json::from_value(value).map_err(|e| e.to_string())?; + Ok(Self { + draft: Some(draft), + ..Default::default() + }) + } + pub(crate) fn scrolled(&mut self, area: ScrollArea, offset: iced::Vector) -> bool { let index = match area { ScrollArea::Canvas => 0, @@ -436,6 +456,57 @@ impl State { Ok(None) } } + +mod recovery_values { + use super::*; + use serde::{Deserialize, Serialize}; + pub fn serialize( + values: &BTreeMap, + serializer: S, + ) -> Result { + values + .iter() + .map(|(key, value)| (&key.path, key.field, value)) + .collect::>() + .serialize(serializer) + } + pub fn deserialize<'de, D: serde::Deserializer<'de>>( + deserializer: D, + ) -> Result, D::Error> { + let values = Vec::<(Vec, Id, String)>::deserialize(deserializer)?; + let mut result = BTreeMap::new(); + for (path, field, value) in values { + if result + .insert(crate::data_values::FieldPathKey { path, field }, value) + .is_some() + { + return Err(serde::de::Error::custom( + "Duplicate recovered scenario value path", + )); + } + } + Ok(result) + } +} + +mod recovery_field_key { + use super::*; + use serde::{Deserialize, Serialize}; + pub fn serialize( + key: &Option, + serializer: S, + ) -> Result { + key.as_ref() + .map(|key| (&key.path, key.field)) + .serialize(serializer) + } + pub fn deserialize<'de, D: serde::Deserializer<'de>>( + deserializer: D, + ) -> Result, D::Error> { + Ok(Option::<(Vec, Id)>::deserialize(deserializer)? + .map(|(path, field)| crate::data_values::FieldPathKey { path, field })) + } +} fn viewpoint(p: &Project, owner: Id) -> ScenarioViewpoint { match p.elements.get(&owner).and_then(|e| e.shared()) { Some(SharedBody::Scenario { viewpoint, .. }) => *viewpoint, diff --git a/crates/oa-desktop/src/behavior/state_canvas.rs b/crates/oa-desktop/src/behavior/state_canvas.rs index 04c231f3..4565679c 100644 --- a/crates/oa-desktop/src/behavior/state_canvas.rs +++ b/crates/oa-desktop/src/behavior/state_canvas.rs @@ -99,6 +99,7 @@ struct PaintKey { enum Hit { Node { placement: Id, element: Id }, Transition(Id), + StateAction(Id), } impl State { @@ -357,6 +358,63 @@ fn internal_rows<'a>(project: &'a Project, node: &ProjectedNode) -> Vec<(&'a Rel .collect() } +/// These rows represent maintained StateAction relations, not containment of +/// the referenced work. Their identity remains selectable independently. +fn state_action_rows<'a>( + project: &'a Project, + node: &ProjectedNode, +) -> Vec<(&'a Relation, Rectangle)> { + if node.collapsed + || !project + .elements + .get(&node.element) + .is_some_and(|e| e.kind().is_stable_state()) + { + return vec![]; + } + let mut actions: Vec<_> = project.relations.values().filter(|r| matches!(r.body, RelationBody::StateAction { source, .. } if source == node.element)).collect(); + actions.sort_by_key(|r| match r.body { + RelationBody::StateAction { phase, .. } => ( + match phase { + StateActionKind::Entry => 0, + StateActionKind::Do => 1, + StateActionKind::Exit => 2, + }, + r.id, + ), + _ => unreachable!(), + }); + let internal_height = internal_rows(project, node).len() as f32 * 56.; + let top = node.position.y + projection::state_header_height(project, node.element) + - internal_height + - actions.len() as f32 * 28.; + actions + .into_iter() + .enumerate() + .map(|(index, relation)| { + ( + relation, + Rectangle::new( + Point::new(node.position.x + 8., top + index as f32 * 28.), + Size::new((node.size.width - 16.).max(1.), 26.), + ), + ) + }) + .collect() +} + +fn state_action_label(project: &Project, relation: &Relation) -> String { + let RelationBody::StateAction { target, phase, .. } = relation.body else { + return String::new(); + }; + let phase = match phase { + StateActionKind::Entry => "entry", + StateActionKind::Do => "do", + StateActionKind::Exit => "exit", + }; + format!("{phase} / {}", project.named(target)) +} + impl StateCanvas<'_> { fn scale(&self) -> f32 { if self.zoom.is_finite() { @@ -486,6 +544,12 @@ impl StateCanvas<'_> { fn hit(&self, scene: &Scene, world: Point) -> Option { let node = self.node_at(scene, world); if let Some(node) = node { + if let Some((relation, _)) = state_action_rows(self.project, node) + .into_iter() + .find(|(_, r)| r.contains(world)) + { + return Some(Hit::StateAction(relation.id)); + } if let Some((relation, _)) = internal_rows(self.project, node) .into_iter() .find(|(_, r)| r.contains(world)) @@ -643,9 +707,11 @@ impl StateCanvas<'_> { }, None => "Draw transition · click a source, then a compatible target, or drag between vertices".into(), }, - _ => if let Some(Hit::Transition(id)) = world.and_then(|p| self.hit(scene, p)) { - self.project.relations.get(&id).map(|r| format!("{} · {} · right-click to edit", r.name, routing::state_transition_summary(self.project, r))).unwrap_or_default() - } else { "Select / move · drag a vertex · drag selected transition endpoints to reconnect · middle-drag to pan".into() }, + _ => match world.and_then(|p| self.hit(scene, p)) { + Some(Hit::Transition(id)) => self.project.relations.get(&id).map(|r| format!("{} · {} · right-click to edit", r.name, routing::state_transition_summary(self.project, r))).unwrap_or_default(), + Some(Hit::StateAction(id)) => self.project.relations.get(&id).map(|r| format!("{} · select the action link to inspect its referenced work", state_action_label(self.project, r))).unwrap_or_default(), + _ => "Select / move · drag a vertex · drag selected transition endpoints to reconnect · middle-drag to pan".into(), + }, } } } @@ -754,7 +820,7 @@ impl canvas::Program for StateCanvas<'_> { state.reset(); let (object, placement) = match self.hit(&scene, world) { Some(Hit::Node { element, placement }) => (element, Some(placement)), - Some(Hit::Transition(id)) => (id, None), + Some(Hit::Transition(id) | Hit::StateAction(id)) => (id, None), None => (self.diagram, None), }; return Some( @@ -799,7 +865,7 @@ impl canvas::Program for StateCanvas<'_> { .and_capture(), ) } - Some(Hit::Transition(id)) => { + Some(Hit::Transition(id) | Hit::StateAction(id)) => { Some(Action::publish(Event::Select(self.context, id)).and_capture()) } None => { @@ -1378,6 +1444,22 @@ fn paint_node( ); } } + for (relation, row) in state_action_rows(project, node) { + if relation.id == selected { + frame.fill_rectangle(row.position(), row.size(), Color::from_rgb8(224, 247, 242)); + } + label_text( + frame, + short(&state_action_label(project, relation), row.width - 12., 11.), + row.position() + Vector::new(6., 5.), + 11., + if relation.id == selected { + style::TEAL + } else { + style::MUTED + }, + ); + } for (relation, row) in internal_rows(project, node) { if relation.id == selected { frame.fill_rectangle(row.position(), row.size(), Color::from_rgb8(224, 247, 242)); @@ -2012,6 +2094,109 @@ mod tests { assert_eq!(accepted.relations[&relation].target(), f.c); } + #[test] + fn typed_state_actions_are_named_selectable_rows_before_internal_transitions_and_regions() { + let mut f = fixture(); + let mut action_ids = vec![]; + for (phase, name) in [ + (StateActionKind::Exit, "Release resources"), + (StateActionKind::Entry, "Restore settings"), + (StateActionKind::Do, "Monitor pressure"), + ] { + let work = f + .project + .add_element(Kind::Function, Layer::Sa, name, None) + .unwrap(); + let id = f + .project + .relate(RelationKind::StateAction, "Action link", f.a, work); + if let RelationBody::StateAction { phase: value, .. } = + &mut f.project.relations.get_mut(&id).unwrap().body + { + *value = phase; + } + action_ids.push(id); + } + if let Some(SharedBody::State { entry, .. }) = + f.project.elements.get_mut(&f.a).unwrap().shared_mut() + { + *entry = "Authored entry notes".into(); + } + let internal = f + .project + .relate(RelationKind::StateTransition, "Update status", f.a, f.a); + if let RelationBody::StateTransition { + transition_kind, .. + } = &mut f.project.relations.get_mut(&internal).unwrap().body + { + *transition_kind = TransitionKind::Internal; + } + let before = f.project.clone(); + let canvas = canvas(&f, Tool::Select, 0.75); + let scene = canvas.scene(&State::default()).unwrap(); + let a = scene.view.nodes.iter().find(|n| n.element == f.a).unwrap(); + let rows = state_action_rows(&f.project, a); + assert_eq!( + rows.iter() + .map(|(r, _)| state_action_label(&f.project, r)) + .collect::>(), + [ + "entry / Restore settings", + "do / Monitor pressure", + "exit / Release resources" + ] + ); + assert_eq!( + projection::state_header_height(&f.project, f.a), + 64. + 28. + 3. * 28. + 56. + ); + let internal_top = internal_rows(&f.project, a)[0].1.y; + assert!(rows[0].1.y >= a.position.y + 92.); + assert!(rows.last().unwrap().1.y + 28. <= internal_top); + let child_region = scene + .view + .nodes + .iter() + .find(|n| n.visual_parent == Some(a.id)) + .unwrap(); + assert!( + child_region.position.y + >= a.position.y + projection::state_header_height(&f.project, f.a) + ); + for (relation, row) in rows { + assert_eq!( + canvas.hit(&scene, row.center()), + Some(Hit::StateAction(relation.id)) + ); + let events = click(&canvas, &mut State::default(), row.center()); + assert!( + matches!(events.as_slice(), [Event::Select(context, id)] if *context == canvas.context && *id == relation.id), + "Clicking a typed action row must select its relation in this view: {events:?}" + ); + assert!( + !scene + .view + .nodes + .iter() + .any(|n| n.element == relation.target()) + ); + assert!(!scene.routed.edges.contains_key(&relation.id)); + } + let mut collapsed = a.clone(); + collapsed.collapsed = true; + assert!(state_action_rows(&f.project, &collapsed).is_empty()); + assert_eq!( + f.project, before, + "Reading and selecting action rows cannot mutate work or ownership" + ); + let target = f.project.relations[&action_ids[1]].target(); + f.project.elements.get_mut(&target).unwrap().name = "Reload settings".into(); + assert_eq!( + state_action_label(&f.project, &f.project.relations[&action_ids[1]]), + "entry / Reload settings" + ); + } + #[test] fn internal_rows_and_pseudostate_hits_use_shared_persisted_geometry() { let mut f = fixture(); diff --git a/crates/oa-desktop/src/behavior/states.rs b/crates/oa-desktop/src/behavior/states.rs index df218ea7..c3843447 100644 --- a/crates/oa-desktop/src/behavior/states.rs +++ b/crates/oa-desktop/src/behavior/states.rs @@ -2,7 +2,7 @@ use super::*; use iced::widget::{checkbox, column, text_input}; -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub enum Tool { Vertex, Region, @@ -46,6 +46,8 @@ pub enum Event { pub struct State { draft: Option, } +#[derive(serde::Serialize, serde::Deserialize)] +#[serde(deny_unknown_fields)] struct Draft { machine: Id, tool: Tool, @@ -184,6 +186,19 @@ impl Draft { } } impl State { + pub(super) fn recovery_snapshot(&self) -> Result, String> { + self.draft + .as_ref() + .map(serde_json::to_value) + .transpose() + .map_err(|e| e.to_string()) + } + + pub(super) fn from_recovery(value: serde_json::Value) -> Result { + let draft = serde_json::from_value(value).map_err(|e| e.to_string())?; + Ok(Self { draft: Some(draft) }) + } + pub(super) fn pending_owner(&self) -> Option { self.draft.as_ref().map(|d| d.machine) } diff --git a/crates/oa-desktop/src/behavior/topology.rs b/crates/oa-desktop/src/behavior/topology.rs index 6ebb58f6..f4cadcbb 100644 --- a/crates/oa-desktop/src/behavior/topology.rs +++ b/crates/oa-desktop/src/behavior/topology.rs @@ -12,7 +12,7 @@ pub(crate) use direct::{ #[cfg(test)] pub(crate) mod direct_tests; -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub enum Tool { Work, Exchange, @@ -76,12 +76,16 @@ pub struct State { zoom: Option, fit: bool, } -#[derive(Default)] +#[derive(Default, serde::Serialize, serde::Deserialize)] +#[serde(deny_unknown_fields)] struct Browser { search: String, page: usize, } +#[derive(serde::Serialize, serde::Deserialize)] +#[serde(deny_unknown_fields)] struct Draft { + #[serde(with = "recovery_insertion")] insertion: Insertion, fixed: bool, browsers: [Browser; 4], @@ -97,6 +101,22 @@ struct Draft { control: ControlKind, } impl State { + pub(super) fn recovery_snapshot(&self) -> Result, String> { + self.draft + .as_ref() + .map(serde_json::to_value) + .transpose() + .map_err(|e| e.to_string()) + } + + pub(super) fn from_recovery(value: serde_json::Value) -> Result { + let draft = serde_json::from_value(value).map_err(|e| e.to_string())?; + Ok(Self { + draft: Some(draft), + ..Default::default() + }) + } + pub(super) fn pending_owner(&self) -> Option { self.draft.as_ref().map(|d| d.owner) } @@ -198,6 +218,23 @@ impl State { .clamp(0.05, 1.) } } + +mod recovery_insertion { + use super::*; + use serde::{Deserialize, Serialize}; + pub fn serialize( + value: &Insertion, + serializer: S, + ) -> Result { + (value.link, value.before).serialize(serializer) + } + pub fn deserialize<'de, D: serde::Deserializer<'de>>( + deserializer: D, + ) -> Result { + let (link, before) = <(Option, Option)>::deserialize(deserializer)?; + Ok(Insertion { link, before }) + } +} fn is_contribution(p: &Project, owner: Id, id: Id) -> bool { p.elements.get(&id).is_some_and(|e| { e.parent == Some(owner) diff --git a/crates/oa-desktop/src/document.rs b/crates/oa-desktop/src/document.rs index b7fc3156..9a9cb1bb 100644 --- a/crates/oa-desktop/src/document.rs +++ b/crates/oa-desktop/src/document.rs @@ -15,6 +15,12 @@ struct HistoryEntry { bytes: usize, } +struct RelocatedHistory { + current: Arc, + past: Vec, + future: Vec, +} + fn estimated_bytes(compiled: &CompiledSourceProject) -> usize { compiled .bundle @@ -92,6 +98,7 @@ pub struct Document { saved: Arc, revision: u64, limits: ProjectLimits, + recovered_unsaved: bool, } impl Document { @@ -120,6 +127,7 @@ impl Document { saved: Arc::new(bundle), revision: 0, limits, + recovered_unsaved: false, }) } @@ -140,7 +148,12 @@ impl Document { } pub fn dirty(&self) -> bool { - self.bundle() != self.saved.as_ref() + self.recovered_unsaved || self.bundle() != self.saved.as_ref() + } + + /// Recovered work is a detached copy until explicitly saved by its user. + pub fn mark_recovered(&mut self) { + self.recovered_unsaved = true; } pub fn can_undo(&self) -> bool { @@ -161,6 +174,119 @@ impl Document { /// The I/O adapter owns document identity and save completion ordering. pub fn mark_saved(&mut self, snapshot: Arc) { self.saved = Arc::new(snapshot.bundle.clone()); + self.recovered_unsaved = false; + } + + /// Check a source Save as before writing any files. A retained included file + /// must never be displaced by the new entry, including on a later Undo. + pub fn check_save_entry(&self, entry: &str) -> Result<(), DocumentError> { + if entry == self.bundle().entry { + return Ok(()); + } + self.relocated_history(entry).map(|_| ()) + } + + /// Adopt a successfully saved source entry without recording an engineering + /// edit or clearing Redo. The caller still marks the exact receipt as saved. + pub fn adopt_saved_entry( + &mut self, + saved: &CompiledSourceProject, + revision: u64, + ) -> Result<(), DocumentError> { + if revision != self.revision { + return Err( + ModelError("Newer edits retain the current source location.".into()).into(), + ); + } + let RelocatedHistory { + current, + past, + future, + } = self.relocated_history(&saved.bundle.entry)?; + if current.bundle != saved.bundle + || current.decoded.project != saved.decoded.project + || current.index != saved.index + { + return Err(ModelError( + "The save receipt changes more than the source entry location.".into(), + ) + .into()); + } + if current.bundle != self.current.bundle { + self.current_bytes = estimated_bytes(¤t); + self.current = current; + self.past = past; + self.future = future; + // Source-position readers must reject their old revision even though + // changing the save location does not create an Undo transaction. + self.revision = self.revision.saturating_add(1); + } + Ok(()) + } + + fn relocated_history(&self, entry: &str) -> Result { + if entry.contains('/') || source_project::resolve_include("", entry).as_deref() != Ok(entry) + { + return Err(ModelError("Choose a portable .arcadia entry file name.".into()).into()); + } + let snapshots = std::iter::once(self.current.as_ref()) + .chain(self.past.iter().map(|h| h.snapshot.as_ref())) + .chain(self.future.iter().map(|h| h.snapshot.as_ref())); + let mut uniform_entry = true; + for snapshot in snapshots { + uniform_entry &= snapshot.bundle.entry == self.bundle().entry; + if snapshot.bundle.files.keys().any(|path| { + path != &snapshot.bundle.entry && path.to_lowercase() == entry.to_lowercase() + }) { + return Err(ModelError(format!( + "The entry name '{entry}' is used by an included file in this document or its Undo/Redo history. Choose another entry name." + )).into()); + } + } + let relocate = |snapshot: &Arc| -> Result, DocumentError> { + if snapshot.bundle.entry == entry { + return Ok(snapshot.clone()); + } + let mut bundle = snapshot.bundle.clone(); + let text = bundle + .files + .remove(&bundle.entry) + .ok_or_else(|| ModelError("The accepted source entry is missing.".into()))?; + bundle.entry = entry.to_owned(); + bundle.files.insert(entry.to_owned(), text); + let compiled = source_project::compile_bundle(&bundle, self.limits)?; + if compiled.bundle != bundle || compiled.decoded.project != snapshot.decoded.project { + return Err(ModelError( + "Changing the entry location would change the model or authored source.".into(), + ) + .into()); + } + Ok(Arc::new(compiled)) + }; + let current = relocate(&self.current)?; + let history = |entries: &[HistoryEntry]| { + entries + .iter() + .map(|old| { + // Different entry names in history are authored source changes. + // Keep those snapshots exact instead of collapsing real edits. + let snapshot = if uniform_entry { + relocate(&old.snapshot)? + } else { + old.snapshot.clone() + }; + Ok(HistoryEntry { + bytes: estimated_bytes(&snapshot), + snapshot, + }) + }) + .collect::, DocumentError>>() + }; + Ok(RelocatedHistory { + current, + past: history(&self.past)?, + future: history(&self.future)?, + }) } pub fn execute(&mut self, command: Command) -> Result, DocumentError> { @@ -178,6 +304,7 @@ impl Document { Ok(candidate.selected) } + #[cfg(test)] pub fn apply_bundle(&mut self, bundle: SourceBundle) -> Result { let compiled = source_project::compile_bundle(&bundle, self.limits)?; Ok(self.commit(compiled)) @@ -259,6 +386,10 @@ impl Document { } } +#[cfg(test)] +#[path = "document_save_as_tests.rs"] +mod save_as_tests; + #[cfg(test)] mod tests { use super::*; diff --git a/crates/oa-desktop/src/document_save_as_tests.rs b/crates/oa-desktop/src/document_save_as_tests.rs new file mode 100644 index 00000000..c9afc149 --- /dev/null +++ b/crates/oa-desktop/src/document_save_as_tests.rs @@ -0,0 +1,154 @@ +use super::*; + +fn renamed(bundle: &SourceBundle, entry: &str) -> SourceBundle { + let mut out = bundle.clone(); + let text = out.files.remove(&out.entry).unwrap(); + out.entry = entry.into(); + out.files.insert(entry.into(), text); + out +} + +fn saved(document: &Document, entry: &str) -> CompiledSourceProject { + source_project::compile_bundle(&renamed(document.bundle(), entry), document.limits).unwrap() +} + +#[test] +fn save_as_rebases_uniform_history_without_an_undo_step_or_lost_redo() { + let mut document = Document::from_project(Project::empty(), ProjectLimits::default()).unwrap(); + let original = document.bundle().clone(); + let mut comments = original.clone(); + comments + .files + .get_mut(&comments.entry) + .unwrap() + .push_str("\r\n// exact rationale\r\n"); + document.apply_bundle(comments.clone()).unwrap(); + document + .execute(Command::Rename( + document.project().id, + "Engineering choice".into(), + )) + .unwrap(); + let future = document.bundle().clone(); + document.undo(); + let counts = (document.past.len(), document.future.len()); + let receipt = saved(&document, "accepted.arcadia"); + document.check_save_entry("accepted.arcadia").unwrap(); + document + .adopt_saved_entry(&receipt, document.revision()) + .unwrap(); + document.mark_saved(Arc::new(receipt)); + assert_eq!((document.past.len(), document.future.len()), counts); + assert!(!document.dirty()); + assert!(document.undo()); + assert_eq!(document.bundle(), &renamed(&original, "accepted.arcadia")); + assert!(document.redo()); + assert_eq!(document.bundle(), &renamed(&comments, "accepted.arcadia")); + assert!(!document.dirty()); + assert!(document.redo()); + assert_eq!(document.bundle(), &renamed(&future, "accepted.arcadia")); + assert!(document.dirty()); + let checked = source_project::compile_bundle(document.bundle(), document.limits).unwrap(); + assert_eq!(document.compiled().index, checked.index); + assert_eq!(document.project(), &checked.decoded.project); +} + +#[test] +fn save_as_preserves_explicit_source_entry_changes_in_history() { + let mut document = Document::from_project(Project::empty(), ProjectLimits::default()).unwrap(); + let original = document.bundle().clone(); + let authored = renamed(&original, "authored.arcadia"); + document.apply_bundle(authored.clone()).unwrap(); + document + .execute(Command::Rename( + document.project().id, + "Later meaning".into(), + )) + .unwrap(); + let receipt = saved(&document, "accepted.arcadia"); + document + .adopt_saved_entry(&receipt, document.revision()) + .unwrap(); + assert_eq!(document.past.len(), 2); + assert!(document.undo()); + assert_eq!(document.bundle(), &authored); + assert!(document.undo()); + assert_eq!(document.bundle(), &original); + assert!(document.redo()); + assert_eq!(document.bundle(), &authored); + assert!(document.redo()); + assert_eq!(document.bundle(), &receipt.bundle); +} + +#[test] +fn save_as_rejects_included_filename_collisions_in_past_and_future_without_mutation() { + for collision_in_future in [false, true] { + let base = source_project::format_bundle(&Project::empty()); + let mut included = base.clone(); + let text = included.files.get_mut(&included.entry).unwrap(); + text.insert_str(text.rfind('}').unwrap(), "\n include \"TAKEN.arcadia\";\n"); + included.files.insert( + "TAKEN.arcadia".into(), + "// retained engineering rationale\n".into(), + ); + let mut document = if collision_in_future { + let mut d = Document::from_bundle(base, ProjectLimits::default()).unwrap(); + d.apply_bundle(included).unwrap(); + d.undo(); + d + } else { + let mut d = Document::from_bundle(included, ProjectLimits::default()).unwrap(); + d.apply_bundle(base).unwrap(); + d + }; + let current = document.snapshot(); + let revision = document.revision(); + let counts = (document.past.len(), document.future.len()); + assert!( + document + .check_save_entry("taken.arcadia") + .unwrap_err() + .to_string() + .contains("Undo/Redo") + ); + let receipt = saved(&document, "taken.arcadia"); + assert!(document.adopt_saved_entry(&receipt, revision).is_err()); + assert!(Arc::ptr_eq(¤t, &document.snapshot())); + assert_eq!(document.revision(), revision); + assert_eq!((document.past.len(), document.future.len()), counts); + } +} + +#[test] +fn save_as_rejects_stale_or_content_changing_receipt_atomically() { + let mut document = Document::from_project(Project::empty(), ProjectLimits::default()).unwrap(); + let mut receipt = saved(&document, "accepted.arcadia"); + let revision = document.revision(); + document + .execute(Command::Rename( + document.project().id, + "Newer meaning".into(), + )) + .unwrap(); + let current = document.snapshot(); + assert!(document.adopt_saved_entry(&receipt, revision).is_err()); + assert!( + document + .adopt_saved_entry(&receipt, document.revision()) + .is_err() + ); + receipt = saved(&document, "accepted.arcadia"); + receipt + .bundle + .files + .get_mut(&receipt.bundle.entry) + .unwrap() + .push_str("// unsaved receipt text\n"); + assert!( + document + .adopt_saved_entry(&receipt, document.revision()) + .is_err() + ); + assert!(Arc::ptr_eq(¤t, &document.snapshot())); + assert_eq!(document.past.len(), 1); +} diff --git a/crates/oa-desktop/src/forms.rs b/crates/oa-desktop/src/forms.rs index cde90ff4..733b618b 100644 --- a/crates/oa-desktop/src/forms.rs +++ b/crates/oa-desktop/src/forms.rs @@ -52,7 +52,87 @@ pub struct Draft { pub value_browsers: BTreeMap, } +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +#[serde(deny_unknown_fields)] +pub struct RecoveryDraft { + original: Id, + value: Value, + exact_strings: bool, + inputs: BTreeMap, + constructors: Vec<(String, String, Value, BTreeMap)>, + ports: BTreeMap, +} + impl Draft { + pub fn recovery_snapshot(&self) -> RecoveryDraft { + let raw = |inputs: &BTreeMap| { + inputs + .iter() + .map(|(path, input)| (path.clone(), input.raw.clone())) + .collect() + }; + RecoveryDraft { + original: self.original, + value: self.value.clone(), + exact_strings: self.exact_strings, + inputs: raw(&self.inputs), + constructors: self + .constructor_drafts + .iter() + .map(|((path, kind), (value, inputs))| { + (path.clone(), kind.clone(), value.clone(), raw(inputs)) + }) + .collect(), + ports: self.port_contract_drafts.clone(), + } + } + pub fn from_recovery(project: &Project, saved: RecoveryDraft) -> Result { + let input_count = saved.inputs.len().saturating_add( + saved + .constructors + .iter() + .map(|(_, _, _, inputs)| inputs.len()) + .sum::(), + ); + if input_count > crate::source_workspace::LIMITS.model.max_records + || saved.constructors.len() > 1024 + || saved.ports.len() > 32 + { + return Err("Recovered property buffers exceed the supported limit.".into()); + } + let mut draft = Self::from_project(project, saved.original) + .ok_or("Recovered property owner no longer exists.")?; + draft.value = saved.value; + draft.exact_strings = saved.exact_strings; + draft.rebuild(); + if !draft.inputs.keys().eq(saved.inputs.keys()) { + return Err("Recovered property fields no longer match their raw buffers.".into()); + } + for (path, raw) in saved.inputs { + draft.input(&path, raw); + } + draft.constructor_drafts = saved + .constructors + .into_iter() + .map(|(path, kind, value, inputs)| { + ( + (path, kind), + ( + value, + inputs + .into_iter() + .map(|(path, raw)| { + let editor = text_editor::Content::with_text(&raw); + (path, Input { raw, editor }) + }) + .collect(), + ), + ) + }) + .collect(); + draft.port_contract_drafts = saved.ports; + Ok(draft) + } pub fn from_project(project: &Project, selected: Id) -> Option { let (kind, value) = if selected == project.id { ( diff --git a/crates/oa-desktop/src/guide.rs b/crates/oa-desktop/src/guide.rs index 1d277b07..0accf6a2 100644 --- a/crates/oa-desktop/src/guide.rs +++ b/crates/oa-desktop/src/guide.rs @@ -575,11 +575,11 @@ fn article(topic: Topic) -> Article { ), explain( "Review drift deliberately", - "Traceability marks descriptive links Reviewed or Review needed by comparing both endpoints with their last review baseline. Independent edits may be intentional. Keeping a target value records that reviewed decision; it does not delete the operational need. A manual trace records correspondence without propagating properties.", + "On an existing trace, choose Review semantics… to compare typed content and prior observations. Choose Keep for the destination, Take for the source, or Set final value / Set final kind and payload where offered. Review mapped references and proposed changes, then choose Apply reviewed changes. Independent edits may be intentional; keeping a value records that decision. A manual trace records correspondence without propagating properties.", ), explain( "Know what reconciliation changes", - "The current counterpart workflow reviews names and descriptions of existing targets. It can create structural dependencies for new counterparts. Reparenting, reallocating, removing or restructuring an existing counterpart remains an explicit engineering edit; do not assume Refresh preview automatically synchronizes every structural change.", + "Semantic review supports explicit structural and scalar changes, typed constructors and individual collection rows, including members, literals, annotations and bindings. Candidate validation checks the complete proposed model before applying one undoable transaction. Ordered collections, attachment/artifact and multi-source review controls remain unfinished; some structured facts are read-only. Do not assume Refresh preview automatically synchronizes arbitrary structure or every field.", ), explain( "Avoid duplicate counterparts", @@ -677,8 +677,8 @@ fn article(topic: Topic) -> Article { Topic::Recovery => { const { Article { - summary: "Structural errors protect the accepted model. Warnings keep incomplete engineering work visible without preventing you from developing it incrementally.", - keywords: "validation checks error warning invalid alias missing reference undo redo revert recover draft unsaved structural external conflict lint", + summary: "Correct rejected drafts, inspect model checks and recover retained work after an unexpected exit. Recovery copies preserve editing separately from your saved models.", + keywords: "validation checks error warning invalid alias missing reference undo redo revert recover recovery checkpoint autosave crash draft unsaved structural external conflict lint home", sections: &[ walkthrough( "Resolve a model issue", @@ -702,11 +702,23 @@ fn article(topic: Topic) -> Article { ), explain( "Recover an interrupted source save", - "If a staged multi-file save was interrupted, Textual model offers Recover interrupted save. Recovery checks the journal and file contents before restoring originals; it does not overwrite unrelated external changes. This is recovery of a save transaction, not periodic autosave of unsaved edits.", + "If a staged multi-file save was interrupted, Textual model offers Recover interrupted save. Recovery checks the journal and file contents before restoring originals; it does not overwrite unrelated external changes. This repairs an interrupted explicit save. Home's recovery copies preserve unsaved editing separately.", + ), + walkthrough( + "Recover after an unexpected exit", + &[ + "Open Home and look under Recover unsaved work. Each retained session shows its model name, original location when known and age. Choose Recover; the current model still uses the normal Save/Discard/Cancel guard.", + "Inspect the recovered copy. Accepted model/source content and raw source buffers are retained, including invalid and detached drafts. Supported pending property/behavior forms and workflow drafts return with rejected values and semantic-review choices. Correct or revert unfinished input before applying it.", + "Choose Save as and select a destination. Recovery does not reconnect the original local or hosted destination, and never restores credentials. Undo/redo history is not restored across sessions.", + ], + ), + explain( + "What a checkpoint protects", + "While work is unsaved, a private local checkpoint is attempted every 10 seconds. It does not apply drafts, modify model files or save to the server. Recent edits since the last completed checkpoint may not survive a crash. Home reports unavailable or damaged recovery files without replacing the currently open model. Explicit saves and version control remain your durable milestones.", ), explain( "Save work before leaving", - "New, Open, loading the example and Close use the unsaved-work prompt. Save and continue, discard or cancel is your decision. There is no autosave recovery journal in the current desktop; save meaningful milestones and use version control or backups.", + "New, Open, opening a recent model, loading the example, Recover and Close use the unsaved-work prompt. Save and continue, discard or cancel is your decision. Checkpoints are private recovery copies; continue saving meaningful milestones and using version control or backups.", ), ], related: &[Topic::Source, Topic::Files, Topic::Transitions], @@ -718,8 +730,12 @@ fn article(topic: Topic) -> Article { const { Article { summary: "Keep editable model files and exported pictures distinct. Save the model to retain its semantics, identities and traceability.", - keywords: "file open save save as keyboard shortcuts ctrl control shift export svg json arcadia backup external changes migration", + keywords: "file home recent missing locate open save save as keyboard shortcuts ctrl control shift export svg json arcadia backup external changes migration", sections: &[ + explain( + "Start from Home", + "Home offers New model, Open model, the rescue example and this guide. Recent models lists local files you successfully opened or saved. A disconnected drive or moved file remains visible; use Locate to choose its new location or Refresh after reconnecting the drive. Remove forgets the recent entry without deleting its model file. Returning to Home retains the current document and uses the same draft navigation guards as other pages.", + ), explain( "Editable formats", ".arcadia is editable text suitable for Git and can include other source files. .json is the typed model exchange format. Both preserve the persistent model, diagrams and traceability; only a source project preserves its comments and file organization. Export SVG creates a diagram image for reports; it is not an editable model backup.", @@ -740,7 +756,7 @@ fn article(topic: Topic) -> Article { ), explain( "Open and migration", - "Open validates a complete candidate before replacing the project. Versions 1, 2 and 3 migrate explicitly to version 4; Model checks shows preserved ports, supplied data defaults, generated interface allocations and unresolved message paths. Old Event items become Information notifications; unknown physical mechanisms remain Unspecified. Source3 upgrades retain files, includes, nested ownership and comments. Opening does not overwrite the original: use Save as if it must remain in its older format. Invalid input leaves the accepted project unchanged.", + "Open validates a complete candidate before replacing the project. Supported earlier versions migrate to the current schema; Model checks shows migration details such as preserved ports, supplied data defaults, generated interface allocations and unresolved message paths. Old Event items become Information notifications; unknown physical mechanisms remain Unspecified. Source upgrades retain files, includes, nested ownership and comments. Opening does not overwrite the original: use Save as if it must remain in its older format. Invalid input leaves the accepted project unchanged.", ), ], related: &[Topic::Source, Topic::Recovery, Topic::Artifacts], @@ -884,7 +900,8 @@ fn article_view(topic: Topic) -> Element<'static, Message> { .width(28), text(*step).size(14).width(Fill), ] - .spacing(9), + .spacing(9) + .width(Fill), ); } if let Some(code) = section.code { @@ -984,11 +1001,15 @@ pub fn view(state: &State) -> Element<'_, Message> { (!state.query.is_empty()).then(|| Message::Guide(Event::Search(String::new()))) ), ] - .spacing(8), + .spacing(8) + .width(Fill), text(format!("{} topics", matches.len())) .size(11) .color(style::MUTED), - row![topic_list, article].spacing(20).height(Fill), + row![topic_list, article] + .spacing(20) + .width(Fill) + .height(Fill), ] .spacing(14) .padding(22) diff --git a/crates/oa-desktop/src/home.rs b/crates/oa-desktop/src/home.rs new file mode 100644 index 00000000..b335380c --- /dev/null +++ b/crates/oa-desktop/src/home.rs @@ -0,0 +1,457 @@ +//! Start workspace and bounded, user-local recent-file metadata. +//! Events request the application's existing guarded actions; this module never +//! opens, creates, saves or modifies a model document. +use crate::{style, views::short}; +use iced::{ + Element, Fill, + widget::{button, column, container, row, scrollable, text}, +}; +use serde::{Deserialize, Serialize}; +use std::{ + fs::{self, File}, + io::{Read, Write}, + path::{Path, PathBuf}, +}; + +const MAX_RECENTS: usize = 12; +const MAX_STORE_BYTES: u64 = 256 * 1024; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Event { + New, + Open, + Example, + Guide, + Resume, + OpenRecent(PathBuf), + Forget(PathBuf), + Refresh, + Recover(oa_core::Id), +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] +#[serde(deny_unknown_fields)] +pub struct Entry { + pub path: PathBuf, + pub name: String, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Availability { + Available, + Missing, + Unavailable(String), +} + +#[derive(Debug, Clone)] +pub struct Recent { + pub entry: Entry, + pub availability: Availability, +} + +#[derive(Debug, Default)] +pub struct State { + pub recent: Vec, + pub notice: Option, + store: Option, + /// Unknown/corrupt metadata is retained on disk until repaired externally. + /// Remember/forget still work for this application session. + read_only: bool, +} + +#[derive(Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +struct Stored { + version: u32, + entries: Vec, +} + +/// User-local application metadata directory, shared with recovery facilities. +/// No current-directory fallback: missing configuration must not write into a +/// model checkout. An explicitly configured relative XDG location is invalid. +pub fn data_directory() -> Option { + if cfg!(target_os = "windows") { + std::env::var_os("LOCALAPPDATA") + .map(PathBuf::from) + .filter(|path| path.is_absolute()) + .map(|path| path.join("ArcadiaStudio")) + } else { + std::env::var_os("XDG_DATA_HOME") + .map(PathBuf::from) + .filter(|path| path.is_absolute()) + .or_else(|| { + std::env::var_os("HOME") + .map(PathBuf::from) + .filter(|path| path.is_absolute()) + .map(|path| path.join(".local/share")) + }) + .map(|path| path.join("arcadia-studio")) + } +} + +impl State { + pub fn load_default() -> Self { + match data_directory() { + Some(directory) => Self::load(directory.join("recent-models.json")), + None => Self { + notice: Some("Recent models are available for this session; the user data directory is unavailable.".into()), + ..Self::default() + }, + } + } + + pub fn load(store: PathBuf) -> Self { + let mut state = Self { + store: Some(store.clone()), + ..Self::default() + }; + match read_store(&store) { + Ok(entries) => { + state.recent = entries + .into_iter() + .map(|entry| Recent { + availability: availability(&entry.path), + entry, + }) + .collect(); + } + Err(error) => { + state.read_only = true; + state.notice = Some(format!( + "Recent model history could not be loaded: {error}. Your model files are unaffected; this session's history will remain in memory." + )); + } + } + state + } + + /// Call only after the application's existing open/save action succeeds. + /// Canonical local file identities prevent duplicate entries for symlinks + /// and alternate spellings. Unavailable files are never newly remembered. + pub fn remember(&mut self, path: &Path, name: &str) -> Result<(), String> { + let path = fs::canonicalize(path).map_err(|error| error.to_string())?; + if !path.is_file() { + return Err("Recent models must reference a file.".into()); + } + self.recent + .retain(|recent| !same_path(&recent.entry.path, &path)); + self.recent.insert( + 0, + Recent { + entry: Entry { + path, + name: short(name, 160), + }, + availability: Availability::Available, + }, + ); + self.recent.truncate(MAX_RECENTS); + self.persist() + } + + /// Removing history only removes metadata; it never deletes a model file. + pub fn forget(&mut self, path: &Path) -> Result<(), String> { + self.recent + .retain(|recent| !same_path(&recent.entry.path, path)); + self.persist() + } + + pub fn refresh(&mut self) { + for recent in &mut self.recent { + recent.availability = availability(&recent.entry.path); + } + } + + fn persist(&mut self) -> Result<(), String> { + let Some(path) = &self.store else { + return Ok(()); + }; + if self.read_only { + return Err("Recent history is retained in memory because its existing metadata could not be read.".into()); + } + let entries = self + .recent + .iter() + .map(|recent| recent.entry.clone()) + .collect(); + match write_store(path, entries) { + Ok(()) => { + self.notice = None; + Ok(()) + } + Err(error) => { + let message = format!( + "Recent model history could not be saved: {error}. It remains available for this session." + ); + self.notice = Some(message.clone()); + Err(message) + } + } + } +} + +fn same_path(a: &Path, b: &Path) -> bool { + #[cfg(windows)] + { + a.to_string_lossy() + .eq_ignore_ascii_case(&b.to_string_lossy()) + } + #[cfg(not(windows))] + { + a == b + } +} + +fn availability(path: &Path) -> Availability { + match fs::metadata(path) { + Ok(metadata) if metadata.is_file() => Availability::Available, + Ok(_) => Availability::Unavailable("The path is not a file".into()), + Err(error) if error.kind() == std::io::ErrorKind::NotFound => Availability::Missing, + Err(error) => Availability::Unavailable(error.to_string()), + } +} + +fn read_store(path: &Path) -> Result, String> { + let file = match File::open(path) { + Ok(file) => file, + Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(vec![]), + Err(error) => return Err(error.to_string()), + }; + let mut bytes = Vec::new(); + file.take(MAX_STORE_BYTES + 1) + .read_to_end(&mut bytes) + .map_err(|e| e.to_string())?; + if bytes.len() as u64 > MAX_STORE_BYTES { + return Err("history file exceeds its size limit".into()); + } + let stored: Stored = serde_json::from_slice(&bytes).map_err(|e| e.to_string())?; + if stored.version != 1 { + return Err("unsupported history version".into()); + } + if stored.entries.len() > MAX_RECENTS + || stored + .entries + .iter() + .any(|entry| !entry.path.is_absolute() || entry.name.chars().count() > 160) + { + return Err("history contains invalid entries".into()); + } + let mut entries: Vec = Vec::new(); + for entry in stored.entries { + if !entries + .iter() + .any(|prior| same_path(&prior.path, &entry.path)) + { + entries.push(entry); + } + } + Ok(entries) +} + +fn write_store(path: &Path, entries: Vec) -> Result<(), String> { + let parent = path + .parent() + .filter(|path| !path.as_os_str().is_empty()) + .ok_or_else(|| "history path requires a parent directory".to_string())?; + fs::create_dir_all(parent).map_err(|e| e.to_string())?; + let bytes = serde_json::to_vec_pretty(&Stored { + version: 1, + entries, + }) + .map_err(|e| e.to_string())?; + if bytes.len() as u64 > MAX_STORE_BYTES { + return Err("history exceeds its size limit".into()); + } + let mut temporary = tempfile::NamedTempFile::new_in(parent).map_err(|e| e.to_string())?; + temporary.write_all(&bytes).map_err(|e| e.to_string())?; + temporary.as_file().sync_all().map_err(|e| e.to_string())?; + temporary.persist(path).map_err(|e| e.error.to_string())?; + #[cfg(unix)] + File::open(parent) + .and_then(|directory| directory.sync_all()) + .map_err(|e| e.to_string())?; + Ok(()) +} + +fn action<'a>(label: &str, event: Event) -> iced::widget::Button<'a, Event> { + button(text(label.to_owned()).size(13)) + .padding([10, 14]) + .on_press(event) +} + +fn recovery_age(modified: u64) -> String { + let now = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap_or_default() + .as_secs(); + let elapsed = now.saturating_sub(modified); + let (count, unit) = match elapsed { + 0..60 => return "Retained just now".into(), + 60..3600 => (elapsed / 60, "minute"), + 3600..86400 => (elapsed / 3600, "hour"), + _ => (elapsed / 86400, "day"), + }; + format!( + "Retained {count} {unit}{} ago", + if count == 1 { "" } else { "s" } + ) +} + +pub fn view<'a>( + state: &'a State, + current: Option<(&str, bool)>, + recovery: &'a crate::recovery::State, +) -> Element<'a, Event> { + let mut content = column![ + text("Your systems engineering workspace").size(28), + text("Develop operational needs and system architecture through connected diagrams, source and engineering evidence.").size(14).color(style::MUTED), + row![ + action("New model", Event::New), + action("Open model…", Event::Open).style(style::nav), + action("Explore rescue example", Event::Example).style(style::nav), + ].spacing(12), + ].spacing(18).max_width(940); + if let Some(warning) = &recovery.warning { + content = content.push(text(warning).size(12).color(style::MUTED)); + } + if !recovery.entries.is_empty() { + content = content.push(text("Recover unsaved work").size(20)); + content = content.push(text("Edits retained from an earlier session. Recovery opens an unsaved copy; choose a destination when saving. Undo history is not restored.").size(12).color(style::MUTED)); + for entry in &recovery.entries { + content = content.push( + container( + row![ + column![ + text(short(&entry.name, 80)).size(15), + text( + entry + .origin + .as_ref() + .map(|p| p.display().to_string()) + .unwrap_or_else(|| "Unsaved model".into()) + ) + .size(11) + .color(style::MUTED), + text(recovery_age(entry.modified)) + .size(11) + .color(style::MUTED), + ] + .spacing(5) + .width(Fill), + action("Recover", Event::Recover(entry.id)), + ] + .spacing(12) + .align_y(iced::Center), + ) + .padding(14) + .style(style::card), + ); + } + } + if let Some((name, dirty)) = current { + content = content.push( + container( + row![ + column![ + text(format!("Current model · {}", short(name, 64))).size(16), + text(if dirty { + "You have unsaved edits. They remain available while Home is open." + } else { + "Continue working with the model already open in this window." + }) + .size(12) + .color(style::MUTED) + ] + .spacing(5) + .width(Fill), + action("Continue editing", Event::Resume), + ] + .spacing(12) + .align_y(iced::Center), + ) + .padding(16) + .style(style::card), + ); + } + content = content.push( + row![ + text("Recent models").size(20).width(Fill), + action("Refresh", Event::Refresh).style(style::nav), + ] + .align_y(iced::Center), + ); + if let Some(notice) = &state.notice { + content = content.push(text(notice).size(12).color(style::MUTED)); + } + if state.recent.is_empty() { + content = content.push(container(column![ + text("Models you open or save will appear here.").size(14), + text("Open an .arcadia project entry or import a .json model. Included source files load together.").size(12).color(style::MUTED), + ].spacing(6)).padding(20).style(style::subtle)); + } + for recent in &state.recent { + let entry = &recent.entry; + let name = if entry.name.is_empty() { + entry + .path + .file_name() + .unwrap_or_default() + .to_string_lossy() + .into_owned() + } else { + entry.name.clone() + }; + let mut description = column![ + text(short(&name, 85)).size(15), + text(entry.path.display().to_string()) + .size(11) + .color(style::MUTED), + ] + .spacing(5) + .width(Fill); + let open = match &recent.availability { + Availability::Available => action("Open", Event::OpenRecent(entry.path.clone())), + availability => { + description = description.push( + text(match availability { + Availability::Missing => { + "File not found. Locate it after moving or reconnecting its drive." + .to_string() + } + Availability::Unavailable(reason) => { + format!("Unavailable: {}", short(reason, 120)) + } + Availability::Available => unreachable!(), + }) + .size(12) + .color(style::MUTED), + ); + action("Locate…", Event::Open) + } + }; + content = content.push( + container( + row![ + description, + open, + action("Remove", Event::Forget(entry.path.clone())).style(style::nav) + ] + .spacing(10) + .align_y(iced::Center), + ) + .padding(14) + .style(style::card), + ); + } + content = content.push(container(column![ + text("Build a model you can explain").size(18), + text("Start with operational actors and activities, trace the needs into system functions, then describe behavior and interfaces. The engineering guide includes concepts, examples and editing workflows.").size(13), + action("Open engineering guide", Event::Guide).style(style::nav), + ].spacing(10)).padding(20).style(style::subtle)); + scrollable(container(content).padding(28).width(Fill).center_x(Fill)) + .height(Fill) + .into() +} + +#[cfg(test)] +mod tests; diff --git a/crates/oa-desktop/src/home/tests.rs b/crates/oa-desktop/src/home/tests.rs new file mode 100644 index 00000000..2dd60a4b --- /dev/null +++ b/crates/oa-desktop/src/home/tests.rs @@ -0,0 +1,177 @@ +use super::*; + +fn model(directory: &Path, name: &str) -> PathBuf { + let path = directory.join(name); + fs::write(&path, b"model contents must remain untouched").unwrap(); + path +} + +#[test] +fn loading_missing_history_does_not_create_files_or_directories() { + let temp = tempfile::tempdir().unwrap(); + let store = temp.path().join("not-created/recent-models.json"); + let state = State::load(store.clone()); + assert!(state.recent.is_empty()); + assert!(state.notice.is_none()); + assert!(!store.parent().unwrap().exists()); +} + +#[test] +fn successful_local_history_is_bounded_ordered_deduplicated_and_durable() { + let temp = tempfile::tempdir().unwrap(); + let store = temp.path().join("settings/recent-models.json"); + let mut state = State::load(store.clone()); + let files: Vec<_> = (0..15) + .map(|i| model(temp.path(), &format!("model-{i}.arcadia"))) + .collect(); + for (i, path) in files.iter().enumerate() { + state.remember(path, &format!("Model {i}")).unwrap(); + } + assert_eq!(state.recent.len(), MAX_RECENTS); + assert_eq!(state.recent[0].entry.name, "Model 14"); + assert_eq!(state.recent[11].entry.name, "Model 3"); + state.remember(&files[8], "Renamed model").unwrap(); + assert_eq!(state.recent.len(), MAX_RECENTS); + assert_eq!(state.recent[0].entry.name, "Renamed model"); + let loaded = State::load(store); + assert!(loaded.notice.is_none()); + assert_eq!( + loaded.recent.iter().map(|r| &r.entry).collect::>(), + state.recent.iter().map(|r| &r.entry).collect::>() + ); + assert!( + loaded + .recent + .iter() + .all(|r| r.availability == Availability::Available) + ); + assert_eq!( + fs::read(&files[8]).unwrap(), + b"model contents must remain untouched" + ); +} + +#[test] +fn forgotten_and_missing_files_never_change_the_model_or_other_entries() { + let temp = tempfile::tempdir().unwrap(); + let store = temp.path().join("recent.json"); + let a = model(temp.path(), "a.arcadia"); + let b = model(temp.path(), "b.json"); + let mut state = State::load(store.clone()); + state.remember(&a, "A").unwrap(); + state.remember(&b, "B").unwrap(); + fs::remove_file(&b).unwrap(); + state.refresh(); + assert_eq!(state.recent[0].availability, Availability::Missing); + assert_eq!(state.recent.len(), 2); + assert_eq!( + State::load(store.clone()).recent[0].availability, + Availability::Missing + ); + let old = fs::read(&store).unwrap(); + assert!(state.remember(&b, "Missing file").is_err()); + assert_eq!(fs::read(&store).unwrap(), old); + state.forget(&a.canonicalize().unwrap()).unwrap(); + assert!(a.exists()); + assert_eq!(state.recent.len(), 1); + assert_eq!(State::load(store).recent.len(), 1); +} + +#[test] +fn unavailable_location_can_reappear_on_refresh_without_losing_identity() { + let temp = tempfile::tempdir().unwrap(); + let path = model(temp.path(), "removable.arcadia"); + let mut state = State::default(); + state.remember(&path, "Portable system").unwrap(); + fs::remove_file(&path).unwrap(); + fs::create_dir(&path).unwrap(); + state.refresh(); + assert!(matches!( + state.recent[0].availability, + Availability::Unavailable(_) + )); + fs::remove_dir(&path).unwrap(); + fs::write(&path, b"restored").unwrap(); + state.refresh(); + assert_eq!(state.recent[0].availability, Availability::Available); + assert_eq!(state.recent[0].entry.name, "Portable system"); +} + +#[test] +fn corrupt_future_and_oversized_history_are_preserved_with_usable_session_history() { + let temp = tempfile::tempdir().unwrap(); + let model = model(temp.path(), "model.arcadia"); + let store = temp.path().join("recent.json"); + for bytes in [ + b"not json".to_vec(), + br#"{"version":99,"entries":[]}"#.to_vec(), + vec![b' '; MAX_STORE_BYTES as usize + 1], + ] { + fs::write(&store, &bytes).unwrap(); + let mut state = State::load(store.clone()); + assert!(state.notice.is_some()); + assert!(state.remember(&model, "Session model").is_err()); + assert_eq!(state.recent.len(), 1); + assert_eq!(fs::read(&store).unwrap(), bytes); + } +} + +#[test] +fn failed_metadata_replace_keeps_previous_history_and_reports_memory_only_entry() { + let temp = tempfile::tempdir().unwrap(); + let a = model(temp.path(), "a.arcadia"); + let b = model(temp.path(), "b.arcadia"); + let parent = temp.path().join("settings"); + let store = parent.join("recent.json"); + let mut state = State::load(store.clone()); + state.remember(&a, "A").unwrap(); + let original = fs::read(&store).unwrap(); + let preserved = temp.path().join("preserved"); + fs::rename(&parent, &preserved).unwrap(); + fs::write(&parent, b"directory unavailable").unwrap(); + assert!(state.remember(&b, "B").is_err()); + assert!(state.notice.is_some()); + assert_eq!(state.recent[0].entry.name, "B"); + assert_eq!(fs::read(preserved.join("recent.json")).unwrap(), original); + fs::remove_file(&parent).unwrap(); + fs::rename(&preserved, &parent).unwrap(); + state.remember(&b, "B").unwrap(); + assert!(state.notice.is_none()); + assert_eq!(State::load(store).recent[0].entry.name, "B"); + assert_eq!( + fs::read_dir(parent).unwrap().count(), + 1, + "no abandoned temp files" + ); +} + +#[test] +fn malformed_entries_and_unknown_fields_never_become_open_requests() { + let temp = tempfile::tempdir().unwrap(); + let store = temp.path().join("recent.json"); + for value in [ + serde_json::json!({"version":1,"entries":[{"path":"relative.arcadia","name":"Bad"}]}), + serde_json::json!({"version":1,"entries":[],"credential":"not accepted"}), + serde_json::json!({"version":1,"entries":[{"path":temp.path().join("absolute.arcadia"),"name":"x".repeat(161)}]}), + ] { + fs::write(&store, serde_json::to_vec(&value).unwrap()).unwrap(); + let state = State::load(store.clone()); + assert!(state.notice.is_some()); + assert!(state.recent.is_empty()); + } +} + +#[test] +fn first_and_replaced_metadata_are_parseable_and_contain_no_model_bytes() { + let temp = tempfile::tempdir().unwrap(); + let path = model(temp.path(), "a.arcadia"); + let store = temp.path().join("recent.json"); + let mut state = State::load(store.clone()); + for name in ["First", "Updated"] { + state.remember(&path, name).unwrap(); + let bytes = fs::read(&store).unwrap(); + let parsed: Stored = serde_json::from_slice(&bytes).unwrap(); + assert_eq!(parsed.entries[0].name, name); + assert!(!String::from_utf8(bytes).unwrap().contains("model contents")); + } +} diff --git a/crates/oa-desktop/src/hosted/client_tests.rs b/crates/oa-desktop/src/hosted/client_tests.rs index e5f43f95..0e0a3a85 100644 --- a/crates/oa-desktop/src/hosted/client_tests.rs +++ b/crates/oa-desktop/src/hosted/client_tests.rs @@ -12,7 +12,7 @@ use std::{ }; use tokio::{ io::{AsyncReadExt, AsyncWriteExt}, - sync::oneshot, + sync::{oneshot, watch}, task::JoinHandle, }; @@ -500,13 +500,19 @@ async fn hosted_response_limits_cover_declared_and_chunked_bodies() { async fn hosted_timeout_and_disconnect_leave_mutation_outcomes_explicit() { let received = Arc::new(AtomicUsize::new(0)); let calls = Arc::clone(&received); + let (release, held) = watch::channel(false); let server = TestServer::fixture(Router::new().route( "/api/v1/projects/import-source-project", post(move || { let calls = Arc::clone(&calls); + let mut held = held.clone(); async move { calls.fetch_add(1, Ordering::SeqCst); - tokio::time::sleep(Duration::from_millis(100)).await; + // Never race a fixed response delay against the client deadline. + // A retained watch value also releases a handler admitted late. + while !*held.borrow_and_update() { + held.changed().await.unwrap(); + } StatusCode::CREATED } }), @@ -518,9 +524,14 @@ async fn hosted_timeout_and_disconnect_leave_mutation_outcomes_explicit() { let failure = client.publish(authored()).await.unwrap_err(); assert_eq!(failure.code, "timeout"); assert!(failure.uncertain); - assert_eq!(received.load(Ordering::SeqCst), 1); + release.send(true).unwrap(); let base = server.base.clone(); server.finish().await; + // Under executor/CPU contention, the deadline can expire before admission. + // Both zero and one admission have an uncertain mutation outcome; neither + // permits a retry. The lost-response test above independently reads a full + // admitted request and requires exactly one publication. + assert!(received.load(Ordering::SeqCst) <= 1); let failure = Connection::with_timeout(&base, Secret::default(), Duration::from_secs(2)) .unwrap() .catalog("", None) diff --git a/crates/oa-desktop/src/main.rs b/crates/oa-desktop/src/main.rs index 501b7ed4..7697af85 100644 --- a/crates/oa-desktop/src/main.rs +++ b/crates/oa-desktop/src/main.rs @@ -13,12 +13,14 @@ mod explorer_view; mod forms; mod guide; mod highlight; +mod home; mod hosted; mod model_menu; mod navigation; mod port_edit; mod port_preview; mod port_workflow; +mod recovery; mod route_edit; mod semantic_review; mod source_navigation; diff --git a/crates/oa-desktop/src/model_menu.rs b/crates/oa-desktop/src/model_menu.rs index 931e9324..65a20cf5 100644 --- a/crates/oa-desktop/src/model_menu.rs +++ b/crates/oa-desktop/src/model_menu.rs @@ -684,7 +684,7 @@ mod tests { #[test] fn right_click_targets_actor_without_changing_selection_until_action() { - let mut app = App::new(); + let mut app = App::with_project(Project::demo()); let actor = app .project() .elements @@ -717,7 +717,7 @@ mod tests { } #[test] fn menu_preserves_pending_properties_and_rejects_delayed_mutations() { - let mut app = App::new(); + let mut app = App::with_project(Project::demo()); let target = app.project().elements.keys().next().copied().unwrap(); let selected = app.selected; let model = app.project().clone(); @@ -747,7 +747,7 @@ mod tests { } #[test] fn late_menu_actions_do_not_dismiss_new_menu_or_cross_view_context() { - let mut app = App::new(); + let mut app = App::with_project(Project::demo()); let id = app.selected; send(&mut app, open(id, Point::ORIGIN)); let old = choose(&app, "Open source"); @@ -763,7 +763,7 @@ mod tests { } #[test] fn pending_source_allows_file_navigation_but_disables_model_actions() { - let mut app = App::new(); + let mut app = App::with_project(Project::demo()); send(&mut app, Message::Page(Page::Text)); send( &mut app, diff --git a/crates/oa-desktop/src/port_workflow.rs b/crates/oa-desktop/src/port_workflow.rs index 2eec945b..a66d75af 100644 --- a/crates/oa-desktop/src/port_workflow.rs +++ b/crates/oa-desktop/src/port_workflow.rs @@ -10,7 +10,7 @@ use iced::{ use oa_core::*; use std::collections::BTreeSet; -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub enum Action { Component, Input, @@ -83,7 +83,7 @@ pub fn actions(p: &Project, id: Id) -> Vec { } } -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub enum ContractKind { Input, Output, @@ -128,7 +128,104 @@ pub struct State { primary_input: bool, preview: Result, String>, } +#[derive(serde::Serialize, serde::Deserialize)] +#[serde(deny_unknown_fields)] +struct RecoveryState { + version: u32, + base: String, + action: Action, + subject: Id, + id: Id, + name: String, + kind: ContractKind, + orientation: ComponentPortOrientation, + oriented: bool, + boundary: Option, + source_port: PortChoice, + target_port: PortChoice, + incoming: BTreeSet, + outgoing: BTreeSet, + primary_input: bool, +} +#[cfg(test)] +mod recovery_tests { + use super::*; + #[test] + fn port_recovery_retains_authored_contract_and_identity_but_rebuilds_preview() { + let mut p = Project::empty(); + let owner = p + .add_element(Kind::Function, Layer::Sa, "Receiver", None) + .unwrap(); + let item = p + .add_element(Kind::ExchangeItem, Layer::Sa, "Payload", None) + .unwrap(); + let mut state = State::new(&p, owner, Action::Input).unwrap(); + state.name = " exact pending port ".into(); + state.incoming.insert(item); + state.primary_input = false; + state.preview = Err("An old transient preview must not be restored".into()); + let saved = state.recovery_snapshot().unwrap(); + assert!(saved.get("preview").is_none()); + let restored = State::from_recovery(&p, saved.clone()).unwrap(); + assert_eq!(restored.recovery_snapshot().unwrap(), saved); + assert_eq!(restored.id, state.id); + assert!(restored.preview.is_ok()); + p.name.push('!'); + assert!(State::from_recovery(&p, saved).is_err()); + } +} impl State { + pub fn recovery_snapshot(&self) -> Result { + serde_json::to_value(RecoveryState { + version: 1, + base: transitions::project_fingerprint(&self.baseline), + action: self.action, + subject: self.subject, + id: self.id, + name: self.name.clone(), + kind: self.kind, + orientation: self.orientation, + oriented: self.oriented, + boundary: self.boundary.as_ref().map(|c| c.id), + source_port: self.source_port.choice, + target_port: self.target_port.choice, + incoming: self.incoming.clone(), + outgoing: self.outgoing.clone(), + primary_input: self.primary_input, + }) + .map_err(|e| e.to_string()) + } + pub fn from_recovery(p: &Project, value: serde_json::Value) -> Result { + let saved: RecoveryState = serde_json::from_value(value).map_err(|e| e.to_string())?; + if saved.version != 1 || saved.base != transitions::project_fingerprint(p) { + return Err( + "Port workflow recovery does not match the accepted model identity and revision." + .into(), + ); + } + if saved.incoming.len().saturating_add(saved.outgoing.len()) + > crate::source_workspace::LIMITS.model.max_records + { + return Err("Recovered port draft exceeds item limits.".into()); + } + let mut state = Self::new(p, saved.subject, saved.action)?; + state.id = saved.id; + state.name = saved.name; + state.kind = saved.kind; + state.orientation = saved.orientation; + state.oriented = saved.oriented; + state.boundary = saved.boundary.map(|id| Choice { + id, + label: p.named(id).into(), + }); + state.source_port = workflow::recovery::endpoint(p, saved.source_port); + state.target_port = workflow::recovery::endpoint(p, saved.target_port); + state.incoming = saved.incoming; + state.outgoing = saved.outgoing; + state.primary_input = saved.primary_input; + state.preview = state.plan(p); + Ok(state) + } pub fn new(p: &Project, subject: Id, action: Action) -> Result { if !actions(p, subject).contains(&action) { return Err("This port action is unavailable for the selected element.".into()); diff --git a/crates/oa-desktop/src/recovery.rs b/crates/oa-desktop/src/recovery.rs new file mode 100644 index 00000000..bda28fd8 --- /dev/null +++ b/crates/oa-desktop/src/recovery.rs @@ -0,0 +1,419 @@ +//! Private crash checkpoints, separate from model files and hosted destinations. +use crate::{ + document::Document, + forms::{Draft, RecoveryDraft}, + source_workspace::{LIMITS, RecoveryWorkspace, Workspace}, +}; +use oa_core::{Id, source_project::SourceBundle}; +use serde::{Deserialize, Serialize}; +use std::{ + fs, + io::{Read, Write}, + path::{Path, PathBuf}, + time::{SystemTime, UNIX_EPOCH}, +}; + +const MAX_BYTES: u64 = 256 * 1024 * 1024; +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct Snapshot { + version: u32, + pub id: Id, + pub name: String, + pub origin: Option, + pub modified: u64, + accepted: SourceBundle, + workspace: RecoveryWorkspace, + property: Option, + behavior: Option, + #[serde(default)] + workflow: Option, + selected: Id, +} +pub struct Restored { + pub workflow: Option, + pub document: Document, + pub workspace: Workspace, + pub property: Option, + pub behavior: crate::behavior::State, + pub selected: Id, +} +#[derive(Debug, Clone)] +pub struct Entry { + pub id: Id, + pub name: String, + pub origin: Option, + pub modified: u64, +} +#[derive(Default)] +pub struct State { + pub directory: Option, + pub entries: Vec, + pub warning: Option, + pub writing: bool, + pub active: Option, + pub restored: Option, +} +impl State { + pub fn load_default() -> Self { + let Some(directory) = crate::home::data_directory().map(|p| p.join("recovery")) else { + return Self { + warning: Some( + "Local recovery is unavailable: no application data directory.".into(), + ), + ..Default::default() + }; + }; + let mut state = Self { + directory: Some(directory), + ..Default::default() + }; + state.refresh(); + state + } + pub fn refresh(&mut self) { + let Some(directory) = &self.directory else { + return; + }; + match list(directory) { + Ok((entries, warning)) => { + self.entries = entries; + self.warning = warning; + } + Err(error) => self.warning = Some(error), + } + } +} +impl Snapshot { + pub fn capture( + id: Id, + document: &Document, + workspace: &Workspace, + property: Option<&Draft>, + behavior: &crate::behavior::State, + origin: Option, + selected: Id, + ) -> Result { + Ok(Self { + version: 1, + id, + name: document.project().name.clone(), + origin, + modified: SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap_or_default() + .as_secs(), + accepted: document.bundle().clone(), + workspace: workspace.recovery_snapshot(), + property: property.filter(|d| d.dirty()).map(Draft::recovery_snapshot), + behavior: behavior.recovery_snapshot()?, + workflow: None, + selected, + }) + } + pub fn with_workflow( + mut self, + workflow: Option<&crate::workflow::Workflow>, + ) -> Result { + self.workflow = workflow + .map(crate::workflow::Workflow::recovery_snapshot) + .transpose()?; + Ok(self) + } + pub fn restore(self) -> Result { + if self.version != 1 { + return Err("This recovery checkpoint uses an unsupported version.".into()); + } + let mut document = + Document::from_bundle(self.accepted.clone(), LIMITS).map_err(|e| e.to_string())?; + if document.bundle() != &self.accepted { + return Err("Checkpoint accepted source requires migration or identity insertion; it cannot be recovered exactly.".into()); + } + let workspace = Workspace::from_recovery(self.workspace)?; + let property = self + .property + .map(|s| Draft::from_recovery(document.project(), s)) + .transpose()?; + let workflow = self + .workflow + .map(|value| crate::workflow::Workflow::from_recovery(document.project(), value)) + .transpose()?; + let mut behavior = crate::behavior::State::default(); + if let Some(saved) = self.behavior { + behavior.restore_recovery(saved)?; + } + document.mark_recovered(); + Ok(Restored { + workflow, + document, + workspace, + property, + behavior, + selected: self.selected, + }) + } +} +fn path(directory: &Path, id: Id) -> PathBuf { + directory.join(format!("{id}.json")) +} +pub fn write(directory: &Path, snapshot: &Snapshot) -> Result<(), String> { + // Rejected source remains editable, but must not replace the last checkpoint + // with buffers that the recovery reader cannot restore. + snapshot.workspace.validate()?; + fs::create_dir_all(directory) + .map_err(|e| format!("Cannot create local recovery directory: {e}"))?; + let bytes = serde_json::to_vec(snapshot).map_err(|e| e.to_string())?; + if bytes.len() as u64 > MAX_BYTES { + return Err( + "Checkpoint exceeds the 256 MiB recovery limit; save your work explicitly.".into(), + ); + } + let mut temp = tempfile::NamedTempFile::new_in(directory).map_err(|e| e.to_string())?; + temp.write_all(&bytes) + .and_then(|()| temp.as_file().sync_all()) + .map_err(|e| e.to_string())?; + temp.persist(path(directory, snapshot.id)) + .map_err(|e| e.to_string())?; + Ok(()) +} +pub fn read(directory: &Path, id: Id) -> Result { + let file = + fs::File::open(path(directory, id)).map_err(|e| format!("Cannot read checkpoint: {e}"))?; + if file.metadata().map_err(|e| e.to_string())?.len() > MAX_BYTES { + return Err("Checkpoint exceeds the recovery size limit.".into()); + } + let mut bytes = vec![]; + file.take(MAX_BYTES + 1) + .read_to_end(&mut bytes) + .map_err(|e| e.to_string())?; + if bytes.len() as u64 > MAX_BYTES { + return Err("Checkpoint exceeds the recovery size limit.".into()); + } + let snapshot: Snapshot = + serde_json::from_slice(&bytes).map_err(|e| format!("Checkpoint is damaged: {e}"))?; + if snapshot.version != 1 || snapshot.id != id { + return Err("Checkpoint identity or version does not match.".into()); + } + Ok(snapshot) +} +pub fn remove(directory: &Path, id: Id) -> Result<(), String> { + match fs::remove_file(path(directory, id)) { + Ok(()) => Ok(()), + Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(()), + Err(e) => Err(format!("Cannot retire checkpoint: {e}")), + } +} +fn list(directory: &Path) -> Result<(Vec, Option), String> { + let files = match fs::read_dir(directory) { + Ok(files) => files, + Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Ok((vec![], None)), + Err(e) => return Err(format!("Cannot list recovery checkpoints: {e}")), + }; + let mut entries = vec![]; + let mut rejected = 0; + for file in files { + let file = file.map_err(|e| e.to_string())?; + let file_path = file.path(); + if file_path.extension().and_then(|s| s.to_str()) != Some("json") { + continue; + } + let Some(id) = file_path + .file_stem() + .and_then(|s| s.to_str()) + .and_then(|s| Id::parse_str(s).ok()) + else { + continue; + }; + match read(directory, id) { + Ok(s) => entries.push(Entry { + id: s.id, + name: s.name, + origin: s.origin, + modified: s.modified, + }), + Err(_) => rejected += 1, + } + } + entries.sort_by_key(|e| std::cmp::Reverse(e.modified)); + Ok(( + entries, + (rejected > 0).then(|| { + format!( + "{rejected} damaged or incompatible checkpoint(s) were retained in {}.", + directory.display() + ) + }), + )) +} + +#[cfg(test)] +mod tests { + use super::*; + use oa_core::{Command, Project}; + fn snapshot() -> Snapshot { + let mut document = Document::from_project(Project::demo(), LIMITS).unwrap(); + document + .execute(Command::Rename( + document.project().id, + "Recovered engineering".into(), + )) + .unwrap(); + let workspace = Workspace::new(document.bundle(), true); + Snapshot::capture( + Id::new_v4(), + &document, + &workspace, + None, + &Default::default(), + Some(PathBuf::from("original.arcadia")), + document.project().id, + ) + .unwrap() + } + #[test] + fn exact_source_and_unfinished_invalid_buffers_restore_without_model_overwrite() { + let directory = tempfile::tempdir().unwrap(); + let mut s = snapshot(); + let mut workspace = Workspace::new(&s.accepted, true); + let active = workspace.active.clone(); + let original = workspace.text().to_owned(); + workspace.edit( + format!("{original}\nunfinished {{"), + workspace.files[&active].cursor, + ); + s.workspace = workspace.recovery_snapshot(); + let id = s.id; + let accepted = s.accepted.clone(); + write(directory.path(), &s).unwrap(); + let restored = read(directory.path(), id).unwrap().restore().unwrap(); + assert_eq!(restored.document.bundle(), &accepted); + assert!(restored.document.dirty()); + assert!(restored.workspace.dirty()); + assert!(restored.workspace.text().ends_with("unfinished {")); + assert!(!restored.document.can_undo()); + assert!(!directory.path().join("original.arcadia").exists()); + } + #[test] + fn multiple_sessions_replace_only_their_own_checkpoint_and_corruption_is_retained() { + let directory = tempfile::tempdir().unwrap(); + let a = snapshot(); + let b = snapshot(); + write(directory.path(), &a).unwrap(); + write(directory.path(), &b).unwrap(); + write(directory.path(), &a).unwrap(); + assert_eq!(list(directory.path()).unwrap().0.len(), 2); + fs::write(path(directory.path(), a.id), b"broken").unwrap(); + let (entries, warning) = list(directory.path()).unwrap(); + assert_eq!(entries.len(), 1); + assert!(warning.is_some()); + assert!(path(directory.path(), a.id).exists()); + remove(directory.path(), b.id).unwrap(); + assert!(path(directory.path(), a.id).exists()); + } + + #[test] + fn excess_retained_buffers_cannot_replace_the_last_recoverable_checkpoint() { + let directory = tempfile::tempdir().unwrap(); + let mut s = snapshot(); + write(directory.path(), &s).unwrap(); + let before = fs::read(path(directory.path(), s.id)).unwrap(); + // Source discovery retains detached buffers even when the accepted + // project itself remains within the file limit. + let mut workspace = Workspace::new(&s.accepted, true); + let detached = SourceBundle { + entry: "detached-0.arcadia".into(), + files: (0..LIMITS.max_files) + .map(|i| (format!("detached-{i}.arcadia"), "unfinished {".into())) + .collect(), + }; + workspace.discovered(&detached, workspace.reachable.clone()); + assert!(workspace.files.len() > LIMITS.max_files); + s.workspace = workspace.recovery_snapshot(); + let error = write(directory.path(), &s).unwrap_err(); + assert!(error.contains("file count")); + assert_eq!(fs::read(path(directory.path(), s.id)).unwrap(), before); + let restored = read(directory.path(), s.id).unwrap().restore().unwrap(); + assert_eq!(restored.document.project().name, "Recovered engineering"); + } + #[test] + fn raw_invalid_property_number_survives_and_stays_rejected_until_repaired() { + let mut s = snapshot(); + let document = Document::from_bundle(s.accepted.clone(), LIMITS).unwrap(); + let node = document + .project() + .diagrams + .values() + .flat_map(|d| d.nodes.keys()) + .next() + .copied() + .unwrap(); + let mut draft = Draft::from_project(document.project(), node).unwrap(); + draft.input("/position/x", "-".into()); + s.property = Some(draft.recovery_snapshot()); + let restored = s.restore().unwrap(); + let property = restored.property.unwrap(); + assert_eq!(property.inputs["/position/x"].raw, "-"); + assert!(property.materialize().is_err()); + assert!(property.dirty()); + } + #[test] + fn invalid_open_retains_distinct_accepted_model_and_repair_workspace() { + let mut s = snapshot(); + let invalid = SourceBundle { + entry: "broken.arcadia".into(), + files: [("broken.arcadia".into(), "unparsed draft".into())].into(), + }; + s.workspace = Workspace::new(&invalid, false).recovery_snapshot(); + let restored = s.restore().unwrap(); + assert_eq!(restored.document.project().name, "Recovered engineering"); + assert!(!restored.workspace.applied); + assert!(restored.workspace.dirty()); + assert_eq!(restored.workspace.text(), "unparsed draft"); + } + + #[test] + fn missing_includes_remain_reachable_repair_targets_without_fabricated_buffers() { + let mut s = snapshot(); + let mut workspace = Workspace::new(&s.accepted, true); + workspace + .reachable + .insert("missing/activities.arcadia".into()); + s.workspace = workspace.recovery_snapshot(); + let restored = s.restore().unwrap(); + assert!( + restored + .workspace + .reachable + .contains("missing/activities.arcadia") + ); + assert!( + !restored + .workspace + .files + .contains_key("missing/activities.arcadia") + ); + } + + #[test] + fn altered_accepted_identity_and_unrecognized_raw_property_fields_reject() { + let s = snapshot(); + let document = Document::from_bundle(s.accepted.clone(), LIMITS).unwrap(); + let draft = Draft::from_project(document.project(), document.project().id).unwrap(); + let mut raw = serde_json::to_value(draft.recovery_snapshot()).unwrap(); + raw["inputs"]["/unknown"] = serde_json::json!("must survive"); + let mut altered = s.clone(); + altered.property = Some(serde_json::from_value(raw).unwrap()); + assert!(altered.restore().err().unwrap().contains("raw buffers")); + let mut altered = s; + for text in altered.accepted.files.values_mut() { + *text = text.replace(&format!("id \"{}\";", document.project().id), ""); + } + // Identity-free accepted source is not silently upgraded during recovery. + // Use a fully valid new declaration to force compiler-assigned identity. + let entry = altered.accepted.entry.clone(); + let text = altered.accepted.files.get_mut(&entry).unwrap(); + let end = text.rfind('}').unwrap(); + text.insert_str(end, "\n oa_activity unassigned_identity \"Draft\" {}\n"); + assert!(altered.restore().is_err()); + } +} diff --git a/crates/oa-desktop/src/semantic_review.rs b/crates/oa-desktop/src/semantic_review.rs index 00788697..0a6c6852 100644 --- a/crates/oa-desktop/src/semantic_review.rs +++ b/crates/oa-desktop/src/semantic_review.rs @@ -11,17 +11,24 @@ use std::collections::{BTreeMap, BTreeSet}; mod constructors; mod history; mod keyed; +mod keyed_rows; mod message_kind; +mod ordered; +mod recovery; mod scalar; mod view; pub(crate) use view::summary; #[cfg(test)] mod constructor_tests; #[cfg(test)] +mod keyed_rows_tests; +#[cfg(test)] mod keyed_tests; #[cfg(test)] mod message_kind_tests; #[cfg(test)] +mod ordered_tests; +#[cfg(test)] mod tests; #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -65,9 +72,19 @@ pub enum Event { EditPair(FacetSelector), StageMember, DropStaged(FacetSelector), + AnnotationAddress(Layer, Option), + AnnotationBrowse(Layer, Option, usize), + #[cfg(test)] + RowExpression(FacetSelector, String), + RowText(FacetSelector, text_editor::Action), + RowLocal(FacetSelector, Choice), + OrderMove(OrderedCollection, usize, i32), + OrderReconcile(OrderedCollection), } pub struct State { + base_identity: Id, + base_fingerprint: String, pub anchor: Id, pub trace: Option, pub direction: TransitionDirection, @@ -78,6 +95,10 @@ pub struct State { pub resets: BTreeSet, pub staged: BTreeSet, pub presence: BTreeMap, + pub row_drafts: BTreeMap, + pub order_drafts: BTreeMap>, + pub annotation_oa: keyed_rows::AddressDraft, + pub annotation_sa: keyed_rows::AddressDraft, pub member_family: keyed::Family, pub member_oa: Option, pub member_sa: Option, @@ -118,6 +139,8 @@ impl State { .map(|t| t.id) .or_else(|| (choices.len() == 1).then(|| choices[0].id)); let mut state = Self { + base_identity: project.id, + base_fingerprint: project_fingerprint(project), anchor, trace, direction: if layer == Layer::Oa { @@ -132,6 +155,10 @@ impl State { resets: BTreeSet::new(), staged: BTreeSet::new(), presence: BTreeMap::new(), + row_drafts: BTreeMap::new(), + order_drafts: BTreeMap::new(), + annotation_oa: Default::default(), + annotation_sa: Default::default(), member_family: keyed::Family::Literal, member_oa: None, member_sa: None, @@ -169,6 +196,10 @@ impl State { self.resets.clear(); self.staged.clear(); self.presence.clear(); + self.row_drafts.clear(); + self.order_drafts.clear(); + self.annotation_oa = Default::default(); + self.annotation_sa = Default::default(); self.member_oa = None; self.member_sa = None; self.literal.clear(); @@ -227,6 +258,7 @@ impl State { } Event::Select(facet) => { self.selected = Some(facet); + self.tab = Tab::Facts; replan = false; Ok(()) } @@ -238,7 +270,16 @@ impl State { { Err("This fact needs the structured review workflow; its history remains unchanged.".into()) } else { - if keyed::supported(&facet) { + if let FacetSelector::Order(which) = facet + && decision == SemanticDecision::Candidate + { + if let Err(error) = self.initialize_order(project, which) { + self.error = Some(error); + return; + } + } else if keyed_rows::supported(&facet) { + self.initialize_row(project, &facet); + } else if keyed::supported(&facet) { let target_presence = self .effective_trace(project) .and_then(|t| capture_facet(project, t.oa, t.sa, &facet).ok()) @@ -332,6 +373,8 @@ impl State { return; } Event::Preview => Ok(()), + Event::OrderMove(which, index, delta) => self.move_order(which, index, delta), + Event::OrderReconcile(which) => self.reconcile_order(project, which), Event::Reset(facet, reset) => self.reset_history(project, facet, reset), Event::Presence(facet, present) => { if keyed::supported(&facet) @@ -383,6 +426,30 @@ impl State { } Ok(()) } + Event::AnnotationAddress(layer, key) => { + self.annotation_address_mut(layer).selected = key; + replan = false; + Ok(()) + } + Event::AnnotationBrowse(layer, scope, page) => { + let address = self.annotation_address_mut(layer); + address.scope = scope; + address.page = page; + replan = false; + Ok(()) + } + #[cfg(test)] + Event::RowExpression(facet, expression) => self.edit_row(&facet, |draft| { + draft.editor = text_editor::Content::with_text(&expression); + draft.expression = expression; + }), + Event::RowText(facet, action) => self.edit_row(&facet, |draft| { + draft.editor.perform(action); + draft.expression = draft.editor.text(); + }), + Event::RowLocal(facet, local) => self.edit_row(&facet, |draft| { + draft.local = Some(local); + }), }; if let Err(error) = result { self.error = Some(error); @@ -431,7 +498,7 @@ impl State { } pub fn editable(&self, project: &Project, facet: &FacetSelector) -> bool { - if keyed::supported(facet) { + if keyed::supported(facet) || matches!(facet, FacetSelector::Order(_)) { return self .effective_trace(project) .is_some_and(|trace| capture_facet(project, trace.oa, trace.sa, facet).is_ok()); @@ -537,6 +604,15 @@ impl State { /// controls. Incomplete inputs retain their stored shape only for controls; /// request construction always requires every selected input to parse. fn combined_value(&self, project: &Project, form_context: bool) -> Result { + self.combine(project, form_context, false) + } + + fn combine( + &self, + project: &Project, + form_context: bool, + skip_orders: bool, + ) -> Result { let trace = self.effective_trace(project).ok_or("Choose a trace.")?; let (source, target) = if self.direction == TransitionDirection::OaToSa { (trace.oa, trace.sa) @@ -564,15 +640,47 @@ impl State { // overlapping replacement scopes; this order also supports first-equal // discriminator acknowledgment together with explicit descendant values. let mut decisions: Vec<_> = self.decisions.iter().collect(); - decisions.sort_by_key(|(facet, _)| match facet { - FacetSelector::Occurrence(OccurrenceFacet::Constructor) => 0, - facet if scalar::grouped(facet) => 1, - _ => 2, + decisions.sort_by_key(|(facet, _)| { + let group = match facet { + FacetSelector::Occurrence(OccurrenceFacet::Constructor) => 0, + facet if scalar::grouped(facet) => 1, + FacetSelector::Order(_) => 3, + _ => 2, + }; + ( + group, + keyed_rows::target_selector(facet, self.direction.target_layer()), + ) }); for (facet, decision) in decisions { if *decision == SemanticDecision::KeepTarget { continue; } + if let FacetSelector::Order(which) = facet { + if !skip_orders { + // Invalid order drafts remain visible while dependent controls use the + // surviving membership; request construction still rejects them. + if let Err(error) = + self.project_order(project, &trace, &mut final_value, *which, *decision) + && !form_context + { + return Err(error); + } + } + continue; + } + if matches!(facet, FacetSelector::Member { collection, .. } if ordered::ordered(*collection).is_some()) + { + self.project_ordered_member( + project, + &trace, + &mut final_value, + facet, + *decision, + form_context, + )?; + continue; + } if keyed::supported(facet) { self.keyed_value(project, &trace, &mut final_value, facet, *decision)?; continue; diff --git a/crates/oa-desktop/src/semantic_review/keyed.rs b/crates/oa-desktop/src/semantic_review/keyed.rs index 91595a4c..0c27ed9e 100644 --- a/crates/oa-desktop/src/semantic_review/keyed.rs +++ b/crates/oa-desktop/src/semantic_review/keyed.rs @@ -1,10 +1,12 @@ //! Closed presence-only adapter. The core independently validates the full final record. use super::*; -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub enum Family { Member(ReferenceCollection), Literal, + Annotation, + Binding, } impl std::fmt::Display for Family { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { @@ -16,12 +18,17 @@ impl std::fmt::Display for Family { Self::Member(ReferenceCollection::TransitionTriggers) => "Transition triggers", Self::Member(ReferenceCollection::TransitionEffects) => "Transition effects", Self::Member(ReferenceCollection::Constrained) => "Constrained identities", + Self::Member(ReferenceCollection::Steps) => "Ordered steps", + Self::Member(ReferenceCollection::FragmentBranches) => "Fragment branches", Self::Literal => "Enumeration literals", - _ => "Ordered membership (read-only)", + Self::Annotation => "Message values", + Self::Binding => "Scenario lifeline bindings", }) } } -pub(super) const FAMILIES: [Family; 8] = [ +pub(super) const FAMILIES: [Family; 12] = [ + Family::Member(ReferenceCollection::Steps), + Family::Member(ReferenceCollection::FragmentBranches), Family::Member(ReferenceCollection::FunctionInputItems), Family::Member(ReferenceCollection::FunctionOutputItems), Family::Member(ReferenceCollection::SignalItems), @@ -30,11 +37,15 @@ pub(super) const FAMILIES: [Family; 8] = [ Family::Member(ReferenceCollection::TransitionEffects), Family::Member(ReferenceCollection::Constrained), Family::Literal, + Family::Annotation, + Family::Binding, ]; pub(super) fn family(facet: &FacetSelector) -> Option { let family = match facet { FacetSelector::Member { collection, .. } => Family::Member(*collection), FacetSelector::Literal(_) => Family::Literal, + FacetSelector::Annotation(_) => Family::Annotation, + FacetSelector::Binding(_) => Family::Binding, _ => return None, }; FAMILIES.contains(&family).then_some(family) @@ -43,11 +54,27 @@ pub(super) fn supported(facet: &FacetSelector) -> bool { family(facet).is_some() } -// Fixed paths for the seven unordered collections and the exact enum literal set. +// Fixed paths for the closed collections and the exact enum literal set. // They are selected by the typed facet; callers cannot supply arbitrary paths. pub(super) fn path(value: &Value, family: Family) -> Option { let prefixes = ["/body/Oa/Shared", "/body/Sa/Shared"]; let tails: &[&str] = match family { + Family::Member(ReferenceCollection::Steps) => { + return [ + "/body/Oa/Process/steps", + "/body/Sa/FunctionalChain/steps", + "/body/Oa/Shared/Scenario/steps", + "/body/Sa/Shared/Scenario/steps", + "/body/Oa/Shared/Occurrence/body/Branch/occurrences", + "/body/Sa/Shared/Occurrence/body/Branch/occurrences", + ] + .into_iter() + .find(|p| value.pointer(p).is_some_and(Value::is_array)) + .map(str::to_owned); + } + Family::Member(ReferenceCollection::FragmentBranches) => { + &["Occurrence/body/Fragment/branches"] + } Family::Member(ReferenceCollection::FunctionInputItems) => { &["Port/contract/FunctionInput/incoming_items"] } @@ -57,6 +84,8 @@ pub(super) fn path(value: &Value, family: Family) -> Option { Family::Member(ReferenceCollection::SignalItems) => &["Event/body/Signal/items"], Family::Member(ReferenceCollection::Constrained) => &["Constraint/constrained"], Family::Literal => &["DataType/definition/Enumeration/literals"], + Family::Annotation => &["Occurrence/body/Exchange/values"], + Family::Binding => &["Occurrence/body/ScenarioUse/bindings"], Family::Member(ReferenceCollection::ExchangeItems) => { return ["Interaction", "FunctionalExchange", "BoundaryExchange"] .iter() @@ -75,14 +104,13 @@ pub(super) fn path(value: &Value, family: Family) -> Option { .is_some_and(Value::is_array) .then(|| "/body/StateTransition/effects".into()); } - _ => return None, }; prefixes .iter() .flat_map(|prefix| tails.iter().map(move |tail| format!("{prefix}/{tail}"))) .find(|p| value.pointer(p).is_some_and(Value::is_array)) } -fn key(facet: &FacetSelector, layer: Layer) -> Option { +pub(super) fn key(facet: &FacetSelector, layer: Layer) -> Option { match facet { FacetSelector::Member { keys, .. } => if layer == Layer::Oa { keys.oa } else { keys.sa } .map(|id| serde_json::to_value(id).expect("Id")), @@ -92,6 +120,14 @@ fn key(facet: &FacetSelector, layer: Layer) -> Option { keys.sa.as_ref() } .map(|v| Value::String(v.clone())), + FacetSelector::Annotation(keys) => if layer == Layer::Oa { + keys.oa.as_ref() + } else { + keys.sa.as_ref() + } + .map(|key| serde_json::to_value(key).expect("Annotation key")), + FacetSelector::Binding(keys) => if layer == Layer::Oa { keys.oa } else { keys.sa } + .map(|id| serde_json::to_value(id).expect("Binding key")), _ => None, } } @@ -137,6 +173,9 @@ impl State { facet: &FacetSelector, decision: SemanticDecision, ) -> Result<(), String> { + if keyed_rows::supported(facet) { + return self.project_row(project, trace, value, facet, decision); + } let present = match decision { SemanticDecision::KeepTarget => return Ok(()), SemanticDecision::TakeSource => { @@ -170,6 +209,9 @@ impl State { } pub(super) fn member_choices(&self, project: &Project, layer: Layer) -> Vec { + if self.member_family == Family::Binding { + return self.binding_choices(project, layer, true); + } let Some(trace) = self.effective_trace(project) else { return vec![]; }; @@ -210,6 +252,20 @@ impl State { ) -> Result<(), String> { self.member_family = family(&facet).ok_or("This keyed family remains read-only.")?; match facet { + FacetSelector::Annotation(keys) => { + self.annotation_oa.selected = keys.oa.map(|key| crate::data_values::FieldPathKey { + path: key.path, + field: key.field, + }); + self.annotation_sa.selected = keys.sa.map(|key| crate::data_values::FieldPathKey { + path: key.path, + field: key.field, + }); + } + FacetSelector::Binding(keys) => { + self.member_oa = keys.oa.map(|id| view::choice(project, id)); + self.member_sa = keys.sa.map(|id| view::choice(project, id)); + } FacetSelector::Member { keys, .. } => { self.member_oa = keys.oa.map(|id| view::choice(project, id)); self.member_sa = keys.sa.map(|id| view::choice(project, id)); @@ -241,9 +297,32 @@ impl State { oa: self.literal_oa.then(|| self.literal.clone()), sa: self.literal_sa.then(|| self.literal.clone()), }), + Family::Annotation => FacetSelector::Annotation(KeyPair { + oa: self + .annotation_oa + .selected + .as_ref() + .map(|key| AnnotationKey { + path: key.path.clone(), + field: key.field, + }), + sa: self + .annotation_sa + .selected + .as_ref() + .map(|key| AnnotationKey { + path: key.path.clone(), + field: key.field, + }), + }), + Family::Binding => FacetSelector::Binding(KeyPair { + oa: self.member_oa.as_ref().map(|c| c.id), + sa: self.member_sa.as_ref().map(|c| c.id), + }), }; let trace = self.effective_trace(project).ok_or("Choose a trace.")?; capture_facet(project, trace.oa, trace.sa, &facet)?; + self.stage_row_references(&facet)?; if let FacetSelector::Member { keys: KeyPair { diff --git a/crates/oa-desktop/src/semantic_review/keyed_rows.rs b/crates/oa-desktop/src/semantic_review/keyed_rows.rs new file mode 100644 index 00000000..54450cc0 --- /dev/null +++ b/crates/oa-desktop/src/semantic_review/keyed_rows.rs @@ -0,0 +1,345 @@ +//! Atomic message-value and lifeline-binding rows with exact, immutable keys. +use super::*; +use crate::data_values::{FieldPathChoice, FieldPathKey}; + +#[derive(Default)] +pub struct AddressDraft { + pub selected: Option, + pub scope: Option, + pub page: usize, +} + +#[derive(Default)] +pub struct RowDraft { + pub expression: String, + pub editor: text_editor::Content, + pub local: Option, +} + +pub(super) fn supported(facet: &FacetSelector) -> bool { + matches!( + facet, + FacetSelector::Annotation(_) | FacetSelector::Binding(_) + ) +} + +pub(super) fn target_selector(facet: &FacetSelector, layer: Layer) -> FacetSelector { + match facet { + FacetSelector::Annotation(keys) => FacetSelector::Annotation(KeyPair { + oa: (layer == Layer::Oa).then(|| keys.oa.clone()).flatten(), + sa: (layer == Layer::Sa).then(|| keys.sa.clone()).flatten(), + }), + FacetSelector::Binding(keys) => FacetSelector::Binding(KeyPair { + oa: if layer == Layer::Oa { keys.oa } else { None }, + sa: if layer == Layer::Sa { keys.sa } else { None }, + }), + _ => facet.clone(), + } +} + +impl State { + pub(super) fn annotation_address(&self, layer: Layer) -> &AddressDraft { + if layer == Layer::Oa { + &self.annotation_oa + } else { + &self.annotation_sa + } + } + + pub(super) fn annotation_address_mut(&mut self, layer: Layer) -> &mut AddressDraft { + if layer == Layer::Oa { + &mut self.annotation_oa + } else { + &mut self.annotation_sa + } + } + + fn row_owner(&self, project: &Project, layer: Layer) -> Option { + let trace = self.effective_trace(project)?; + let id = if layer == Layer::Oa { + trace.oa + } else { + trace.sa + }; + if layer == self.direction.target_layer() + && let Ok(value) = self.combined_value(project, true) + && let Ok(element) = serde_json::from_value(value) + { + return Some(element); + } + project.elements.get(&id).cloned() + } + + pub(super) fn annotation_choices( + &self, + project: &Project, + layer: Layer, + ) -> Result, String> { + let element = self.row_owner(project, layer).ok_or("Choose a trace.")?; + let Some(SharedBody::Occurrence { + body: + OccurrenceBody::Exchange { + exchange: Some(exchange), + message_kind, + .. + }, + .. + }) = element.shared() + else { + return Err( + "Choose an exchange on this message before selecting a carried field.".into(), + ); + }; + let exchange = project + .relations + .get(exchange) + .ok_or("The message exchange is unavailable.")?; + crate::data_values::at_scope( + project, + exchange, + message_kind, + self.annotation_address(layer).scope.as_ref(), + ) + } + + pub(super) fn binding_choices( + &self, + project: &Project, + layer: Layer, + referenced: bool, + ) -> Vec { + let Some(element) = self.row_owner(project, layer) else { + return vec![]; + }; + let scenario = if referenced { + match element.shared() { + Some(SharedBody::Occurrence { + body: OccurrenceBody::ScenarioUse { scenario, .. }, + .. + }) => *scenario, + _ => None, + } + } else { + element + .parent + .and_then(|parent| validation::ancestor_of_kind(project, parent, Kind::Scenario)) + }; + let mut choices: Vec<_> = project + .elements + .values() + .filter(|e| { + e.kind() == Kind::Lifeline + && e.layer() == layer + && scenario.is_some() + && e.parent == scenario + }) + .map(|e| view::choice(project, e.id)) + .collect(); + choices.sort_by_cached_key(|c| c.label.to_lowercase()); + choices + } + + pub(super) fn stage_row_references(&mut self, facet: &FacetSelector) -> Result<(), String> { + let pairs: Vec<_> = match facet { + FacetSelector::Annotation(KeyPair { + oa: Some(oa), + sa: Some(sa), + }) => { + if oa.path.len() != sa.path.len() { + return Err("Paired annotation paths must have the same depth; review distinct addresses separately.".into()); + } + oa.path + .iter() + .chain(std::iter::once(&oa.field)) + .copied() + .zip(sa.path.iter().chain(std::iter::once(&sa.field)).copied()) + .collect() + } + FacetSelector::Binding(KeyPair { + oa: Some(oa), + sa: Some(sa), + }) => vec![(*oa, *sa)], + _ => vec![], + }; + // A repeated recursive field may occur several times in one address. + // Never let the last occurrence silently replace a conflicting pair. + let mut supplied = BTreeMap::new(); + for (oa, sa) in pairs { + if supplied.insert(oa, sa).is_some_and(|prior| prior != sa) { + return Err( + "This address pairs the same Operational field with different System fields." + .into(), + ); + } + } + self.references.extend(supplied); + Ok(()) + } + + pub(super) fn initialize_row(&mut self, project: &Project, facet: &FacetSelector) { + if self.row_drafts.contains_key(facet) { + return; + } + let value = self + .effective_trace(project) + .and_then(|t| capture_facet(project, t.oa, t.sa, facet).ok()) + .and_then(|f| scalar::value(&f, self.direction.target_layer())); + let present = value.as_ref().is_some_and(|v| !v.is_null()); + let mut draft = RowDraft::default(); + if let Some(value) = value { + draft.expression = value["expression"].as_str().unwrap_or_default().into(); + draft.editor = text_editor::Content::with_text(&draft.expression); + draft.local = value["local"] + .as_str() + .and_then(|v| v.parse().ok()) + .map(|id| view::choice(project, id)); + } + self.presence.entry(facet.clone()).or_insert(present); + self.row_drafts.insert(facet.clone(), draft); + } + + pub(super) fn edit_row( + &mut self, + facet: &FacetSelector, + action: impl FnOnce(&mut RowDraft), + ) -> Result<(), String> { + if !supported(facet) + || self.selected.as_ref() != Some(facet) + || self.decisions.get(facet) != Some(&SemanticDecision::Candidate) + { + return Err("Select this row and choose Set final value before editing it.".into()); + } + action( + self.row_drafts + .get_mut(facet) + .ok_or("The row draft is unavailable.")?, + ); + Ok(()) + } + + pub(super) fn project_row( + &self, + project: &Project, + trace: &Trace, + value: &mut Value, + facet: &FacetSelector, + decision: SemanticDecision, + ) -> Result<(), String> { + if decision == SemanticDecision::KeepTarget { + return Ok(()); + } + let source_layer = self.direction.source_layer(); + let target_layer = self.direction.target_layer(); + if decision == SemanticDecision::TakeSource && keyed::key(facet, source_layer).is_none() { + return Ok(()); + } + let target_key = keyed::key(facet, target_layer) + .ok_or("Choose the destination row address before setting or taking its value.")?; + let row = if decision == SemanticDecision::Candidate { + if !self.presence.get(facet).copied().unwrap_or(false) { + Value::Null + } else { + let draft = self + .row_drafts + .get(facet) + .ok_or("Set the final row value.")?; + match facet { + FacetSelector::Annotation(_) => { + let key: AnnotationKey = serde_json::from_value(target_key.clone()) + .map_err(|e| e.to_string())?; + serde_json::to_value(MessageValue { + path: key.path, + field: key.field, + expression: draft.expression.clone(), + }) + .expect("Message value") + } + FacetSelector::Binding(_) => { + serde_json::json!({"referenced": target_key, "local": draft.local.as_ref().ok_or("Choose the destination scenario's local lifeline.")?.id}) + } + _ => return Err("Unsupported row.".into()), + } + } + } else { + let fact = capture_facet(project, trace.oa, trace.sa, facet)?; + let row = scalar::value(&fact, source_layer) + .ok_or("Source row collection is not applicable; review its constructor first.")?; + if row.is_null() { + row + } else { + let map = |id: Id| -> Result { + if source_layer == Layer::Oa { + self.references.get(&id).copied().ok_or_else(|| { + format!("Choose a reference counterpart for {}.", project.named(id)) + }) + } else { + let matches: Vec<_> = self + .references + .iter() + .filter(|(_, sa)| **sa == id) + .map(|(oa, _)| *oa) + .collect(); + if matches.len() == 1 { + Ok(matches[0]) + } else { + Err(format!( + "Choose one unambiguous reference counterpart for {}.", + project.named(id) + )) + } + } + }; + match facet { + FacetSelector::Annotation(_) => { + let mut row: MessageValue = + serde_json::from_value(row).map_err(|e| e.to_string())?; + row.path = row.path.into_iter().map(map).collect::>()?; + row.field = map(row.field)?; + if serde_json::to_value(AnnotationKey::from(&row)).expect("key") + != target_key + { + return Err("Mapped annotation address disagrees with the selected destination address.".into()); + } + serde_json::to_value(row).expect("row") + } + FacetSelector::Binding(_) => { + let row: LifelineBinding = + serde_json::from_value(row).map_err(|e| e.to_string())?; + let mapped = LifelineBinding { + referenced: map(row.referenced)?, + local: map(row.local)?, + }; + if serde_json::to_value(mapped.referenced).expect("Id") != target_key { + return Err("Mapped binding key disagrees with the selected destination lifeline.".into()); + } + serde_json::to_value(mapped).expect("row") + } + _ => return Err("Unsupported row.".into()), + } + } + }; + let family = keyed::family(facet).ok_or("Unsupported row.")?; + let path = keyed::path(value, family) + .ok_or("The destination constructor has no collection for this row.")?; + let rows = value + .pointer_mut(&path) + .and_then(Value::as_array_mut) + .ok_or("Destination rows are unavailable.")?; + let position = rows.iter().position(|row| match facet { + FacetSelector::Annotation(_) => { + serde_json::json!({"path": row["path"], "field": row["field"]}) == target_key + } + FacetSelector::Binding(_) => row["referenced"] == target_key, + _ => false, + }); + match (position, row.is_null()) { + (Some(index), false) => rows[index] = row, + (Some(index), true) => { + rows.remove(index); + } + (None, false) => rows.push(row), + (None, true) => {} + } + Ok(()) + } +} diff --git a/crates/oa-desktop/src/semantic_review/keyed_rows_tests.rs b/crates/oa-desktop/src/semantic_review/keyed_rows_tests.rs new file mode 100644 index 00000000..2d02a748 --- /dev/null +++ b/crates/oa-desktop/src/semantic_review/keyed_rows_tests.rs @@ -0,0 +1,736 @@ +use super::{tests::*, *}; +use crate::{app::App, document::Document}; + +struct Fixture { + p: Project, + owners: [Id; 2], + trace: Id, + facet: FacetSelector, + sibling: FacetSelector, + locals: [Id; 2], +} +fn errors(s: &State) -> String { + s.error + .iter() + .chain(s.plan.iter().flat_map(|p| &p.conflicts)) + .cloned() + .collect::>() + .join("; ") +} +fn add(p: &mut Project, kind: Kind, layer: Layer, parent: Option) -> Id { + p.add_element(kind, layer, kind.label(), parent).unwrap() +} +fn fixture(binding: bool) -> Fixture { + let mut p = Project::empty(); + let mut owners = vec![]; + let mut keys = vec![]; + let mut sibling_keys = vec![]; + let mut locals = vec![]; + for layer in [Layer::Oa, Layer::Sa] { + let scenario = add(&mut p, Kind::Scenario, layer, None); + let owner = add(&mut p, Kind::Occurrence, layer, Some(scenario)); + p.elements + .get_mut(&scenario) + .unwrap() + .steps_mut() + .unwrap() + .push(owner); + let body = if binding { + let reused = add(&mut p, Kind::Scenario, layer, None); + let referenced = add(&mut p, Kind::Lifeline, layer, Some(reused)); + let sibling = add(&mut p, Kind::Lifeline, layer, Some(reused)); + let local = add(&mut p, Kind::Lifeline, layer, Some(scenario)); + let sibling_local = add(&mut p, Kind::Lifeline, layer, Some(scenario)); + keys.push(AnnotationKey { + path: vec![], + field: referenced, + }); + sibling_keys.push(AnnotationKey { + path: vec![], + field: sibling, + }); + locals.push(local); + OccurrenceBody::ScenarioUse { + scenario: Some(reused), + bindings: vec![ + LifelineBinding { referenced, local }, + LifelineBinding { + referenced: sibling, + local: sibling_local, + }, + ], + } + } else { + let work = if layer == Layer::Oa { + Kind::Activity + } else { + Kind::Function + }; + let from = add(&mut p, work, layer, None); + let to = add(&mut p, work, layer, None); + let item = add(&mut p, Kind::ExchangeItem, layer, None); + let record = add(&mut p, Kind::DataType, layer, None); + *p.elements.get_mut(&record).unwrap().shared_mut().unwrap() = SharedBody::DataType { + definition: DataTypeDefinition::Record { supertype: None }, + default: None, + unit: String::new(), + }; + let first = add(&mut p, Kind::DataField, layer, Some(item)); + let second = add(&mut p, Kind::DataField, layer, Some(item)); + for field in [first, second] { + if let Some(SharedBody::DataField { type_ref, .. }) = + p.elements.get_mut(&field).unwrap().shared_mut() + { + *type_ref = TypeRef::Named(record); + } + } + let leaf = add(&mut p, Kind::DataField, layer, Some(record)); + let exchange = p.relate( + if layer == Layer::Oa { + RelationKind::Interaction + } else { + RelationKind::FunctionalExchange + }, + "Carrier", + from, + to, + ); + let ports = match &mut p.relations.get_mut(&exchange).unwrap().body { + RelationBody::Interaction { items, .. } => { + items.push(item); + None + } + RelationBody::FunctionalExchange { + items, + source_port, + target_port, + .. + } => { + items.push(item); + Some((*source_port, *target_port)) + } + _ => unreachable!(), + }; + if let Some((output, input)) = ports { + if let Some(SharedBody::Port { + contract: PortContract::FunctionOutput { outgoing_items }, + .. + }) = p.elements.get_mut(&output).unwrap().shared_mut() + { + outgoing_items.push(item); + } + if let Some(SharedBody::Port { + contract: PortContract::FunctionInput { incoming_items }, + .. + }) = p.elements.get_mut(&input).unwrap().shared_mut() + { + incoming_items.push(item); + } + } + keys.push(AnnotationKey { + path: vec![first], + field: leaf, + }); + sibling_keys.push(AnnotationKey { + path: vec![second], + field: leaf, + }); + locals.push(leaf); + OccurrenceBody::Exchange { + exchange: Some(exchange), + sender: None, + receiver: None, + message_kind: MessageKind::Asynchronous, + reply_to: None, + values: vec![ + MessageValue { + path: vec![first], + field: leaf, + expression: if layer == Layer::Oa { + "operational" + } else { + "system" + } + .into(), + }, + MessageValue { + path: vec![second], + field: leaf, + expression: "sibling stays".into(), + }, + ], + } + }; + *p.elements.get_mut(&owner).unwrap().shared_mut().unwrap() = SharedBody::Occurrence { + body, + precondition: "Retain precondition".into(), + postcondition: "Retain postcondition".into(), + }; + owners.push(owner); + } + let trace = add_trace(&mut p, owners[0], owners[1], "Individual row review"); + let facet = |keys: &[AnnotationKey]| { + if binding { + FacetSelector::Binding(KeyPair { + oa: Some(keys[0].field), + sa: Some(keys[1].field), + }) + } else { + FacetSelector::Annotation(KeyPair { + oa: Some(keys[0].clone()), + sa: Some(keys[1].clone()), + }) + } + }; + validate_candidate(&p).unwrap(); + Fixture { + p, + owners: [owners[0], owners[1]], + trace, + facet: facet(&keys), + sibling: facet(&sibling_keys), + locals: [locals[0], locals[1]], + } +} +fn ready(f: &Fixture, reverse: bool) -> State { + let mut s = State::new(&f.p, f.owners[usize::from(reverse)]).unwrap(); + s.stage_row_references(&f.facet).unwrap(); + s.references.insert(f.locals[0], f.locals[1]); + s.update(&f.p, Event::Select(f.facet.clone())); + s +} + +#[test] +fn independent_rows_keep_take_candidate_match_core_and_preserve_siblings_both_directions() { + for binding in [false, true] { + for reverse in [false, true] { + for decision in [ + SemanticDecision::KeepTarget, + SemanticDecision::TakeSource, + SemanticDecision::Candidate, + ] { + let f = fixture(binding); + let mut s = ready(&f, reverse); + let layer = s.direction.target_layer(); + let before_sibling = scalar::value( + &capture_facet(&f.p, f.owners[0], f.owners[1], &f.sibling).unwrap(), + layer, + ); + s.update(&f.p, Event::Decide(f.facet.clone(), Some(decision))); + if decision == SemanticDecision::Candidate { + s.update(&f.p, Event::Presence(f.facet.clone(), !binding)); + if !binding { + s.update( + &f.p, + Event::RowExpression(f.facet.clone(), f.locals[0].to_string()), + ); + } + } + // Force the GUI to compose the row alongside another candidate, + // so Take/Keep also exercise adapter scope and mapped references. + let header = FacetSelector::Header(HeaderFacet::Description); + s.update(&f.p, Event::Select(header.clone())); + s.update( + &f.p, + Event::Decide(header, Some(SemanticDecision::Candidate)), + ); + s.update( + &f.p, + Event::Input("/description".into(), "Reviewed one row".into()), + ); + assert!( + s.can_apply(), + "{binding}/{reverse}/{decision:?}: {}", + errors(&s) + ); + let proposed = s.proposed.as_ref().unwrap().clone(); + assert_eq!( + scalar::value( + &capture_facet(&proposed, f.owners[0], f.owners[1], &f.sibling).unwrap(), + layer + ), + before_sibling + ); + let current = scalar::value( + &capture_facet(&proposed, f.owners[0], f.owners[1], &f.facet).unwrap(), + layer, + ) + .unwrap(); + if decision == SemanticDecision::Candidate { + if binding { + assert!(current.is_null()); + } else { + assert_eq!(current["expression"], f.locals[0].to_string()); + } + } + assert_eq!( + prepare_command(&f.p, &s.command(&f.p).unwrap()) + .unwrap() + .project, + proposed + ); + let _ = s.view(&f.p); + } + } + } +} + +#[test] +fn keyed_row_drafts_retain_expression_local_and_presence_across_decisions_and_staging() { + for binding in [false, true] { + let f = fixture(binding); + let mut s = ready(&f, false); + s.staged.insert(f.facet.clone()); + s.update( + &f.p, + Event::Decide(f.facet.clone(), Some(SemanticDecision::Candidate)), + ); + if binding { + s.update( + &f.p, + Event::RowLocal(f.facet.clone(), view::choice(&f.p, f.locals[1])), + ); + } else { + s.update( + &f.p, + Event::RowExpression(f.facet.clone(), "unfinished (\n literal UUID text".into()), + ); + } + s.update(&f.p, Event::Presence(f.facet.clone(), false)); + s.update( + &f.p, + Event::Decide(f.facet.clone(), Some(SemanticDecision::KeepTarget)), + ); + s.update(&f.p, Event::DropStaged(f.facet.clone())); + s.update(&f.p, Event::Select(f.facet.clone())); + s.update( + &f.p, + Event::Decide(f.facet.clone(), Some(SemanticDecision::Candidate)), + ); + assert!(!s.presence[&f.facet]); + if binding { + assert_eq!( + s.row_drafts[&f.facet].local.as_ref().unwrap().id, + f.locals[1] + ); + } else { + assert_eq!( + s.row_drafts[&f.facet].expression, + "unfinished (\n literal UUID text" + ); + } + assert!(s.can_apply(), "{}", errors(&s)); + // A forged event for an unselected sibling cannot alter its row draft. + s.update( + &f.p, + Event::RowExpression(f.sibling.clone(), "wrong row".into()), + ); + assert!(s.error.as_ref().unwrap().contains("Select this row")); + } +} + +#[test] +fn annotation_address_browser_descends_qualified_fields_without_changing_rows() { + let f = fixture(false); + let mut s = ready(&f, false); + s.update(&f.p, Event::MemberFamily(keyed::Family::Annotation)); + for layer in [Layer::Oa, Layer::Sa] { + let roots = s.annotation_choices(&f.p, layer).unwrap(); + assert_eq!(roots.len(), 2); + let key = roots[0].key.clone(); + s.update(&f.p, Event::AnnotationBrowse(layer, Some(key.clone()), 0)); + let nested = s.annotation_choices(&f.p, layer).unwrap(); + assert_eq!(nested.len(), 1); + assert_eq!(nested[0].key.path, vec![key.field]); + s.update( + &f.p, + Event::AnnotationAddress(layer, Some(nested[0].key.clone())), + ); + } + s.update(&f.p, Event::StageMember); + assert!(s.error.is_none(), "{}", errors(&s)); + assert_eq!(s.staged.len(), 1); + assert!(s.decisions.is_empty()); + let _ = s.view(&f.p); +} + +#[test] +fn binding_choices_separate_reused_keys_from_local_lifelines() { + let f = fixture(true); + let mut s = ready(&f, false); + s.update(&f.p, Event::MemberFamily(keyed::Family::Binding)); + for (index, layer) in [Layer::Oa, Layer::Sa].into_iter().enumerate() { + assert_eq!(s.member_choices(&f.p, layer).len(), 2); + assert!( + !s.member_choices(&f.p, layer) + .iter() + .any(|c| c.id == f.locals[index]) + ); + let local_choices = s.binding_choices(&f.p, layer, false); + assert_eq!(local_choices.len(), 2); + assert!(local_choices.iter().any(|c| c.id == f.locals[index])); + } +} + +#[test] +fn binding_local_picker_follows_a_candidate_parent_before_the_review_is_valid() { + let mut f = fixture(true); + let destination = add(&mut f.p, Kind::Scenario, Layer::Sa, None); + let local = add(&mut f.p, Kind::Lifeline, Layer::Sa, Some(destination)); + let mut s = ready(&f, false); + let parent = FacetSelector::Header(HeaderFacet::Parent); + s.update(&f.p, Event::Select(parent.clone())); + s.update( + &f.p, + Event::Decide(parent, Some(SemanticDecision::Candidate)), + ); + s.update( + &f.p, + Event::Shape("/parent".into(), serde_json::json!(destination)), + ); + assert_eq!( + s.binding_choices(&f.p, Layer::Sa, false) + .iter() + .map(|c| c.id) + .collect::>(), + vec![local] + ); + assert!( + !s.binding_choices(&f.p, Layer::Sa, false) + .iter() + .any(|c| c.id == f.locals[1]) + ); +} + +#[test] +fn row_review_apply_preserves_exact_source_and_semantic_history_through_undo_redo() { + for binding in [false, true] { + let f = fixture(binding); + let mut app = App::with_project(f.p); + let mut bundle = app.document.bundle().clone(); + bundle + .files + .get_mut(&bundle.entry.clone()) + .unwrap() + .insert_str(0, "// Exact row rationale\r\n"); + app.document.apply_bundle(bundle).unwrap(); + let before = app.document.snapshot(); + start(&mut app, f.owners[0]); + send(&mut app, Event::EditPair(f.facet.clone())); + send(&mut app, Event::StageMember); + send(&mut app, Event::Select(f.facet.clone())); + send( + &mut app, + Event::Decide(f.facet.clone(), Some(SemanticDecision::Candidate)), + ); + send(&mut app, Event::Presence(f.facet.clone(), false)); + assert!(state(&app).can_apply(), "{}", errors(state(&app))); + let expected = state(&app).proposed.as_ref().unwrap().clone(); + apply(&mut app); + assert_eq!(app.project(), &expected); + assert!( + app.project().traces[&f.trace] + .baseline + .as_ref() + .unwrap() + .semantic + .as_ref() + .unwrap() + .facets + .iter() + .any(|r| r.selector() == f.facet) + ); + let after = app.document.snapshot(); + assert!( + app.document.bundle().files[&app.document.bundle().entry] + .starts_with("// Exact row rationale\r\n") + ); + let _ = app.update(Message::Undo); + assert_eq!(app.document.bundle(), &before.bundle); + assert_eq!(app.project(), &before.decoded.project); + let _ = app.update(Message::Redo); + assert_eq!(app.document.bundle(), &after.bundle); + assert_eq!(app.project(), &after.decoded.project); + assert_eq!( + Document::from_bundle( + after.bundle.clone(), + source_project::ProjectLimits::default() + ) + .unwrap() + .project(), + &expected + ); + } +} + +#[test] +fn keyed_rows_distinguish_unpaired_source_from_explicit_absence_and_require_exact_reset() { + for binding in [false, true] { + let mut f = fixture(binding); + let (source_only, target_only) = match &f.facet { + FacetSelector::Annotation(keys) => ( + FacetSelector::Annotation(KeyPair { + oa: keys.oa.clone(), + sa: None, + }), + FacetSelector::Annotation(KeyPair { + oa: None, + sa: keys.sa.clone(), + }), + ), + FacetSelector::Binding(keys) => ( + FacetSelector::Binding(KeyPair { + oa: keys.oa, + sa: None, + }), + FacetSelector::Binding(KeyPair { + oa: None, + sa: keys.sa, + }), + ), + _ => unreachable!(), + }; + assert!(keyed::overlap(&source_only, &f.facet)); + assert!(!keyed::overlap(&f.sibling, &f.facet)); + let prior = capture_facet(&f.p, f.owners[0], f.owners[1], &source_only).unwrap(); + f.p.traces.get_mut(&f.trace).unwrap().baseline = Some(TraceBaseline { + oa: f.p.properties(f.owners[0]).unwrap(), + sa: f.p.properties(f.owners[1]).unwrap(), + semantic: Some(SemanticBaseline { + facets: vec![prior], + }), + }); + let mut s = ready(&f, false); + s.update( + &f.p, + Event::Decide(f.facet.clone(), Some(SemanticDecision::KeepTarget)), + ); + assert!( + !s.can_apply(), + "An old one-sided address cannot silently acquire a paired history" + ); + s.update(&f.p, Event::Reset(source_only.clone(), true)); + assert!(s.can_apply(), "{}", errors(&s)); + let next = s.proposed.as_ref().unwrap(); + let facts = &next.traces[&f.trace] + .baseline + .as_ref() + .unwrap() + .semantic + .as_ref() + .unwrap() + .facets; + assert!( + facts + .iter() + .any(|f| f.selector() == s.selected.clone().unwrap()) + ); + assert!(!facts.iter().any(|f| f.selector() == source_only)); + + f.p.traces.get_mut(&f.trace).unwrap().baseline = None; + if let Some(SharedBody::Occurrence { body, .. }) = + f.p.elements.get_mut(&f.owners[0]).unwrap().shared_mut() + { + match body { + OccurrenceBody::Exchange { values, .. } => values.clear(), + OccurrenceBody::ScenarioUse { bindings, .. } => bindings.clear(), + _ => unreachable!(), + } + } + for (facet, expect_absent) in [(target_only, false), (f.facet.clone(), true)] { + let mut s = ready(&f, false); + s.update( + &f.p, + Event::Decide(facet, Some(SemanticDecision::TakeSource)), + ); + let header = FacetSelector::Header(HeaderFacet::Description); + s.update( + &f.p, + Event::Decide(header.clone(), Some(SemanticDecision::Candidate)), + ); + s.update(&f.p, Event::Select(header)); + s.update( + &f.p, + Event::Input("/description".into(), "Compose explicit absence".into()), + ); + assert!(s.can_apply(), "{}", errors(&s)); + let fact = capture_facet( + s.proposed.as_ref().unwrap(), + f.owners[0], + f.owners[1], + &f.facet, + ) + .unwrap(); + assert_eq!( + scalar::value(&fact, Layer::Sa).unwrap().is_null(), + expect_absent + ); + } + } +} + +#[test] +fn binding_take_requires_local_correspondence_and_candidate_rejects_wrong_scenario() { + let f = fixture(true); + let mut s = ready(&f, false); + s.references.remove(&f.locals[0]); + s.update( + &f.p, + Event::Decide(f.facet.clone(), Some(SemanticDecision::TakeSource)), + ); + assert!(!s.can_apply()); + s.references.insert(f.locals[0], f.locals[1]); + s.update(&f.p, Event::Preview); + assert!(s.can_apply(), "{}", errors(&s)); + s.update( + &f.p, + Event::Decide(f.facet.clone(), Some(SemanticDecision::Candidate)), + ); + let wrong = s.binding_choices(&f.p, Layer::Sa, true)[0].clone(); + s.update(&f.p, Event::RowLocal(f.facet.clone(), wrong)); + assert!(!s.can_apply()); + assert!( + s.row_drafts[&f.facet].local.is_some(), + "Invalid typed selection remains reviewable" + ); + s.update( + &f.p, + Event::RowLocal(f.facet.clone(), view::choice(&f.p, f.locals[1])), + ); + assert!(s.can_apply(), "{}", errors(&s)); +} + +#[test] +fn annotation_observations_keep_uuid_shaped_expression_text_in_both_sides_and_history() { + let mut f = fixture(false); + for owner in f.owners { + if let Some(SharedBody::Occurrence { + body: OccurrenceBody::Exchange { values, .. }, + .. + }) = f.p.elements.get_mut(&owner).unwrap().shared_mut() + { + values[0].expression = values[0].field.to_string(); + } + } + let historical = capture_facet(&f.p, f.owners[0], f.owners[1], &f.facet).unwrap(); + for layer in [Layer::Oa, Layer::Sa] { + let expression = scalar::value(&historical, layer).unwrap()["expression"] + .as_str() + .unwrap() + .to_owned(); + let display = view::observed(&f.p, Some(&historical), layer); + assert!( + display.contains(&format!("Expression: {expression:?}")), + "{display}" + ); + } +} + +#[test] +fn keyed_recovery_retains_raw_rows_and_staged_pairs_both_directions_without_applying() { + for binding in [false, true] { + for reverse in [false, true] { + let f = fixture(binding); + let mut s = ready(&f, reverse); + s.staged.insert(f.facet.clone()); + s.update( + &f.p, + Event::Decide(f.facet.clone(), Some(SemanticDecision::Candidate)), + ); + if !binding { + s.update( + &f.p, + Event::RowExpression(f.facet.clone(), "unfinished (\n exact raw draft".into()), + ); + } + s.update(&f.p, Event::Presence(f.facet.clone(), false)); + s.update(&f.p, Event::EditPair(f.facet.clone())); + let saved = s.recovery_snapshot().unwrap(); + let restored = State::from_recovery(&f.p, saved).unwrap(); + assert_eq!(restored.direction, s.direction); + assert_eq!(restored.decisions, s.decisions); + assert_eq!(restored.references, s.references); + assert_eq!(restored.presence, s.presence); + assert_eq!(restored.staged, s.staged); + assert_eq!( + restored.row_drafts[&f.facet].expression, + s.row_drafts[&f.facet].expression + ); + assert_eq!( + restored.row_drafts[&f.facet].local, + s.row_drafts[&f.facet].local + ); + assert_eq!(restored.proposed, s.proposed); + assert_eq!(restored.annotation_oa.selected, s.annotation_oa.selected); + assert_eq!(restored.annotation_sa.selected, s.annotation_sa.selected); + assert!(restored.can_apply(), "{}", errors(&restored)); + } + } +} + +#[test] +fn multiple_new_rows_compose_in_empty_collections_using_destination_key_order() { + for binding in [false, true] { + for reverse in [false, true] { + let mut f = fixture(binding); + let target_layer = if reverse { Layer::Oa } else { Layer::Sa }; + let target = f.owners[usize::from(!reverse)]; + let facets = [f.facet.clone(), f.sibling.clone()]; + let original: Vec<_> = facets + .iter() + .map(|facet| { + scalar::value( + &capture_facet(&f.p, f.owners[0], f.owners[1], facet).unwrap(), + target_layer, + ) + .unwrap() + }) + .collect(); + if let Some(SharedBody::Occurrence { body, .. }) = + f.p.elements.get_mut(&target).unwrap().shared_mut() + { + match body { + OccurrenceBody::Exchange { values, .. } => values.clear(), + OccurrenceBody::ScenarioUse { bindings, .. } => bindings.clear(), + _ => unreachable!(), + } + } + let object_count = f.p.elements.len(); + let mut s = ready(&f, reverse); + for (facet, row) in facets.iter().zip(&original).rev() { + s.update(&f.p, Event::EditPair(facet.clone())); + s.update(&f.p, Event::StageMember); + s.update( + &f.p, + Event::Decide(facet.clone(), Some(SemanticDecision::Candidate)), + ); + s.update(&f.p, Event::Presence(facet.clone(), true)); + if binding { + let local = row["local"].as_str().unwrap().parse().unwrap(); + s.update( + &f.p, + Event::RowLocal(facet.clone(), view::choice(&f.p, local)), + ); + } else { + s.update( + &f.p, + Event::RowExpression(facet.clone(), "new independent value".into()), + ); + } + } + assert!(s.can_apply(), "{binding}/{reverse}: {}", errors(&s)); + let proposed = s.proposed.as_ref().unwrap(); + assert_eq!(proposed.elements.len(), object_count); + for facet in facets { + assert!( + !scalar::value( + &capture_facet(proposed, f.owners[0], f.owners[1], &facet).unwrap(), + target_layer + ) + .unwrap() + .is_null() + ); + } + } + } +} diff --git a/crates/oa-desktop/src/semantic_review/ordered.rs b/crates/oa-desktop/src/semantic_review/ordered.rs new file mode 100644 index 00000000..2166d252 --- /dev/null +++ b/crates/oa-desktop/src/semantic_review/ordered.rs @@ -0,0 +1,254 @@ +//! Independent membership and complete final order; the core validates every proposal. +use super::*; + +pub(super) fn collection(which: OrderedCollection) -> ReferenceCollection { + match which { + OrderedCollection::Steps => ReferenceCollection::Steps, + OrderedCollection::FragmentBranches => ReferenceCollection::FragmentBranches, + } +} +pub(super) fn ordered(which: ReferenceCollection) -> Option { + match which { + ReferenceCollection::Steps => Some(OrderedCollection::Steps), + ReferenceCollection::FragmentBranches => Some(OrderedCollection::FragmentBranches), + _ => None, + } +} +pub(super) fn read(value: &Value, which: OrderedCollection) -> Result, String> { + let path = keyed::path(value, keyed::Family::Member(collection(which))) + .ok_or("This ordered collection is unavailable on the destination.")?; + serde_json::from_value(value.pointer(&path).unwrap().clone()).map_err(|e| e.to_string()) +} +fn write(value: &mut Value, which: OrderedCollection, ids: Vec) -> Result<(), String> { + let path = keyed::path(value, keyed::Family::Member(collection(which))) + .ok_or("This ordered collection is unavailable on the destination.")?; + *value.pointer_mut(&path).unwrap() = serde_json::to_value(ids).expect("Ordered identities"); + Ok(()) +} + +impl State { + pub(super) fn ordered_members( + &self, + project: &Project, + which: OrderedCollection, + ) -> Result, String> { + read(&self.combine(project, true, true)?, which) + } + + pub(super) fn initialize_order( + &mut self, + project: &Project, + which: OrderedCollection, + ) -> Result<(), String> { + if !self.order_drafts.contains_key(&which) { + let ids = self.ordered_members(project, which)?; + self.order_drafts.insert(which, ids); + } + Ok(()) + } + + pub(super) fn reconcile_order( + &mut self, + project: &Project, + which: OrderedCollection, + ) -> Result<(), String> { + self.require_order_candidate(which)?; + let members = self.ordered_members(project, which)?; + let draft = self + .order_drafts + .get_mut(&which) + .ok_or("Choose a final order first.")?; + // This is an explicit user action. Ordinary refresh never discards an authored order. + let mut seen = BTreeSet::new(); + draft.retain(|id| members.contains(id) && seen.insert(*id)); + for id in members { + if seen.insert(id) { + draft.push(id); + } + } + Ok(()) + } + + fn require_order_candidate(&self, which: OrderedCollection) -> Result<(), String> { + let facet = FacetSelector::Order(which); + if self.selected.as_ref() != Some(&facet) + || self.decisions.get(&facet) != Some(&SemanticDecision::Candidate) + { + return Err("Select Set final value for this order before changing it.".into()); + } + Ok(()) + } + + pub(super) fn move_order( + &mut self, + which: OrderedCollection, + index: usize, + delta: i32, + ) -> Result<(), String> { + self.require_order_candidate(which)?; + let ids = self + .order_drafts + .get_mut(&which) + .ok_or("Choose a final order first.")?; + let destination = match delta { + -1 => index.checked_sub(1), + 1 => index.checked_add(1), + _ => None, + }; + let destination = destination + .filter(|i| index < ids.len() && *i < ids.len()) + .ok_or("Choose an existing adjacent position in the final order.")?; + ids.swap(index, destination); + Ok(()) + } + + pub(super) fn project_order( + &self, + project: &Project, + trace: &Trace, + value: &mut Value, + which: OrderedCollection, + decision: SemanticDecision, + ) -> Result<(), String> { + let current = read(value, which)?; + let supplied = match decision { + SemanticDecision::KeepTarget => return Ok(()), + SemanticDecision::Candidate => self + .order_drafts + .get(&which) + .cloned() + .ok_or("Choose a final order.")?, + SemanticDecision::TakeSource => { + let fact = + capture_facet(project, trace.oa, trace.sa, &FacetSelector::Order(which))?; + let source: Vec = serde_json::from_value( + scalar::value(&fact, self.direction.source_layer()) + .ok_or("The source has no applicable order.")?, + ) + .map_err(|e| e.to_string())?; + let map = self.order_reference_map()?; + source + .into_iter() + .map(|id| { + map.get(&id).copied().ok_or_else(|| { + format!( + "Pair source order member {} explicitly in Reference pairs.", + project.named(id) + ) + }) + }) + .collect::, _>>()? + } + }; + let actual: BTreeSet<_> = current.iter().copied().collect(); + let desired: BTreeSet<_> = supplied.iter().copied().collect(); + if desired.len() != supplied.len() { + return Err( + "The final order contains a duplicate identity; reconcile its members explicitly." + .into(), + ); + } + if desired != actual { + if decision == SemanticDecision::TakeSource + && desired.is_subset(&actual) + && current + .iter() + .filter(|id| desired.contains(id)) + .copied() + .collect::>() + == supplied + { + return Ok(()); + } + return Err("Final order must contain every surviving destination member exactly once. Reconcile members, then choose their positions; membership is reviewed separately.".into()); + } + write(value, which, supplied) + } + + fn order_reference_map(&self) -> Result, String> { + let mut map = BTreeMap::new(); + for (oa, sa) in &self.references { + let (a, b) = if self.direction == TransitionDirection::OaToSa { + (*oa, *sa) + } else { + (*sa, *oa) + }; + if map.insert(a, b).is_some_and(|old| old != b) { + return Err("Reverse reference correspondence is ambiguous.".into()); + } + } + Ok(map) + } + + pub(super) fn project_ordered_member( + &self, + project: &Project, + trace: &Trace, + value: &mut Value, + facet: &FacetSelector, + decision: SemanticDecision, + form_context: bool, + ) -> Result<(), String> { + let FacetSelector::Member { + collection: member_collection, + keys, + } = facet + else { + unreachable!() + }; + let which = ordered(*member_collection).expect("Ordered member"); + let before = read(value, which)?; + self.keyed_value(project, trace, value, facet, decision)?; + let after = read(value, which)?; + let explicit = self + .decisions + .get(&FacetSelector::Order(which)) + .is_some_and(|d| *d != SemanticDecision::KeepTarget); + if decision != SemanticDecision::TakeSource || explicit || after.len() <= before.len() { + return Ok(()); + } + let (source_key, target_key) = if self.direction == TransitionDirection::OaToSa { + (keys.oa, keys.sa) + } else { + (keys.sa, keys.oa) + }; + let (Some(source_key), Some(target_key)) = (source_key, target_key) else { + return Ok(()); + }; + let fact = capture_facet(project, trace.oa, trace.sa, &FacetSelector::Order(which))?; + let source: Vec = serde_json::from_value( + scalar::value(&fact, self.direction.source_layer()) + .ok_or("Source order is unavailable.")?, + ) + .map_err(|e| e.to_string())?; + let position = source + .iter() + .position(|id| *id == source_key) + .ok_or("Source member is absent.")?; + let map = self.order_reference_map()?; + let anchor = |id: &Id| { + map.get(id) + .and_then(|mapped| before.iter().position(|id| id == mapped)) + }; + let left = source[..position].iter().rev().find_map(anchor); + let right = source[position + 1..].iter().find_map(anchor); + let at = match (left, right) { + (Some(a), Some(b)) if a + 1 == b => Some(b), + (Some(a), None) if a + 1 == before.len() => Some(a + 1), + (None, Some(0)) => Some(0), + (None, None) if before.is_empty() => Some(0), + _ => None, + }; + if let Some(at) = at { + let mut ordered = before; + ordered.insert(at, target_key); + write(value, which, ordered) + } else if form_context { + // An uncommitted membership list lets the user author a final order. + // The actual request still rejects ambiguous placement until explicitly decided. + Ok(()) + } else { + Err("Ordered member insertion has no unique unchanged anchor; select an explicit final Order in Properties.".into()) + } + } +} diff --git a/crates/oa-desktop/src/semantic_review/ordered_tests.rs b/crates/oa-desktop/src/semantic_review/ordered_tests.rs new file mode 100644 index 00000000..21c7263a --- /dev/null +++ b/crates/oa-desktop/src/semantic_review/ordered_tests.rs @@ -0,0 +1,567 @@ +use super::{tests::*, *}; +use crate::{app::App, document::Document}; + +struct Fixture { + p: Project, + owners: [Id; 2], + rows: [[Id; 4]; 2], + trace: Id, + which: OrderedCollection, +} +fn fixture(shape: u8, reverse: bool, local: bool) -> Fixture { + let mut p = Project::empty(); + let mut owners = vec![]; + let mut rows = vec![]; + let which = if shape == 3 { + OrderedCollection::FragmentBranches + } else { + OrderedCollection::Steps + }; + for layer in [Layer::Oa, Layer::Sa] { + let owner = if shape == 0 { + p.add_element( + if layer == Layer::Oa { + Kind::Process + } else { + Kind::FunctionalChain + }, + layer, + "Startup sequence", + None, + ) + .unwrap() + } else { + let scenario = p + .add_element(Kind::Scenario, layer, "Startup scenario", None) + .unwrap(); + if shape == 1 { + scenario + } else { + let fragment = p + .add_element(Kind::Occurrence, layer, "Alternatives", Some(scenario)) + .unwrap(); + *p.elements.get_mut(&fragment).unwrap().shared_mut().unwrap() = + SharedBody::Occurrence { + body: OccurrenceBody::Fragment { + operator: FragmentOperator::Alternative, + branches: vec![], + }, + precondition: String::new(), + postcondition: String::new(), + }; + p.elements + .get_mut(&scenario) + .unwrap() + .steps_mut() + .unwrap() + .push(fragment); + if shape == 3 { + fragment + } else { + let branch = p + .add_element(Kind::Occurrence, layer, "Start permitted", Some(fragment)) + .unwrap(); + *p.elements.get_mut(&branch).unwrap().shared_mut().unwrap() = + SharedBody::Occurrence { + body: OccurrenceBody::Branch { + guard: "permitted".into(), + occurrences: vec![], + }, + precondition: String::new(), + postcondition: String::new(), + }; + if let Some(SharedBody::Occurrence { + body: OccurrenceBody::Fragment { branches, .. }, + .. + }) = p.elements.get_mut(&fragment).unwrap().shared_mut() + { + branches.push(branch); + } + branch + } + } + }; + let ids = [ + "Request start", + "Energize", + "Verify interlock", + "Local self-test", + ] + .map(|name| { + let id = p + .add_element(Kind::Occurrence, layer, name, Some(owner)) + .unwrap(); + *p.elements.get_mut(&id).unwrap().shared_mut().unwrap() = SharedBody::Occurrence { + body: if shape == 3 { + OccurrenceBody::Branch { + guard: name.into(), + occurrences: vec![], + } + } else if shape == 0 { + OccurrenceBody::Control { + kind: ControlKind::Choice, + } + } else { + OccurrenceBody::Action { + action: None, + lifeline: None, + } + }, + precondition: String::new(), + postcondition: String::new(), + }; + id + }); + owners.push(owner); + rows.push(ids); + } + let source = usize::from(reverse); + let target = 1 - source; + set_order( + &mut p, + owners[source], + which, + vec![rows[source][0], rows[source][2], rows[source][1]], + ); + set_order( + &mut p, + owners[target], + which, + if local { + vec![rows[target][0], rows[target][3], rows[target][1]] + } else { + vec![rows[target][0], rows[target][1]] + }, + ); + let trace = add_trace(&mut p, owners[0], owners[1], "Startup ordered review"); + validate_candidate(&p).unwrap(); + Fixture { + p, + owners: [owners[0], owners[1]], + rows: [rows[0], rows[1]], + trace, + which, + } +} +fn set_order(p: &mut Project, id: Id, which: OrderedCollection, rows: Vec) { + if which == OrderedCollection::Steps { + *p.elements.get_mut(&id).unwrap().steps_mut().unwrap() = rows; + } else if let Some(SharedBody::Occurrence { + body: OccurrenceBody::Fragment { branches, .. }, + .. + }) = p.elements.get_mut(&id).unwrap().shared_mut() + { + *branches = rows; + } +} +fn member(f: &Fixture, index: usize) -> FacetSelector { + FacetSelector::Member { + collection: ordered::collection(f.which), + keys: KeyPair { + oa: Some(f.rows[0][index]), + sa: Some(f.rows[1][index]), + }, + } +} +fn ready(f: &Fixture, reverse: bool) -> State { + let mut state = State::new(&f.p, f.owners[usize::from(reverse)]).unwrap(); + for i in 0..3 { + state.references.insert(f.rows[0][i], f.rows[1][i]); + } + state.refresh(&f.p); + state +} +fn decide(state: &mut State, p: &Project, facet: FacetSelector, decision: SemanticDecision) { + state.update(p, Event::Select(facet.clone())); + state.update(p, Event::Decide(facet, Some(decision))); +} +fn errors(s: &State) -> String { + s.error + .iter() + .chain(s.plan.iter().flat_map(|p| &p.conflicts)) + .cloned() + .collect::>() + .join("; ") +} +fn oracle(f: &Fixture, s: &State, expected: Option) -> Project { + let reverse = s.direction == TransitionDirection::SaToOa; + let source = f.owners[usize::from(reverse)]; + let target = f.owners[1 - usize::from(reverse)]; + let mut transition = TransitionRequest::new(s.direction, vec![source]); + transition.mappings.push(TransitionMapping { + source, + target: Some(target), + target_kind: None, + }); + let mut request = SemanticTransitionRequest::new(transition); + request.traces.insert(source, f.trace); + request.references = s.references.clone(); + request.reviews.push(SemanticReviewRequest { + source, + resolutions: s + .decisions + .iter() + .map(|(facet, decision)| FacetResolution { + facet: facet.clone(), + decision: *decision, + }) + .collect(), + reset: s.resets.iter().cloned().collect(), + }); + if let Some(expected) = expected { + request.candidates.push(SemanticRecord::Element(expected)); + } + let plan = plan_semantic_transition(&f.p, request).unwrap(); + assert!(plan.conflicts.is_empty(), "{:?}", plan.conflicts); + let result = apply_semantic_transition(&f.p, &plan).unwrap(); + assert!(s.can_apply(), "{}", errors(s)); + assert_eq!(s.proposed.as_ref().unwrap(), &result); + assert_eq!(s.plan.as_ref().unwrap().reviews, plan.reviews); + result +} + +#[test] +fn ordered_take_uses_unique_anchors_and_matches_core_with_unrelated_candidate_both_directions() { + for shape in 0..4 { + for reverse in [false, true] { + let f = fixture(shape, reverse, false); + let mut s = ready(&f, reverse); + decide(&mut s, &f.p, member(&f, 2), SemanticDecision::TakeSource); + let header = FacetSelector::Header(HeaderFacet::Description); + decide(&mut s, &f.p, header, SemanticDecision::Candidate); + s.update( + &f.p, + Event::Input("/description".into(), "Reviewed startup insertion".into()), + ); + let ti = 1 - usize::from(reverse); + let mut expected = f.p.clone(); + set_order( + &mut expected, + f.owners[ti], + f.which, + vec![f.rows[ti][0], f.rows[ti][2], f.rows[ti][1]], + ); + expected + .elements + .get_mut(&f.owners[ti]) + .unwrap() + .description = "Reviewed startup insertion".into(); + oracle(&f, &s, Some(expected.elements[&f.owners[ti]].clone())); + } + } +} + +#[test] +fn ambiguous_order_preserves_local_member_and_requires_explicit_candidate_positions() { + for shape in 0..4 { + for reverse in [false, true] { + let f = fixture(shape, reverse, true); + let mut s = ready(&f, reverse); + decide(&mut s, &f.p, member(&f, 2), SemanticDecision::TakeSource); + assert!(!s.can_apply()); + assert!(errors(&s).contains("anchor"), "{}", errors(&s)); + + let order = FacetSelector::Order(f.which); + decide(&mut s, &f.p, order.clone(), SemanticDecision::Candidate); + let ti = 1 - usize::from(reverse); + assert_eq!( + s.order_drafts[&f.which], + vec![f.rows[ti][0], f.rows[ti][3], f.rows[ti][1], f.rows[ti][2]] + ); + s.update(&f.p, Event::OrderMove(f.which, 3, -1)); + let mut expected = f.p.clone(); + set_order( + &mut expected, + f.owners[ti], + f.which, + vec![f.rows[ti][0], f.rows[ti][3], f.rows[ti][2], f.rows[ti][1]], + ); + let result = oracle(&f, &s, Some(expected.elements[&f.owners[ti]].clone())); + let baseline = result.traces[&f.trace] + .baseline + .as_ref() + .unwrap() + .semantic + .as_ref() + .unwrap(); + assert_eq!(baseline.facets.len(), 2); + assert!(baseline.facets.iter().any(|v| v.selector() == order)); + } + } +} + +#[test] +fn ordered_membership_choices_stage_exact_pairs_in_all_owner_shapes() { + for shape in 0..4 { + let f = fixture(shape, false, true); + let mut s = ready(&f, false); + s.update( + &f.p, + Event::MemberFamily(keyed::Family::Member(ordered::collection(f.which))), + ); + for (i, layer) in [(0, Layer::Oa), (1, Layer::Sa)] { + let choices = s.member_choices(&f.p, layer); + for id in f.rows[i] { + assert!( + choices.iter().any(|c| c.id == id), + "shape {shape}: {choices:?}" + ); + } + assert!(choices.iter().all(|c| !f.rows[1 - i].contains(&c.id))); + } + s.update( + &f.p, + Event::MemberOa(Some(view::choice(&f.p, f.rows[0][2]))), + ); + s.update( + &f.p, + Event::MemberSa(Some(view::choice(&f.p, f.rows[1][2]))), + ); + s.update(&f.p, Event::StageMember); + assert!(s.staged.contains(&member(&f, 2))); + assert_eq!(s.references[&f.rows[0][2]], f.rows[1][2]); + } +} + +#[test] +fn raw_order_survives_membership_changes_decisions_and_recovery_until_explicit_reconcile() { + for reverse in [false, true] { + let f = fixture(0, reverse, true); + let mut s = ready(&f, reverse); + let order = FacetSelector::Order(f.which); + decide(&mut s, &f.p, order.clone(), SemanticDecision::Candidate); + s.update(&f.p, Event::OrderMove(f.which, 1, -1)); + let authored = s.order_drafts[&f.which].clone(); + decide(&mut s, &f.p, member(&f, 2), SemanticDecision::Candidate); + s.update(&f.p, Event::Presence(member(&f, 2), true)); + assert!(!s.can_apply()); + assert_eq!(s.order_drafts[&f.which], authored); + s.update(&f.p, Event::Decide(order.clone(), None)); + decide(&mut s, &f.p, order.clone(), SemanticDecision::Candidate); + let saved = s.recovery_snapshot().unwrap(); + let mut restored = State::from_recovery(&f.p, saved.clone()).unwrap(); + assert_eq!(restored.order_drafts[&f.which], authored); + assert!(!restored.can_apply()); + assert_eq!(restored.recovery_snapshot().unwrap(), saved); + restored.update(&f.p, Event::OrderReconcile(f.which)); + let ti = 1 - usize::from(reverse); + let mut expected = f.p.clone(); + let mut final_order = authored; + final_order.push(f.rows[ti][2]); + set_order(&mut expected, f.owners[ti], f.which, final_order); + oracle( + &f, + &restored, + Some(expected.elements[&f.owners[ti]].clone()), + ); + let mut stale = f.p.clone(); + stale.name.push_str(" changed"); + assert!(State::from_recovery(&stale, saved).is_err()); + } +} + +#[test] +fn order_take_preserves_destination_only_positions_and_order_alone_cannot_add_members() { + for reverse in [false, true] { + let mut f = fixture(0, reverse, true); + let si = usize::from(reverse); + let ti = 1 - si; + set_order( + &mut f.p, + f.owners[si], + f.which, + vec![f.rows[si][0], f.rows[si][1]], + ); + let mut s = ready(&f, reverse); + let order = FacetSelector::Order(f.which); + decide(&mut s, &f.p, order.clone(), SemanticDecision::TakeSource); + let result = oracle(&f, &s, None); + assert_eq!(result.elements[&f.owners[ti]], f.p.elements[&f.owners[ti]]); + decide(&mut s, &f.p, order.clone(), SemanticDecision::Candidate); + s.order_drafts + .get_mut(&f.which) + .unwrap() + .push(f.rows[ti][2]); + s.refresh(&f.p); + assert!(!s.can_apply()); + assert!(errors(&s).contains("surviving"), "{}", errors(&s)); + s.update(&f.p, Event::OrderReconcile(f.which)); + assert!(s.can_apply(), "{}", errors(&s)); + } +} + +#[test] +fn ordered_review_apply_is_exact_source_history_transaction_with_reopen_and_undo_redo() { + let f = fixture(0, false, true); + let mut app = App::with_project(f.p.clone()); + let mut bundle = app.document.bundle().clone(); + bundle + .files + .get_mut(&bundle.entry.clone()) + .unwrap() + .insert_str(0, "// Keep startup explanation verbatim\r\n"); + app.document.apply_bundle(bundle).unwrap(); + let before = app.document.snapshot(); + start(&mut app, f.owners[0]); + for i in 0..3 { + send( + &mut app, + Event::ReferenceOa(view::choice(&f.p, f.rows[0][i])), + ); + send( + &mut app, + Event::ReferenceSa(view::choice(&f.p, f.rows[1][i])), + ); + send(&mut app, Event::AddReference); + } + send(&mut app, Event::Select(member(&f, 2))); + send( + &mut app, + Event::Decide(member(&f, 2), Some(SemanticDecision::TakeSource)), + ); + send(&mut app, Event::Select(FacetSelector::Order(f.which))); + send( + &mut app, + Event::Decide( + FacetSelector::Order(f.which), + Some(SemanticDecision::Candidate), + ), + ); + send(&mut app, Event::OrderMove(f.which, 3, -1)); + assert!(state(&app).can_apply(), "{}", errors(state(&app))); + let expected = state(&app).proposed.clone().unwrap(); + apply(&mut app); + assert_eq!(app.project(), &expected); + let after = app.document.snapshot(); + assert!( + app.document.bundle().files[&app.document.bundle().entry] + .starts_with("// Keep startup explanation verbatim\r\n") + ); + let _ = app.update(Message::Undo); + assert_eq!(app.document.bundle(), &before.bundle); + assert_eq!(app.project(), &before.decoded.project); + let _ = app.update(Message::Redo); + assert_eq!(app.document.bundle(), &after.bundle); + assert_eq!(app.project(), &expected); + assert_eq!( + Document::from_bundle( + after.bundle.clone(), + source_project::ProjectLimits::default() + ) + .unwrap() + .project(), + &expected + ); +} + +#[test] +fn several_incoming_members_follow_source_order_despite_independent_identity_sorting() { + for shape in 0..4 { + for reverse in [false, true] { + let mut f = fixture(shape, reverse, false); + let si = usize::from(reverse); + let ti = 1 - si; + set_order( + &mut f.p, + f.owners[si], + f.which, + vec![f.rows[si][0], f.rows[si][2], f.rows[si][3], f.rows[si][1]], + ); + let mut s = ready(&f, reverse); + s.references.insert(f.rows[0][3], f.rows[1][3]); + decide(&mut s, &f.p, member(&f, 3), SemanticDecision::TakeSource); + decide(&mut s, &f.p, member(&f, 2), SemanticDecision::TakeSource); + decide( + &mut s, + &f.p, + FacetSelector::Header(HeaderFacet::Description), + SemanticDecision::Candidate, + ); + s.update( + &f.p, + Event::Input("/description".into(), "Multiple incoming steps".into()), + ); + let mut expected = f.p.clone(); + set_order( + &mut expected, + f.owners[ti], + f.which, + vec![f.rows[ti][0], f.rows[ti][2], f.rows[ti][3], f.rows[ti][1]], + ); + expected + .elements + .get_mut(&f.owners[ti]) + .unwrap() + .description = "Multiple incoming steps".into(); + oracle(&f, &s, Some(expected.elements[&f.owners[ti]].clone())); + } + } +} + +#[test] +fn ordered_presence_keep_take_candidate_remove_only_selected_entry_and_order_take_reorders_only() { + for reverse in [false, true] { + for decision in [ + SemanticDecision::KeepTarget, + SemanticDecision::TakeSource, + SemanticDecision::Candidate, + ] { + let mut f = fixture(0, reverse, true); + let si = usize::from(reverse); + let ti = 1 - si; + set_order(&mut f.p, f.owners[si], f.which, vec![f.rows[si][1]]); + let mut s = ready(&f, reverse); + decide(&mut s, &f.p, member(&f, 0), decision); + if decision == SemanticDecision::Candidate { + s.update(&f.p, Event::Presence(member(&f, 0), false)); + } + decide( + &mut s, + &f.p, + FacetSelector::Header(HeaderFacet::Description), + SemanticDecision::Candidate, + ); + s.update( + &f.p, + Event::Input("/description".into(), "Only selected membership".into()), + ); + let mut expected = f.p.clone(); + if decision != SemanticDecision::KeepTarget { + set_order( + &mut expected, + f.owners[ti], + f.which, + vec![f.rows[ti][3], f.rows[ti][1]], + ); + } + expected + .elements + .get_mut(&f.owners[ti]) + .unwrap() + .description = "Only selected membership".into(); + oracle(&f, &s, Some(expected.elements[&f.owners[ti]].clone())); + } + let mut f = fixture(0, reverse, false); + let si = usize::from(reverse); + let ti = 1 - si; + set_order( + &mut f.p, + f.owners[si], + f.which, + vec![f.rows[si][1], f.rows[si][0]], + ); + for decision in [SemanticDecision::KeepTarget, SemanticDecision::TakeSource] { + let mut s = ready(&f, reverse); + decide(&mut s, &f.p, FacetSelector::Order(f.which), decision); + let result = oracle(&f, &s, None); + assert_eq!( + result.elements[&f.owners[ti]].steps().unwrap(), + &if decision == SemanticDecision::KeepTarget { + vec![f.rows[ti][0], f.rows[ti][1]] + } else { + vec![f.rows[ti][1], f.rows[ti][0]] + } + ); + } + } +} diff --git a/crates/oa-desktop/src/semantic_review/recovery.rs b/crates/oa-desktop/src/semantic_review/recovery.rs new file mode 100644 index 00000000..181ca272 --- /dev/null +++ b/crates/oa-desktop/src/semantic_review/recovery.rs @@ -0,0 +1,347 @@ +//! Recover authored intent against the exact accepted model; recompute every plan. +use super::*; +use crate::forms::RecoveryDraft; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +struct Saved { + version: u32, + base_identity: Id, + base_fingerprint: String, + anchor: Id, + trace: Option, + direction: TransitionDirection, + selected: Option, + decisions: Vec<(FacetSelector, SemanticDecision)>, + references: BTreeMap, + resets: BTreeSet, + staged: BTreeSet, + presence: Vec<(FacetSelector, bool)>, + member_family: keyed::Family, + member_oa: Option, + member_sa: Option, + literal: String, + literal_oa: bool, + literal_sa: bool, + annotation_oa: Option, + annotation_sa: Option, + draft: Option, + group_drafts: Vec<(FacetSelector, RecoveryDraft)>, + rows: Vec<(FacetSelector, String, Option)>, + #[serde(default)] + orders: Vec<(OrderedCollection, Vec)>, + reference_oa: Option, + reference_sa: Option, + reference_search: String, + accepted_impacts: Vec, +} +fn unique(values: Vec<(K, V)>) -> Result, String> { + let mut map = BTreeMap::new(); + for (key, value) in values { + if map.insert(key, value).is_some() { + return Err("Recovery contains a duplicate semantic draft address.".into()); + } + } + Ok(map) +} + +impl State { + pub fn recovery_snapshot(&self) -> Result { + let key = |address: &keyed_rows::AddressDraft| { + address.selected.as_ref().map(|k| AnnotationKey { + path: k.path.clone(), + field: k.field, + }) + }; + serde_json::to_value(Saved { + version: 1, + base_identity: self.base_identity, + base_fingerprint: self.base_fingerprint.clone(), + anchor: self.anchor, + trace: self.trace, + direction: self.direction, + selected: self.selected.clone(), + decisions: self + .decisions + .iter() + .map(|(key, value)| (key.clone(), *value)) + .collect(), + references: self.references.clone(), + resets: self.resets.clone(), + staged: self.staged.clone(), + presence: self + .presence + .iter() + .map(|(key, value)| (key.clone(), *value)) + .collect(), + member_family: self.member_family, + member_oa: self.member_oa.as_ref().map(|c| c.id), + member_sa: self.member_sa.as_ref().map(|c| c.id), + literal: self.literal.clone(), + literal_oa: self.literal_oa, + literal_sa: self.literal_sa, + annotation_oa: key(&self.annotation_oa), + annotation_sa: key(&self.annotation_sa), + draft: self.draft.as_ref().map(Draft::recovery_snapshot), + group_drafts: self + .group_drafts + .iter() + .map(|(key, draft)| (key.clone(), draft.recovery_snapshot())) + .collect(), + rows: self + .row_drafts + .iter() + .map(|(key, draft)| { + ( + key.clone(), + draft.expression.clone(), + draft.local.as_ref().map(|c| c.id), + ) + }) + .collect(), + reference_oa: self.reference_oa.as_ref().map(|c| c.id), + orders: self + .order_drafts + .iter() + .map(|(key, value)| (*key, value.clone())) + .collect(), + reference_sa: self.reference_sa.as_ref().map(|c| c.id), + reference_search: self.reference_search.clone(), + accepted_impacts: self.accepted_impacts.clone(), + }) + .map_err(|e| e.to_string()) + } + + pub fn from_recovery(project: &Project, value: Value) -> Result { + let saved: Saved = serde_json::from_value(value).map_err(|e| e.to_string())?; + if saved.version != 1 + || saved.base_identity != project.id + || saved.base_fingerprint != project_fingerprint(project) + { + return Err( + "Semantic review recovery does not match the accepted model identity and revision." + .into(), + ); + } + let count = [ + saved.decisions.len(), + saved.references.len(), + saved.resets.len(), + saved.staged.len(), + saved.presence.len(), + saved.rows.len(), + saved + .orders + .iter() + .fold(0usize, |total, (_, rows)| total.saturating_add(rows.len())), + saved.accepted_impacts.len(), + ] + .into_iter() + .fold(0usize, usize::saturating_add); + if count > crate::source_workspace::LIMITS.model.max_records + || saved.group_drafts.len() > constructors::selectors().len() + { + return Err("Semantic recovery exceeds the supported draft limits.".into()); + } + let mut state = Self::new(project, saved.anchor)?; + if saved.trace.is_some_and(|id| { + !project + .traces + .get(&id) + .is_some_and(|trace| trace.oa == saved.anchor || trace.sa == saved.anchor) + }) { + return Err("Semantic recovery trace and selected object disagree.".into()); + } + state.trace = saved.trace; + state.direction = saved.direction; + state.load_trace(project); + state.selected = saved.selected; + state.decisions = unique(saved.decisions)?; + state.references = saved.references; + state.resets = saved.resets; + state.staged = saved.staged; + state.presence = unique(saved.presence)?; + state.member_family = saved.member_family; + state.member_oa = saved.member_oa.map(|id| view::choice(project, id)); + state.member_sa = saved.member_sa.map(|id| view::choice(project, id)); + state.literal = saved.literal; + state.literal_oa = saved.literal_oa; + state.literal_sa = saved.literal_sa; + let address = |key: Option| keyed_rows::AddressDraft { + selected: key.map(|key| crate::data_values::FieldPathKey { + path: key.path, + field: key.field, + }), + ..Default::default() + }; + state.annotation_oa = address(saved.annotation_oa); + state.annotation_sa = address(saved.annotation_sa); + let target = state.target(project); + state.draft = saved + .draft + .map(|draft| Draft::from_recovery(project, draft)) + .transpose()?; + if state + .draft + .as_ref() + .is_some_and(|draft| Some(draft.original) != target) + { + return Err("Recovered semantic draft belongs to another target.".into()); + } + state.group_drafts = unique( + saved + .group_drafts + .into_iter() + .map(|(facet, draft)| { + if !constructors::selectors().contains(&facet) { + return Err("Recovered constructor draft has an unknown scope.".to_owned()); + } + let mut draft = Draft::from_recovery(project, draft)?; + if Some(draft.original) != target { + return Err("Recovered constructor draft belongs to another target.".into()); + } + draft.semantic_groups = true; + Ok((facet, draft)) + }) + .collect::, String>>()?, + )?; + state.row_drafts = unique( + saved + .rows + .into_iter() + .map(|(facet, expression, local)| { + if !keyed_rows::supported(&facet) { + return Err("Recovered row draft has an unknown scope.".to_owned()); + } + Ok(( + facet, + keyed_rows::RowDraft { + editor: text_editor::Content::with_text(&expression), + expression, + local: local.map(|id| view::choice(project, id)), + }, + )) + }) + .collect::, String>>()?, + )?; + state.reference_oa = saved.reference_oa.map(|id| view::choice(project, id)); + state.order_drafts = unique(saved.orders)?; + state.reference_sa = saved.reference_sa.map(|id| view::choice(project, id)); + state.reference_search = saved.reference_search; + state.accepted_impacts = saved.accepted_impacts; + // Invalid numbers and incomplete mappings are recoverable authored input. + // Refresh reports their errors instead of dropping them or applying a plan. + state.refresh(project); + Ok(state) + } +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn version_one_without_orders_restores_raw_intent_and_replans_without_execution() { + let mut p = Project::empty(); + let oa = p + .add_element(Kind::DataType, Layer::Oa, "Operational", None) + .unwrap(); + let sa = p + .add_element(Kind::DataType, Layer::Sa, "System", None) + .unwrap(); + for id in [oa, sa] { + *p.elements.get_mut(&id).unwrap().shared_mut().unwrap() = SharedBody::DataType { + definition: DataTypeDefinition::Integer { + minimum: Some(0), + maximum: Some(100), + }, + default: None, + unit: String::new(), + }; + } + super::super::tests::add_trace(&mut p, oa, sa, "Version one review"); + let accepted = p.clone(); + let facet = FacetSelector::Data(DataFacet::IntegerMinimum); + let path = "/body/Sa/Shared/DataType/definition/Integer/minimum"; + for (raw, valid) in [("7", true), ("-", false)] { + let mut state = State::new(&p, oa).unwrap(); + state.update(&p, Event::Select(facet.clone())); + state.update( + &p, + Event::Decide(facet.clone(), Some(SemanticDecision::Candidate)), + ); + state.update(&p, Event::Input(path.into(), raw.into())); + assert_eq!(state.can_apply(), valid); + let current = state.recovery_snapshot().unwrap(); + let mut old = current.clone(); + assert_eq!(old["version"], 1); + assert_eq!( + old.as_object_mut().unwrap().remove("orders"), + Some(serde_json::json!([])) + ); + let restored = State::from_recovery(&p, old.clone()).unwrap(); + assert!(restored.order_drafts.is_empty()); + assert_eq!(restored.draft.as_ref().unwrap().inputs[path].raw, raw); + assert_eq!(restored.can_apply(), valid); + assert_eq!(restored.recovery_snapshot().unwrap(), current); + assert_eq!( + p, accepted, + "Recovery prepares a preview, never an accepted edit" + ); + let mut changed = p.clone(); + changed.name.push_str(" changed accepted model"); + assert!(State::from_recovery(&changed, old).is_err()); + } + } + + #[test] + fn raw_scalar_and_closed_constructor_drafts_recover_against_exact_model_only() { + let mut p = Project::empty(); + let oa = p + .add_element(Kind::DataType, Layer::Oa, "Operational", None) + .unwrap(); + let sa = p + .add_element(Kind::DataType, Layer::Sa, "System", None) + .unwrap(); + for id in [oa, sa] { + *p.elements.get_mut(&id).unwrap().shared_mut().unwrap() = SharedBody::DataType { + definition: DataTypeDefinition::Integer { + minimum: Some(0), + maximum: Some(100), + }, + default: None, + unit: String::new(), + }; + } + super::super::tests::add_trace(&mut p, oa, sa, "Recover review"); + let mut state = State::new(&p, oa).unwrap(); + let facet = FacetSelector::Data(DataFacet::IntegerMinimum); + state.update(&p, Event::Select(facet.clone())); + state.update( + &p, + Event::Decide(facet.clone(), Some(SemanticDecision::Candidate)), + ); + let path = "/body/Sa/Shared/DataType/definition/Integer/minimum"; + state.update(&p, Event::Input(path.into(), "-".into())); + assert!(!state.can_apply()); + let saved = state.recovery_snapshot().unwrap(); + let restored = State::from_recovery(&p, saved.clone()).unwrap(); + assert_eq!(restored.draft.as_ref().unwrap().inputs[path].raw, "-"); + assert_eq!(restored.decisions, state.decisions); + assert!(!restored.can_apply()); + assert!(restored.plan.is_none()); + assert!( + restored + .group_drafts + .values() + .all(|draft| draft.semantic_groups) + ); + p.name.push_str(" changed"); + assert!( + State::from_recovery(&p, saved) + .err() + .unwrap() + .contains("identity and revision") + ); + } +} diff --git a/crates/oa-desktop/src/semantic_review/view.rs b/crates/oa-desktop/src/semantic_review/view.rs index 71cd9f0b..73595631 100644 --- a/crates/oa-desktop/src/semantic_review/view.rs +++ b/crates/oa-desktop/src/semantic_review/view.rs @@ -208,14 +208,24 @@ fn formatted(project: &Project, value: &Value, references: &BTreeSet) -> Str .join("\n"), } } -fn observed(project: &Project, fact: Option<&FacetReview>, layer: Layer) -> String { +pub(super) fn observed(project: &Project, fact: Option<&FacetReview>, layer: Layer) -> String { let Some(fact) = fact else { return "Unreviewed — no previous observation".into(); }; let value = scalar::observation(fact, layer); if value.as_str() == Some("NotApplicable") { "Not applicable".into() - } else if keyed::supported(&fact.selector()) { + } else if matches!(fact, FacetReview::Annotation { .. }) { + if let Ok(row) = serde_json::from_value::(value["Value"].clone()) { + format!( + "Field: {}\nExpression: {:?}", + annotation_title(project, &AnnotationKey::from(&row)), + row.expression + ) + } else { + "Absent".into() + } + } else if keyed::supported(&fact.selector()) && !keyed_rows::supported(&fact.selector()) { if value["Value"].as_bool() == Some(true) { "Present".into() } else { @@ -259,7 +269,7 @@ impl State { action(direction, Event::Direction(if self.direction == TransitionDirection::OaToSa { TransitionDirection::SaToOa } else { TransitionDirection::OaToSa })), ].spacing(8), text("Changing trace or direction starts a new review draft. Other pending edits remain separate.").size(10).color(style::MUTED), - row![action("Properties", Event::Tab(Tab::Facts)), action("Members / literals", Event::Tab(Tab::Members)), action(format!("History ({})", self.resets.len()), Event::Tab(Tab::History)), action(format!("Reference pairs ({})", self.references.len()), Event::Tab(Tab::References)), action(format!("Additional changes ({})", self.plan.as_ref().map_or(0, |p| p.impacts.len())), Event::Tab(Tab::Impacts)), Space::new().width(Fill), action("Refresh preview", Event::Preview)].spacing(6).wrap(), + row![action("Properties", Event::Tab(Tab::Facts)), action("Members / rows", Event::Tab(Tab::Members)), action(format!("History ({})", self.resets.len()), Event::Tab(Tab::History)), action(format!("Reference pairs ({})", self.references.len()), Event::Tab(Tab::References)), action(format!("Additional changes ({})", self.plan.as_ref().map_or(0, |p| p.impacts.len())), Event::Tab(Tab::Impacts)), Space::new().width(Fill), action("Refresh preview", Event::Preview)].spacing(6).wrap(), ].spacing(8); let body = match self.tab { Tab::Facts => self.facts_view(project), @@ -290,7 +300,7 @@ impl State { .iter() .filter(|f| f.drift == Drift::Unreviewed) .count(); - content = content.push(text(format!("{} decisions · {} exact resets · {unreviewed} unreviewed facts · Ordered, annotation, binding and attached-record decisions remain pending.", self.decisions.len(), self.resets.len())).size(10).color(style::MUTED)); + content = content.push(text(format!("{} decisions · {} exact resets · {unreviewed} unreviewed facts · Attached-record and multi-source decisions remain pending.", self.decisions.len(), self.resets.len())).size(10).color(style::MUTED)); if let Some(trace) = trace { content = content.push(text(summary(project, trace)).size(10).color(style::MUTED)); } @@ -460,7 +470,7 @@ impl State { ); } if keyed::supported(&fact.selector) { - detail = detail.push(text("Presence changes only this collection entry; referenced objects stay intact. Take preserves a row with no source identity. An explicit source identity that is absent can remove its mapped destination entry.").size(11)) + detail = detail.push(text("Review changes only this collection entry; referenced objects stay intact. Take preserves a row with no source identity. An explicit source identity that is absent can remove its mapped destination entry. Changing a row key requires separate removal and addition.").size(11)) .push(action("Choose / change exact row identities…", Event::EditPair(fact.selector.clone()))); if let Some(baseline) = trace .and_then(|t| t.baseline.as_ref()) @@ -482,6 +492,12 @@ impl State { } } } + if let FacetSelector::Order(which) = fact.selector { + detail = detail.push(text("Order changes positions only. Review member presence separately; the final order must include every surviving destination identity exactly once. Take preserves destination-only members when their surrounding order already agrees.").size(11)); + if self.decisions.get(&fact.selector) == Some(&SemanticDecision::Candidate) { + detail = detail.push(self.order_view(project, which)); + } + } if let Some(proposed) = &self.proposed && let Some(trace) = trace && let Ok(after) = capture_facet(proposed, trace.oa, trace.sa, &fact.selector) @@ -569,6 +585,44 @@ impl State { .text_size(12) .on_toggle(move |v| event(Event::Presence(facet.clone(), v))), ); + if keyed_rows::supported(&fact.selector) + && let Some(draft) = self.row_drafts.get(&fact.selector) + { + let facet = fact.selector.clone(); + detail = detail.push(text("The exact destination key stays fixed; its row value is independent of sibling rows.").size(11)); + match &fact.selector { + FacetSelector::Annotation(_) => { + detail = detail.push( + iced::widget::text_editor(&draft.editor) + .placeholder("Expression") + .on_action(move |action| { + event(Event::RowText(facet.clone(), action)) + }) + .size(12) + .height(90) + .padding(7), + ); + } + FacetSelector::Binding(_) => { + detail = detail.push( + pick_list( + self.binding_choices( + project, + self.direction.target_layer(), + false, + ), + draft.local.clone(), + move |value| event(Event::RowLocal(facet.clone(), value)), + ) + .placeholder("Local lifeline in destination scenario") + .text_size(12) + .padding(7) + .width(Fill), + ); + } + _ => {} + } + } } if self.decisions.get(&fact.selector) == Some(&SemanticDecision::Candidate) && let Some(draft) = self.candidate_draft(&fact.selector) @@ -634,6 +688,7 @@ impl State { } if (is_group || keyed::supported(&fact.selector) + || matches!(fact.selector, FacetSelector::Order(_)) || constructors::selectors().iter().any(|group| { self.decisions.contains_key(group) && constructors::contains(group, &fact.selector) })) @@ -676,12 +731,22 @@ impl State { let families = self.member_families(project); let available = families.contains(&self.member_family); let mut content = column![ - text("Review an individual member or literal").size(16), - text("Choose an address, then review its presence in Properties. This also stages a new row in an empty collection. No model object is created.").size(12), + text("Review an individual member or row").size(16), + text("Choose an exact address, then review its value and presence in Properties. This also stages a new row in an empty collection. No model object is created.").size(12), pick_list(families, Some(self.member_family), |v| event(Event::MemberFamily(v))).text_size(12).padding(7), ].spacing(9); + if let keyed::Family::Member(collection) = self.member_family + && let Some(which) = ordered::ordered(collection) + { + content = content.push(text("Membership and order are separate decisions. An incoming member with ambiguous placement requires an explicit final order; referenced objects are not created or reparented here.").size(11)) + .push(action("Review final order…", Event::Select(FacetSelector::Order(which)))); + } if !available { content = content.push(text("This collection is not available on the current counterparts. Choose an available collection above; existing staged addresses remain removable below.").size(12)); + } else if self.member_family == keyed::Family::Annotation { + content = content.push(text("Pair the complete carried-field paths explicitly. Each path component establishes a reference pair. Repeated fields under different paths remain independent rows.").size(11)) + .push(self.annotation_address_view(project, Layer::Oa)) + .push(self.annotation_address_view(project, Layer::Sa)); } else if self.member_family == keyed::Family::Literal { content = content.push(text("Literal identity is exact, including case and spaces. Both sides use the same string; a rename requires separate old/new rows.").size(11)) .push(text_input("Exact literal", &self.literal).on_input(|v| event(Event::Literal(v))).size(12).padding(7)) @@ -691,7 +756,7 @@ impl State { .push(row![pick_list(self.member_choices(project, Layer::Oa), self.member_oa.clone(), |v| event(Event::MemberOa(Some(v)))).placeholder("Operational identity").text_size(12).padding(7).width(Fill), action("No OA identity", Event::MemberOa(None))].spacing(7)) .push(row![pick_list(self.member_choices(project, Layer::Sa), self.member_sa.clone(), |v| event(Event::MemberSa(Some(v)))).placeholder("System identity").text_size(12).padding(7).width(Fill), action("No SA identity", Event::MemberSa(None))].spacing(7)); } - content = content.push(button(text("Use these identities and review presence").size(12)).padding([7,9]).style(style::nav).on_press_maybe(available.then_some(event(Event::StageMember)))) + content = content.push(button(text("Use these addresses and review row").size(12)).padding([7,9]).style(style::nav).on_press_maybe(available.then_some(event(Event::StageMember)))) .push(text("Changing a row address retains its earlier decisions and drafts. Reset conflicting historical observations and leave overlapping decisions unselected explicitly.").size(11).color(style::MUTED)); if !self.staged.is_empty() { content = content.push(text("Staged addresses in this draft").size(13)); @@ -711,6 +776,170 @@ impl State { scrollable(content).height(305).into() } + fn order_view<'a>( + &'a self, + project: &'a Project, + which: OrderedCollection, + ) -> Element<'a, Message> { + let mut content = column![text("Final destination order").size(13), + text("Up/Down changes only positions. Reconcile members explicitly after changing presence: retain surviving positions, remove excluded entries and append newly included entries.").size(11), + action("Reconcile with selected membership", Event::OrderReconcile(which))].spacing(6); + if let Some(ids) = self.order_drafts.get(&which) { + if ids.is_empty() { + content = content.push(text("Empty order").size(12)); + } + for (index, id) in ids.iter().enumerate() { + let up = button(text("Up").size(11)).padding(5).on_press_maybe( + (index > 0).then_some(event(Event::OrderMove(which, index, -1))), + ); + let down = button(text("Down").size(11)).padding(5).on_press_maybe( + (index + 1 < ids.len()).then_some(event(Event::OrderMove(which, index, 1))), + ); + content = content.push( + row![ + text(format!("{}. {}", index + 1, choice(project, *id).label)) + .size(12) + .width(Fill), + up, + down + ] + .spacing(5), + ); + } + match self.ordered_members(project, which) { + Ok(members) => { + let missing: Vec<_> = members + .iter() + .filter(|id| !ids.contains(id)) + .map(|id| choice(project, *id).label) + .collect(); + let excluded: Vec<_> = ids + .iter() + .filter(|id| !members.contains(id)) + .map(|id| choice(project, *id).label) + .collect(); + if !missing.is_empty() { + content = content.push( + text(format!("Missing from this draft: {}", missing.join(", "))) + .size(11), + ); + } + if !excluded.is_empty() { + content = content.push( + text(format!("No longer included: {}", excluded.join(", "))).size(11), + ); + } + } + Err(error) => content = content.push(text(error).size(11)), + } + } + content.into() + } + + fn annotation_address_view<'a>( + &'a self, + project: &'a Project, + layer: Layer, + ) -> Element<'a, Message> { + let address = self.annotation_address(layer); + let side = if layer == Layer::Oa { + "Operational" + } else { + "System" + }; + let mut content = + column![text(format!("{side} carried-field address")).size(13)].spacing(6); + if let Some(key) = &address.selected { + content = content.push( + text(annotation_title( + project, + &AnnotationKey { + path: key.path.clone(), + field: key.field, + }, + )) + .size(11), + ); + } else { + content = content.push(text("No identity on this side").size(11)); + } + content = content.push( + row![ + action("Root fields", Event::AnnotationBrowse(layer, None, 0)), + action("No identity", Event::AnnotationAddress(layer, None)), + ] + .spacing(6), + ); + if let Some(scope) = &address.scope { + content = content + .push( + text(format!( + "Browsing: {}", + annotation_title( + project, + &AnnotationKey { + path: scope.path.clone(), + field: scope.field + } + ) + )) + .size(11), + ) + .push(action( + "Up", + Event::AnnotationBrowse(layer, crate::data_values::parent(scope), 0), + )); + } + match self.annotation_choices(project, layer) { + Err(error) => content = content.push(text(error).size(11).color(style::MUTED)), + Ok(choices) => { + const PAGE: usize = 24; + let pages = choices.len().div_ceil(PAGE).max(1); + let page = address.page.min(pages - 1); + let visible = choices + .into_iter() + .skip(page * PAGE) + .take(PAGE) + .collect::>(); + let selected = visible + .iter() + .find(|choice| Some(&choice.key) == address.selected.as_ref()) + .cloned(); + content = content.push( + pick_list(visible, selected, move |choice| { + event(Event::AnnotationAddress(layer, Some(choice.key))) + }) + .placeholder("Choose a field") + .text_size(12) + .padding(7) + .width(Fill), + ); + if let Some(selected) = &address.selected { + content = content.push(action( + "Browse fields within selected record", + Event::AnnotationBrowse(layer, Some(selected.clone()), 0), + )); + } + let mut navigation = + row![text(format!("Page {} of {pages}", page + 1)).size(11)].spacing(7); + if page > 0 { + navigation = navigation.push(action( + "Previous", + Event::AnnotationBrowse(layer, address.scope.clone(), page - 1), + )); + } + if page + 1 < pages { + navigation = navigation.push(action( + "Next", + Event::AnnotationBrowse(layer, address.scope.clone(), page + 1), + )); + } + content = content.push(navigation); + } + } + content.into() + } + fn history_view<'a>(&'a self, project: &'a Project) -> Element<'a, Message> { let mut content = column![text("Exact previous observations").size(16), text("Check only the observations to forget. Resets run before this review's decisions; reset alone changes history, not model values. Uncheck to restore the pending observation. Prose history remains separate.").size(12)].spacing(9); let baseline = self @@ -777,8 +1006,8 @@ impl State { pick_list(self.all_references(project, Layer::Oa), self.reference_oa.clone(), |c| event(Event::ReferenceOa(c))).placeholder("Operational reference").padding(7).text_size(12).width(Fill), pick_list(self.all_references(project, Layer::Sa), self.reference_sa.clone(), |c| event(Event::ReferenceSa(c))).placeholder("System reference").padding(7).text_size(12).width(Fill), action("Use pair", Event::AddReference), - ].spacing(7), - ].spacing(9); + ].spacing(7).width(Fill), + ].spacing(9).width(Fill); for (oa, sa) in &self.references { content = content.push( row![ @@ -791,7 +1020,8 @@ impl State { .width(Fill), action("Remove pair", Event::RemoveReference(*oa)) ] - .spacing(6), + .spacing(6) + .width(Fill), ); } if let Some(trace) = self.trace.and_then(|id| project.traces.get(&id)) { @@ -828,7 +1058,7 @@ impl State { Err(error) => content = content.push(text(error).size(12)), } } - scrollable(content).height(305).into() + scrollable(content).width(Fill).height(305).into() } fn impacts_view<'a>(&'a self, project: &'a Project) -> Element<'a, Message> { diff --git a/crates/oa-desktop/src/source_workspace.rs b/crates/oa-desktop/src/source_workspace.rs index b692cbbf..5d2055b1 100644 --- a/crates/oa-desktop/src/source_workspace.rs +++ b/crates/oa-desktop/src/source_workspace.rs @@ -20,6 +20,50 @@ pub const LIMITS: ProjectLimits = ProjectLimits { const DRAFT_HISTORY_BYTES: usize = 32 * 1024 * 1024; +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +#[serde(deny_unknown_fields)] +pub struct RecoveryWorkspace { + entry: String, + active: String, + files: BTreeMap, + reachable: BTreeSet, + applied: bool, + new_path: String, +} + +impl RecoveryWorkspace { + /// Check the same bounds before replacing a checkpoint and before restoring it. + pub(crate) fn validate(&self) -> Result<(), String> { + if self.files.len() > LIMITS.max_files + || !self.files.contains_key(&self.entry) + || !self.files.contains_key(&self.active) + || !self.reachable.contains(&self.entry) + || self.reachable.len() > LIMITS.max_include_edges + 1 + { + return Err("Recovery has an invalid file count, entry or active file.".into()); + } + if self.files.values().fold(0usize, |bytes, (text, base)| { + bytes.saturating_add(text.len()).saturating_add(base.len()) + }) > LIMITS.model.max_source_bytes.saturating_mul(2) + { + return Err("Recovery source buffers exceed the size limit.".into()); + } + for path in self.files.keys().chain(self.reachable.iter()) { + // A recovery buffer is never a filesystem path to open implicitly. + if path.starts_with('/') + || path.contains('\\') + || path.contains(':') + || path + .split('/') + .any(|part| part == ".." || part == "." || part.is_empty()) + { + return Err("Recovery contains an unsafe source path.".into()); + } + } + Ok(()) + } +} + pub struct Buffer { pub text: String, baseline: String, @@ -57,6 +101,41 @@ pub struct Workspace { } impl Workspace { + pub fn recovery_snapshot(&self) -> RecoveryWorkspace { + RecoveryWorkspace { + entry: self.entry.clone(), + active: self.active.clone(), + files: self + .files + .iter() + .map(|(path, buffer)| { + (path.clone(), (buffer.text.clone(), buffer.baseline.clone())) + }) + .collect(), + reachable: self.reachable.clone(), + applied: self.applied, + new_path: self.new_path.clone(), + } + } + pub fn from_recovery(saved: RecoveryWorkspace) -> Result { + saved.validate()?; + Ok(Self { + entry: saved.entry, + active: saved.active, + files: saved + .files + .into_iter() + .map(|(path, (text, baseline))| { + let mut buffer = Buffer::new(text); + buffer.baseline = baseline; + (path, buffer) + }) + .collect(), + reachable: saved.reachable, + applied: saved.applied, + new_path: saved.new_path, + }) + } pub fn new(bundle: &SourceBundle, applied: bool) -> Self { Self { entry: bundle.entry.clone(), diff --git a/crates/oa-desktop/src/views.rs b/crates/oa-desktop/src/views.rs index 64d93d67..61a6950d 100644 --- a/crates/oa-desktop/src/views.rs +++ b/crates/oa-desktop/src/views.rs @@ -99,6 +99,7 @@ pub fn view(app: &App) -> View<'_, Message> { .spacing(8); let mut task_nav = row![].spacing(2); for (page, name) in [ + (Page::Home, "Home"), (Page::Diagram, "Architecture"), (Page::Catalog, "Model explorer"), (Page::Behavior, "Behavior & scenarios"), @@ -119,7 +120,7 @@ pub fn view(app: &App) -> View<'_, Message> { }), ); } - let task_nav = container(task_nav) + let task_nav = container(task_nav.wrap()) .width(Fill) .padding([4, 14]) .style(style::panel); @@ -204,6 +205,16 @@ pub fn view(app: &App) -> View<'_, Message> { .height(Fill) .style(style::panel); let main = match app.page { + Page::Home => crate::home::view( + &app.home, + (app.dirty() + || !p.elements.is_empty() + || app.path.is_some() + || app.hosted.session.is_some()) + .then_some((p.name.as_str(), app.dirty())), + &app.recovery, + ) + .map(Message::Home), Page::Diagram => diagram(app), Page::Catalog => crate::explorer_view::catalog(app), Page::Data => data_catalog(app), @@ -213,8 +224,12 @@ pub fn view(app: &App) -> View<'_, Message> { Page::Validation => checks(app), Page::Guide => crate::guide::view(&app.guide), }; - let mut body = row![sidebar, container(main).width(Fill).height(Fill)].height(Fill); - if !matches!(app.page, Page::Text | Page::Guide) { + let mut body = if app.page == Page::Home { + row![container(main).width(Fill).height(Fill)].height(Fill) + } else { + row![sidebar, container(main).width(Fill).height(Fill)].height(Fill) + }; + if !matches!(app.page, Page::Home | Page::Text | Page::Guide) { body = body.push(inspector(app)); } let footer = container( diff --git a/crates/oa-desktop/src/workflow.rs b/crates/oa-desktop/src/workflow.rs index ec9c11fb..89ea2deb 100644 --- a/crates/oa-desktop/src/workflow.rs +++ b/crates/oa-desktop/src/workflow.rs @@ -12,6 +12,7 @@ use iced::{ }; use oa_core::{transitions::*, *}; use std::collections::BTreeSet; +pub(crate) mod recovery; #[derive(Debug, Clone)] pub enum Event { @@ -38,6 +39,7 @@ pub enum Workflow { Ports(Box), Relation(Box), ManualTrace { + base_fingerprint: String, name: String, source: Choice, target: Option, @@ -45,6 +47,7 @@ pub enum Workflow { Transition(Box), } pub struct Relationship { + base_fingerprint: String, id: Id, pub name: String, pub kind: RelationKind, @@ -254,6 +257,7 @@ impl Workflow { return Err("Select an OA or SA element or relationship to trace.".into()); } Ok(Self::ManualTrace { + base_fingerprint: project_fingerprint(p), name: "Engineering correspondence".into(), source: choice(p, selected), target: None, @@ -292,6 +296,7 @@ impl Workflow { incoming: bool, ) -> Self { let mut draft = Relationship { + base_fingerprint: project_fingerprint(p), id: Id::new_v4(), name: String::new(), kind, @@ -345,6 +350,7 @@ impl Workflow { name, source, target, + .. } => match event { Event::RelationName(value) => *name = value, Event::Target(value) => *target = Some(value), @@ -610,6 +616,7 @@ impl Workflow { name, source, target, + .. } => { let mut choices = p .elements diff --git a/crates/oa-desktop/src/workflow/recovery.rs b/crates/oa-desktop/src/workflow/recovery.rs new file mode 100644 index 00000000..5c69cf8c --- /dev/null +++ b/crates/oa-desktop/src/workflow/recovery.rs @@ -0,0 +1,206 @@ +//! Typed authored workflow inputs; no stored command is executable on recovery. +use super::*; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +enum Saved { + Semantic(serde_json::Value), + Ports(serde_json::Value), + Relation { + base: String, + id: Id, + name: String, + kind: RelationKind, + source: Option, + target: Option, + condition: String, + layer: Layer, + items: Vec, + source_port: PortChoice, + target_port: PortChoice, + oriented: bool, + }, + ManualTrace { + base: String, + name: String, + source: Id, + target: Option, + }, + Transition { + base: String, + request: TransitionRequest, + }, +} +pub(crate) fn endpoint(p: &Project, port: PortChoice) -> EndpointChoice { + let label = match port { + PortChoice::Auto => "Automatic · reuse only if unique".into(), + PortChoice::Create => "Create a new port".into(), + PortChoice::Reuse(id) => format!("Reuse {}", choice(p, id).label), + }; + EndpointChoice { + choice: port, + label, + } +} +fn check(p: &Project, base: &str) -> Result<(), String> { + if project_fingerprint(p) != base { + return Err( + "Workflow recovery does not match the accepted model identity and revision.".into(), + ); + } + Ok(()) +} +impl Workflow { + pub fn recovery_snapshot(&self) -> Result { + let saved = match self { + Self::Semantic(state) => Saved::Semantic(state.recovery_snapshot()?), + Self::Ports(state) => Saved::Ports(state.recovery_snapshot()?), + Self::Relation(d) => Saved::Relation { + base: d.base_fingerprint.clone(), + id: d.id, + name: d.name.clone(), + kind: d.kind, + source: d.source.as_ref().map(|c| c.id), + target: d.target.as_ref().map(|c| c.id), + condition: d.condition.text(), + layer: d.layer, + items: d.items.clone(), + source_port: d.source_port.choice, + target_port: d.target_port.choice, + oriented: d.oriented, + }, + Self::ManualTrace { + base_fingerprint, + name, + source, + target, + } => Saved::ManualTrace { + base: base_fingerprint.clone(), + name: name.clone(), + source: source.id, + target: target.as_ref().map(|c| c.id), + }, + Self::Transition(d) => Saved::Transition { + base: d.plan.base_fingerprint.clone(), + request: d.request.clone(), + }, + }; + serde_json::to_value(saved).map_err(|e| e.to_string()) + } + pub fn from_recovery(p: &Project, value: serde_json::Value) -> Result { + match serde_json::from_value::(value).map_err(|e| e.to_string())? { + Saved::Semantic(value) => crate::semantic_review::State::from_recovery(p, value) + .map(|state| Self::Semantic(Box::new(state))), + Saved::Ports(value) => crate::port_workflow::State::from_recovery(p, value) + .map(|state| Self::Ports(Box::new(state))), + Saved::Relation { + base, + id, + name, + kind, + source, + target, + condition, + layer, + items, + source_port, + target_port, + oriented, + } => { + check(p, &base)?; + if items.len() > crate::source_workspace::LIMITS.model.max_records { + return Err("Recovered relationship exceeds item limits.".into()); + } + let mut d = Relationship { + base_fingerprint: base, + id, + name, + kind, + source: source.map(|id| choice(p, id)), + target: target.map(|id| choice(p, id)), + condition: text_editor::Content::with_text(&condition), + layer, + items, + source_port: endpoint(p, source_port), + target_port: endpoint(p, target_port), + oriented, + sources: vec![], + targets: vec![], + }; + d.refresh_choices(p); + Ok(Self::Relation(Box::new(d))) + } + Saved::ManualTrace { + base, + name, + source, + target, + } => { + check(p, &base)?; + let mut workflow = Self::manual_trace(p, source)?; + if let Self::ManualTrace { + name: n, target: t, .. + } = &mut workflow + { + *n = name; + *t = target.map(|id| choice(p, id)); + } + Ok(workflow) + } + Saved::Transition { base, request } => { + check(p, &base)?; + let plan = plan_transition(p, &request).map_err(|e| e.to_string())?; + Ok(Self::Transition(Box::new(Transition { request, plan }))) + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn incomplete_relationship_and_trace_recover_raw_fields_and_reject_changed_model() { + let mut p = Project::empty(); + let a = p + .add_element(Kind::Activity, Layer::Oa, "Source", None) + .unwrap(); + let mut relation = Workflow::relationship(&p, a, Layer::Oa); + if let Workflow::Relation(d) = &mut relation { + d.name = " unfinished name ".into(); + d.condition = text_editor::Content::with_text("raw (\n unfinished"); + d.target = None; + } + let saved = relation.recovery_snapshot().unwrap(); + let restored = Workflow::from_recovery(&p, saved.clone()).unwrap(); + assert_eq!(restored.recovery_snapshot().unwrap(), saved); + let trace = Workflow::manual_trace(&p, a).unwrap(); + let saved_trace = trace.recovery_snapshot().unwrap(); + assert_eq!( + Workflow::from_recovery(&p, saved_trace.clone()) + .unwrap() + .recovery_snapshot() + .unwrap(), + saved_trace + ); + p.name.push('!'); + assert!(Workflow::from_recovery(&p, saved).is_err()); + assert!(Workflow::from_recovery(&p, saved_trace).is_err()); + } + #[test] + fn transition_recovery_replans_typed_request_without_persisted_commands() { + let mut p = Project::empty(); + let a = p + .add_element(Kind::Activity, Layer::Oa, "Source", None) + .unwrap(); + let workflow = Workflow::transition(&p, a).unwrap(); + let saved = workflow.recovery_snapshot().unwrap(); + assert!(!serde_json::to_string(&saved).unwrap().contains("commands")); + let Workflow::Transition(restored) = Workflow::from_recovery(&p, saved).unwrap() else { + panic!() + }; + assert_eq!(restored.plan.base_fingerprint, project_fingerprint(&p)); + assert_eq!(restored.request.sources, vec![a]); + } +} diff --git a/crates/oa-server/build.rs b/crates/oa-server/build.rs new file mode 100644 index 00000000..f182d7d0 --- /dev/null +++ b/crates/oa-server/build.rs @@ -0,0 +1 @@ +include!("../../build/windows_manifest.rs"); diff --git a/crates/oa-server/tests/http.rs b/crates/oa-server/tests/http.rs index e6ee95ac..0ce74eab 100644 --- a/crates/oa-server/tests/http.rs +++ b/crates/oa-server/tests/http.rs @@ -487,9 +487,12 @@ async fn oversized_source_and_json_are_rejected_without_stalling_the_catalog() { .expect("catalog stalled by rejected import") .unwrap(); assert_eq!(read.status(), StatusCode::OK); - let rejected = tokio::time::timeout(std::time::Duration::from_secs(5), upload) + // Completion includes transport/decoding while other tests may saturate + // the two-core runner. Catalog responsiveness is asserted separately + // above; this watchdog only prevents a hung request from hanging CI. + let rejected = tokio::time::timeout(std::time::Duration::from_secs(30), upload) .await - .expect("oversized import not rejected promptly") + .expect("oversized import did not complete within the test watchdog") .unwrap(); assert_eq!(rejected.status(), StatusCode::UNPROCESSABLE_ENTITY); assert_eq!( @@ -500,12 +503,14 @@ async fn oversized_source_and_json_are_rejected_without_stalling_the_catalog() { } // The same budgets apply to replacement and non-mutating candidate checks; // neither endpoint may run expensive structural validation before admission. + // Their completion watchdog is not a catalog latency measurement: real + // JSON encoding/decoding can contend with accepted deep-graph preparation. for (suffix, media, body) in [ ("source", "text/plain", source.as_bytes().to_vec()), ("model", "application/json", native_json), ] { let rejected = tokio::time::timeout( - std::time::Duration::from_secs(5), + std::time::Duration::from_secs(30), server .client .put(server.url(&format!("/projects/{existing}/{suffix}"))) @@ -528,7 +533,7 @@ async fn oversized_source_and_json_are_rejected_without_stalling_the_catalog() { json!({"format":"json", "model":model}), ] { let checked = tokio::time::timeout( - std::time::Duration::from_secs(5), + std::time::Duration::from_secs(30), server .client .post(server.url(&format!("/projects/{existing}/validation"))) @@ -568,7 +573,10 @@ async fn accepted_slow_source_does_not_lock_catalog_and_loses_to_a_newer_http_re ); // This is within all admission budgets. A nested engineering graph forces // real validation work after the source route's initial revision snapshot. - for i in 1..2_000 { + // Keep this concurrency fixture below the service's 30-second deadline: + // 2,000 nested activities returned a legitimate 504 on the two-core runner. + // The overlap assertion below prevents shrinking it into a sequential test. + for i in 1..1_000 { source.push_str(&format!( "oa_activity a{i} \"Activity {i}\" {{ parent @a{}; }}\n", i - 1 @@ -614,9 +622,12 @@ async fn accepted_slow_source_does_not_lock_catalog_and_loses_to_a_newer_http_re .expect("unrelated preparation held the commit lock") .unwrap(); assert_eq!(winner.status(), StatusCode::OK); - let loser = tokio::time::timeout(std::time::Duration::from_secs(25), proposed) + // The two-second read/PATCH bounds above test responsiveness and lock + // ownership. Finishing the deliberately expensive admitted candidate is + // a separate watchdog, not a latency promise under parallel CPU contention. + let loser = tokio::time::timeout(std::time::Duration::from_secs(45), proposed) .await - .unwrap() + .expect("admitted candidate did not complete within the test watchdog") .unwrap(); assert_eq!(loser.status(), StatusCode::PRECONDITION_FAILED); let failure: Value = loser.json().await.unwrap(); diff --git a/deployment/Dockerfile b/deployment/Dockerfile index 113baac0..3bdf39d2 100644 --- a/deployment/Dockerfile +++ b/deployment/Dockerfile @@ -1,6 +1,7 @@ FROM rust:1.90-bookworm AS builder WORKDIR /src COPY Cargo.toml Cargo.lock ./ +COPY build ./build COPY crates ./crates COPY docs/openapi.json ./docs/openapi.json RUN cargo build --release --locked -p oa-server diff --git a/docs/language.rst b/docs/language.rst index e36a514d..0c114f5c 100644 --- a/docs/language.rst +++ b/docs/language.rst @@ -286,7 +286,7 @@ references. Their kind is the declaration keyword: - Work, participant, capability, or process/chain → state/mode - None * - ``exchange_allocation`` - - Boundary-exchange relation → SA functional-exchange relation + - Boundary-exchange relation → same-layer interaction (OA) or functional exchange (SA) - None * - ``interface_provision``, ``interface_requirement`` - Participant/port → provided/required interface @@ -1662,6 +1662,55 @@ the line. Structural errors and engineering warnings point to a relevant declaration or field when available. Clients MUST use severity/code rather than parse the human message; diagnostic wording is not a stable machine API. +.. rubric:: Explicit behavior coverage diagnostics + +The following shared engineering checks are advisory ``Warning`` diagnostics. +They MUST NOT reject graphical Apply, source compilation, Save, or an API +transaction solely because the model is incomplete. They run only after the +structural pass reports no ``Error``, avoiding cascades from invalid ownership, +references, layers, or recursion. Their policy concerns explicit evidence in +the authored model, not proof that no engineering behavior exists. + +* ``non_leaf_work_exchange`` locates each ``source`` or ``target`` of an + Interaction or FunctionalExchange that has direct same-kind child work. + Repair the exchange to name its responsible leaf Activity/Function, keeping + SA function-port ownership consistent. +* ``work_without_behavior_context`` locates a leaf Activity/Function lacking + an included Action or an included Interaction/FunctionalExchange occurrence + that explicitly names it as an endpoint. +* ``exchange_without_behavior_context`` locates an Interaction or + FunctionalExchange lacking an included Exchange occurrence. It is suppressed + while a more specific non-leaf endpoint warning applies to that exchange. +* ``mission_without_capability`` locates an OperationalMission/Mission lacking + a valid same-layer outgoing MissionCapability. Participant responsibility + through MissionInvolvement does not satisfy this check. +* ``capability_without_behavior_context`` locates a supported leaf + Capability/SystemCapability lacking either an incoming ChainInvolvement or + a same-layer Scenario explicitly naming it as ``context``. It MUST NOT + duplicate ``unsupported_capability`` on the same record. + +Record-level checks have no invented field path. Coverage traverses all Process, +FunctionalChain and Scenario roots, following only their explicitly listed, +correctly owned occurrences and Fragment branches. Reusable sequence/scenario +contents are traversed with identity-based cycle guards. Known work/exchange +references count despite unfinished lifelines; unordered occurrences do not. +Repeated exchanges and correlated replies contribute the same identity once. +Parent work does not cover its children. Bare lifelines, state observations, +StateAction/Availability links, timer-only occurrences, and participant/boundary +messages do not imply work/exchange coverage. Allocation does not imply that +every exchange carried by a boundary message occurs. Diagram layout is irrelevant. + +As an explicit Studio completeness policy, same-kind capability grouping parents +are excluded from both capability support warnings; their leaf capabilities are +checked independently. A valid direct Scenario context also satisfies existing +``unsupported_capability`` support. An empty linked process/chain or scenario +satisfies explicit context presence while retaining its own ``empty_sequence`` +warning. Include/extend/generalization, parent containment, and shared performers +MUST NOT manufacture a context. These advisory checks follow the distinction +between dataflow and its usage contexts described in the official +`Arcadia Questions and Answers `_; +they are not a declaration of full Capella conformance. + ``parse_limited`` and ``decode_limited`` accept caller admission budgets for input bytes, graph objects, total persistent records and ordered step references. Counts MUST be checked before expensive structural validation; migration budgets diff --git a/docs/scope.md b/docs/scope.md index 8a1fbd04..796ab8cf 100644 --- a/docs/scope.md +++ b/docs/scope.md @@ -1,6 +1,6 @@ # Current scope and tradeoffs -Arcadia Studio implements a systems-engineering workbench spanning Operational Analysis and System Analysis. The normative [language and editing contract](language.rst) defines the supported Studio profile. It does not claim full Arcadia, Eclipse Capella or SysML conformance. The [first-release plan](../harness/PLAN.md) remains incomplete; [current state](../harness/STATE.md) records current implementation and verification. The [V2 acceptance](acceptance.md) and [validation](validation.md) records describe the earlier baseline, not acceptance of every subsequent change. +Arcadia Studio implements a systems-engineering workbench spanning Operational Analysis and System Analysis. The normative [language and editing contract](language.rst) defines the supported Studio profile. It does not claim full Arcadia, Eclipse Capella or SysML conformance. The [first-release plan](../harness/PLAN.md) remains incomplete; [current state](../harness/STATE.md) and the [2026-09-06 coverage audit](../harness/release-current-coverage-20260906.md) distinguish implementation, verification and remaining requirements. The [V2 acceptance](acceptance.md) and [validation](validation.md) records describe the earlier baseline, not acceptance of every subsequent change. ## Supported modeling profile @@ -11,9 +11,9 @@ Arcadia Studio implements a systems-engineering workbench spanning Operational A | Data and interfaces | Information/material/energy nature and Flow/Event/Operation/SharedData mechanisms, bounded primitive/enum/record types, typed defaults, field multiplicity/units/direction/storage, specialization, identified interface allocations and reference associations, separate function input/output and Standard/Flow component ports, interface provision/requirement, port and exchange allocations | | Behavior | Process/chain work and exchange contributions, control topology and sequence reuse; scenario lifelines, correlated calls/replies, timers, action/state occurrences, ALT/PAR/LOOP/OPTIONAL fragments, scenario reuse and pre/postconditions | | States and conditions | Participant-owned state machines, nested regions, states/modes and typed pseudostates, signal/time/change events, trigger/guard/effect transitions, linked entry/do/exit work, availability and constraints | -| Perspectives and evidence | Bidirectional counterpart creation/linking, reviewed name/description updates, many-to-many typed/manual traces, arbitrary artifact URI/revision/fragment references | +| Perspectives and evidence | Bidirectional counterpart creation/linking, reviewed descriptive and typed semantic changes, many-to-many typed/manual traces, arbitrary artifact URI/revision/fragment references | | Views and editing | Independent diagrams, decomposition/allocation nesting, collapse, pan/zoom/Fit/Arrange, editable connection routes and labels, four-side port positioning, direct interaction/functional/boundary exchange tools, contextual menus, SVG export, model explorer and typed properties | -| Text and storage | `arcadia 4` source projects with relative includes and nested declarations, stable aliases/UUIDs, a source-file tree, per-file drafts and diagnostics, comment-preserving graphical updates, exact model/source Undo/Redo, legacy migration, atomic saves and interrupted source-save recovery | +| Text and storage | `arcadia 5` source projects with relative includes and nested declarations, stable aliases/UUIDs, a source-file tree, per-file drafts and diagnostics, comment-preserving graphical updates, exact model/source Undo/Redo, legacy migration, atomic saves, interrupted source-save recovery, Home/recents and private unsaved-work checkpoints | | Automation and hosted editing | Rust REST service for project/source-bundle/query/validation/transaction/transition/artifact workflows; optional desktop catalog/open/publish, explicit conditional Save, server review and local Save as; ETag preconditions, bounded requests and atomic current-revision storage | Graphical and textual parity applies to the persistent fields of this profile. Diagram placements are model data; camera, selection, source cursor and undo history are session state. Canvas gestures need not match textual operations one for one: typed inspectors and explicit commands expose precise semantics. Full source replacement must repair dependencies in its candidate, while graphical delete/reidentify commands maintain references automatically. @@ -23,7 +23,7 @@ Graphical and textual parity applies to the persistent fields of this profile. D - OA captures operational needs; SA defines the selected system contribution. SA contains no internal logical/physical components, and Studio does not decompose SA Actors. The System may be absent while authoring, with a completeness warning. - Work decomposition and performer allocation are independent. Only leaves have direct allocations. A parent drawn within one performer can be a computed leaf summary, not an additional allocation. Role sharing does not create several direct performers for one function. - Nesting expresses semantic containment or the selected allocation projection. Placement movement does not change ownership or allocation. Hide and delete have different effects. -- Counterpart creation copies mapped typed content and required references; existing-target updates compare only names/descriptions against a reviewed baseline. They do not synchronize arbitrary structure, reverse-engineer operational needs or automatically overwrite the other perspective. Scope follows ownership/reference dependencies, not every incident relationship. +- Counterpart creation copies mapped typed content and required references. Existing-counterpart semantic review compares typed facts and prior observations, with explicit keep, take or final-value decisions for supported scalar, structural, constructor and individual collection-row changes. Reference mapping and candidate validation apply before an undoable transaction. Ordered collections, attachment/artifact and multi-source GUI reconciliation remain unfinished. Review does not automatically synchronize arbitrary structure or overwrite the other perspective. Creation scope follows ownership/reference dependencies, not every incident relationship. - Processes/chains use contributions and explicit sequence links; imported ordered exchange paths retain their implied order until topology conversion. Rich fragments belong to scenarios. Guards, expressions and state behavior text are descriptive; there is no execution engine. Mixed states and modes are permitted by this Studio profile. - Preserved OA boundary exchanges use Legacy ports. Capability grouping is containment; include, extend and generalization use separate typed relationships in OA and SA. Data/item references remain within their layer and can have explicit transitioned counterparts. - Applying a source project preserves authored comments and formatting while assigning missing identities. Graphical updates preserve unaffected source bytes; canonical export is a separate operation. JSON does not preserve comments or source-file organization. See the [source contract](language.rst) for comment ownership and update rules. @@ -36,7 +36,7 @@ These are concrete design responses, not claims that every Capella user experien | Concern | Studio response | Remaining tradeoff | |---|---|---| -| Dense workbench and difficult onboarding | Task workspaces, OA/SA context, contextual menus, direct architecture connections, searchable guidance and relation/transition builders | Direct behavior gestures and Home/recent-project workflows remain release work | +| Dense workbench and difficult onboarding | Task workspaces, OA/SA context, contextual menus, direct architecture and behavior gestures, searchable guidance, Home/recents and relation/transition builders | Current Home/recovery and expanded review controls still need native acceptance | | Noisy architecture pictures | Nested ownership/allocation views, independent placements, collapse, automatic routing, editable bends/labels and port positions | Dense diagrams may need manual adjustment; routing has a bounded solve budget | | Hard-to-review model files | Modular typed source, stable aliases/UUIDs, preserved comments/formatting and source-aware history | Semantic merges, completion and an LSP are absent | | Hidden mutation effects | Validated atomic commands, dependency cleanup, identity maintenance, previewed counterpart changes and undo | No distributed editing or automatic structural reconciliation | @@ -45,7 +45,13 @@ These are concrete design responses, not claims that every Capella user experien ## Required first-release work -Direct scenario/process canvas authoring, semantic-content drift and reviewed counterpart reconciliation, native acceptance of expanded data contracts, persistent coverage/filter views, Home/recent-project and unsaved-work recovery, guidance and final acceptance remain in the [release plan](../harness/PLAN.md). These are unfinished requirements, not exclusions from the release. Hosted desktop editing is implemented; its remaining native acceptance is tracked in the release plan and current state. Direct state editing has scoped Windows/Linux acceptance recorded in [state evidence](../harness/release-state-editing.md). Interrupted source-save recovery restores a save transaction; it does not checkpoint unsaved models or drafts. +Direct process/scenario/state authoring, typed semantic drift/reconciliation, Home/recents and unsaved-work checkpoints are implemented. Their evidence and outstanding acceptance cases are separated in the [current coverage audit](../harness/release-current-coverage-20260906.md). Direct state editing has earlier scoped Windows/Linux acceptance recorded in [state evidence](../harness/release-state-editing.md); that does not accept every current change. + +Required unfinished implementation includes ordinary OA CommunicationMean authoring with an accurate participant-level contract; persisted selective relation/category/coverage views; completeness checks for exchanges on non-leaf work, behavior-context coverage and missions without capabilities; and ordered-collection, attachment, artifact and multi-source semantic-review GUI controls. Existing Legacy OA boundary exchanges and their Interaction allocations remain supported, but do not close the CommunicationMean requirement. + +The desktop attempts a private checkpoint every 10 seconds while work is unsaved. It retains accepted source, raw source buffers, pending property/behavior forms and supported workflow drafts, including semantic-review input. Home recovery opens a detached unsaved copy requiring Save as; it restores no undo stack and writes neither the original model nor a hosted destination. Interrupted source-save recovery is a separate mechanism for an incomplete save transaction. + +Remaining release gates include current-tree automated checks, expanded data/semantic-review and Home/recovery native acceptance, outstanding behavior and hosted workflow cases, default/minimum-window Windows/Linux acceptance, fresh build/MSRV and specification/OpenAPI checks, real REST/restart/parity verification, packages/checksums and independent final review. Guidance must follow the accepted controls. These are unfinished release requirements, not exclusions or release-acceptance claims. ## Remaining boundaries diff --git a/docs/windows-packaging.md b/docs/windows-packaging.md new file mode 100644 index 00000000..5255a9d1 --- /dev/null +++ b/docs/windows-packaging.md @@ -0,0 +1,69 @@ +# Portable Windows packaging + +The standard build target is x86_64 MSVC. The optional gnullvm build uses LLVM-MinGW UCRT and currently needs `libunwind.dll` alongside both executables. Packaging is a separate operation on already-built inputs; it neither builds nor launches applications and does not confer release acceptance. + +Run from a Git checkout with Windows PowerShell 5.1 or PowerShell 7. Build and test first using the [README](../README.md). Select the actual output directory explicitly; a native-host Cargo build normally uses `target/release`, while an explicit `--target` uses `target//release`. + +Packaging also needs the build's matching `cargo` and `rustc` on the packaging process PATH, the locked dependency sources/archive cache, and installed Rust target libraries and copyright reports. The script invokes only read-only `cargo metadata --locked --offline --filter-platform ` and Rust version/sysroot queries. It does not download dependencies or build. These tools are packaging prerequisites; the extracted application does not require them. + +```powershell +# Standard MSVC outputs, already built: +./scripts/package-windows.ps1 -Target x86_64-pc-windows-msvc ` + -Version 0.2.0-rc1 -BinaryDirectory ./target/release + +# Optional LLVM-MinGW outputs, already built in a separate target directory: +./scripts/package-windows.ps1 -Target x86_64-pc-windows-gnullvm ` + -Version 0.2.0-rc1 -BinaryDirectory ./target-gnullvm/release ` + -LlvmMinGWRoot C:/Tools/llvm-mingw-20260826-ucrt-x86_64 +``` + +The caller must supply the matching build target, version, and final binaries. PE headers establish x86_64 architecture, but do not prove a source revision or Cargo profile. `package.json` records input hashes and inspected imports; retain build/test evidence tied to those hashes separately. The version labels the package and does not change the embedded application version. + +The script creates a fresh `dist/ArcadiaStudio--/`, matching ZIP, and ZIP `.sha256` file. `-OutputRoot` selects another location. Existing output names are refused; no directories are deleted or overwritten. A `.packaging.lock` claim and partial outputs remain after failure for inspection; retry under a fresh version/output location. ZIP file order and timestamps are fixed so unchanged inputs can be compared across runs; this is not a reproducible compiler-build guarantee. + +Each package includes desktop and server executables, README, MIT license, built-in Help in the application, tracked docs (including language/API specifications and OpenAPI), tracked examples and deployment recipes, current coverage/runtime notes, START-HERE, provenance, and SHA-256 inventory. It includes current working-tree contents of tracked files, so review uncommitted documentation edits before packaging. Untracked sample/user files are excluded. Repository sources are needed separately for the documented Cargo and Docker build recipes. + +For gnullvm, the exact selected toolchain's x86_64 `libunwind.dll` and full distribution `LICENSE.TXT` are included; both are hashed. Standard MSVC packaging has no LLVM-MinGW coupling. The script reads ordinary PE imports recursively for packaged runtime DLLs, accepts a bounded list of Windows system components/API sets, and fails on unknown imports. For example, a build importing a VC redistributable DLL must have its redistribution and dependency handling resolved before packaging; it is not silently treated as part of Windows. Do not copy arbitrary DLLs from System32. Dynamically loaded graphics libraries and delay-load imports require the runtime check below. + +Every staged copy is SHA-256 compared with its source; all ZIP entries are read back and SHA-256 compared with staging, and names/counts are checked. `SHA256SUMS.txt` covers every package file except itself; the separate archive hash covers the ZIP including that inventory. Integrity success establishes transport contents, not application behavior. + +## Dependency and asset notices + +`THIRD-PARTY-NOTICES.txt` indexes the collected texts. `dependency-notices.json` records exact package versions and license expressions, the Cargo.lock hash, registry archive checksums verified against the lock, hashes of actual local source files, resolved features, embedded font identities, and staged notice paths/hashes. The selected graph starts at `oa-desktop` and `oa-server` and follows transitive normal/build dependency edges; dev-only edges are excluded. Cargo workspace feature unification can retain a conservative superset. This inventory is tied to packaging inputs and is not proof that caller-supplied binaries were built from them. Keep matching compiler/build evidence separately. + +Available LICENSE, COPYING, NOTICE, COPYRIGHT, OFL and equivalent named files are retained from each selected crate, including nested notices. Workspace crates receive this repository's license. Published crates that omit required texts need supplements in `scripts/windows-notices/manifest.json` (or `-NoticeManifest `), bound to their exact version and locked registry checksum. Supplemental files must be local, stay inside that manifest's directory, and match the recorded SHA-256 and upstream HTTPS source URL. This manifest is deliberately reviewed input; the packaging script does not fetch notices or substitute a generic SPDX license text. Updates to dependencies can require new reviewed supplements. + +Literal `include_bytes!` font references in production/build Rust files receive separate exact asset hash and notice mappings, conservatively including feature-gated references; dependency test/example font files are not assumed to ship. This scan is not a complete binary asset detector: generated, indirect or non-font embeds need deliberate review. Rust's installed `COPYRIGHT-library.html` and full `COPYRIGHT.html` are included, with available distribution runtime copying notices, compiler identity, and target-library hashes. LLVM-MinGW includes its distribution LICENSE plus a component notice mapping bound to the exact `libunwind.dll` hash. Missing sources, unsupported source types, missing required notices, unknown embedded font mappings, or changed supplemental hashes stop packaging before creating an output claim. + +Notice collection is an auditable aid and does not constitute a universal license audit. An unresolved item requires source/provenance review; do not remove the check merely to produce a ZIP. + +## Clean-environment verification + +Use an ordinary Windows account with a supported graphical desktop/driver. Extract the ZIP to a fresh user-writable directory, keep the DLL beside both executables, verify hashes, and test from an unrelated working directory. Record exact binary/runtime/ZIP hashes, Windows version and graphics adapter, stdout/stderr, exit codes, and native screenshots. A second Windows machine without development tools remains the stronger portability check. + +For a controlled smoke, set **only the current PowerShell process's** PATH to Windows system paths, launch hidden with absolute executable paths, then restore PATH in `finally`. This does not change machine/user environment settings. Use a fresh temporary server data directory and an unused loopback port. Example setup (replace `$bundle` with the actual extracted directory): + +```powershell +$bundle = 'C:/Users/YourName/Downloads/ArcadiaStudio-0.2.0-rc1-x86_64-pc-windows-gnullvm' +$qa = Join-Path $env:TEMP ('arcadia-package-smoke-' + [guid]::NewGuid().ToString('N')) +[void][IO.Directory]::CreateDirectory($qa) +$savedPath = $env:PATH +try { + $env:PATH = "$env:SystemRoot/System32;$env:SystemRoot" + $server = Start-Process -FilePath (Join-Path $bundle 'arcadia-server.exe') ` + -ArgumentList @('--bind', '127.0.0.1:18787', '--data-dir', ('"' + (Join-Path $qa 'store') + '"')) ` + -WorkingDirectory $qa -WindowStyle Hidden -PassThru ` + -RedirectStandardOutput (Join-Path $qa 'server.stdout.log') ` + -RedirectStandardError (Join-Path $qa 'server.stderr.log') + $desktop = Start-Process -FilePath (Join-Path $bundle 'arcadia-studio.exe') ` + -WorkingDirectory $qa -WindowStyle Hidden -PassThru ` + -RedirectStandardOutput (Join-Path $qa 'desktop.stdout.log') ` + -RedirectStandardError (Join-Path $qa 'desktop.stderr.log') +} finally { + $env:PATH = $savedPath +} +``` + +This snippet only starts processes; it does not verify readiness. Check the server's health route documented in [server setup](server.md), import/read/update a model, restart with the same temporary store, and verify persistence. Close only the processes started for this smoke. Exercise the desktop's Home/example/open/save/reopen and Help, inspect real diagrams, and perform the [Save-as Undo/Redo recheck](../harness/release-save-as-runtime-packaging.md). Close normally and check stderr/exit state. A process that survives a few seconds alone is not adequate GUI evidence. + +Acceptance is pending until these checks are actually performed against the packaged final binaries. Historical V2 bundles, this machine's Cargo checks, and a successful development launch with compiler PATH are separate evidence. diff --git a/harness/DECISIONS.md b/harness/DECISIONS.md index 640ac584..3186a900 100644 --- a/harness/DECISIONS.md +++ b/harness/DECISIONS.md @@ -1,5 +1,14 @@ # Architectural and product decisions +## Current continuation decisions — 2026-09-06 + +- Saving under another source entry basename is destination bookkeeping, not an engineering edit. Adopt an exact current save receipt without adding Undo or clearing Redo; rebase uniform entry names across retained history after collision and compilation checks. Stale receipts cannot redirect newer raw source. Native discovery and eleven focused regression results are in release-save-as-runtime-packaging.md; updated native verification remains required. +- Home Resume returns to the last actual workspace. A saved state diagram opens in Behavior with its dedicated renderer; entering Architecture explicitly remains possible. Constructor choice among UUID-ordered views is not a guarantee that an application starts in Architecture. Tests of architecture placement must enter that workspace explicitly. +- Coverage warnings describe missing engineering context while structural errors remain rejecting. Mission/capability support and work/exchange behavior coverage use actual explicit links and occurrence membership, not inferred performer allocation or descriptive state notes. The shared core diagnostics are exposed equally to source, desktop and REST; see release-coverage-diagnostics-design.md. +- Portable release contents need the selected binary's complete runtime and notice inventory. Adding the gnullvm libunwind DLL resolves its ordinary import, but the application MIT license does not replace linked dependency notices. Archive hashes and clean-PATH launch are separate checks; neither alone establishes full release acceptance. + +The following older decisions retain their historical scope. Newer first-release decisions and the normative language contract supersede prototype exclusions. + - **One typed model, three Rust crates.** `oa-core` owns semantics, validation, transactions, history, references, language and projection. `oa-desktop` is an Iced editor; `oa-server` is an Axum transport/store. A second GUI/text model, event framework or runtime plugin ABI would introduce unnecessary synchronization and extension complexity. - **Bounded OA/SA profile.** Typed element bodies determine layer/kind; separate records describe relationships, diagrams, realization traces and artifacts. SA has one System and external Actors; LA/PA deployment and executable simulation are outside V2. The normative reST contract and scope document state exact semantics instead of implying full Capella/SysML conformance. - **Nesting derives from meaning.** Semantic containment and leaf allocation feed independent diagram projections. A placement drag changes geometry, never ownership. Shared projection drives canvas, Fit and SVG. Scenario/state graphics project the same behavior records and select them directly; numbered routes lead to full transition details. @@ -131,3 +140,65 @@ while the diagram retains its own camera/scroll state. Nesting both under the pa scroll supplied infinite height and clipped the fields at the supported minimum. Real Iced layout tests verify finite field space; native recheck remains required. +## Private recovery and integrated continuation — 2026-09-06 + +The branch audit selects c091f80 as the integrated production baseline: side-branch +state/process/scenario/data/hosted/semantic work is present or patch-equivalent. +The stale checkout's duplicate investigation stays in its named stash. New work +continues on codex/release1-continuation rather than replacing accepted editors. + +Unsaved recovery uses separate per-session, versioned private checkpoints. Store +the accepted authored source bundle and raw source/property/behavior/workflow +drafts; regenerate validation and command previews on restore. Never serialize +credentials, destination handles or reusable executable plans. Restored work is +an unsaved copy requiring an explicit destination. Undo history is deliberately +not restored: retaining a coherent current model and rejected input is the useful +recovery boundary, while persistent history would add a separate migration and +storage contract. Exact accepted-source validation precedes replacement of the +currently open document. + +Checkpoint writes and restoration share workspace admission limits. A failed +checkpoint must retain the last usable file. Late completions are scoped to the +document ID, and discard/close waits for an outstanding writer before retiring +its result. Model-file Save and private recovery remain distinct operations. +Native crash/restart and exact21-file Save as evidence is recorded in +release-current-native-verification.md; larger recovery/history requirements +would justify revisiting these explicit bounds. + +## Semantic rows and state-view entry — 2026-09-06 + +Annotation and Binding keys identify independent rows; replacing an existing row +preserves its position, an identical Keep does not modify it, removal deletes only +that row, and a new row appends. GUI composition and core materialization agree +without normalizing untouched records into a different order. Core regressions +cover Keep plus an unrelated Header Candidate in both directions and both row +families. + +A persisted StateMachine diagram has one editing canvas and tool set. Opening or +selecting it routes to the existing Behavior workspace, preserving exact diagram +identity, selected object and raw property input. Explicit Architecture navigation +selects an ordinary view or its empty state. This fixes the native-observed blank +state-action header and inappropriate architecture connection tools without adding +a second state renderer. Pending drafts remain subject to the existing guards. + +## OA communication means: selected representation — 2026-09-06 + +The existing OA Interaction describes activity exchange; ordinary participant +communication is a verified first-release gap. Independent representation and +adversarial reviews compared a new CommunicationMean, widened BoundaryExchange +endpoints and a first-class OA port contract. Select explicit CommunicationMean +with OA Entity/Actor endpoints, orientation and carried items, reusing identified +ExchangeAllocation links to Interactions. Existing BoundaryExchange keeps its +port contract; legacy OA data is preserved without automatic conversion. + +This choice matches primary Arcadia/Capella method evidence and avoids inventing +technical OA ports. Known actual performer mismatches reject allocation; missing +performers are incomplete work, not inferred through role responsibility. Scenario +direction must resolve explicit lifelines, and correlated Reply reverses the +actual Call direction. OA participant/SA port counterpart choices must be scoped +explicitly per exchange rather than corrupting global participant correspondence. +See release-oa-communication-design.md and the independent critique for sources, +alternatives and falsification tests. This is a representation decision, not an +implemented or accepted feature; versioning and view contracts remain to be +integrated with it. Revisit if realistic OA workflows demonstrate separately +identified operational endpoint contracts that a participant relation cannot carry. diff --git a/harness/PLAN.md b/harness/PLAN.md index c9691cec..97837a6e 100644 --- a/harness/PLAN.md +++ b/harness/PLAN.md @@ -4,7 +4,9 @@ Deliver the first release of Arcadia Studio for system engineers: complete Operational Analysis and System Analysis/System Architecture workflows, equivalent graphical and textual models, clear artifact traceability and a deployable/programmatically accessible Rust service. Logical/Physical Architecture and later Arcadia layers are future releases, as the new objective explicitly states. -The authoritative objective is attachment10134df9-bea2-4c02-a820-67b53d6b86f7/goal-objective.md. It retains first-release scope and source/include, drag-selection and direct behavior editing requirements, and strengthens domain coherence, alternative evaluation and target-user task evidence. TASK_LEDGER.md separates given/verified facts, assumptions and open candidates; STATE.md records progress. The accepted0.2.0 prototype is the baseline, not proof of release completeness. Its source is preserved at D:/CodexBuilds/Capella2-release1-01a06e2e/v2-source-baseline.zip and existing archives remain in dist until replacement passes acceptance. +The authoritative objective is attachment `10b0f701-1743-4de7-9ee1-225920e19649/goal-objective.md`. It retains first-release scope and strengthens domain coherence, alternative evaluation and target-user task evidence. TASK_LEDGER.md separates given/verified facts, assumptions and open candidates; STATE.md records progress. The accepted 0.2.0 prototype is a historical baseline, not proof of release completeness. Its old D:/CodexBuilds archives are unavailable on this machine; existing dist archives must not be replaced until new acceptance passes. + +The user's cross-branch reminder led to an ancestry/patch/tree audit. Continue from integrated `c091f80` on `codex/release1-continuation`; direct state/scenario/process, data, hosted and semantic review work already exists here. `release-branch-integration-20260906.md` records the audit. `release-current-coverage-20260906.md` is the current measurable acceptance map and supersedes stale absence claims in older narratives below. ## Existing architecture and retained decisions diff --git a/harness/STATE.md b/harness/STATE.md index 63a90cce..e356521b 100644 --- a/harness/STATE.md +++ b/harness/STATE.md @@ -1,3 +1,37 @@ +# Current continuation — 2026-09-06 + +The full first-release goal remains active. User requested that completed Git branches be accounted for: current branch codex/release1-continuation starts from integrated c091f80; the branch audit proves meaningful side-branch work is included or patch-equivalent. No stale stash is restored. + +Current Windows evidence: 869 workspace tests pass with one existing ignored benchmark (.local/current-integrated-tests-corrected.log). Strict all-target Clippy and clean GUI/server builds pass (.local/current-integrated-clippy.log, .local/current-integrated-build-clean.log). Two equivalent guard rewrites and a test-only helper annotation closed Clippy findings after the passing test run. Shared Windows Cargo target is idle. Portable Rust1.90 GNU host plus gnullvm target is prepared; MSRV compilation is still pending. + +Native process10896 is running from .local/native-qa/bin-20260906-corrected with isolated appdata and system-only PATH. Binary660BECC8DB96B218D12805B39661B46C9F48DF4F8F07CB3B7D42C1F803BE089F includes all behavior/Home/save/layout fixes; the later clean build differs only in the stated lint/test-helper changes. Computer Use was stopped with Escape, then explicitly reauthorized by the user's @Computer message. The unfinished timed-transition checkpoint recovered exactly, including invalid empty expression. Repair30s created one Event and changed its Internal transition atomically. Save-as to state-recovered-renamed.arcadia, one Undo, Save, Redo, Save passed: Undo entry exactly pristine; Redo entry exactly accepted; checkpoints retired. Home Continue editing returns to the same Behavior state view. Minimum1100x700 navigation shows all9pages and guide Clear is no longer clipped. Root is currently on the guide; ordered and reference-pair layout native checks remain. + +REST: the staged server passed25 captured requests, twice from isolated stores, with exact five coverage warnings across source/JSON/current validation, atomic422 rejection, repaired persistence after forced restart and warning restoration by explicit replacement. Reproducible probe/fixture is tracked in harness/fixtures/rest-coverage. This is Windows service evidence, not Linux acceptance. + +Packaging: scripts stage selected dependency/runtime notices and verify source copies and every ZIP entry. QA b has528files including485licensefiles; ZIP SHA256237b30ee0c704efad89ed5c371d2c965f9e7d11ea318c97ba16ee34282456f73. Iced Icons uses its declared distributed MIT plus preserved font copyright metadata and candid provenance limits; no contrary license was established. See release-windows-package-review.md for exact clean-build hashes. Earlier QA package native clean-PATH Home/example/help/close passed; final QA b native acceptance remains. + +Linux: the QEMU pilot achieved actual Ubuntu SSH/cloud-init/filesystem/DNS validation with no failed units. Guest-only startup timeouts were adapted for slow emulation; no application build ran. User then installed WSL and authorized creating a distro. Our QEMU processes are stopped. WSL2 Ubuntu24.04 import failed HCS_E_SERVICE_NOT_AVAILABLE; VMP is enabled but Windows reports a pending reboot, with vmcompute/hns absent. User was asked to restart when convenient. No existing distro was touched and no Windows features/settings were changed by the agent. Verified official image and exact isolated retry command are in release-linux-validation-options.md. Recheck after restart before concluding nested virtualization is unavailable. + +Required next work: ordered/keyed/native view checks and source-oracle capture; checkpoint verified work in Git; implement first-class OA CommunicationMean plus deliberate source6 admission, saved selective/category/context views, remaining attached-child/incident/artifact/multi-source review GUI, full engineering guide, MSRV/optimized packages and fresh Windows/Linux end-state acceptance. Selective-view candidate and independent critique now define family-specific category sets, global versus selected-context coverage, preserved port geometry and a dense40-edge readability gate. Nothing here narrows the original goal or marks the release complete. + +The material below records earlier checkpoints. This current section takes precedence for process state, tool ownership and current evidence. +Authoritative objective: attachment `10b0f701-1743-4de7-9ee1-225920e19649/goal-objective.md`, read in full. User requested accounting for other Git branches. Current working branch is `codex/release1-continuation` from integrated `c091f80`; all meaningful side-branch production work is present or patch-equivalent. See release-branch-integration-20260906.md. Original checkout `ea2e8e9` was stale; the duplicate investigation is stashed and superseded. + +Historical paths/windows/builds below are evidence from the previous environment, not current active processes. This machine has no D:/CodexBuilds or installed WSL. The first release is incomplete. + +Current implementation: Home/recents, semantic Annotation/Binding review, private accepted-source/raw-draft checkpoints including workflow recovery, and typed StateAction detail rows. Independent reviews found and closed checkpoint late-write/discard races, candidate-parent picker context and literal-UUID expression rendering defects. Native acceptance is still required. + +Current toolchain: user-local Rust 1.98.1 `stable-x86_64-pc-windows-gnullvm` plus official LLVM-MinGW 20260826 UCRT under ignored `.local/toolchain`; dot-source `.local/toolchain/env.ps1`. GNU test executables with installer-like filenames initially failed Windows error 740. Shared build scripts now embed an explicit `asInvoker` manifest in GNU/gnullvm binaries and tests, without changing system security or requiring elevation. + +Fresh workspace tests pass **840 tests** with one existing benchmark ignored; `.local/current-workspace-tests-final.log`. Strict all-target workspace Clippy passed in `.local/current-clippy.log`. Initial keyed fixture, core row-order and state test failures are fixed; a hosted timeout test's invalid admission-timing assumption is also corrected. Independent review fixed write/restore checkpoint workspace limits before a prior valid checkpoint can be replaced. + +Current native Windows evidence is in `release-current-native-verification.md`: empty Home/default/minimum layout, example, rejected raw source crash/recover, exact21-file Save as, recovery retirement, Recents reopen, rejected raw UUID crash/recover and guarded Save & continue passed. All three typed StateAction rows select the correct relation/phase/target in Behavior. Native found a saved-state-view entry bug: Architecture uses a generic renderer and wrong tools. Routing fix passed435 desktop tests and GUI/server build. Updated native process796 is running; invalid Binding crash recovery, exact repaired Save and one-step Undo/Redo passed. Annotation workflow recovered and saved exactly against the oracle; native found Save-as basename rename adds an unwanted Undo step and can clear Redo. A bounded history-preserving correction is being implemented. + +Root controls native input. Semantic worker currently owns shared Cargo for ordered-review focused tests; coverage diagnostics follow. Save-as history worker must coordinate build access. All native trial data lives under ignored `.local/native-qa`, with app metadata redirected away from the user's real recents. Current uncommitted continuation still needs native semantic/binding/history and behavior/workflow acceptance, fresh routing checks and a milestone commit. It is not a complete release. + +The current coverage audit is `release-current-coverage-20260906.md`. Independent OA design/critique agrees that ordinary participant communication needs a first-class CommunicationMean instead of broadening SA port-bound BoundaryExchange. Other remaining release work includes saved selective/coverage views, completeness diagnostics, ordered/attachment/artifact/multi-source review controls and final platform/package gates. These are required unfinished work, not exclusions. + +--- # Current release state — 2026-09-06 The first-release goal remains active and incomplete. Authoritative objective: diff --git a/harness/TASK_LEDGER.md b/harness/TASK_LEDGER.md index 931ce454..8be25b97 100644 --- a/harness/TASK_LEDGER.md +++ b/harness/TASK_LEDGER.md @@ -137,3 +137,6 @@ Candidate exactProject and remaining constructors/Linux native are still pending Strategy: finish structured parity while Home/recents progresses independently; recovery, ordered/attachments/artifacts/multi-source and final acceptance remain. + +## 2026-09-06 branch reconciliation +Verified integrated baseline c091f80 contains direct state/scenario/process editing, data contracts, schema 5 semantic review and hosted editing. Current branch codex/release1-continuation adopts it; independent ancestry/patch/tree audits found no required production transplant. Historical environment is absent here. Current user objective is attachment 10b0f701-1743-4de7-9ee1-225920e19649/goal-objective.md. See release-branch-integration-20260906.md for evidence and ownership. Assumption: portable user-local GNU tooling can verify Windows without system installation; being tested, not yet established. diff --git a/harness/fixtures/release1-native/README.md b/harness/fixtures/release1-native/README.md new file mode 100644 index 00000000..621f9676 --- /dev/null +++ b/harness/fixtures/release1-native/README.md @@ -0,0 +1,9 @@ +# First-release native acceptance fixtures + +Synthetic, identity-stable source/JSON models and direct-core expected outputs for the current source-v5 continuation. Work on copies. The first user-facing state and keyed review trials were performed with Windows binary SHA25670069D6CBBE1917E1597794B3E190FF9CF23664D458F794B3270DDBE6EE683A2; see ../../release-current-native-verification.md for exact accepted scope and open findings. Ordered fixtures are validated oracles awaiting native acceptance, not evidence that those workflows have passed. + +- state-actions.arcadia: typed Entry/Do/Exit rows, legacy notes, Internal transition and nested region. Initial14elements/10relations/one8-placement state view. With the new advisory coverage pass, uncovered function warnings are expected; they do not invalidate the model. +- semantic-keyed.steps.md: Annotation Take and rejected/repaired Binding Candidate, with exact source and JSON oracles, frozen requests, plans and identities. The independent expected sources preserve the leading comment and row order. +- semantic-ordered.steps.md: ordered member insertion ambiguity and explicit final order. Main, reverse, Scenario, Branch steps and Fragment branch variants each include initial/final source and JSON, request, plan and manifest. + +The steps originally name .local/native-fixtures, the isolated trial directory; use a copied directory here equivalently. Never overwrite an oracle with a native Save. Versioned source snapshots must be migrated deliberately when the normative source version changes; keep historical native evidence scoped to its actual binary. These fixtures do not replace the Rust integration tests or constitute complete release acceptance. diff --git a/harness/fixtures/release1-native/semantic-keyed.after-repair.arcadia b/harness/fixtures/release1-native/semantic-keyed.after-repair.arcadia new file mode 100644 index 00000000..45ec6569 --- /dev/null +++ b/harness/fixtures/release1-native/semantic-keyed.after-repair.arcadia @@ -0,0 +1,482 @@ +// Native keyed review: preserve this exact comment. +arcadia 5; +project mission "Semantic keyed native review" id "13089f56-2a58-4f42-a0e3-8c9812fb5857" { + description ""; + + sa_function sa_navigation_b "SA Navigation B" id "0114bf4c-343b-4a96-918c-3b4087df4c5e" { + description ""; + parent none; + } + + oa_data_field oa_navigation_latitude "OA Navigation latitude" id "01d025e3-ee88-4449-a613-217b44a61ae2" { + default none; + description ""; + direction Unspecified; + maximum 1; + minimum 0; + parent @oa_navigation_coordinate; + storage Value; + type_ref Real; + unit ""; + } + + oa_data_field oa_navigation_destination "OA Navigation destination" id "02a82c95-f030-4be3-9ffe-6ebccf40fcb1" { + default none; + description ""; + direction Unspecified; + maximum 1; + minimum 0; + parent @oa_navigation_item; + storage Value; + type_ref { + Named @oa_navigation_coordinate; + }; + unit ""; + } + + sa_scenario sa_rendezvous_caller "SA Rendezvous caller" id "06ec35b3-ce60-43a7-8300-c239fa91c898" { + context none; + description ""; + parent none; + postcondition ""; + precondition ""; + steps [@sa_rendezvous_use]; + viewpoint Functional; + } + + sa_lifeline sa_rendezvous_local "SA Rendezvous local" id "0ba127bc-cd2d-4e53-b76e-9c01025a7c3f" { + description ""; + parent @sa_rendezvous_caller; + represented none; + } + + sa_data_field sa_navigation_origin "SA Navigation origin" id "0cf0ba32-01c8-41f5-a44c-ed31daa3ca92" { + default none; + description ""; + direction Unspecified; + maximum 1; + minimum 0; + parent @sa_navigation_item; + storage Value; + type_ref { + Named @sa_navigation_coordinate; + }; + unit ""; + } + + oa_lifeline oa_rendezvous_referenced "OA Rendezvous referenced" id "0e3d7b11-4c0f-41a0-beae-a5440e30782f" { + description ""; + parent @oa_rendezvous_callee; + represented none; + } + + sa_scenario sa_rendezvous_callee "SA Rendezvous callee" id "12143ec8-bcf9-4755-809c-87b653465888" { + context none; + description ""; + parent none; + postcondition ""; + precondition ""; + steps []; + viewpoint Functional; + } + + oa_data_field oa_navigation_origin "OA Navigation origin" id "19be028f-96cd-440e-bbcb-e151fbe00b45" { + default none; + description ""; + direction Unspecified; + maximum 1; + minimum 0; + parent @oa_navigation_item; + storage Value; + type_ref { + Named @oa_navigation_coordinate; + }; + unit ""; + } + + sa_lifeline sa_rendezvous_referenced "SA Rendezvous referenced" id "1cb0eb67-6fa7-414c-a2aa-155cb39da564" { + description ""; + parent @sa_rendezvous_callee; + represented none; + } + + oa_lifeline oa_rendezvous_extra_local "OA Rendezvous extra local" id "1f697a2c-8455-48f1-bbdf-bdf657fe8fc4" { + description ""; + parent @oa_rendezvous_caller; + represented none; + } + + oa_exchange_item oa_navigation_item "OA Navigation Item" id "20619f0c-4419-46b5-a15c-49b004acfa91" { + description ""; + kind Information; + mechanism Unspecified; + parent none; + supertype none; + } + + oa_activity oa_navigation_b "OA Navigation B" id "264ff2ee-7786-4db7-b78d-2be802c268ac" { + description ""; + parent none; + } + + sa_scenario sa_navigation_scenario "SA Navigation scenario" id "409c78a0-28bc-4810-86ba-300204a3513f" { + context none; + description ""; + parent none; + postcondition ""; + precondition ""; + steps [@sa_navigation_message]; + viewpoint Functional; + } + + oa_lifeline oa_rendezvous_local "OA Rendezvous local" id "424d78d2-9145-4b34-a18f-80a335164a9b" { + description ""; + parent @oa_rendezvous_caller; + represented none; + } + + oa_lifeline oa_rendezvous_alternate "OA Rendezvous alternate" id "59173870-bc74-4c85-89b3-b0cfb8ad70cb" { + description ""; + parent @oa_rendezvous_caller; + represented none; + } + + sa_exchange_item sa_navigation_item "SA Navigation Item" id "6e8218df-2d57-4247-a401-e0458c4d5bc1" { + description ""; + kind Information; + mechanism Unspecified; + parent none; + supertype none; + } + + sa_data_type sa_navigation_coordinate "SA Navigation Coordinate" id "715228b2-a573-47ad-bf53-0a9b968ded96" { + default none; + definition { + Record { + supertype none; + }; + }; + description ""; + parent none; + unit ""; + } + + oa_occurrence oa_navigation_message "OA Navigation message" id "73de257c-d816-4f91-b507-36f79ca49f08" { + body { + Exchange { + exchange @oa_navigation_exchange; + message_kind Asynchronous; + receiver none; + reply_to none; + sender none; + values [{ + expression "latitude_deg + 0.25"; + field @oa_navigation_latitude; + path [@oa_navigation_origin]; + }, { + expression "second independent"; + field @oa_navigation_latitude; + path [@oa_navigation_destination]; + }]; + }; + }; + description ""; + parent @oa_navigation_scenario; + postcondition ""; + precondition ""; + } + + sa_occurrence sa_rendezvous_use "SA Rendezvous use" id "7d9832e5-0288-4d04-a5df-1551d050f79e" { + body { + ScenarioUse { + bindings [{ + local @sa_rendezvous_alternate; + referenced @sa_rendezvous_referenced; + }, { + local @sa_rendezvous_extra_local; + referenced @sa_rendezvous_extra_referenced; + }]; + scenario @sa_rendezvous_callee; + }; + }; + description ""; + parent @sa_rendezvous_caller; + postcondition ""; + precondition ""; + } + + oa_scenario oa_navigation_scenario "OA Navigation scenario" id "814c985a-a8c8-409a-b11b-c9572f29eabc" { + context none; + description ""; + parent none; + postcondition ""; + precondition ""; + steps [@oa_navigation_message]; + viewpoint OperationalActivity; + } + + sa_lifeline sa_rendezvous_extra_referenced "SA Rendezvous extra referenced" id "82798865-193a-46b0-b40d-b22ae91eba28" { + description ""; + parent @sa_rendezvous_callee; + represented none; + } + + oa_data_type oa_navigation_coordinate "OA Navigation Coordinate" id "85eee860-b8ee-4c14-8551-ec4ce8b84aa8" { + default none; + definition { + Record { + supertype none; + }; + }; + description ""; + parent none; + unit ""; + } + + sa_data_field sa_navigation_destination "SA Navigation destination" id "8dbaedfd-4a4a-4e5d-a9f9-d040d5f144d8" { + default none; + description ""; + direction Unspecified; + maximum 1; + minimum 0; + parent @sa_navigation_item; + storage Value; + type_ref { + Named @sa_navigation_coordinate; + }; + unit ""; + } + + oa_occurrence oa_rendezvous_use "OA Rendezvous use" id "9a78b793-a987-438e-a041-233dc41f2b10" { + body { + ScenarioUse { + bindings [{ + local @oa_rendezvous_local; + referenced @oa_rendezvous_referenced; + }, { + local @oa_rendezvous_extra_local; + referenced @oa_rendezvous_extra_referenced; + }]; + scenario @oa_rendezvous_callee; + }; + }; + description ""; + parent @oa_rendezvous_caller; + postcondition ""; + precondition ""; + } + + oa_lifeline oa_rendezvous_extra_referenced "OA Rendezvous extra referenced" id "9e60984f-9ca3-4f3d-bc3f-b6b973cdc698" { + description ""; + parent @oa_rendezvous_callee; + represented none; + } + + sa_port sa_navigation_exchange_output "SA Navigation exchange output" id "9eb225ee-7103-5eb7-88d0-ff2128408748" { + contract { + FunctionOutput { + outgoing_items [@sa_navigation_item]; + }; + }; + description ""; + legacy none; + parent @sa_navigation_a; + } + + sa_lifeline sa_rendezvous_extra_local "SA Rendezvous extra local" id "a8518771-fa4d-4e6e-bb6a-9e5f719f874a" { + description ""; + parent @sa_rendezvous_caller; + represented none; + } + + sa_data_field sa_navigation_latitude "SA Navigation latitude" id "ab1e449f-24f1-4bee-938c-b49510d19bf3" { + default none; + description ""; + direction Unspecified; + maximum 1; + minimum 0; + parent @sa_navigation_coordinate; + storage Value; + type_ref Real; + unit ""; + } + + sa_port sa_navigation_exchange_input "SA Navigation exchange input" id "ad717be1-3f30-55db-94b9-7a51558b6dd6" { + contract { + FunctionInput { + incoming_items [@sa_navigation_item]; + }; + }; + description ""; + legacy none; + parent @sa_navigation_b; + } + + sa_function sa_navigation_a "SA Navigation A" id "ad7dede7-59bf-4355-afec-08c905a564ff" { + description ""; + parent none; + } + + sa_lifeline sa_rendezvous_alternate "SA Rendezvous alternate" id "bb0ad838-eb7c-4470-bf27-f3a21e69fdf8" { + description ""; + parent @sa_rendezvous_caller; + represented none; + } + + sa_occurrence sa_navigation_message "SA Navigation message" id "d10ef3c1-479f-4780-9fd5-3b4d21dffe74" { + body { + Exchange { + exchange @sa_navigation_exchange; + message_kind Asynchronous; + receiver none; + reply_to none; + sender none; + values [{ + expression "latitude_deg + 0.25"; + field @sa_navigation_latitude; + path [@sa_navigation_origin]; + }, { + expression "second independent"; + field @sa_navigation_latitude; + path [@sa_navigation_destination]; + }]; + }; + }; + description ""; + parent @sa_navigation_scenario; + postcondition ""; + precondition ""; + } + + oa_activity oa_navigation_a "OA Navigation A" id "d7d34062-6820-471d-bb5f-abb7db4c8198" { + description ""; + parent none; + } + + oa_scenario oa_rendezvous_callee "OA Rendezvous callee" id "eacfc6df-4e22-4ebd-a90d-6b5146e56513" { + context none; + description ""; + parent none; + postcondition ""; + precondition ""; + steps []; + viewpoint OperationalActivity; + } + + oa_scenario oa_rendezvous_caller "OA Rendezvous caller" id "fd6e0287-66e0-4e84-8a92-98c4c167cb88" { + context none; + description ""; + parent none; + postcondition ""; + precondition ""; + steps [@oa_rendezvous_use]; + viewpoint OperationalActivity; + } + + interaction oa_navigation_exchange "OA Navigation exchange" id "6a5930e2-0cfb-4aff-a4a4-8c4798f1cda5" { + description ""; + items [@oa_navigation_item]; + source @oa_navigation_a; + target @oa_navigation_b; + } + + functional_exchange sa_navigation_exchange "SA Navigation exchange" id "9c1085f2-c91d-4776-bb56-52c864a80e22" { + description ""; + items [@sa_navigation_item]; + source @sa_navigation_a; + source_port @sa_navigation_exchange_output; + target @sa_navigation_b; + target_port @sa_navigation_exchange_input; + } + + trace rendezvous_binding_review "Rendezvous participant binding review" id "551c0dbc-ede6-42ef-ae60-2e0136ffc6e6" { + baseline { + oa { + description ""; + name "OA Rendezvous use"; + }; + sa { + description ""; + name "SA Rendezvous use"; + }; + semantic { + facets [{ + Binding { + keys { + oa uuid "0e3d7b11-4c0f-41a0-beae-a5440e30782f"; + sa uuid "1cb0eb67-6fa7-414c-a2aa-155cb39da564"; + }; + review { + oa { + Value { + local uuid "424d78d2-9145-4b34-a18f-80a335164a9b"; + referenced uuid "0e3d7b11-4c0f-41a0-beae-a5440e30782f"; + }; + }; + sa { + Value { + local uuid "bb0ad838-eb7c-4470-bf27-f3a21e69fdf8"; + referenced uuid "1cb0eb67-6fa7-414c-a2aa-155cb39da564"; + }; + }; + }; + }; + }]; + }; + }; + description ""; + kind Manual; + oa @oa_rendezvous_use; + sa @sa_rendezvous_use; + } + + trace navigation_latitude_review "Navigation origin latitude review" id "b647f858-1a69-4292-8710-674b1692d54f" { + baseline { + oa { + description ""; + name "OA Navigation message"; + }; + sa { + description ""; + name "SA Navigation message"; + }; + semantic { + facets [{ + Annotation { + keys { + oa { + field uuid "01d025e3-ee88-4449-a613-217b44a61ae2"; + path [uuid "19be028f-96cd-440e-bbcb-e151fbe00b45"]; + }; + sa { + field uuid "ab1e449f-24f1-4bee-938c-b49510d19bf3"; + path [uuid "0cf0ba32-01c8-41f5-a44c-ed31daa3ca92"]; + }; + }; + review { + oa { + Value { + expression "latitude_deg + 0.25"; + field uuid "01d025e3-ee88-4449-a613-217b44a61ae2"; + path [uuid "19be028f-96cd-440e-bbcb-e151fbe00b45"]; + }; + }; + sa { + Value { + expression "latitude_deg + 0.25"; + field uuid "ab1e449f-24f1-4bee-938c-b49510d19bf3"; + path [uuid "0cf0ba32-01c8-41f5-a44c-ed31daa3ca92"]; + }; + }; + }; + }; + }]; + }; + }; + description ""; + kind Manual; + oa @oa_navigation_message; + sa @sa_navigation_message; + } + +} diff --git a/harness/fixtures/release1-native/semantic-keyed.after-repair.json b/harness/fixtures/release1-native/semantic-keyed.after-repair.json new file mode 100644 index 00000000..a33c42cd --- /dev/null +++ b/harness/fixtures/release1-native/semantic-keyed.after-repair.json @@ -0,0 +1,895 @@ +{ + "schema_version": 5, + "id": "13089f56-2a58-4f42-a0e3-8c9812fb5857", + "alias": "mission", + "name": "Semantic keyed native review", + "description": "", + "elements": { + "0114bf4c-343b-4a96-918c-3b4087df4c5e": { + "id": "0114bf4c-343b-4a96-918c-3b4087df4c5e", + "alias": "sa_navigation_b", + "name": "SA Navigation B", + "description": "", + "parent": null, + "body": { + "Sa": "Function" + } + }, + "01d025e3-ee88-4449-a613-217b44a61ae2": { + "id": "01d025e3-ee88-4449-a613-217b44a61ae2", + "alias": "oa_navigation_latitude", + "name": "OA Navigation latitude", + "description": "", + "parent": "85eee860-b8ee-4c14-8551-ec4ce8b84aa8", + "body": { + "Oa": { + "Shared": { + "DataField": { + "type_ref": "Real", + "minimum": 0, + "maximum": 1, + "unit": "", + "direction": "Unspecified", + "storage": "Value", + "default": null + } + } + } + } + }, + "02a82c95-f030-4be3-9ffe-6ebccf40fcb1": { + "id": "02a82c95-f030-4be3-9ffe-6ebccf40fcb1", + "alias": "oa_navigation_destination", + "name": "OA Navigation destination", + "description": "", + "parent": "20619f0c-4419-46b5-a15c-49b004acfa91", + "body": { + "Oa": { + "Shared": { + "DataField": { + "type_ref": { + "Named": "85eee860-b8ee-4c14-8551-ec4ce8b84aa8" + }, + "minimum": 0, + "maximum": 1, + "unit": "", + "direction": "Unspecified", + "storage": "Value", + "default": null + } + } + } + } + }, + "06ec35b3-ce60-43a7-8300-c239fa91c898": { + "id": "06ec35b3-ce60-43a7-8300-c239fa91c898", + "alias": "sa_rendezvous_caller", + "name": "SA Rendezvous caller", + "description": "", + "parent": null, + "body": { + "Sa": { + "Shared": { + "Scenario": { + "viewpoint": "Functional", + "context": null, + "precondition": "", + "postcondition": "", + "steps": [ + "7d9832e5-0288-4d04-a5df-1551d050f79e" + ] + } + } + } + } + }, + "0ba127bc-cd2d-4e53-b76e-9c01025a7c3f": { + "id": "0ba127bc-cd2d-4e53-b76e-9c01025a7c3f", + "alias": "sa_rendezvous_local", + "name": "SA Rendezvous local", + "description": "", + "parent": "06ec35b3-ce60-43a7-8300-c239fa91c898", + "body": { + "Sa": { + "Shared": { + "Lifeline": { + "represented": null + } + } + } + } + }, + "0cf0ba32-01c8-41f5-a44c-ed31daa3ca92": { + "id": "0cf0ba32-01c8-41f5-a44c-ed31daa3ca92", + "alias": "sa_navigation_origin", + "name": "SA Navigation origin", + "description": "", + "parent": "6e8218df-2d57-4247-a401-e0458c4d5bc1", + "body": { + "Sa": { + "Shared": { + "DataField": { + "type_ref": { + "Named": "715228b2-a573-47ad-bf53-0a9b968ded96" + }, + "minimum": 0, + "maximum": 1, + "unit": "", + "direction": "Unspecified", + "storage": "Value", + "default": null + } + } + } + } + }, + "0e3d7b11-4c0f-41a0-beae-a5440e30782f": { + "id": "0e3d7b11-4c0f-41a0-beae-a5440e30782f", + "alias": "oa_rendezvous_referenced", + "name": "OA Rendezvous referenced", + "description": "", + "parent": "eacfc6df-4e22-4ebd-a90d-6b5146e56513", + "body": { + "Oa": { + "Shared": { + "Lifeline": { + "represented": null + } + } + } + } + }, + "12143ec8-bcf9-4755-809c-87b653465888": { + "id": "12143ec8-bcf9-4755-809c-87b653465888", + "alias": "sa_rendezvous_callee", + "name": "SA Rendezvous callee", + "description": "", + "parent": null, + "body": { + "Sa": { + "Shared": { + "Scenario": { + "viewpoint": "Functional", + "context": null, + "precondition": "", + "postcondition": "", + "steps": [] + } + } + } + } + }, + "19be028f-96cd-440e-bbcb-e151fbe00b45": { + "id": "19be028f-96cd-440e-bbcb-e151fbe00b45", + "alias": "oa_navigation_origin", + "name": "OA Navigation origin", + "description": "", + "parent": "20619f0c-4419-46b5-a15c-49b004acfa91", + "body": { + "Oa": { + "Shared": { + "DataField": { + "type_ref": { + "Named": "85eee860-b8ee-4c14-8551-ec4ce8b84aa8" + }, + "minimum": 0, + "maximum": 1, + "unit": "", + "direction": "Unspecified", + "storage": "Value", + "default": null + } + } + } + } + }, + "1cb0eb67-6fa7-414c-a2aa-155cb39da564": { + "id": "1cb0eb67-6fa7-414c-a2aa-155cb39da564", + "alias": "sa_rendezvous_referenced", + "name": "SA Rendezvous referenced", + "description": "", + "parent": "12143ec8-bcf9-4755-809c-87b653465888", + "body": { + "Sa": { + "Shared": { + "Lifeline": { + "represented": null + } + } + } + } + }, + "1f697a2c-8455-48f1-bbdf-bdf657fe8fc4": { + "id": "1f697a2c-8455-48f1-bbdf-bdf657fe8fc4", + "alias": "oa_rendezvous_extra_local", + "name": "OA Rendezvous extra local", + "description": "", + "parent": "fd6e0287-66e0-4e84-8a92-98c4c167cb88", + "body": { + "Oa": { + "Shared": { + "Lifeline": { + "represented": null + } + } + } + } + }, + "20619f0c-4419-46b5-a15c-49b004acfa91": { + "id": "20619f0c-4419-46b5-a15c-49b004acfa91", + "alias": "oa_navigation_item", + "name": "OA Navigation Item", + "description": "", + "parent": null, + "body": { + "Oa": { + "Shared": { + "ExchangeItem": { + "kind": "Information", + "mechanism": "Unspecified", + "supertype": null + } + } + } + } + }, + "264ff2ee-7786-4db7-b78d-2be802c268ac": { + "id": "264ff2ee-7786-4db7-b78d-2be802c268ac", + "alias": "oa_navigation_b", + "name": "OA Navigation B", + "description": "", + "parent": null, + "body": { + "Oa": "Activity" + } + }, + "409c78a0-28bc-4810-86ba-300204a3513f": { + "id": "409c78a0-28bc-4810-86ba-300204a3513f", + "alias": "sa_navigation_scenario", + "name": "SA Navigation scenario", + "description": "", + "parent": null, + "body": { + "Sa": { + "Shared": { + "Scenario": { + "viewpoint": "Functional", + "context": null, + "precondition": "", + "postcondition": "", + "steps": [ + "d10ef3c1-479f-4780-9fd5-3b4d21dffe74" + ] + } + } + } + } + }, + "424d78d2-9145-4b34-a18f-80a335164a9b": { + "id": "424d78d2-9145-4b34-a18f-80a335164a9b", + "alias": "oa_rendezvous_local", + "name": "OA Rendezvous local", + "description": "", + "parent": "fd6e0287-66e0-4e84-8a92-98c4c167cb88", + "body": { + "Oa": { + "Shared": { + "Lifeline": { + "represented": null + } + } + } + } + }, + "59173870-bc74-4c85-89b3-b0cfb8ad70cb": { + "id": "59173870-bc74-4c85-89b3-b0cfb8ad70cb", + "alias": "oa_rendezvous_alternate", + "name": "OA Rendezvous alternate", + "description": "", + "parent": "fd6e0287-66e0-4e84-8a92-98c4c167cb88", + "body": { + "Oa": { + "Shared": { + "Lifeline": { + "represented": null + } + } + } + } + }, + "6e8218df-2d57-4247-a401-e0458c4d5bc1": { + "id": "6e8218df-2d57-4247-a401-e0458c4d5bc1", + "alias": "sa_navigation_item", + "name": "SA Navigation Item", + "description": "", + "parent": null, + "body": { + "Sa": { + "Shared": { + "ExchangeItem": { + "kind": "Information", + "mechanism": "Unspecified", + "supertype": null + } + } + } + } + }, + "715228b2-a573-47ad-bf53-0a9b968ded96": { + "id": "715228b2-a573-47ad-bf53-0a9b968ded96", + "alias": "sa_navigation_coordinate", + "name": "SA Navigation Coordinate", + "description": "", + "parent": null, + "body": { + "Sa": { + "Shared": { + "DataType": { + "definition": { + "Record": { + "supertype": null + } + }, + "default": null, + "unit": "" + } + } + } + } + }, + "73de257c-d816-4f91-b507-36f79ca49f08": { + "id": "73de257c-d816-4f91-b507-36f79ca49f08", + "alias": "oa_navigation_message", + "name": "OA Navigation message", + "description": "", + "parent": "814c985a-a8c8-409a-b11b-c9572f29eabc", + "body": { + "Oa": { + "Shared": { + "Occurrence": { + "body": { + "Exchange": { + "exchange": "6a5930e2-0cfb-4aff-a4a4-8c4798f1cda5", + "sender": null, + "receiver": null, + "message_kind": "Asynchronous", + "reply_to": null, + "values": [ + { + "path": [ + "19be028f-96cd-440e-bbcb-e151fbe00b45" + ], + "field": "01d025e3-ee88-4449-a613-217b44a61ae2", + "expression": "latitude_deg + 0.25" + }, + { + "path": [ + "02a82c95-f030-4be3-9ffe-6ebccf40fcb1" + ], + "field": "01d025e3-ee88-4449-a613-217b44a61ae2", + "expression": "second independent" + } + ] + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + }, + "7d9832e5-0288-4d04-a5df-1551d050f79e": { + "id": "7d9832e5-0288-4d04-a5df-1551d050f79e", + "alias": "sa_rendezvous_use", + "name": "SA Rendezvous use", + "description": "", + "parent": "06ec35b3-ce60-43a7-8300-c239fa91c898", + "body": { + "Sa": { + "Shared": { + "Occurrence": { + "body": { + "ScenarioUse": { + "scenario": "12143ec8-bcf9-4755-809c-87b653465888", + "bindings": [ + { + "referenced": "1cb0eb67-6fa7-414c-a2aa-155cb39da564", + "local": "bb0ad838-eb7c-4470-bf27-f3a21e69fdf8" + }, + { + "referenced": "82798865-193a-46b0-b40d-b22ae91eba28", + "local": "a8518771-fa4d-4e6e-bb6a-9e5f719f874a" + } + ] + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + }, + "814c985a-a8c8-409a-b11b-c9572f29eabc": { + "id": "814c985a-a8c8-409a-b11b-c9572f29eabc", + "alias": "oa_navigation_scenario", + "name": "OA Navigation scenario", + "description": "", + "parent": null, + "body": { + "Oa": { + "Shared": { + "Scenario": { + "viewpoint": "OperationalActivity", + "context": null, + "precondition": "", + "postcondition": "", + "steps": [ + "73de257c-d816-4f91-b507-36f79ca49f08" + ] + } + } + } + } + }, + "82798865-193a-46b0-b40d-b22ae91eba28": { + "id": "82798865-193a-46b0-b40d-b22ae91eba28", + "alias": "sa_rendezvous_extra_referenced", + "name": "SA Rendezvous extra referenced", + "description": "", + "parent": "12143ec8-bcf9-4755-809c-87b653465888", + "body": { + "Sa": { + "Shared": { + "Lifeline": { + "represented": null + } + } + } + } + }, + "85eee860-b8ee-4c14-8551-ec4ce8b84aa8": { + "id": "85eee860-b8ee-4c14-8551-ec4ce8b84aa8", + "alias": "oa_navigation_coordinate", + "name": "OA Navigation Coordinate", + "description": "", + "parent": null, + "body": { + "Oa": { + "Shared": { + "DataType": { + "definition": { + "Record": { + "supertype": null + } + }, + "default": null, + "unit": "" + } + } + } + } + }, + "8dbaedfd-4a4a-4e5d-a9f9-d040d5f144d8": { + "id": "8dbaedfd-4a4a-4e5d-a9f9-d040d5f144d8", + "alias": "sa_navigation_destination", + "name": "SA Navigation destination", + "description": "", + "parent": "6e8218df-2d57-4247-a401-e0458c4d5bc1", + "body": { + "Sa": { + "Shared": { + "DataField": { + "type_ref": { + "Named": "715228b2-a573-47ad-bf53-0a9b968ded96" + }, + "minimum": 0, + "maximum": 1, + "unit": "", + "direction": "Unspecified", + "storage": "Value", + "default": null + } + } + } + } + }, + "9a78b793-a987-438e-a041-233dc41f2b10": { + "id": "9a78b793-a987-438e-a041-233dc41f2b10", + "alias": "oa_rendezvous_use", + "name": "OA Rendezvous use", + "description": "", + "parent": "fd6e0287-66e0-4e84-8a92-98c4c167cb88", + "body": { + "Oa": { + "Shared": { + "Occurrence": { + "body": { + "ScenarioUse": { + "scenario": "eacfc6df-4e22-4ebd-a90d-6b5146e56513", + "bindings": [ + { + "referenced": "0e3d7b11-4c0f-41a0-beae-a5440e30782f", + "local": "424d78d2-9145-4b34-a18f-80a335164a9b" + }, + { + "referenced": "9e60984f-9ca3-4f3d-bc3f-b6b973cdc698", + "local": "1f697a2c-8455-48f1-bbdf-bdf657fe8fc4" + } + ] + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + }, + "9e60984f-9ca3-4f3d-bc3f-b6b973cdc698": { + "id": "9e60984f-9ca3-4f3d-bc3f-b6b973cdc698", + "alias": "oa_rendezvous_extra_referenced", + "name": "OA Rendezvous extra referenced", + "description": "", + "parent": "eacfc6df-4e22-4ebd-a90d-6b5146e56513", + "body": { + "Oa": { + "Shared": { + "Lifeline": { + "represented": null + } + } + } + } + }, + "9eb225ee-7103-5eb7-88d0-ff2128408748": { + "id": "9eb225ee-7103-5eb7-88d0-ff2128408748", + "alias": "sa_navigation_exchange_output", + "name": "SA Navigation exchange output", + "description": "", + "parent": "ad7dede7-59bf-4355-afec-08c905a564ff", + "body": { + "Sa": { + "Shared": { + "Port": { + "contract": { + "FunctionOutput": { + "outgoing_items": [ + "6e8218df-2d57-4247-a401-e0458c4d5bc1" + ] + } + }, + "legacy": null + } + } + } + } + }, + "a8518771-fa4d-4e6e-bb6a-9e5f719f874a": { + "id": "a8518771-fa4d-4e6e-bb6a-9e5f719f874a", + "alias": "sa_rendezvous_extra_local", + "name": "SA Rendezvous extra local", + "description": "", + "parent": "06ec35b3-ce60-43a7-8300-c239fa91c898", + "body": { + "Sa": { + "Shared": { + "Lifeline": { + "represented": null + } + } + } + } + }, + "ab1e449f-24f1-4bee-938c-b49510d19bf3": { + "id": "ab1e449f-24f1-4bee-938c-b49510d19bf3", + "alias": "sa_navigation_latitude", + "name": "SA Navigation latitude", + "description": "", + "parent": "715228b2-a573-47ad-bf53-0a9b968ded96", + "body": { + "Sa": { + "Shared": { + "DataField": { + "type_ref": "Real", + "minimum": 0, + "maximum": 1, + "unit": "", + "direction": "Unspecified", + "storage": "Value", + "default": null + } + } + } + } + }, + "ad717be1-3f30-55db-94b9-7a51558b6dd6": { + "id": "ad717be1-3f30-55db-94b9-7a51558b6dd6", + "alias": "sa_navigation_exchange_input", + "name": "SA Navigation exchange input", + "description": "", + "parent": "0114bf4c-343b-4a96-918c-3b4087df4c5e", + "body": { + "Sa": { + "Shared": { + "Port": { + "contract": { + "FunctionInput": { + "incoming_items": [ + "6e8218df-2d57-4247-a401-e0458c4d5bc1" + ] + } + }, + "legacy": null + } + } + } + } + }, + "ad7dede7-59bf-4355-afec-08c905a564ff": { + "id": "ad7dede7-59bf-4355-afec-08c905a564ff", + "alias": "sa_navigation_a", + "name": "SA Navigation A", + "description": "", + "parent": null, + "body": { + "Sa": "Function" + } + }, + "bb0ad838-eb7c-4470-bf27-f3a21e69fdf8": { + "id": "bb0ad838-eb7c-4470-bf27-f3a21e69fdf8", + "alias": "sa_rendezvous_alternate", + "name": "SA Rendezvous alternate", + "description": "", + "parent": "06ec35b3-ce60-43a7-8300-c239fa91c898", + "body": { + "Sa": { + "Shared": { + "Lifeline": { + "represented": null + } + } + } + } + }, + "d10ef3c1-479f-4780-9fd5-3b4d21dffe74": { + "id": "d10ef3c1-479f-4780-9fd5-3b4d21dffe74", + "alias": "sa_navigation_message", + "name": "SA Navigation message", + "description": "", + "parent": "409c78a0-28bc-4810-86ba-300204a3513f", + "body": { + "Sa": { + "Shared": { + "Occurrence": { + "body": { + "Exchange": { + "exchange": "9c1085f2-c91d-4776-bb56-52c864a80e22", + "sender": null, + "receiver": null, + "message_kind": "Asynchronous", + "reply_to": null, + "values": [ + { + "path": [ + "0cf0ba32-01c8-41f5-a44c-ed31daa3ca92" + ], + "field": "ab1e449f-24f1-4bee-938c-b49510d19bf3", + "expression": "latitude_deg + 0.25" + }, + { + "path": [ + "8dbaedfd-4a4a-4e5d-a9f9-d040d5f144d8" + ], + "field": "ab1e449f-24f1-4bee-938c-b49510d19bf3", + "expression": "second independent" + } + ] + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + }, + "d7d34062-6820-471d-bb5f-abb7db4c8198": { + "id": "d7d34062-6820-471d-bb5f-abb7db4c8198", + "alias": "oa_navigation_a", + "name": "OA Navigation A", + "description": "", + "parent": null, + "body": { + "Oa": "Activity" + } + }, + "eacfc6df-4e22-4ebd-a90d-6b5146e56513": { + "id": "eacfc6df-4e22-4ebd-a90d-6b5146e56513", + "alias": "oa_rendezvous_callee", + "name": "OA Rendezvous callee", + "description": "", + "parent": null, + "body": { + "Oa": { + "Shared": { + "Scenario": { + "viewpoint": "OperationalActivity", + "context": null, + "precondition": "", + "postcondition": "", + "steps": [] + } + } + } + } + }, + "fd6e0287-66e0-4e84-8a92-98c4c167cb88": { + "id": "fd6e0287-66e0-4e84-8a92-98c4c167cb88", + "alias": "oa_rendezvous_caller", + "name": "OA Rendezvous caller", + "description": "", + "parent": null, + "body": { + "Oa": { + "Shared": { + "Scenario": { + "viewpoint": "OperationalActivity", + "context": null, + "precondition": "", + "postcondition": "", + "steps": [ + "9a78b793-a987-438e-a041-233dc41f2b10" + ] + } + } + } + } + } + }, + "relations": { + "6a5930e2-0cfb-4aff-a4a4-8c4798f1cda5": { + "id": "6a5930e2-0cfb-4aff-a4a4-8c4798f1cda5", + "alias": "oa_navigation_exchange", + "name": "OA Navigation exchange", + "description": "", + "body": { + "Interaction": { + "source": "d7d34062-6820-471d-bb5f-abb7db4c8198", + "target": "264ff2ee-7786-4db7-b78d-2be802c268ac", + "items": [ + "20619f0c-4419-46b5-a15c-49b004acfa91" + ] + } + } + }, + "9c1085f2-c91d-4776-bb56-52c864a80e22": { + "id": "9c1085f2-c91d-4776-bb56-52c864a80e22", + "alias": "sa_navigation_exchange", + "name": "SA Navigation exchange", + "description": "", + "body": { + "FunctionalExchange": { + "source": "ad7dede7-59bf-4355-afec-08c905a564ff", + "target": "0114bf4c-343b-4a96-918c-3b4087df4c5e", + "source_port": "9eb225ee-7103-5eb7-88d0-ff2128408748", + "target_port": "ad717be1-3f30-55db-94b9-7a51558b6dd6", + "items": [ + "6e8218df-2d57-4247-a401-e0458c4d5bc1" + ] + } + } + } + }, + "diagrams": {}, + "artifact_links": {}, + "traces": { + "551c0dbc-ede6-42ef-ae60-2e0136ffc6e6": { + "id": "551c0dbc-ede6-42ef-ae60-2e0136ffc6e6", + "alias": "rendezvous_binding_review", + "name": "Rendezvous participant binding review", + "description": "", + "oa": "9a78b793-a987-438e-a041-233dc41f2b10", + "sa": "7d9832e5-0288-4d04-a5df-1551d050f79e", + "kind": "Manual", + "baseline": { + "oa": { + "name": "OA Rendezvous use", + "description": "" + }, + "sa": { + "name": "SA Rendezvous use", + "description": "" + }, + "semantic": { + "facets": [ + { + "Binding": { + "keys": { + "oa": "0e3d7b11-4c0f-41a0-beae-a5440e30782f", + "sa": "1cb0eb67-6fa7-414c-a2aa-155cb39da564" + }, + "review": { + "oa": { + "Value": { + "referenced": "0e3d7b11-4c0f-41a0-beae-a5440e30782f", + "local": "424d78d2-9145-4b34-a18f-80a335164a9b" + } + }, + "sa": { + "Value": { + "referenced": "1cb0eb67-6fa7-414c-a2aa-155cb39da564", + "local": "bb0ad838-eb7c-4470-bf27-f3a21e69fdf8" + } + } + } + } + } + ] + } + } + }, + "b647f858-1a69-4292-8710-674b1692d54f": { + "id": "b647f858-1a69-4292-8710-674b1692d54f", + "alias": "navigation_latitude_review", + "name": "Navigation origin latitude review", + "description": "", + "oa": "73de257c-d816-4f91-b507-36f79ca49f08", + "sa": "d10ef3c1-479f-4780-9fd5-3b4d21dffe74", + "kind": "Manual", + "baseline": { + "oa": { + "name": "OA Navigation message", + "description": "" + }, + "sa": { + "name": "SA Navigation message", + "description": "" + }, + "semantic": { + "facets": [ + { + "Annotation": { + "keys": { + "oa": { + "path": [ + "19be028f-96cd-440e-bbcb-e151fbe00b45" + ], + "field": "01d025e3-ee88-4449-a613-217b44a61ae2" + }, + "sa": { + "path": [ + "0cf0ba32-01c8-41f5-a44c-ed31daa3ca92" + ], + "field": "ab1e449f-24f1-4bee-938c-b49510d19bf3" + } + }, + "review": { + "oa": { + "Value": { + "path": [ + "19be028f-96cd-440e-bbcb-e151fbe00b45" + ], + "field": "01d025e3-ee88-4449-a613-217b44a61ae2", + "expression": "latitude_deg + 0.25" + } + }, + "sa": { + "Value": { + "path": [ + "0cf0ba32-01c8-41f5-a44c-ed31daa3ca92" + ], + "field": "ab1e449f-24f1-4bee-938c-b49510d19bf3", + "expression": "latitude_deg + 0.25" + } + } + } + } + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/harness/fixtures/release1-native/semantic-keyed.after-take.arcadia b/harness/fixtures/release1-native/semantic-keyed.after-take.arcadia new file mode 100644 index 00000000..3af41167 --- /dev/null +++ b/harness/fixtures/release1-native/semantic-keyed.after-take.arcadia @@ -0,0 +1,482 @@ +// Native keyed review: preserve this exact comment. +arcadia 5; +project mission "Semantic keyed native review" id "13089f56-2a58-4f42-a0e3-8c9812fb5857" { + description ""; + + sa_function sa_navigation_b "SA Navigation B" id "0114bf4c-343b-4a96-918c-3b4087df4c5e" { + description ""; + parent none; + } + + oa_data_field oa_navigation_latitude "OA Navigation latitude" id "01d025e3-ee88-4449-a613-217b44a61ae2" { + default none; + description ""; + direction Unspecified; + maximum 1; + minimum 0; + parent @oa_navigation_coordinate; + storage Value; + type_ref Real; + unit ""; + } + + oa_data_field oa_navigation_destination "OA Navigation destination" id "02a82c95-f030-4be3-9ffe-6ebccf40fcb1" { + default none; + description ""; + direction Unspecified; + maximum 1; + minimum 0; + parent @oa_navigation_item; + storage Value; + type_ref { + Named @oa_navigation_coordinate; + }; + unit ""; + } + + sa_scenario sa_rendezvous_caller "SA Rendezvous caller" id "06ec35b3-ce60-43a7-8300-c239fa91c898" { + context none; + description ""; + parent none; + postcondition ""; + precondition ""; + steps [@sa_rendezvous_use]; + viewpoint Functional; + } + + sa_lifeline sa_rendezvous_local "SA Rendezvous local" id "0ba127bc-cd2d-4e53-b76e-9c01025a7c3f" { + description ""; + parent @sa_rendezvous_caller; + represented none; + } + + sa_data_field sa_navigation_origin "SA Navigation origin" id "0cf0ba32-01c8-41f5-a44c-ed31daa3ca92" { + default none; + description ""; + direction Unspecified; + maximum 1; + minimum 0; + parent @sa_navigation_item; + storage Value; + type_ref { + Named @sa_navigation_coordinate; + }; + unit ""; + } + + oa_lifeline oa_rendezvous_referenced "OA Rendezvous referenced" id "0e3d7b11-4c0f-41a0-beae-a5440e30782f" { + description ""; + parent @oa_rendezvous_callee; + represented none; + } + + sa_scenario sa_rendezvous_callee "SA Rendezvous callee" id "12143ec8-bcf9-4755-809c-87b653465888" { + context none; + description ""; + parent none; + postcondition ""; + precondition ""; + steps []; + viewpoint Functional; + } + + oa_data_field oa_navigation_origin "OA Navigation origin" id "19be028f-96cd-440e-bbcb-e151fbe00b45" { + default none; + description ""; + direction Unspecified; + maximum 1; + minimum 0; + parent @oa_navigation_item; + storage Value; + type_ref { + Named @oa_navigation_coordinate; + }; + unit ""; + } + + sa_lifeline sa_rendezvous_referenced "SA Rendezvous referenced" id "1cb0eb67-6fa7-414c-a2aa-155cb39da564" { + description ""; + parent @sa_rendezvous_callee; + represented none; + } + + oa_lifeline oa_rendezvous_extra_local "OA Rendezvous extra local" id "1f697a2c-8455-48f1-bbdf-bdf657fe8fc4" { + description ""; + parent @oa_rendezvous_caller; + represented none; + } + + oa_exchange_item oa_navigation_item "OA Navigation Item" id "20619f0c-4419-46b5-a15c-49b004acfa91" { + description ""; + kind Information; + mechanism Unspecified; + parent none; + supertype none; + } + + oa_activity oa_navigation_b "OA Navigation B" id "264ff2ee-7786-4db7-b78d-2be802c268ac" { + description ""; + parent none; + } + + sa_scenario sa_navigation_scenario "SA Navigation scenario" id "409c78a0-28bc-4810-86ba-300204a3513f" { + context none; + description ""; + parent none; + postcondition ""; + precondition ""; + steps [@sa_navigation_message]; + viewpoint Functional; + } + + oa_lifeline oa_rendezvous_local "OA Rendezvous local" id "424d78d2-9145-4b34-a18f-80a335164a9b" { + description ""; + parent @oa_rendezvous_caller; + represented none; + } + + oa_lifeline oa_rendezvous_alternate "OA Rendezvous alternate" id "59173870-bc74-4c85-89b3-b0cfb8ad70cb" { + description ""; + parent @oa_rendezvous_caller; + represented none; + } + + sa_exchange_item sa_navigation_item "SA Navigation Item" id "6e8218df-2d57-4247-a401-e0458c4d5bc1" { + description ""; + kind Information; + mechanism Unspecified; + parent none; + supertype none; + } + + sa_data_type sa_navigation_coordinate "SA Navigation Coordinate" id "715228b2-a573-47ad-bf53-0a9b968ded96" { + default none; + definition { + Record { + supertype none; + }; + }; + description ""; + parent none; + unit ""; + } + + oa_occurrence oa_navigation_message "OA Navigation message" id "73de257c-d816-4f91-b507-36f79ca49f08" { + body { + Exchange { + exchange @oa_navigation_exchange; + message_kind Asynchronous; + receiver none; + reply_to none; + sender none; + values [{ + expression "latitude_deg + 0.25"; + field @oa_navigation_latitude; + path [@oa_navigation_origin]; + }, { + expression "second independent"; + field @oa_navigation_latitude; + path [@oa_navigation_destination]; + }]; + }; + }; + description ""; + parent @oa_navigation_scenario; + postcondition ""; + precondition ""; + } + + sa_occurrence sa_rendezvous_use "SA Rendezvous use" id "7d9832e5-0288-4d04-a5df-1551d050f79e" { + body { + ScenarioUse { + bindings [{ + local @sa_rendezvous_local; + referenced @sa_rendezvous_referenced; + }, { + local @sa_rendezvous_extra_local; + referenced @sa_rendezvous_extra_referenced; + }]; + scenario @sa_rendezvous_callee; + }; + }; + description ""; + parent @sa_rendezvous_caller; + postcondition ""; + precondition ""; + } + + oa_scenario oa_navigation_scenario "OA Navigation scenario" id "814c985a-a8c8-409a-b11b-c9572f29eabc" { + context none; + description ""; + parent none; + postcondition ""; + precondition ""; + steps [@oa_navigation_message]; + viewpoint OperationalActivity; + } + + sa_lifeline sa_rendezvous_extra_referenced "SA Rendezvous extra referenced" id "82798865-193a-46b0-b40d-b22ae91eba28" { + description ""; + parent @sa_rendezvous_callee; + represented none; + } + + oa_data_type oa_navigation_coordinate "OA Navigation Coordinate" id "85eee860-b8ee-4c14-8551-ec4ce8b84aa8" { + default none; + definition { + Record { + supertype none; + }; + }; + description ""; + parent none; + unit ""; + } + + sa_data_field sa_navigation_destination "SA Navigation destination" id "8dbaedfd-4a4a-4e5d-a9f9-d040d5f144d8" { + default none; + description ""; + direction Unspecified; + maximum 1; + minimum 0; + parent @sa_navigation_item; + storage Value; + type_ref { + Named @sa_navigation_coordinate; + }; + unit ""; + } + + oa_occurrence oa_rendezvous_use "OA Rendezvous use" id "9a78b793-a987-438e-a041-233dc41f2b10" { + body { + ScenarioUse { + bindings [{ + local @oa_rendezvous_local; + referenced @oa_rendezvous_referenced; + }, { + local @oa_rendezvous_extra_local; + referenced @oa_rendezvous_extra_referenced; + }]; + scenario @oa_rendezvous_callee; + }; + }; + description ""; + parent @oa_rendezvous_caller; + postcondition ""; + precondition ""; + } + + oa_lifeline oa_rendezvous_extra_referenced "OA Rendezvous extra referenced" id "9e60984f-9ca3-4f3d-bc3f-b6b973cdc698" { + description ""; + parent @oa_rendezvous_callee; + represented none; + } + + sa_port sa_navigation_exchange_output "SA Navigation exchange output" id "9eb225ee-7103-5eb7-88d0-ff2128408748" { + contract { + FunctionOutput { + outgoing_items [@sa_navigation_item]; + }; + }; + description ""; + legacy none; + parent @sa_navigation_a; + } + + sa_lifeline sa_rendezvous_extra_local "SA Rendezvous extra local" id "a8518771-fa4d-4e6e-bb6a-9e5f719f874a" { + description ""; + parent @sa_rendezvous_caller; + represented none; + } + + sa_data_field sa_navigation_latitude "SA Navigation latitude" id "ab1e449f-24f1-4bee-938c-b49510d19bf3" { + default none; + description ""; + direction Unspecified; + maximum 1; + minimum 0; + parent @sa_navigation_coordinate; + storage Value; + type_ref Real; + unit ""; + } + + sa_port sa_navigation_exchange_input "SA Navigation exchange input" id "ad717be1-3f30-55db-94b9-7a51558b6dd6" { + contract { + FunctionInput { + incoming_items [@sa_navigation_item]; + }; + }; + description ""; + legacy none; + parent @sa_navigation_b; + } + + sa_function sa_navigation_a "SA Navigation A" id "ad7dede7-59bf-4355-afec-08c905a564ff" { + description ""; + parent none; + } + + sa_lifeline sa_rendezvous_alternate "SA Rendezvous alternate" id "bb0ad838-eb7c-4470-bf27-f3a21e69fdf8" { + description ""; + parent @sa_rendezvous_caller; + represented none; + } + + sa_occurrence sa_navigation_message "SA Navigation message" id "d10ef3c1-479f-4780-9fd5-3b4d21dffe74" { + body { + Exchange { + exchange @sa_navigation_exchange; + message_kind Asynchronous; + receiver none; + reply_to none; + sender none; + values [{ + expression "latitude_deg + 0.25"; + field @sa_navigation_latitude; + path [@sa_navigation_origin]; + }, { + expression "second independent"; + field @sa_navigation_latitude; + path [@sa_navigation_destination]; + }]; + }; + }; + description ""; + parent @sa_navigation_scenario; + postcondition ""; + precondition ""; + } + + oa_activity oa_navigation_a "OA Navigation A" id "d7d34062-6820-471d-bb5f-abb7db4c8198" { + description ""; + parent none; + } + + oa_scenario oa_rendezvous_callee "OA Rendezvous callee" id "eacfc6df-4e22-4ebd-a90d-6b5146e56513" { + context none; + description ""; + parent none; + postcondition ""; + precondition ""; + steps []; + viewpoint OperationalActivity; + } + + oa_scenario oa_rendezvous_caller "OA Rendezvous caller" id "fd6e0287-66e0-4e84-8a92-98c4c167cb88" { + context none; + description ""; + parent none; + postcondition ""; + precondition ""; + steps [@oa_rendezvous_use]; + viewpoint OperationalActivity; + } + + interaction oa_navigation_exchange "OA Navigation exchange" id "6a5930e2-0cfb-4aff-a4a4-8c4798f1cda5" { + description ""; + items [@oa_navigation_item]; + source @oa_navigation_a; + target @oa_navigation_b; + } + + functional_exchange sa_navigation_exchange "SA Navigation exchange" id "9c1085f2-c91d-4776-bb56-52c864a80e22" { + description ""; + items [@sa_navigation_item]; + source @sa_navigation_a; + source_port @sa_navigation_exchange_output; + target @sa_navigation_b; + target_port @sa_navigation_exchange_input; + } + + trace rendezvous_binding_review "Rendezvous participant binding review" id "551c0dbc-ede6-42ef-ae60-2e0136ffc6e6" { + baseline { + oa { + description ""; + name "OA Rendezvous use"; + }; + sa { + description ""; + name "SA Rendezvous use"; + }; + semantic { + facets [{ + Binding { + keys { + oa uuid "0e3d7b11-4c0f-41a0-beae-a5440e30782f"; + sa uuid "1cb0eb67-6fa7-414c-a2aa-155cb39da564"; + }; + review { + oa { + Value { + local uuid "424d78d2-9145-4b34-a18f-80a335164a9b"; + referenced uuid "0e3d7b11-4c0f-41a0-beae-a5440e30782f"; + }; + }; + sa { + Value { + local uuid "0ba127bc-cd2d-4e53-b76e-9c01025a7c3f"; + referenced uuid "1cb0eb67-6fa7-414c-a2aa-155cb39da564"; + }; + }; + }; + }; + }]; + }; + }; + description ""; + kind Manual; + oa @oa_rendezvous_use; + sa @sa_rendezvous_use; + } + + trace navigation_latitude_review "Navigation origin latitude review" id "b647f858-1a69-4292-8710-674b1692d54f" { + baseline { + oa { + description ""; + name "OA Navigation message"; + }; + sa { + description ""; + name "SA Navigation message"; + }; + semantic { + facets [{ + Annotation { + keys { + oa { + field uuid "01d025e3-ee88-4449-a613-217b44a61ae2"; + path [uuid "19be028f-96cd-440e-bbcb-e151fbe00b45"]; + }; + sa { + field uuid "ab1e449f-24f1-4bee-938c-b49510d19bf3"; + path [uuid "0cf0ba32-01c8-41f5-a44c-ed31daa3ca92"]; + }; + }; + review { + oa { + Value { + expression "latitude_deg + 0.25"; + field uuid "01d025e3-ee88-4449-a613-217b44a61ae2"; + path [uuid "19be028f-96cd-440e-bbcb-e151fbe00b45"]; + }; + }; + sa { + Value { + expression "latitude_deg + 0.25"; + field uuid "ab1e449f-24f1-4bee-938c-b49510d19bf3"; + path [uuid "0cf0ba32-01c8-41f5-a44c-ed31daa3ca92"]; + }; + }; + }; + }; + }]; + }; + }; + description ""; + kind Manual; + oa @oa_navigation_message; + sa @sa_navigation_message; + } + +} diff --git a/harness/fixtures/release1-native/semantic-keyed.after-take.json b/harness/fixtures/release1-native/semantic-keyed.after-take.json new file mode 100644 index 00000000..5d9a61bb --- /dev/null +++ b/harness/fixtures/release1-native/semantic-keyed.after-take.json @@ -0,0 +1,895 @@ +{ + "schema_version": 5, + "id": "13089f56-2a58-4f42-a0e3-8c9812fb5857", + "alias": "mission", + "name": "Semantic keyed native review", + "description": "", + "elements": { + "0114bf4c-343b-4a96-918c-3b4087df4c5e": { + "id": "0114bf4c-343b-4a96-918c-3b4087df4c5e", + "alias": "sa_navigation_b", + "name": "SA Navigation B", + "description": "", + "parent": null, + "body": { + "Sa": "Function" + } + }, + "01d025e3-ee88-4449-a613-217b44a61ae2": { + "id": "01d025e3-ee88-4449-a613-217b44a61ae2", + "alias": "oa_navigation_latitude", + "name": "OA Navigation latitude", + "description": "", + "parent": "85eee860-b8ee-4c14-8551-ec4ce8b84aa8", + "body": { + "Oa": { + "Shared": { + "DataField": { + "type_ref": "Real", + "minimum": 0, + "maximum": 1, + "unit": "", + "direction": "Unspecified", + "storage": "Value", + "default": null + } + } + } + } + }, + "02a82c95-f030-4be3-9ffe-6ebccf40fcb1": { + "id": "02a82c95-f030-4be3-9ffe-6ebccf40fcb1", + "alias": "oa_navigation_destination", + "name": "OA Navigation destination", + "description": "", + "parent": "20619f0c-4419-46b5-a15c-49b004acfa91", + "body": { + "Oa": { + "Shared": { + "DataField": { + "type_ref": { + "Named": "85eee860-b8ee-4c14-8551-ec4ce8b84aa8" + }, + "minimum": 0, + "maximum": 1, + "unit": "", + "direction": "Unspecified", + "storage": "Value", + "default": null + } + } + } + } + }, + "06ec35b3-ce60-43a7-8300-c239fa91c898": { + "id": "06ec35b3-ce60-43a7-8300-c239fa91c898", + "alias": "sa_rendezvous_caller", + "name": "SA Rendezvous caller", + "description": "", + "parent": null, + "body": { + "Sa": { + "Shared": { + "Scenario": { + "viewpoint": "Functional", + "context": null, + "precondition": "", + "postcondition": "", + "steps": [ + "7d9832e5-0288-4d04-a5df-1551d050f79e" + ] + } + } + } + } + }, + "0ba127bc-cd2d-4e53-b76e-9c01025a7c3f": { + "id": "0ba127bc-cd2d-4e53-b76e-9c01025a7c3f", + "alias": "sa_rendezvous_local", + "name": "SA Rendezvous local", + "description": "", + "parent": "06ec35b3-ce60-43a7-8300-c239fa91c898", + "body": { + "Sa": { + "Shared": { + "Lifeline": { + "represented": null + } + } + } + } + }, + "0cf0ba32-01c8-41f5-a44c-ed31daa3ca92": { + "id": "0cf0ba32-01c8-41f5-a44c-ed31daa3ca92", + "alias": "sa_navigation_origin", + "name": "SA Navigation origin", + "description": "", + "parent": "6e8218df-2d57-4247-a401-e0458c4d5bc1", + "body": { + "Sa": { + "Shared": { + "DataField": { + "type_ref": { + "Named": "715228b2-a573-47ad-bf53-0a9b968ded96" + }, + "minimum": 0, + "maximum": 1, + "unit": "", + "direction": "Unspecified", + "storage": "Value", + "default": null + } + } + } + } + }, + "0e3d7b11-4c0f-41a0-beae-a5440e30782f": { + "id": "0e3d7b11-4c0f-41a0-beae-a5440e30782f", + "alias": "oa_rendezvous_referenced", + "name": "OA Rendezvous referenced", + "description": "", + "parent": "eacfc6df-4e22-4ebd-a90d-6b5146e56513", + "body": { + "Oa": { + "Shared": { + "Lifeline": { + "represented": null + } + } + } + } + }, + "12143ec8-bcf9-4755-809c-87b653465888": { + "id": "12143ec8-bcf9-4755-809c-87b653465888", + "alias": "sa_rendezvous_callee", + "name": "SA Rendezvous callee", + "description": "", + "parent": null, + "body": { + "Sa": { + "Shared": { + "Scenario": { + "viewpoint": "Functional", + "context": null, + "precondition": "", + "postcondition": "", + "steps": [] + } + } + } + } + }, + "19be028f-96cd-440e-bbcb-e151fbe00b45": { + "id": "19be028f-96cd-440e-bbcb-e151fbe00b45", + "alias": "oa_navigation_origin", + "name": "OA Navigation origin", + "description": "", + "parent": "20619f0c-4419-46b5-a15c-49b004acfa91", + "body": { + "Oa": { + "Shared": { + "DataField": { + "type_ref": { + "Named": "85eee860-b8ee-4c14-8551-ec4ce8b84aa8" + }, + "minimum": 0, + "maximum": 1, + "unit": "", + "direction": "Unspecified", + "storage": "Value", + "default": null + } + } + } + } + }, + "1cb0eb67-6fa7-414c-a2aa-155cb39da564": { + "id": "1cb0eb67-6fa7-414c-a2aa-155cb39da564", + "alias": "sa_rendezvous_referenced", + "name": "SA Rendezvous referenced", + "description": "", + "parent": "12143ec8-bcf9-4755-809c-87b653465888", + "body": { + "Sa": { + "Shared": { + "Lifeline": { + "represented": null + } + } + } + } + }, + "1f697a2c-8455-48f1-bbdf-bdf657fe8fc4": { + "id": "1f697a2c-8455-48f1-bbdf-bdf657fe8fc4", + "alias": "oa_rendezvous_extra_local", + "name": "OA Rendezvous extra local", + "description": "", + "parent": "fd6e0287-66e0-4e84-8a92-98c4c167cb88", + "body": { + "Oa": { + "Shared": { + "Lifeline": { + "represented": null + } + } + } + } + }, + "20619f0c-4419-46b5-a15c-49b004acfa91": { + "id": "20619f0c-4419-46b5-a15c-49b004acfa91", + "alias": "oa_navigation_item", + "name": "OA Navigation Item", + "description": "", + "parent": null, + "body": { + "Oa": { + "Shared": { + "ExchangeItem": { + "kind": "Information", + "mechanism": "Unspecified", + "supertype": null + } + } + } + } + }, + "264ff2ee-7786-4db7-b78d-2be802c268ac": { + "id": "264ff2ee-7786-4db7-b78d-2be802c268ac", + "alias": "oa_navigation_b", + "name": "OA Navigation B", + "description": "", + "parent": null, + "body": { + "Oa": "Activity" + } + }, + "409c78a0-28bc-4810-86ba-300204a3513f": { + "id": "409c78a0-28bc-4810-86ba-300204a3513f", + "alias": "sa_navigation_scenario", + "name": "SA Navigation scenario", + "description": "", + "parent": null, + "body": { + "Sa": { + "Shared": { + "Scenario": { + "viewpoint": "Functional", + "context": null, + "precondition": "", + "postcondition": "", + "steps": [ + "d10ef3c1-479f-4780-9fd5-3b4d21dffe74" + ] + } + } + } + } + }, + "424d78d2-9145-4b34-a18f-80a335164a9b": { + "id": "424d78d2-9145-4b34-a18f-80a335164a9b", + "alias": "oa_rendezvous_local", + "name": "OA Rendezvous local", + "description": "", + "parent": "fd6e0287-66e0-4e84-8a92-98c4c167cb88", + "body": { + "Oa": { + "Shared": { + "Lifeline": { + "represented": null + } + } + } + } + }, + "59173870-bc74-4c85-89b3-b0cfb8ad70cb": { + "id": "59173870-bc74-4c85-89b3-b0cfb8ad70cb", + "alias": "oa_rendezvous_alternate", + "name": "OA Rendezvous alternate", + "description": "", + "parent": "fd6e0287-66e0-4e84-8a92-98c4c167cb88", + "body": { + "Oa": { + "Shared": { + "Lifeline": { + "represented": null + } + } + } + } + }, + "6e8218df-2d57-4247-a401-e0458c4d5bc1": { + "id": "6e8218df-2d57-4247-a401-e0458c4d5bc1", + "alias": "sa_navigation_item", + "name": "SA Navigation Item", + "description": "", + "parent": null, + "body": { + "Sa": { + "Shared": { + "ExchangeItem": { + "kind": "Information", + "mechanism": "Unspecified", + "supertype": null + } + } + } + } + }, + "715228b2-a573-47ad-bf53-0a9b968ded96": { + "id": "715228b2-a573-47ad-bf53-0a9b968ded96", + "alias": "sa_navigation_coordinate", + "name": "SA Navigation Coordinate", + "description": "", + "parent": null, + "body": { + "Sa": { + "Shared": { + "DataType": { + "definition": { + "Record": { + "supertype": null + } + }, + "default": null, + "unit": "" + } + } + } + } + }, + "73de257c-d816-4f91-b507-36f79ca49f08": { + "id": "73de257c-d816-4f91-b507-36f79ca49f08", + "alias": "oa_navigation_message", + "name": "OA Navigation message", + "description": "", + "parent": "814c985a-a8c8-409a-b11b-c9572f29eabc", + "body": { + "Oa": { + "Shared": { + "Occurrence": { + "body": { + "Exchange": { + "exchange": "6a5930e2-0cfb-4aff-a4a4-8c4798f1cda5", + "sender": null, + "receiver": null, + "message_kind": "Asynchronous", + "reply_to": null, + "values": [ + { + "path": [ + "19be028f-96cd-440e-bbcb-e151fbe00b45" + ], + "field": "01d025e3-ee88-4449-a613-217b44a61ae2", + "expression": "latitude_deg + 0.25" + }, + { + "path": [ + "02a82c95-f030-4be3-9ffe-6ebccf40fcb1" + ], + "field": "01d025e3-ee88-4449-a613-217b44a61ae2", + "expression": "second independent" + } + ] + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + }, + "7d9832e5-0288-4d04-a5df-1551d050f79e": { + "id": "7d9832e5-0288-4d04-a5df-1551d050f79e", + "alias": "sa_rendezvous_use", + "name": "SA Rendezvous use", + "description": "", + "parent": "06ec35b3-ce60-43a7-8300-c239fa91c898", + "body": { + "Sa": { + "Shared": { + "Occurrence": { + "body": { + "ScenarioUse": { + "scenario": "12143ec8-bcf9-4755-809c-87b653465888", + "bindings": [ + { + "referenced": "1cb0eb67-6fa7-414c-a2aa-155cb39da564", + "local": "0ba127bc-cd2d-4e53-b76e-9c01025a7c3f" + }, + { + "referenced": "82798865-193a-46b0-b40d-b22ae91eba28", + "local": "a8518771-fa4d-4e6e-bb6a-9e5f719f874a" + } + ] + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + }, + "814c985a-a8c8-409a-b11b-c9572f29eabc": { + "id": "814c985a-a8c8-409a-b11b-c9572f29eabc", + "alias": "oa_navigation_scenario", + "name": "OA Navigation scenario", + "description": "", + "parent": null, + "body": { + "Oa": { + "Shared": { + "Scenario": { + "viewpoint": "OperationalActivity", + "context": null, + "precondition": "", + "postcondition": "", + "steps": [ + "73de257c-d816-4f91-b507-36f79ca49f08" + ] + } + } + } + } + }, + "82798865-193a-46b0-b40d-b22ae91eba28": { + "id": "82798865-193a-46b0-b40d-b22ae91eba28", + "alias": "sa_rendezvous_extra_referenced", + "name": "SA Rendezvous extra referenced", + "description": "", + "parent": "12143ec8-bcf9-4755-809c-87b653465888", + "body": { + "Sa": { + "Shared": { + "Lifeline": { + "represented": null + } + } + } + } + }, + "85eee860-b8ee-4c14-8551-ec4ce8b84aa8": { + "id": "85eee860-b8ee-4c14-8551-ec4ce8b84aa8", + "alias": "oa_navigation_coordinate", + "name": "OA Navigation Coordinate", + "description": "", + "parent": null, + "body": { + "Oa": { + "Shared": { + "DataType": { + "definition": { + "Record": { + "supertype": null + } + }, + "default": null, + "unit": "" + } + } + } + } + }, + "8dbaedfd-4a4a-4e5d-a9f9-d040d5f144d8": { + "id": "8dbaedfd-4a4a-4e5d-a9f9-d040d5f144d8", + "alias": "sa_navigation_destination", + "name": "SA Navigation destination", + "description": "", + "parent": "6e8218df-2d57-4247-a401-e0458c4d5bc1", + "body": { + "Sa": { + "Shared": { + "DataField": { + "type_ref": { + "Named": "715228b2-a573-47ad-bf53-0a9b968ded96" + }, + "minimum": 0, + "maximum": 1, + "unit": "", + "direction": "Unspecified", + "storage": "Value", + "default": null + } + } + } + } + }, + "9a78b793-a987-438e-a041-233dc41f2b10": { + "id": "9a78b793-a987-438e-a041-233dc41f2b10", + "alias": "oa_rendezvous_use", + "name": "OA Rendezvous use", + "description": "", + "parent": "fd6e0287-66e0-4e84-8a92-98c4c167cb88", + "body": { + "Oa": { + "Shared": { + "Occurrence": { + "body": { + "ScenarioUse": { + "scenario": "eacfc6df-4e22-4ebd-a90d-6b5146e56513", + "bindings": [ + { + "referenced": "0e3d7b11-4c0f-41a0-beae-a5440e30782f", + "local": "424d78d2-9145-4b34-a18f-80a335164a9b" + }, + { + "referenced": "9e60984f-9ca3-4f3d-bc3f-b6b973cdc698", + "local": "1f697a2c-8455-48f1-bbdf-bdf657fe8fc4" + } + ] + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + }, + "9e60984f-9ca3-4f3d-bc3f-b6b973cdc698": { + "id": "9e60984f-9ca3-4f3d-bc3f-b6b973cdc698", + "alias": "oa_rendezvous_extra_referenced", + "name": "OA Rendezvous extra referenced", + "description": "", + "parent": "eacfc6df-4e22-4ebd-a90d-6b5146e56513", + "body": { + "Oa": { + "Shared": { + "Lifeline": { + "represented": null + } + } + } + } + }, + "9eb225ee-7103-5eb7-88d0-ff2128408748": { + "id": "9eb225ee-7103-5eb7-88d0-ff2128408748", + "alias": "sa_navigation_exchange_output", + "name": "SA Navigation exchange output", + "description": "", + "parent": "ad7dede7-59bf-4355-afec-08c905a564ff", + "body": { + "Sa": { + "Shared": { + "Port": { + "contract": { + "FunctionOutput": { + "outgoing_items": [ + "6e8218df-2d57-4247-a401-e0458c4d5bc1" + ] + } + }, + "legacy": null + } + } + } + } + }, + "a8518771-fa4d-4e6e-bb6a-9e5f719f874a": { + "id": "a8518771-fa4d-4e6e-bb6a-9e5f719f874a", + "alias": "sa_rendezvous_extra_local", + "name": "SA Rendezvous extra local", + "description": "", + "parent": "06ec35b3-ce60-43a7-8300-c239fa91c898", + "body": { + "Sa": { + "Shared": { + "Lifeline": { + "represented": null + } + } + } + } + }, + "ab1e449f-24f1-4bee-938c-b49510d19bf3": { + "id": "ab1e449f-24f1-4bee-938c-b49510d19bf3", + "alias": "sa_navigation_latitude", + "name": "SA Navigation latitude", + "description": "", + "parent": "715228b2-a573-47ad-bf53-0a9b968ded96", + "body": { + "Sa": { + "Shared": { + "DataField": { + "type_ref": "Real", + "minimum": 0, + "maximum": 1, + "unit": "", + "direction": "Unspecified", + "storage": "Value", + "default": null + } + } + } + } + }, + "ad717be1-3f30-55db-94b9-7a51558b6dd6": { + "id": "ad717be1-3f30-55db-94b9-7a51558b6dd6", + "alias": "sa_navigation_exchange_input", + "name": "SA Navigation exchange input", + "description": "", + "parent": "0114bf4c-343b-4a96-918c-3b4087df4c5e", + "body": { + "Sa": { + "Shared": { + "Port": { + "contract": { + "FunctionInput": { + "incoming_items": [ + "6e8218df-2d57-4247-a401-e0458c4d5bc1" + ] + } + }, + "legacy": null + } + } + } + } + }, + "ad7dede7-59bf-4355-afec-08c905a564ff": { + "id": "ad7dede7-59bf-4355-afec-08c905a564ff", + "alias": "sa_navigation_a", + "name": "SA Navigation A", + "description": "", + "parent": null, + "body": { + "Sa": "Function" + } + }, + "bb0ad838-eb7c-4470-bf27-f3a21e69fdf8": { + "id": "bb0ad838-eb7c-4470-bf27-f3a21e69fdf8", + "alias": "sa_rendezvous_alternate", + "name": "SA Rendezvous alternate", + "description": "", + "parent": "06ec35b3-ce60-43a7-8300-c239fa91c898", + "body": { + "Sa": { + "Shared": { + "Lifeline": { + "represented": null + } + } + } + } + }, + "d10ef3c1-479f-4780-9fd5-3b4d21dffe74": { + "id": "d10ef3c1-479f-4780-9fd5-3b4d21dffe74", + "alias": "sa_navigation_message", + "name": "SA Navigation message", + "description": "", + "parent": "409c78a0-28bc-4810-86ba-300204a3513f", + "body": { + "Sa": { + "Shared": { + "Occurrence": { + "body": { + "Exchange": { + "exchange": "9c1085f2-c91d-4776-bb56-52c864a80e22", + "sender": null, + "receiver": null, + "message_kind": "Asynchronous", + "reply_to": null, + "values": [ + { + "path": [ + "0cf0ba32-01c8-41f5-a44c-ed31daa3ca92" + ], + "field": "ab1e449f-24f1-4bee-938c-b49510d19bf3", + "expression": "latitude_deg + 0.25" + }, + { + "path": [ + "8dbaedfd-4a4a-4e5d-a9f9-d040d5f144d8" + ], + "field": "ab1e449f-24f1-4bee-938c-b49510d19bf3", + "expression": "second independent" + } + ] + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + }, + "d7d34062-6820-471d-bb5f-abb7db4c8198": { + "id": "d7d34062-6820-471d-bb5f-abb7db4c8198", + "alias": "oa_navigation_a", + "name": "OA Navigation A", + "description": "", + "parent": null, + "body": { + "Oa": "Activity" + } + }, + "eacfc6df-4e22-4ebd-a90d-6b5146e56513": { + "id": "eacfc6df-4e22-4ebd-a90d-6b5146e56513", + "alias": "oa_rendezvous_callee", + "name": "OA Rendezvous callee", + "description": "", + "parent": null, + "body": { + "Oa": { + "Shared": { + "Scenario": { + "viewpoint": "OperationalActivity", + "context": null, + "precondition": "", + "postcondition": "", + "steps": [] + } + } + } + } + }, + "fd6e0287-66e0-4e84-8a92-98c4c167cb88": { + "id": "fd6e0287-66e0-4e84-8a92-98c4c167cb88", + "alias": "oa_rendezvous_caller", + "name": "OA Rendezvous caller", + "description": "", + "parent": null, + "body": { + "Oa": { + "Shared": { + "Scenario": { + "viewpoint": "OperationalActivity", + "context": null, + "precondition": "", + "postcondition": "", + "steps": [ + "9a78b793-a987-438e-a041-233dc41f2b10" + ] + } + } + } + } + } + }, + "relations": { + "6a5930e2-0cfb-4aff-a4a4-8c4798f1cda5": { + "id": "6a5930e2-0cfb-4aff-a4a4-8c4798f1cda5", + "alias": "oa_navigation_exchange", + "name": "OA Navigation exchange", + "description": "", + "body": { + "Interaction": { + "source": "d7d34062-6820-471d-bb5f-abb7db4c8198", + "target": "264ff2ee-7786-4db7-b78d-2be802c268ac", + "items": [ + "20619f0c-4419-46b5-a15c-49b004acfa91" + ] + } + } + }, + "9c1085f2-c91d-4776-bb56-52c864a80e22": { + "id": "9c1085f2-c91d-4776-bb56-52c864a80e22", + "alias": "sa_navigation_exchange", + "name": "SA Navigation exchange", + "description": "", + "body": { + "FunctionalExchange": { + "source": "ad7dede7-59bf-4355-afec-08c905a564ff", + "target": "0114bf4c-343b-4a96-918c-3b4087df4c5e", + "source_port": "9eb225ee-7103-5eb7-88d0-ff2128408748", + "target_port": "ad717be1-3f30-55db-94b9-7a51558b6dd6", + "items": [ + "6e8218df-2d57-4247-a401-e0458c4d5bc1" + ] + } + } + } + }, + "diagrams": {}, + "artifact_links": {}, + "traces": { + "551c0dbc-ede6-42ef-ae60-2e0136ffc6e6": { + "id": "551c0dbc-ede6-42ef-ae60-2e0136ffc6e6", + "alias": "rendezvous_binding_review", + "name": "Rendezvous participant binding review", + "description": "", + "oa": "9a78b793-a987-438e-a041-233dc41f2b10", + "sa": "7d9832e5-0288-4d04-a5df-1551d050f79e", + "kind": "Manual", + "baseline": { + "oa": { + "name": "OA Rendezvous use", + "description": "" + }, + "sa": { + "name": "SA Rendezvous use", + "description": "" + }, + "semantic": { + "facets": [ + { + "Binding": { + "keys": { + "oa": "0e3d7b11-4c0f-41a0-beae-a5440e30782f", + "sa": "1cb0eb67-6fa7-414c-a2aa-155cb39da564" + }, + "review": { + "oa": { + "Value": { + "referenced": "0e3d7b11-4c0f-41a0-beae-a5440e30782f", + "local": "424d78d2-9145-4b34-a18f-80a335164a9b" + } + }, + "sa": { + "Value": { + "referenced": "1cb0eb67-6fa7-414c-a2aa-155cb39da564", + "local": "0ba127bc-cd2d-4e53-b76e-9c01025a7c3f" + } + } + } + } + } + ] + } + } + }, + "b647f858-1a69-4292-8710-674b1692d54f": { + "id": "b647f858-1a69-4292-8710-674b1692d54f", + "alias": "navigation_latitude_review", + "name": "Navigation origin latitude review", + "description": "", + "oa": "73de257c-d816-4f91-b507-36f79ca49f08", + "sa": "d10ef3c1-479f-4780-9fd5-3b4d21dffe74", + "kind": "Manual", + "baseline": { + "oa": { + "name": "OA Navigation message", + "description": "" + }, + "sa": { + "name": "SA Navigation message", + "description": "" + }, + "semantic": { + "facets": [ + { + "Annotation": { + "keys": { + "oa": { + "path": [ + "19be028f-96cd-440e-bbcb-e151fbe00b45" + ], + "field": "01d025e3-ee88-4449-a613-217b44a61ae2" + }, + "sa": { + "path": [ + "0cf0ba32-01c8-41f5-a44c-ed31daa3ca92" + ], + "field": "ab1e449f-24f1-4bee-938c-b49510d19bf3" + } + }, + "review": { + "oa": { + "Value": { + "path": [ + "19be028f-96cd-440e-bbcb-e151fbe00b45" + ], + "field": "01d025e3-ee88-4449-a613-217b44a61ae2", + "expression": "latitude_deg + 0.25" + } + }, + "sa": { + "Value": { + "path": [ + "0cf0ba32-01c8-41f5-a44c-ed31daa3ca92" + ], + "field": "ab1e449f-24f1-4bee-938c-b49510d19bf3", + "expression": "latitude_deg + 0.25" + } + } + } + } + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/harness/fixtures/release1-native/semantic-keyed.arcadia b/harness/fixtures/release1-native/semantic-keyed.arcadia new file mode 100644 index 00000000..2519d20a --- /dev/null +++ b/harness/fixtures/release1-native/semantic-keyed.arcadia @@ -0,0 +1,482 @@ +// Native keyed review: preserve this exact comment. +arcadia 5; +project mission "Semantic keyed native review" id "13089f56-2a58-4f42-a0e3-8c9812fb5857" { + description ""; + + sa_function sa_navigation_b "SA Navigation B" id "0114bf4c-343b-4a96-918c-3b4087df4c5e" { + description ""; + parent none; + } + + oa_data_field oa_navigation_latitude "OA Navigation latitude" id "01d025e3-ee88-4449-a613-217b44a61ae2" { + default none; + description ""; + direction Unspecified; + maximum 1; + minimum 0; + parent @oa_navigation_coordinate; + storage Value; + type_ref Real; + unit ""; + } + + oa_data_field oa_navigation_destination "OA Navigation destination" id "02a82c95-f030-4be3-9ffe-6ebccf40fcb1" { + default none; + description ""; + direction Unspecified; + maximum 1; + minimum 0; + parent @oa_navigation_item; + storage Value; + type_ref { + Named @oa_navigation_coordinate; + }; + unit ""; + } + + sa_scenario sa_rendezvous_caller "SA Rendezvous caller" id "06ec35b3-ce60-43a7-8300-c239fa91c898" { + context none; + description ""; + parent none; + postcondition ""; + precondition ""; + steps [@sa_rendezvous_use]; + viewpoint Functional; + } + + sa_lifeline sa_rendezvous_local "SA Rendezvous local" id "0ba127bc-cd2d-4e53-b76e-9c01025a7c3f" { + description ""; + parent @sa_rendezvous_caller; + represented none; + } + + sa_data_field sa_navigation_origin "SA Navigation origin" id "0cf0ba32-01c8-41f5-a44c-ed31daa3ca92" { + default none; + description ""; + direction Unspecified; + maximum 1; + minimum 0; + parent @sa_navigation_item; + storage Value; + type_ref { + Named @sa_navigation_coordinate; + }; + unit ""; + } + + oa_lifeline oa_rendezvous_referenced "OA Rendezvous referenced" id "0e3d7b11-4c0f-41a0-beae-a5440e30782f" { + description ""; + parent @oa_rendezvous_callee; + represented none; + } + + sa_scenario sa_rendezvous_callee "SA Rendezvous callee" id "12143ec8-bcf9-4755-809c-87b653465888" { + context none; + description ""; + parent none; + postcondition ""; + precondition ""; + steps []; + viewpoint Functional; + } + + oa_data_field oa_navigation_origin "OA Navigation origin" id "19be028f-96cd-440e-bbcb-e151fbe00b45" { + default none; + description ""; + direction Unspecified; + maximum 1; + minimum 0; + parent @oa_navigation_item; + storage Value; + type_ref { + Named @oa_navigation_coordinate; + }; + unit ""; + } + + sa_lifeline sa_rendezvous_referenced "SA Rendezvous referenced" id "1cb0eb67-6fa7-414c-a2aa-155cb39da564" { + description ""; + parent @sa_rendezvous_callee; + represented none; + } + + oa_lifeline oa_rendezvous_extra_local "OA Rendezvous extra local" id "1f697a2c-8455-48f1-bbdf-bdf657fe8fc4" { + description ""; + parent @oa_rendezvous_caller; + represented none; + } + + oa_exchange_item oa_navigation_item "OA Navigation Item" id "20619f0c-4419-46b5-a15c-49b004acfa91" { + description ""; + kind Information; + mechanism Unspecified; + parent none; + supertype none; + } + + oa_activity oa_navigation_b "OA Navigation B" id "264ff2ee-7786-4db7-b78d-2be802c268ac" { + description ""; + parent none; + } + + sa_scenario sa_navigation_scenario "SA Navigation scenario" id "409c78a0-28bc-4810-86ba-300204a3513f" { + context none; + description ""; + parent none; + postcondition ""; + precondition ""; + steps [@sa_navigation_message]; + viewpoint Functional; + } + + oa_lifeline oa_rendezvous_local "OA Rendezvous local" id "424d78d2-9145-4b34-a18f-80a335164a9b" { + description ""; + parent @oa_rendezvous_caller; + represented none; + } + + oa_lifeline oa_rendezvous_alternate "OA Rendezvous alternate" id "59173870-bc74-4c85-89b3-b0cfb8ad70cb" { + description ""; + parent @oa_rendezvous_caller; + represented none; + } + + sa_exchange_item sa_navigation_item "SA Navigation Item" id "6e8218df-2d57-4247-a401-e0458c4d5bc1" { + description ""; + kind Information; + mechanism Unspecified; + parent none; + supertype none; + } + + sa_data_type sa_navigation_coordinate "SA Navigation Coordinate" id "715228b2-a573-47ad-bf53-0a9b968ded96" { + default none; + definition { + Record { + supertype none; + }; + }; + description ""; + parent none; + unit ""; + } + + oa_occurrence oa_navigation_message "OA Navigation message" id "73de257c-d816-4f91-b507-36f79ca49f08" { + body { + Exchange { + exchange @oa_navigation_exchange; + message_kind Asynchronous; + receiver none; + reply_to none; + sender none; + values [{ + expression "latitude_deg + 0.25"; + field @oa_navigation_latitude; + path [@oa_navigation_origin]; + }, { + expression "second independent"; + field @oa_navigation_latitude; + path [@oa_navigation_destination]; + }]; + }; + }; + description ""; + parent @oa_navigation_scenario; + postcondition ""; + precondition ""; + } + + sa_occurrence sa_rendezvous_use "SA Rendezvous use" id "7d9832e5-0288-4d04-a5df-1551d050f79e" { + body { + ScenarioUse { + bindings [{ + local @sa_rendezvous_local; + referenced @sa_rendezvous_referenced; + }, { + local @sa_rendezvous_extra_local; + referenced @sa_rendezvous_extra_referenced; + }]; + scenario @sa_rendezvous_callee; + }; + }; + description ""; + parent @sa_rendezvous_caller; + postcondition ""; + precondition ""; + } + + oa_scenario oa_navigation_scenario "OA Navigation scenario" id "814c985a-a8c8-409a-b11b-c9572f29eabc" { + context none; + description ""; + parent none; + postcondition ""; + precondition ""; + steps [@oa_navigation_message]; + viewpoint OperationalActivity; + } + + sa_lifeline sa_rendezvous_extra_referenced "SA Rendezvous extra referenced" id "82798865-193a-46b0-b40d-b22ae91eba28" { + description ""; + parent @sa_rendezvous_callee; + represented none; + } + + oa_data_type oa_navigation_coordinate "OA Navigation Coordinate" id "85eee860-b8ee-4c14-8551-ec4ce8b84aa8" { + default none; + definition { + Record { + supertype none; + }; + }; + description ""; + parent none; + unit ""; + } + + sa_data_field sa_navigation_destination "SA Navigation destination" id "8dbaedfd-4a4a-4e5d-a9f9-d040d5f144d8" { + default none; + description ""; + direction Unspecified; + maximum 1; + minimum 0; + parent @sa_navigation_item; + storage Value; + type_ref { + Named @sa_navigation_coordinate; + }; + unit ""; + } + + oa_occurrence oa_rendezvous_use "OA Rendezvous use" id "9a78b793-a987-438e-a041-233dc41f2b10" { + body { + ScenarioUse { + bindings [{ + local @oa_rendezvous_local; + referenced @oa_rendezvous_referenced; + }, { + local @oa_rendezvous_extra_local; + referenced @oa_rendezvous_extra_referenced; + }]; + scenario @oa_rendezvous_callee; + }; + }; + description ""; + parent @oa_rendezvous_caller; + postcondition ""; + precondition ""; + } + + oa_lifeline oa_rendezvous_extra_referenced "OA Rendezvous extra referenced" id "9e60984f-9ca3-4f3d-bc3f-b6b973cdc698" { + description ""; + parent @oa_rendezvous_callee; + represented none; + } + + sa_port sa_navigation_exchange_output "SA Navigation exchange output" id "9eb225ee-7103-5eb7-88d0-ff2128408748" { + contract { + FunctionOutput { + outgoing_items [@sa_navigation_item]; + }; + }; + description ""; + legacy none; + parent @sa_navigation_a; + } + + sa_lifeline sa_rendezvous_extra_local "SA Rendezvous extra local" id "a8518771-fa4d-4e6e-bb6a-9e5f719f874a" { + description ""; + parent @sa_rendezvous_caller; + represented none; + } + + sa_data_field sa_navigation_latitude "SA Navigation latitude" id "ab1e449f-24f1-4bee-938c-b49510d19bf3" { + default none; + description ""; + direction Unspecified; + maximum 1; + minimum 0; + parent @sa_navigation_coordinate; + storage Value; + type_ref Real; + unit ""; + } + + sa_port sa_navigation_exchange_input "SA Navigation exchange input" id "ad717be1-3f30-55db-94b9-7a51558b6dd6" { + contract { + FunctionInput { + incoming_items [@sa_navigation_item]; + }; + }; + description ""; + legacy none; + parent @sa_navigation_b; + } + + sa_function sa_navigation_a "SA Navigation A" id "ad7dede7-59bf-4355-afec-08c905a564ff" { + description ""; + parent none; + } + + sa_lifeline sa_rendezvous_alternate "SA Rendezvous alternate" id "bb0ad838-eb7c-4470-bf27-f3a21e69fdf8" { + description ""; + parent @sa_rendezvous_caller; + represented none; + } + + sa_occurrence sa_navigation_message "SA Navigation message" id "d10ef3c1-479f-4780-9fd5-3b4d21dffe74" { + body { + Exchange { + exchange @sa_navigation_exchange; + message_kind Asynchronous; + receiver none; + reply_to none; + sender none; + values [{ + expression "latitude_deg"; + field @sa_navigation_latitude; + path [@sa_navigation_origin]; + }, { + expression "second independent"; + field @sa_navigation_latitude; + path [@sa_navigation_destination]; + }]; + }; + }; + description ""; + parent @sa_navigation_scenario; + postcondition ""; + precondition ""; + } + + oa_activity oa_navigation_a "OA Navigation A" id "d7d34062-6820-471d-bb5f-abb7db4c8198" { + description ""; + parent none; + } + + oa_scenario oa_rendezvous_callee "OA Rendezvous callee" id "eacfc6df-4e22-4ebd-a90d-6b5146e56513" { + context none; + description ""; + parent none; + postcondition ""; + precondition ""; + steps []; + viewpoint OperationalActivity; + } + + oa_scenario oa_rendezvous_caller "OA Rendezvous caller" id "fd6e0287-66e0-4e84-8a92-98c4c167cb88" { + context none; + description ""; + parent none; + postcondition ""; + precondition ""; + steps [@oa_rendezvous_use]; + viewpoint OperationalActivity; + } + + interaction oa_navigation_exchange "OA Navigation exchange" id "6a5930e2-0cfb-4aff-a4a4-8c4798f1cda5" { + description ""; + items [@oa_navigation_item]; + source @oa_navigation_a; + target @oa_navigation_b; + } + + functional_exchange sa_navigation_exchange "SA Navigation exchange" id "9c1085f2-c91d-4776-bb56-52c864a80e22" { + description ""; + items [@sa_navigation_item]; + source @sa_navigation_a; + source_port @sa_navigation_exchange_output; + target @sa_navigation_b; + target_port @sa_navigation_exchange_input; + } + + trace rendezvous_binding_review "Rendezvous participant binding review" id "551c0dbc-ede6-42ef-ae60-2e0136ffc6e6" { + baseline { + oa { + description ""; + name "OA Rendezvous use"; + }; + sa { + description ""; + name "SA Rendezvous use"; + }; + semantic { + facets [{ + Binding { + keys { + oa uuid "0e3d7b11-4c0f-41a0-beae-a5440e30782f"; + sa uuid "1cb0eb67-6fa7-414c-a2aa-155cb39da564"; + }; + review { + oa { + Value { + local uuid "424d78d2-9145-4b34-a18f-80a335164a9b"; + referenced uuid "0e3d7b11-4c0f-41a0-beae-a5440e30782f"; + }; + }; + sa { + Value { + local uuid "0ba127bc-cd2d-4e53-b76e-9c01025a7c3f"; + referenced uuid "1cb0eb67-6fa7-414c-a2aa-155cb39da564"; + }; + }; + }; + }; + }]; + }; + }; + description ""; + kind Manual; + oa @oa_rendezvous_use; + sa @sa_rendezvous_use; + } + + trace navigation_latitude_review "Navigation origin latitude review" id "b647f858-1a69-4292-8710-674b1692d54f" { + baseline { + oa { + description ""; + name "OA Navigation message"; + }; + sa { + description ""; + name "SA Navigation message"; + }; + semantic { + facets [{ + Annotation { + keys { + oa { + field uuid "01d025e3-ee88-4449-a613-217b44a61ae2"; + path [uuid "19be028f-96cd-440e-bbcb-e151fbe00b45"]; + }; + sa { + field uuid "ab1e449f-24f1-4bee-938c-b49510d19bf3"; + path [uuid "0cf0ba32-01c8-41f5-a44c-ed31daa3ca92"]; + }; + }; + review { + oa { + Value { + expression "latitude_deg + 0.25"; + field uuid "01d025e3-ee88-4449-a613-217b44a61ae2"; + path [uuid "19be028f-96cd-440e-bbcb-e151fbe00b45"]; + }; + }; + sa { + Value { + expression "latitude_deg"; + field uuid "ab1e449f-24f1-4bee-938c-b49510d19bf3"; + path [uuid "0cf0ba32-01c8-41f5-a44c-ed31daa3ca92"]; + }; + }; + }; + }; + }]; + }; + }; + description ""; + kind Manual; + oa @oa_navigation_message; + sa @sa_navigation_message; + } + +} diff --git a/harness/fixtures/release1-native/semantic-keyed.invalid-request.json b/harness/fixtures/release1-native/semantic-keyed.invalid-request.json new file mode 100644 index 00000000..1bb93e72 --- /dev/null +++ b/harness/fixtures/release1-native/semantic-keyed.invalid-request.json @@ -0,0 +1,80 @@ +{ + "transition": { + "direction": "OaToSa", + "sources": [ + "9a78b793-a987-438e-a041-233dc41f2b10" + ], + "scope": "SelectedOnly", + "mappings": [ + { + "source": "9a78b793-a987-438e-a041-233dc41f2b10", + "target": "7d9832e5-0288-4d04-a5df-1551d050f79e", + "target_kind": null + } + ], + "fields": [], + "resolutions": [], + "destination_diagram": null + }, + "traces": { + "9a78b793-a987-438e-a041-233dc41f2b10": "551c0dbc-ede6-42ef-ae60-2e0136ffc6e6" + }, + "references": { + "0e3d7b11-4c0f-41a0-beae-a5440e30782f": "1cb0eb67-6fa7-414c-a2aa-155cb39da564", + "9a78b793-a987-438e-a041-233dc41f2b10": "7d9832e5-0288-4d04-a5df-1551d050f79e" + }, + "reviews": [ + { + "source": "9a78b793-a987-438e-a041-233dc41f2b10", + "resolutions": [ + { + "facet": { + "Binding": { + "oa": "0e3d7b11-4c0f-41a0-beae-a5440e30782f", + "sa": "1cb0eb67-6fa7-414c-a2aa-155cb39da564" + } + }, + "decision": "Candidate" + } + ], + "reset": [] + } + ], + "candidates": [ + { + "Element": { + "id": "7d9832e5-0288-4d04-a5df-1551d050f79e", + "alias": "sa_rendezvous_use", + "name": "SA Rendezvous use", + "description": "", + "parent": "06ec35b3-ce60-43a7-8300-c239fa91c898", + "body": { + "Sa": { + "Shared": { + "Occurrence": { + "body": { + "ScenarioUse": { + "scenario": "12143ec8-bcf9-4755-809c-87b653465888", + "bindings": [ + { + "referenced": "1cb0eb67-6fa7-414c-a2aa-155cb39da564", + "local": "a8518771-fa4d-4e6e-bb6a-9e5f719f874a" + }, + { + "referenced": "82798865-193a-46b0-b40d-b22ae91eba28", + "local": "a8518771-fa4d-4e6e-bb6a-9e5f719f874a" + } + ] + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + } + } + ], + "accepted_impacts": [] +} \ No newline at end of file diff --git a/harness/fixtures/release1-native/semantic-keyed.invalid.txt b/harness/fixtures/release1-native/semantic-keyed.invalid.txt new file mode 100644 index 00000000..7187782a --- /dev/null +++ b/harness/fixtures/release1-native/semantic-keyed.invalid.txt @@ -0,0 +1 @@ +["duplicate_lifeline_binding: Scenario-use lifeline bindings are one-to-one."] \ No newline at end of file diff --git a/harness/fixtures/release1-native/semantic-keyed.json b/harness/fixtures/release1-native/semantic-keyed.json new file mode 100644 index 00000000..06f23fb5 --- /dev/null +++ b/harness/fixtures/release1-native/semantic-keyed.json @@ -0,0 +1,895 @@ +{ + "schema_version": 5, + "id": "13089f56-2a58-4f42-a0e3-8c9812fb5857", + "alias": "mission", + "name": "Semantic keyed native review", + "description": "", + "elements": { + "0114bf4c-343b-4a96-918c-3b4087df4c5e": { + "id": "0114bf4c-343b-4a96-918c-3b4087df4c5e", + "alias": "sa_navigation_b", + "name": "SA Navigation B", + "description": "", + "parent": null, + "body": { + "Sa": "Function" + } + }, + "01d025e3-ee88-4449-a613-217b44a61ae2": { + "id": "01d025e3-ee88-4449-a613-217b44a61ae2", + "alias": "oa_navigation_latitude", + "name": "OA Navigation latitude", + "description": "", + "parent": "85eee860-b8ee-4c14-8551-ec4ce8b84aa8", + "body": { + "Oa": { + "Shared": { + "DataField": { + "type_ref": "Real", + "minimum": 0, + "maximum": 1, + "unit": "", + "direction": "Unspecified", + "storage": "Value", + "default": null + } + } + } + } + }, + "02a82c95-f030-4be3-9ffe-6ebccf40fcb1": { + "id": "02a82c95-f030-4be3-9ffe-6ebccf40fcb1", + "alias": "oa_navigation_destination", + "name": "OA Navigation destination", + "description": "", + "parent": "20619f0c-4419-46b5-a15c-49b004acfa91", + "body": { + "Oa": { + "Shared": { + "DataField": { + "type_ref": { + "Named": "85eee860-b8ee-4c14-8551-ec4ce8b84aa8" + }, + "minimum": 0, + "maximum": 1, + "unit": "", + "direction": "Unspecified", + "storage": "Value", + "default": null + } + } + } + } + }, + "06ec35b3-ce60-43a7-8300-c239fa91c898": { + "id": "06ec35b3-ce60-43a7-8300-c239fa91c898", + "alias": "sa_rendezvous_caller", + "name": "SA Rendezvous caller", + "description": "", + "parent": null, + "body": { + "Sa": { + "Shared": { + "Scenario": { + "viewpoint": "Functional", + "context": null, + "precondition": "", + "postcondition": "", + "steps": [ + "7d9832e5-0288-4d04-a5df-1551d050f79e" + ] + } + } + } + } + }, + "0ba127bc-cd2d-4e53-b76e-9c01025a7c3f": { + "id": "0ba127bc-cd2d-4e53-b76e-9c01025a7c3f", + "alias": "sa_rendezvous_local", + "name": "SA Rendezvous local", + "description": "", + "parent": "06ec35b3-ce60-43a7-8300-c239fa91c898", + "body": { + "Sa": { + "Shared": { + "Lifeline": { + "represented": null + } + } + } + } + }, + "0cf0ba32-01c8-41f5-a44c-ed31daa3ca92": { + "id": "0cf0ba32-01c8-41f5-a44c-ed31daa3ca92", + "alias": "sa_navigation_origin", + "name": "SA Navigation origin", + "description": "", + "parent": "6e8218df-2d57-4247-a401-e0458c4d5bc1", + "body": { + "Sa": { + "Shared": { + "DataField": { + "type_ref": { + "Named": "715228b2-a573-47ad-bf53-0a9b968ded96" + }, + "minimum": 0, + "maximum": 1, + "unit": "", + "direction": "Unspecified", + "storage": "Value", + "default": null + } + } + } + } + }, + "0e3d7b11-4c0f-41a0-beae-a5440e30782f": { + "id": "0e3d7b11-4c0f-41a0-beae-a5440e30782f", + "alias": "oa_rendezvous_referenced", + "name": "OA Rendezvous referenced", + "description": "", + "parent": "eacfc6df-4e22-4ebd-a90d-6b5146e56513", + "body": { + "Oa": { + "Shared": { + "Lifeline": { + "represented": null + } + } + } + } + }, + "12143ec8-bcf9-4755-809c-87b653465888": { + "id": "12143ec8-bcf9-4755-809c-87b653465888", + "alias": "sa_rendezvous_callee", + "name": "SA Rendezvous callee", + "description": "", + "parent": null, + "body": { + "Sa": { + "Shared": { + "Scenario": { + "viewpoint": "Functional", + "context": null, + "precondition": "", + "postcondition": "", + "steps": [] + } + } + } + } + }, + "19be028f-96cd-440e-bbcb-e151fbe00b45": { + "id": "19be028f-96cd-440e-bbcb-e151fbe00b45", + "alias": "oa_navigation_origin", + "name": "OA Navigation origin", + "description": "", + "parent": "20619f0c-4419-46b5-a15c-49b004acfa91", + "body": { + "Oa": { + "Shared": { + "DataField": { + "type_ref": { + "Named": "85eee860-b8ee-4c14-8551-ec4ce8b84aa8" + }, + "minimum": 0, + "maximum": 1, + "unit": "", + "direction": "Unspecified", + "storage": "Value", + "default": null + } + } + } + } + }, + "1cb0eb67-6fa7-414c-a2aa-155cb39da564": { + "id": "1cb0eb67-6fa7-414c-a2aa-155cb39da564", + "alias": "sa_rendezvous_referenced", + "name": "SA Rendezvous referenced", + "description": "", + "parent": "12143ec8-bcf9-4755-809c-87b653465888", + "body": { + "Sa": { + "Shared": { + "Lifeline": { + "represented": null + } + } + } + } + }, + "1f697a2c-8455-48f1-bbdf-bdf657fe8fc4": { + "id": "1f697a2c-8455-48f1-bbdf-bdf657fe8fc4", + "alias": "oa_rendezvous_extra_local", + "name": "OA Rendezvous extra local", + "description": "", + "parent": "fd6e0287-66e0-4e84-8a92-98c4c167cb88", + "body": { + "Oa": { + "Shared": { + "Lifeline": { + "represented": null + } + } + } + } + }, + "20619f0c-4419-46b5-a15c-49b004acfa91": { + "id": "20619f0c-4419-46b5-a15c-49b004acfa91", + "alias": "oa_navigation_item", + "name": "OA Navigation Item", + "description": "", + "parent": null, + "body": { + "Oa": { + "Shared": { + "ExchangeItem": { + "kind": "Information", + "mechanism": "Unspecified", + "supertype": null + } + } + } + } + }, + "264ff2ee-7786-4db7-b78d-2be802c268ac": { + "id": "264ff2ee-7786-4db7-b78d-2be802c268ac", + "alias": "oa_navigation_b", + "name": "OA Navigation B", + "description": "", + "parent": null, + "body": { + "Oa": "Activity" + } + }, + "409c78a0-28bc-4810-86ba-300204a3513f": { + "id": "409c78a0-28bc-4810-86ba-300204a3513f", + "alias": "sa_navigation_scenario", + "name": "SA Navigation scenario", + "description": "", + "parent": null, + "body": { + "Sa": { + "Shared": { + "Scenario": { + "viewpoint": "Functional", + "context": null, + "precondition": "", + "postcondition": "", + "steps": [ + "d10ef3c1-479f-4780-9fd5-3b4d21dffe74" + ] + } + } + } + } + }, + "424d78d2-9145-4b34-a18f-80a335164a9b": { + "id": "424d78d2-9145-4b34-a18f-80a335164a9b", + "alias": "oa_rendezvous_local", + "name": "OA Rendezvous local", + "description": "", + "parent": "fd6e0287-66e0-4e84-8a92-98c4c167cb88", + "body": { + "Oa": { + "Shared": { + "Lifeline": { + "represented": null + } + } + } + } + }, + "59173870-bc74-4c85-89b3-b0cfb8ad70cb": { + "id": "59173870-bc74-4c85-89b3-b0cfb8ad70cb", + "alias": "oa_rendezvous_alternate", + "name": "OA Rendezvous alternate", + "description": "", + "parent": "fd6e0287-66e0-4e84-8a92-98c4c167cb88", + "body": { + "Oa": { + "Shared": { + "Lifeline": { + "represented": null + } + } + } + } + }, + "6e8218df-2d57-4247-a401-e0458c4d5bc1": { + "id": "6e8218df-2d57-4247-a401-e0458c4d5bc1", + "alias": "sa_navigation_item", + "name": "SA Navigation Item", + "description": "", + "parent": null, + "body": { + "Sa": { + "Shared": { + "ExchangeItem": { + "kind": "Information", + "mechanism": "Unspecified", + "supertype": null + } + } + } + } + }, + "715228b2-a573-47ad-bf53-0a9b968ded96": { + "id": "715228b2-a573-47ad-bf53-0a9b968ded96", + "alias": "sa_navigation_coordinate", + "name": "SA Navigation Coordinate", + "description": "", + "parent": null, + "body": { + "Sa": { + "Shared": { + "DataType": { + "definition": { + "Record": { + "supertype": null + } + }, + "default": null, + "unit": "" + } + } + } + } + }, + "73de257c-d816-4f91-b507-36f79ca49f08": { + "id": "73de257c-d816-4f91-b507-36f79ca49f08", + "alias": "oa_navigation_message", + "name": "OA Navigation message", + "description": "", + "parent": "814c985a-a8c8-409a-b11b-c9572f29eabc", + "body": { + "Oa": { + "Shared": { + "Occurrence": { + "body": { + "Exchange": { + "exchange": "6a5930e2-0cfb-4aff-a4a4-8c4798f1cda5", + "sender": null, + "receiver": null, + "message_kind": "Asynchronous", + "reply_to": null, + "values": [ + { + "path": [ + "19be028f-96cd-440e-bbcb-e151fbe00b45" + ], + "field": "01d025e3-ee88-4449-a613-217b44a61ae2", + "expression": "latitude_deg + 0.25" + }, + { + "path": [ + "02a82c95-f030-4be3-9ffe-6ebccf40fcb1" + ], + "field": "01d025e3-ee88-4449-a613-217b44a61ae2", + "expression": "second independent" + } + ] + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + }, + "7d9832e5-0288-4d04-a5df-1551d050f79e": { + "id": "7d9832e5-0288-4d04-a5df-1551d050f79e", + "alias": "sa_rendezvous_use", + "name": "SA Rendezvous use", + "description": "", + "parent": "06ec35b3-ce60-43a7-8300-c239fa91c898", + "body": { + "Sa": { + "Shared": { + "Occurrence": { + "body": { + "ScenarioUse": { + "scenario": "12143ec8-bcf9-4755-809c-87b653465888", + "bindings": [ + { + "referenced": "1cb0eb67-6fa7-414c-a2aa-155cb39da564", + "local": "0ba127bc-cd2d-4e53-b76e-9c01025a7c3f" + }, + { + "referenced": "82798865-193a-46b0-b40d-b22ae91eba28", + "local": "a8518771-fa4d-4e6e-bb6a-9e5f719f874a" + } + ] + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + }, + "814c985a-a8c8-409a-b11b-c9572f29eabc": { + "id": "814c985a-a8c8-409a-b11b-c9572f29eabc", + "alias": "oa_navigation_scenario", + "name": "OA Navigation scenario", + "description": "", + "parent": null, + "body": { + "Oa": { + "Shared": { + "Scenario": { + "viewpoint": "OperationalActivity", + "context": null, + "precondition": "", + "postcondition": "", + "steps": [ + "73de257c-d816-4f91-b507-36f79ca49f08" + ] + } + } + } + } + }, + "82798865-193a-46b0-b40d-b22ae91eba28": { + "id": "82798865-193a-46b0-b40d-b22ae91eba28", + "alias": "sa_rendezvous_extra_referenced", + "name": "SA Rendezvous extra referenced", + "description": "", + "parent": "12143ec8-bcf9-4755-809c-87b653465888", + "body": { + "Sa": { + "Shared": { + "Lifeline": { + "represented": null + } + } + } + } + }, + "85eee860-b8ee-4c14-8551-ec4ce8b84aa8": { + "id": "85eee860-b8ee-4c14-8551-ec4ce8b84aa8", + "alias": "oa_navigation_coordinate", + "name": "OA Navigation Coordinate", + "description": "", + "parent": null, + "body": { + "Oa": { + "Shared": { + "DataType": { + "definition": { + "Record": { + "supertype": null + } + }, + "default": null, + "unit": "" + } + } + } + } + }, + "8dbaedfd-4a4a-4e5d-a9f9-d040d5f144d8": { + "id": "8dbaedfd-4a4a-4e5d-a9f9-d040d5f144d8", + "alias": "sa_navigation_destination", + "name": "SA Navigation destination", + "description": "", + "parent": "6e8218df-2d57-4247-a401-e0458c4d5bc1", + "body": { + "Sa": { + "Shared": { + "DataField": { + "type_ref": { + "Named": "715228b2-a573-47ad-bf53-0a9b968ded96" + }, + "minimum": 0, + "maximum": 1, + "unit": "", + "direction": "Unspecified", + "storage": "Value", + "default": null + } + } + } + } + }, + "9a78b793-a987-438e-a041-233dc41f2b10": { + "id": "9a78b793-a987-438e-a041-233dc41f2b10", + "alias": "oa_rendezvous_use", + "name": "OA Rendezvous use", + "description": "", + "parent": "fd6e0287-66e0-4e84-8a92-98c4c167cb88", + "body": { + "Oa": { + "Shared": { + "Occurrence": { + "body": { + "ScenarioUse": { + "scenario": "eacfc6df-4e22-4ebd-a90d-6b5146e56513", + "bindings": [ + { + "referenced": "0e3d7b11-4c0f-41a0-beae-a5440e30782f", + "local": "424d78d2-9145-4b34-a18f-80a335164a9b" + }, + { + "referenced": "9e60984f-9ca3-4f3d-bc3f-b6b973cdc698", + "local": "1f697a2c-8455-48f1-bbdf-bdf657fe8fc4" + } + ] + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + }, + "9e60984f-9ca3-4f3d-bc3f-b6b973cdc698": { + "id": "9e60984f-9ca3-4f3d-bc3f-b6b973cdc698", + "alias": "oa_rendezvous_extra_referenced", + "name": "OA Rendezvous extra referenced", + "description": "", + "parent": "eacfc6df-4e22-4ebd-a90d-6b5146e56513", + "body": { + "Oa": { + "Shared": { + "Lifeline": { + "represented": null + } + } + } + } + }, + "9eb225ee-7103-5eb7-88d0-ff2128408748": { + "id": "9eb225ee-7103-5eb7-88d0-ff2128408748", + "alias": "sa_navigation_exchange_output", + "name": "SA Navigation exchange output", + "description": "", + "parent": "ad7dede7-59bf-4355-afec-08c905a564ff", + "body": { + "Sa": { + "Shared": { + "Port": { + "contract": { + "FunctionOutput": { + "outgoing_items": [ + "6e8218df-2d57-4247-a401-e0458c4d5bc1" + ] + } + }, + "legacy": null + } + } + } + } + }, + "a8518771-fa4d-4e6e-bb6a-9e5f719f874a": { + "id": "a8518771-fa4d-4e6e-bb6a-9e5f719f874a", + "alias": "sa_rendezvous_extra_local", + "name": "SA Rendezvous extra local", + "description": "", + "parent": "06ec35b3-ce60-43a7-8300-c239fa91c898", + "body": { + "Sa": { + "Shared": { + "Lifeline": { + "represented": null + } + } + } + } + }, + "ab1e449f-24f1-4bee-938c-b49510d19bf3": { + "id": "ab1e449f-24f1-4bee-938c-b49510d19bf3", + "alias": "sa_navigation_latitude", + "name": "SA Navigation latitude", + "description": "", + "parent": "715228b2-a573-47ad-bf53-0a9b968ded96", + "body": { + "Sa": { + "Shared": { + "DataField": { + "type_ref": "Real", + "minimum": 0, + "maximum": 1, + "unit": "", + "direction": "Unspecified", + "storage": "Value", + "default": null + } + } + } + } + }, + "ad717be1-3f30-55db-94b9-7a51558b6dd6": { + "id": "ad717be1-3f30-55db-94b9-7a51558b6dd6", + "alias": "sa_navigation_exchange_input", + "name": "SA Navigation exchange input", + "description": "", + "parent": "0114bf4c-343b-4a96-918c-3b4087df4c5e", + "body": { + "Sa": { + "Shared": { + "Port": { + "contract": { + "FunctionInput": { + "incoming_items": [ + "6e8218df-2d57-4247-a401-e0458c4d5bc1" + ] + } + }, + "legacy": null + } + } + } + } + }, + "ad7dede7-59bf-4355-afec-08c905a564ff": { + "id": "ad7dede7-59bf-4355-afec-08c905a564ff", + "alias": "sa_navigation_a", + "name": "SA Navigation A", + "description": "", + "parent": null, + "body": { + "Sa": "Function" + } + }, + "bb0ad838-eb7c-4470-bf27-f3a21e69fdf8": { + "id": "bb0ad838-eb7c-4470-bf27-f3a21e69fdf8", + "alias": "sa_rendezvous_alternate", + "name": "SA Rendezvous alternate", + "description": "", + "parent": "06ec35b3-ce60-43a7-8300-c239fa91c898", + "body": { + "Sa": { + "Shared": { + "Lifeline": { + "represented": null + } + } + } + } + }, + "d10ef3c1-479f-4780-9fd5-3b4d21dffe74": { + "id": "d10ef3c1-479f-4780-9fd5-3b4d21dffe74", + "alias": "sa_navigation_message", + "name": "SA Navigation message", + "description": "", + "parent": "409c78a0-28bc-4810-86ba-300204a3513f", + "body": { + "Sa": { + "Shared": { + "Occurrence": { + "body": { + "Exchange": { + "exchange": "9c1085f2-c91d-4776-bb56-52c864a80e22", + "sender": null, + "receiver": null, + "message_kind": "Asynchronous", + "reply_to": null, + "values": [ + { + "path": [ + "0cf0ba32-01c8-41f5-a44c-ed31daa3ca92" + ], + "field": "ab1e449f-24f1-4bee-938c-b49510d19bf3", + "expression": "latitude_deg" + }, + { + "path": [ + "8dbaedfd-4a4a-4e5d-a9f9-d040d5f144d8" + ], + "field": "ab1e449f-24f1-4bee-938c-b49510d19bf3", + "expression": "second independent" + } + ] + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + }, + "d7d34062-6820-471d-bb5f-abb7db4c8198": { + "id": "d7d34062-6820-471d-bb5f-abb7db4c8198", + "alias": "oa_navigation_a", + "name": "OA Navigation A", + "description": "", + "parent": null, + "body": { + "Oa": "Activity" + } + }, + "eacfc6df-4e22-4ebd-a90d-6b5146e56513": { + "id": "eacfc6df-4e22-4ebd-a90d-6b5146e56513", + "alias": "oa_rendezvous_callee", + "name": "OA Rendezvous callee", + "description": "", + "parent": null, + "body": { + "Oa": { + "Shared": { + "Scenario": { + "viewpoint": "OperationalActivity", + "context": null, + "precondition": "", + "postcondition": "", + "steps": [] + } + } + } + } + }, + "fd6e0287-66e0-4e84-8a92-98c4c167cb88": { + "id": "fd6e0287-66e0-4e84-8a92-98c4c167cb88", + "alias": "oa_rendezvous_caller", + "name": "OA Rendezvous caller", + "description": "", + "parent": null, + "body": { + "Oa": { + "Shared": { + "Scenario": { + "viewpoint": "OperationalActivity", + "context": null, + "precondition": "", + "postcondition": "", + "steps": [ + "9a78b793-a987-438e-a041-233dc41f2b10" + ] + } + } + } + } + } + }, + "relations": { + "6a5930e2-0cfb-4aff-a4a4-8c4798f1cda5": { + "id": "6a5930e2-0cfb-4aff-a4a4-8c4798f1cda5", + "alias": "oa_navigation_exchange", + "name": "OA Navigation exchange", + "description": "", + "body": { + "Interaction": { + "source": "d7d34062-6820-471d-bb5f-abb7db4c8198", + "target": "264ff2ee-7786-4db7-b78d-2be802c268ac", + "items": [ + "20619f0c-4419-46b5-a15c-49b004acfa91" + ] + } + } + }, + "9c1085f2-c91d-4776-bb56-52c864a80e22": { + "id": "9c1085f2-c91d-4776-bb56-52c864a80e22", + "alias": "sa_navigation_exchange", + "name": "SA Navigation exchange", + "description": "", + "body": { + "FunctionalExchange": { + "source": "ad7dede7-59bf-4355-afec-08c905a564ff", + "target": "0114bf4c-343b-4a96-918c-3b4087df4c5e", + "source_port": "9eb225ee-7103-5eb7-88d0-ff2128408748", + "target_port": "ad717be1-3f30-55db-94b9-7a51558b6dd6", + "items": [ + "6e8218df-2d57-4247-a401-e0458c4d5bc1" + ] + } + } + } + }, + "diagrams": {}, + "artifact_links": {}, + "traces": { + "551c0dbc-ede6-42ef-ae60-2e0136ffc6e6": { + "id": "551c0dbc-ede6-42ef-ae60-2e0136ffc6e6", + "alias": "rendezvous_binding_review", + "name": "Rendezvous participant binding review", + "description": "", + "oa": "9a78b793-a987-438e-a041-233dc41f2b10", + "sa": "7d9832e5-0288-4d04-a5df-1551d050f79e", + "kind": "Manual", + "baseline": { + "oa": { + "name": "OA Rendezvous use", + "description": "" + }, + "sa": { + "name": "SA Rendezvous use", + "description": "" + }, + "semantic": { + "facets": [ + { + "Binding": { + "keys": { + "oa": "0e3d7b11-4c0f-41a0-beae-a5440e30782f", + "sa": "1cb0eb67-6fa7-414c-a2aa-155cb39da564" + }, + "review": { + "oa": { + "Value": { + "referenced": "0e3d7b11-4c0f-41a0-beae-a5440e30782f", + "local": "424d78d2-9145-4b34-a18f-80a335164a9b" + } + }, + "sa": { + "Value": { + "referenced": "1cb0eb67-6fa7-414c-a2aa-155cb39da564", + "local": "0ba127bc-cd2d-4e53-b76e-9c01025a7c3f" + } + } + } + } + } + ] + } + } + }, + "b647f858-1a69-4292-8710-674b1692d54f": { + "id": "b647f858-1a69-4292-8710-674b1692d54f", + "alias": "navigation_latitude_review", + "name": "Navigation origin latitude review", + "description": "", + "oa": "73de257c-d816-4f91-b507-36f79ca49f08", + "sa": "d10ef3c1-479f-4780-9fd5-3b4d21dffe74", + "kind": "Manual", + "baseline": { + "oa": { + "name": "OA Navigation message", + "description": "" + }, + "sa": { + "name": "SA Navigation message", + "description": "" + }, + "semantic": { + "facets": [ + { + "Annotation": { + "keys": { + "oa": { + "path": [ + "19be028f-96cd-440e-bbcb-e151fbe00b45" + ], + "field": "01d025e3-ee88-4449-a613-217b44a61ae2" + }, + "sa": { + "path": [ + "0cf0ba32-01c8-41f5-a44c-ed31daa3ca92" + ], + "field": "ab1e449f-24f1-4bee-938c-b49510d19bf3" + } + }, + "review": { + "oa": { + "Value": { + "path": [ + "19be028f-96cd-440e-bbcb-e151fbe00b45" + ], + "field": "01d025e3-ee88-4449-a613-217b44a61ae2", + "expression": "latitude_deg + 0.25" + } + }, + "sa": { + "Value": { + "path": [ + "0cf0ba32-01c8-41f5-a44c-ed31daa3ca92" + ], + "field": "ab1e449f-24f1-4bee-938c-b49510d19bf3", + "expression": "latitude_deg" + } + } + } + } + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/harness/fixtures/release1-native/semantic-keyed.manifest.json b/harness/fixtures/release1-native/semantic-keyed.manifest.json new file mode 100644 index 00000000..301d384e --- /dev/null +++ b/harness/fixtures/release1-native/semantic-keyed.manifest.json @@ -0,0 +1,35 @@ +{ + "annotation_facet": { + "Annotation": { + "oa": { + "field": "01d025e3-ee88-4449-a613-217b44a61ae2", + "path": [ + "19be028f-96cd-440e-bbcb-e151fbe00b45" + ] + }, + "sa": { + "field": "ab1e449f-24f1-4bee-938c-b49510d19bf3", + "path": [ + "0cf0ba32-01c8-41f5-a44c-ed31daa3ca92" + ] + } + } + }, + "annotation_source": "73de257c-d816-4f91-b507-36f79ca49f08", + "annotation_target": "d10ef3c1-479f-4780-9fd5-3b4d21dffe74", + "annotation_trace": "b647f858-1a69-4292-8710-674b1692d54f", + "binding_facet": { + "Binding": { + "oa": "0e3d7b11-4c0f-41a0-beae-a5440e30782f", + "sa": "1cb0eb67-6fa7-414c-a2aa-155cb39da564" + } + }, + "binding_source": "9a78b793-a987-438e-a041-233dc41f2b10", + "binding_target": "7d9832e5-0288-4d04-a5df-1551d050f79e", + "binding_trace": "551c0dbc-ede6-42ef-ae60-2e0136ffc6e6", + "elements": 36, + "invalid_local": "a8518771-fa4d-4e6e-bb6a-9e5f719f874a", + "relations": 2, + "repaired_local": "bb0ad838-eb7c-4470-bf27-f3a21e69fdf8", + "traces": 2 +} \ No newline at end of file diff --git a/harness/fixtures/release1-native/semantic-keyed.repair-plan.json b/harness/fixtures/release1-native/semantic-keyed.repair-plan.json new file mode 100644 index 00000000..cb811c83 --- /dev/null +++ b/harness/fixtures/release1-native/semantic-keyed.repair-plan.json @@ -0,0 +1,543 @@ +{ + "base_fingerprint": "793cea8b7ddff23e", + "request": { + "transition": { + "direction": "OaToSa", + "sources": [ + "9a78b793-a987-438e-a041-233dc41f2b10" + ], + "scope": "SelectedOnly", + "mappings": [ + { + "source": "9a78b793-a987-438e-a041-233dc41f2b10", + "target": "7d9832e5-0288-4d04-a5df-1551d050f79e", + "target_kind": null + } + ], + "fields": [], + "resolutions": [], + "destination_diagram": null + }, + "traces": { + "9a78b793-a987-438e-a041-233dc41f2b10": "551c0dbc-ede6-42ef-ae60-2e0136ffc6e6" + }, + "references": { + "0e3d7b11-4c0f-41a0-beae-a5440e30782f": "1cb0eb67-6fa7-414c-a2aa-155cb39da564", + "9a78b793-a987-438e-a041-233dc41f2b10": "7d9832e5-0288-4d04-a5df-1551d050f79e" + }, + "reviews": [ + { + "source": "9a78b793-a987-438e-a041-233dc41f2b10", + "resolutions": [ + { + "facet": { + "Binding": { + "oa": "0e3d7b11-4c0f-41a0-beae-a5440e30782f", + "sa": "1cb0eb67-6fa7-414c-a2aa-155cb39da564" + } + }, + "decision": "Candidate" + } + ], + "reset": [] + } + ], + "candidates": [ + { + "Element": { + "id": "7d9832e5-0288-4d04-a5df-1551d050f79e", + "alias": "sa_rendezvous_use", + "name": "SA Rendezvous use", + "description": "", + "parent": "06ec35b3-ce60-43a7-8300-c239fa91c898", + "body": { + "Sa": { + "Shared": { + "Occurrence": { + "body": { + "ScenarioUse": { + "scenario": "12143ec8-bcf9-4755-809c-87b653465888", + "bindings": [ + { + "referenced": "1cb0eb67-6fa7-414c-a2aa-155cb39da564", + "local": "bb0ad838-eb7c-4470-bf27-f3a21e69fdf8" + }, + { + "referenced": "82798865-193a-46b0-b40d-b22ae91eba28", + "local": "a8518771-fa4d-4e6e-bb6a-9e5f719f874a" + } + ] + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + } + } + ], + "accepted_impacts": [] + }, + "transition": { + "base_fingerprint": "793cea8b7ddff23e", + "request": { + "direction": "OaToSa", + "sources": [ + "9a78b793-a987-438e-a041-233dc41f2b10" + ], + "scope": "SelectedOnly", + "mappings": [ + { + "source": "9a78b793-a987-438e-a041-233dc41f2b10", + "target": "7d9832e5-0288-4d04-a5df-1551d050f79e", + "target_kind": null + } + ], + "fields": [], + "resolutions": [], + "destination_diagram": null + }, + "changes": [ + { + "source": "9a78b793-a987-438e-a041-233dc41f2b10", + "target": "7d9832e5-0288-4d04-a5df-1551d050f79e", + "action": "Reuse", + "fields": [] + } + ], + "conflicts": [], + "warnings": [ + "Identify the SA system of interest.", + "Allocate leaf 'SA Navigation B' to a performer.", + "Choose represented.", + "Choose represented.", + "Add ordered occurrences.", + "Choose represented.", + "Choose represented.", + "Choose the exchange mechanism when its contract is known.", + "Allocate leaf 'OA Navigation B' to a performer.", + "Choose represented.", + "Choose represented.", + "Choose the exchange mechanism when its contract is known.", + "Choose sender.", + "Choose receiver.", + "Choose represented.", + "Choose represented.", + "Choose represented.", + "Allocate leaf 'SA Navigation A' to a performer.", + "Choose represented.", + "Choose sender.", + "Choose receiver.", + "Allocate leaf 'OA Navigation A' to a performer.", + "Add ordered occurrences." + ], + "commands": [] + }, + "reviews": [ + { + "source": "9a78b793-a987-438e-a041-233dc41f2b10", + "target": "7d9832e5-0288-4d04-a5df-1551d050f79e", + "facets": [ + { + "selector": { + "Header": "Name" + }, + "current": { + "Header": { + "Name": { + "oa": { + "Value": "OA Rendezvous use" + }, + "sa": { + "Value": "SA Rendezvous use" + } + } + } + }, + "drift": "Unreviewed" + }, + { + "selector": { + "Header": "Description" + }, + "current": { + "Header": { + "Description": { + "oa": { + "Value": "" + }, + "sa": { + "Value": "" + } + } + } + }, + "drift": "Unreviewed" + }, + { + "selector": { + "Header": "Parent" + }, + "current": { + "Header": { + "Parent": { + "oa": { + "Value": "fd6e0287-66e0-4e84-8a92-98c4c167cb88" + }, + "sa": { + "Value": "06ec35b3-ce60-43a7-8300-c239fa91c898" + } + } + } + }, + "drift": "Unreviewed" + }, + { + "selector": { + "Occurrence": "Constructor" + }, + "current": { + "Occurrence": { + "Constructor": { + "oa": { + "Value": "ScenarioUse" + }, + "sa": { + "Value": "ScenarioUse" + } + } + } + }, + "drift": "Unreviewed" + }, + { + "selector": { + "Occurrence": "Precondition" + }, + "current": { + "Occurrence": { + "Precondition": { + "oa": { + "Value": "" + }, + "sa": { + "Value": "" + } + } + } + }, + "drift": "Unreviewed" + }, + { + "selector": { + "Occurrence": "Postcondition" + }, + "current": { + "Occurrence": { + "Postcondition": { + "oa": { + "Value": "" + }, + "sa": { + "Value": "" + } + } + } + }, + "drift": "Unreviewed" + }, + { + "selector": { + "Occurrence": "Scenario" + }, + "current": { + "Occurrence": { + "Scenario": { + "oa": { + "Value": "eacfc6df-4e22-4ebd-a90d-6b5146e56513" + }, + "sa": { + "Value": "12143ec8-bcf9-4755-809c-87b653465888" + } + } + } + }, + "drift": "Unreviewed" + }, + { + "selector": { + "Binding": { + "oa": null, + "sa": "82798865-193a-46b0-b40d-b22ae91eba28" + } + }, + "current": { + "Binding": { + "keys": { + "oa": null, + "sa": "82798865-193a-46b0-b40d-b22ae91eba28" + }, + "review": { + "oa": { + "Value": null + }, + "sa": { + "Value": { + "referenced": "82798865-193a-46b0-b40d-b22ae91eba28", + "local": "a8518771-fa4d-4e6e-bb6a-9e5f719f874a" + } + } + } + } + }, + "drift": "Unreviewed" + }, + { + "selector": { + "Binding": { + "oa": "0e3d7b11-4c0f-41a0-beae-a5440e30782f", + "sa": "1cb0eb67-6fa7-414c-a2aa-155cb39da564" + } + }, + "current": { + "Binding": { + "keys": { + "oa": "0e3d7b11-4c0f-41a0-beae-a5440e30782f", + "sa": "1cb0eb67-6fa7-414c-a2aa-155cb39da564" + }, + "review": { + "oa": { + "Value": { + "referenced": "0e3d7b11-4c0f-41a0-beae-a5440e30782f", + "local": "424d78d2-9145-4b34-a18f-80a335164a9b" + } + }, + "sa": { + "Value": { + "referenced": "1cb0eb67-6fa7-414c-a2aa-155cb39da564", + "local": "0ba127bc-cd2d-4e53-b76e-9c01025a7c3f" + } + } + } + } + }, + "drift": "Unchanged" + }, + { + "selector": { + "Binding": { + "oa": "9e60984f-9ca3-4f3d-bc3f-b6b973cdc698", + "sa": null + } + }, + "current": { + "Binding": { + "keys": { + "oa": "9e60984f-9ca3-4f3d-bc3f-b6b973cdc698", + "sa": null + }, + "review": { + "oa": { + "Value": { + "referenced": "9e60984f-9ca3-4f3d-bc3f-b6b973cdc698", + "local": "1f697a2c-8455-48f1-bbdf-bdf657fe8fc4" + } + }, + "sa": { + "Value": null + } + } + } + }, + "drift": "Unreviewed" + } + ], + "acknowledged": [ + { + "Binding": { + "oa": "0e3d7b11-4c0f-41a0-beae-a5440e30782f", + "sa": "1cb0eb67-6fa7-414c-a2aa-155cb39da564" + } + } + ], + "retired": [], + "linked_impacts": [ + { + "objects": { + "oa": "0e3d7b11-4c0f-41a0-beae-a5440e30782f", + "sa": "1cb0eb67-6fa7-414c-a2aa-155cb39da564" + }, + "traces": [], + "drift": "Unreviewed", + "unreviewed": true + }, + { + "objects": { + "oa": "1f697a2c-8455-48f1-bbdf-bdf657fe8fc4", + "sa": null + }, + "traces": [], + "drift": "Unreviewed", + "unreviewed": true + }, + { + "objects": { + "oa": "424d78d2-9145-4b34-a18f-80a335164a9b", + "sa": null + }, + "traces": [], + "drift": "Unreviewed", + "unreviewed": true + }, + { + "objects": { + "oa": "9e60984f-9ca3-4f3d-bc3f-b6b973cdc698", + "sa": null + }, + "traces": [], + "drift": "Unreviewed", + "unreviewed": true + }, + { + "objects": { + "oa": "eacfc6df-4e22-4ebd-a90d-6b5146e56513", + "sa": null + }, + "traces": [], + "drift": "Unreviewed", + "unreviewed": true + }, + { + "objects": { + "oa": null, + "sa": "0ba127bc-cd2d-4e53-b76e-9c01025a7c3f" + }, + "traces": [], + "drift": "Unreviewed", + "unreviewed": true + }, + { + "objects": { + "oa": null, + "sa": "12143ec8-bcf9-4755-809c-87b653465888" + }, + "traces": [], + "drift": "Unreviewed", + "unreviewed": true + }, + { + "objects": { + "oa": null, + "sa": "82798865-193a-46b0-b40d-b22ae91eba28" + }, + "traces": [], + "drift": "Unreviewed", + "unreviewed": true + }, + { + "objects": { + "oa": null, + "sa": "a8518771-fa4d-4e6e-bb6a-9e5f719f874a" + }, + "traces": [], + "drift": "Unreviewed", + "unreviewed": true + } + ] + } + ], + "impacts": [], + "conflicts": [], + "commands": [ + { + "EditElement": [ + "7d9832e5-0288-4d04-a5df-1551d050f79e", + { + "id": "7d9832e5-0288-4d04-a5df-1551d050f79e", + "alias": "sa_rendezvous_use", + "name": "SA Rendezvous use", + "description": "", + "parent": "06ec35b3-ce60-43a7-8300-c239fa91c898", + "body": { + "Sa": { + "Shared": { + "Occurrence": { + "body": { + "ScenarioUse": { + "scenario": "12143ec8-bcf9-4755-809c-87b653465888", + "bindings": [ + { + "referenced": "1cb0eb67-6fa7-414c-a2aa-155cb39da564", + "local": "bb0ad838-eb7c-4470-bf27-f3a21e69fdf8" + }, + { + "referenced": "82798865-193a-46b0-b40d-b22ae91eba28", + "local": "a8518771-fa4d-4e6e-bb6a-9e5f719f874a" + } + ] + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + } + ] + }, + { + "EditTrace": [ + "551c0dbc-ede6-42ef-ae60-2e0136ffc6e6", + { + "id": "551c0dbc-ede6-42ef-ae60-2e0136ffc6e6", + "alias": "rendezvous_binding_review", + "name": "Rendezvous participant binding review", + "description": "", + "oa": "9a78b793-a987-438e-a041-233dc41f2b10", + "sa": "7d9832e5-0288-4d04-a5df-1551d050f79e", + "kind": "Manual", + "baseline": { + "oa": { + "name": "OA Rendezvous use", + "description": "" + }, + "sa": { + "name": "SA Rendezvous use", + "description": "" + }, + "semantic": { + "facets": [ + { + "Binding": { + "keys": { + "oa": "0e3d7b11-4c0f-41a0-beae-a5440e30782f", + "sa": "1cb0eb67-6fa7-414c-a2aa-155cb39da564" + }, + "review": { + "oa": { + "Value": { + "referenced": "0e3d7b11-4c0f-41a0-beae-a5440e30782f", + "local": "424d78d2-9145-4b34-a18f-80a335164a9b" + } + }, + "sa": { + "Value": { + "referenced": "1cb0eb67-6fa7-414c-a2aa-155cb39da564", + "local": "bb0ad838-eb7c-4470-bf27-f3a21e69fdf8" + } + } + } + } + } + ] + } + } + } + ] + } + ], + "prepared_fingerprint": "4acba5da105c3f36" +} \ No newline at end of file diff --git a/harness/fixtures/release1-native/semantic-keyed.repair-request.json b/harness/fixtures/release1-native/semantic-keyed.repair-request.json new file mode 100644 index 00000000..941efcec --- /dev/null +++ b/harness/fixtures/release1-native/semantic-keyed.repair-request.json @@ -0,0 +1,80 @@ +{ + "transition": { + "direction": "OaToSa", + "sources": [ + "9a78b793-a987-438e-a041-233dc41f2b10" + ], + "scope": "SelectedOnly", + "mappings": [ + { + "source": "9a78b793-a987-438e-a041-233dc41f2b10", + "target": "7d9832e5-0288-4d04-a5df-1551d050f79e", + "target_kind": null + } + ], + "fields": [], + "resolutions": [], + "destination_diagram": null + }, + "traces": { + "9a78b793-a987-438e-a041-233dc41f2b10": "551c0dbc-ede6-42ef-ae60-2e0136ffc6e6" + }, + "references": { + "0e3d7b11-4c0f-41a0-beae-a5440e30782f": "1cb0eb67-6fa7-414c-a2aa-155cb39da564", + "9a78b793-a987-438e-a041-233dc41f2b10": "7d9832e5-0288-4d04-a5df-1551d050f79e" + }, + "reviews": [ + { + "source": "9a78b793-a987-438e-a041-233dc41f2b10", + "resolutions": [ + { + "facet": { + "Binding": { + "oa": "0e3d7b11-4c0f-41a0-beae-a5440e30782f", + "sa": "1cb0eb67-6fa7-414c-a2aa-155cb39da564" + } + }, + "decision": "Candidate" + } + ], + "reset": [] + } + ], + "candidates": [ + { + "Element": { + "id": "7d9832e5-0288-4d04-a5df-1551d050f79e", + "alias": "sa_rendezvous_use", + "name": "SA Rendezvous use", + "description": "", + "parent": "06ec35b3-ce60-43a7-8300-c239fa91c898", + "body": { + "Sa": { + "Shared": { + "Occurrence": { + "body": { + "ScenarioUse": { + "scenario": "12143ec8-bcf9-4755-809c-87b653465888", + "bindings": [ + { + "referenced": "1cb0eb67-6fa7-414c-a2aa-155cb39da564", + "local": "bb0ad838-eb7c-4470-bf27-f3a21e69fdf8" + }, + { + "referenced": "82798865-193a-46b0-b40d-b22ae91eba28", + "local": "a8518771-fa4d-4e6e-bb6a-9e5f719f874a" + } + ] + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + } + } + ], + "accepted_impacts": [] +} \ No newline at end of file diff --git a/harness/fixtures/release1-native/semantic-keyed.steps.md b/harness/fixtures/release1-native/semantic-keyed.steps.md new file mode 100644 index 00000000..0376311b --- /dev/null +++ b/harness/fixtures/release1-native/semantic-keyed.steps.md @@ -0,0 +1,66 @@ +# Semantic keyed native fixture + +Open semantic-keyed.arcadia. This ignored fixture has 36 elements, 2 relationships, +2 named manual traces and pre-recorded explicit paired Annotation/Binding facts. +The normal Traceability "Review semantics…" entry needs no trace creation. +No diagram is required. All files here are local QA artifacts. + +## 1. Take one qualified message value + +Select trace "Navigation origin latitude review" (alias navigation_latitude_review) +and open Review semantics. Direction: Operational → System. +Enable Show unchanged if the historical paired fact is hidden. +Select the Annotation whose OA qualified field is +"OA Navigation origin / OA Navigation latitude" and whose SA field is +"SA Navigation origin / SA Navigation latitude". The destination-path annotation +is a separate sibling and must remain unchanged. + +Click "Choose / change exact row identities…", retain the prefilled explicit +OA/SA keys, then "Use these addresses and review row". This deliberately installs +the typed path/field reference pairs; recorded history alone does not infer them. +Click "Take Operational". The System expression should become +`latitude_deg + 0.25` from `latitude_deg`. Additional changes: 0. +Apply once. The sibling expression stays `second independent` at its original +position. Compare the resulting accepted project with semantic-keyed.after-take.json +and exact single-file source with semantic-keyed.after-take.arcadia. +Undo restores semantic-keyed.json/source; Redo restores after-take. + +## 2. Reject and repair one binding Candidate + +Continue from after-take. Select trace "Rendezvous participant binding review" +(alias rendezvous_binding_review), Review semantics, Operational → System. +Enable Show unchanged if needed. Select the paired Binding keyed by +"OA Rendezvous referenced" / "SA Rendezvous referenced". +Click "Choose / change exact row identities…" and then +"Use these addresses and review row" to retain that explicit pair. +Click "Set final value…"; leave Include in destination collection checked. +Choose local lifeline "SA Rendezvous extra local". The sibling binding already +uses it, so preview must reject with: +`duplicate_lifeline_binding: Scenario-use lifeline bindings are one-to-one.` +Apply must stay disabled; source/project/history remain exactly after-take. +The chosen invalid local and presence draft must remain visible across tab changes. + +Repair the local picker to "SA Rendezvous alternate". Additional changes: 0. +Apply once. Only the selected row's local lifeline changes; the sibling binding +and row order remain intact. Compare accepted project/source with +semantic-keyed.after-repair.json / semantic-keyed.after-repair.arcadia. +One Undo restores after-take exactly; Redo restores after-repair. Save/reopen must +preserve the exact leading comment and all typed review history. + +## Validated core oracles + +`semantic-keyed.validation.txt`: all three projects pass core validation, +production source and JSON decode round trips; successful plans have zero impacts. +`semantic-keyed.take-request.json` and `semantic-keyed.take-plan.json`: direct core +Take oracle against initial project. +`semantic-keyed.invalid-request.json` and `semantic-keyed.invalid.txt`: rejected +Candidate against after-take; the accepted project is unchanged. +`semantic-keyed.repair-request.json` and `semantic-keyed.repair-plan.json`: repaired +Candidate against after-take. +`semantic-keyed.manifest.json`: exact trace, occurrence, field and lifeline IDs. + +Source oracles were generated through source_project::update_bundle, retaining +unmodified declarations and the fixture comment, rather than replacing the whole +file with a freshly formatted model. Generator source is generate-semantic-keyed.rs; +it reuses the production-tested r3_semantic payload/scenario-use constructors. +Standalone rustc output stays in this directory; Cargo and shared outputs untouched. diff --git a/harness/fixtures/release1-native/semantic-keyed.take-plan.json b/harness/fixtures/release1-native/semantic-keyed.take-plan.json new file mode 100644 index 00000000..44cc7590 --- /dev/null +++ b/harness/fixtures/release1-native/semantic-keyed.take-plan.json @@ -0,0 +1,670 @@ +{ + "base_fingerprint": "ab57364ff66893a6", + "request": { + "transition": { + "direction": "OaToSa", + "sources": [ + "73de257c-d816-4f91-b507-36f79ca49f08" + ], + "scope": "SelectedOnly", + "mappings": [ + { + "source": "73de257c-d816-4f91-b507-36f79ca49f08", + "target": "d10ef3c1-479f-4780-9fd5-3b4d21dffe74", + "target_kind": null + } + ], + "fields": [], + "resolutions": [], + "destination_diagram": null + }, + "traces": { + "73de257c-d816-4f91-b507-36f79ca49f08": "b647f858-1a69-4292-8710-674b1692d54f" + }, + "references": { + "01d025e3-ee88-4449-a613-217b44a61ae2": "ab1e449f-24f1-4bee-938c-b49510d19bf3", + "19be028f-96cd-440e-bbcb-e151fbe00b45": "0cf0ba32-01c8-41f5-a44c-ed31daa3ca92", + "73de257c-d816-4f91-b507-36f79ca49f08": "d10ef3c1-479f-4780-9fd5-3b4d21dffe74" + }, + "reviews": [ + { + "source": "73de257c-d816-4f91-b507-36f79ca49f08", + "resolutions": [ + { + "facet": { + "Annotation": { + "oa": { + "path": [ + "19be028f-96cd-440e-bbcb-e151fbe00b45" + ], + "field": "01d025e3-ee88-4449-a613-217b44a61ae2" + }, + "sa": { + "path": [ + "0cf0ba32-01c8-41f5-a44c-ed31daa3ca92" + ], + "field": "ab1e449f-24f1-4bee-938c-b49510d19bf3" + } + } + }, + "decision": "TakeSource" + } + ], + "reset": [] + } + ], + "candidates": [], + "accepted_impacts": [] + }, + "transition": { + "base_fingerprint": "ab57364ff66893a6", + "request": { + "direction": "OaToSa", + "sources": [ + "73de257c-d816-4f91-b507-36f79ca49f08" + ], + "scope": "SelectedOnly", + "mappings": [ + { + "source": "73de257c-d816-4f91-b507-36f79ca49f08", + "target": "d10ef3c1-479f-4780-9fd5-3b4d21dffe74", + "target_kind": null + } + ], + "fields": [], + "resolutions": [], + "destination_diagram": null + }, + "changes": [ + { + "source": "73de257c-d816-4f91-b507-36f79ca49f08", + "target": "d10ef3c1-479f-4780-9fd5-3b4d21dffe74", + "action": "Reuse", + "fields": [] + } + ], + "conflicts": [], + "warnings": [ + "Identify the SA system of interest.", + "Allocate leaf 'SA Navigation B' to a performer.", + "Choose represented.", + "Choose represented.", + "Add ordered occurrences.", + "Choose represented.", + "Choose represented.", + "Choose the exchange mechanism when its contract is known.", + "Allocate leaf 'OA Navigation B' to a performer.", + "Choose represented.", + "Choose represented.", + "Choose the exchange mechanism when its contract is known.", + "Choose sender.", + "Choose receiver.", + "Choose represented.", + "Choose represented.", + "Choose represented.", + "Allocate leaf 'SA Navigation A' to a performer.", + "Choose represented.", + "Choose sender.", + "Choose receiver.", + "Allocate leaf 'OA Navigation A' to a performer.", + "Add ordered occurrences." + ], + "commands": [] + }, + "reviews": [ + { + "source": "73de257c-d816-4f91-b507-36f79ca49f08", + "target": "d10ef3c1-479f-4780-9fd5-3b4d21dffe74", + "facets": [ + { + "selector": { + "Header": "Name" + }, + "current": { + "Header": { + "Name": { + "oa": { + "Value": "OA Navigation message" + }, + "sa": { + "Value": "SA Navigation message" + } + } + } + }, + "drift": "Unreviewed" + }, + { + "selector": { + "Header": "Description" + }, + "current": { + "Header": { + "Description": { + "oa": { + "Value": "" + }, + "sa": { + "Value": "" + } + } + } + }, + "drift": "Unreviewed" + }, + { + "selector": { + "Header": "Parent" + }, + "current": { + "Header": { + "Parent": { + "oa": { + "Value": "814c985a-a8c8-409a-b11b-c9572f29eabc" + }, + "sa": { + "Value": "409c78a0-28bc-4810-86ba-300204a3513f" + } + } + } + }, + "drift": "Unreviewed" + }, + { + "selector": { + "Occurrence": "Constructor" + }, + "current": { + "Occurrence": { + "Constructor": { + "oa": { + "Value": "Exchange" + }, + "sa": { + "Value": "Exchange" + } + } + } + }, + "drift": "Unreviewed" + }, + { + "selector": { + "Occurrence": "Precondition" + }, + "current": { + "Occurrence": { + "Precondition": { + "oa": { + "Value": "" + }, + "sa": { + "Value": "" + } + } + } + }, + "drift": "Unreviewed" + }, + { + "selector": { + "Occurrence": "Postcondition" + }, + "current": { + "Occurrence": { + "Postcondition": { + "oa": { + "Value": "" + }, + "sa": { + "Value": "" + } + } + } + }, + "drift": "Unreviewed" + }, + { + "selector": { + "Occurrence": "Exchange" + }, + "current": { + "Occurrence": { + "Exchange": { + "oa": { + "Value": "6a5930e2-0cfb-4aff-a4a4-8c4798f1cda5" + }, + "sa": { + "Value": "9c1085f2-c91d-4776-bb56-52c864a80e22" + } + } + } + }, + "drift": "Unreviewed" + }, + { + "selector": { + "Occurrence": "Sender" + }, + "current": { + "Occurrence": { + "Sender": { + "oa": { + "Value": null + }, + "sa": { + "Value": null + } + } + } + }, + "drift": "Unreviewed" + }, + { + "selector": { + "Occurrence": "Receiver" + }, + "current": { + "Occurrence": { + "Receiver": { + "oa": { + "Value": null + }, + "sa": { + "Value": null + } + } + } + }, + "drift": "Unreviewed" + }, + { + "selector": { + "Occurrence": "MessageKind" + }, + "current": { + "Occurrence": { + "MessageKind": { + "oa": { + "Value": "Asynchronous" + }, + "sa": { + "Value": "Asynchronous" + } + } + } + }, + "drift": "Unreviewed" + }, + { + "selector": { + "Occurrence": "ReplyTo" + }, + "current": { + "Occurrence": { + "ReplyTo": { + "oa": { + "Value": null + }, + "sa": { + "Value": null + } + } + } + }, + "drift": "Unreviewed" + }, + { + "selector": { + "Annotation": { + "oa": null, + "sa": { + "path": [ + "8dbaedfd-4a4a-4e5d-a9f9-d040d5f144d8" + ], + "field": "ab1e449f-24f1-4bee-938c-b49510d19bf3" + } + } + }, + "current": { + "Annotation": { + "keys": { + "oa": null, + "sa": { + "path": [ + "8dbaedfd-4a4a-4e5d-a9f9-d040d5f144d8" + ], + "field": "ab1e449f-24f1-4bee-938c-b49510d19bf3" + } + }, + "review": { + "oa": { + "Value": null + }, + "sa": { + "Value": { + "path": [ + "8dbaedfd-4a4a-4e5d-a9f9-d040d5f144d8" + ], + "field": "ab1e449f-24f1-4bee-938c-b49510d19bf3", + "expression": "second independent" + } + } + } + } + }, + "drift": "Unreviewed" + }, + { + "selector": { + "Annotation": { + "oa": { + "path": [ + "02a82c95-f030-4be3-9ffe-6ebccf40fcb1" + ], + "field": "01d025e3-ee88-4449-a613-217b44a61ae2" + }, + "sa": null + } + }, + "current": { + "Annotation": { + "keys": { + "oa": { + "path": [ + "02a82c95-f030-4be3-9ffe-6ebccf40fcb1" + ], + "field": "01d025e3-ee88-4449-a613-217b44a61ae2" + }, + "sa": null + }, + "review": { + "oa": { + "Value": { + "path": [ + "02a82c95-f030-4be3-9ffe-6ebccf40fcb1" + ], + "field": "01d025e3-ee88-4449-a613-217b44a61ae2", + "expression": "second independent" + } + }, + "sa": { + "Value": null + } + } + } + }, + "drift": "Unreviewed" + }, + { + "selector": { + "Annotation": { + "oa": { + "path": [ + "19be028f-96cd-440e-bbcb-e151fbe00b45" + ], + "field": "01d025e3-ee88-4449-a613-217b44a61ae2" + }, + "sa": { + "path": [ + "0cf0ba32-01c8-41f5-a44c-ed31daa3ca92" + ], + "field": "ab1e449f-24f1-4bee-938c-b49510d19bf3" + } + } + }, + "current": { + "Annotation": { + "keys": { + "oa": { + "path": [ + "19be028f-96cd-440e-bbcb-e151fbe00b45" + ], + "field": "01d025e3-ee88-4449-a613-217b44a61ae2" + }, + "sa": { + "path": [ + "0cf0ba32-01c8-41f5-a44c-ed31daa3ca92" + ], + "field": "ab1e449f-24f1-4bee-938c-b49510d19bf3" + } + }, + "review": { + "oa": { + "Value": { + "path": [ + "19be028f-96cd-440e-bbcb-e151fbe00b45" + ], + "field": "01d025e3-ee88-4449-a613-217b44a61ae2", + "expression": "latitude_deg + 0.25" + } + }, + "sa": { + "Value": { + "path": [ + "0cf0ba32-01c8-41f5-a44c-ed31daa3ca92" + ], + "field": "ab1e449f-24f1-4bee-938c-b49510d19bf3", + "expression": "latitude_deg" + } + } + } + } + }, + "drift": "Unchanged" + } + ], + "acknowledged": [ + { + "Annotation": { + "oa": { + "path": [ + "19be028f-96cd-440e-bbcb-e151fbe00b45" + ], + "field": "01d025e3-ee88-4449-a613-217b44a61ae2" + }, + "sa": { + "path": [ + "0cf0ba32-01c8-41f5-a44c-ed31daa3ca92" + ], + "field": "ab1e449f-24f1-4bee-938c-b49510d19bf3" + } + } + } + ], + "retired": [], + "linked_impacts": [ + { + "objects": { + "oa": "01d025e3-ee88-4449-a613-217b44a61ae2", + "sa": "ab1e449f-24f1-4bee-938c-b49510d19bf3" + }, + "traces": [], + "drift": "Unreviewed", + "unreviewed": true + }, + { + "objects": { + "oa": "02a82c95-f030-4be3-9ffe-6ebccf40fcb1", + "sa": null + }, + "traces": [], + "drift": "Unreviewed", + "unreviewed": true + }, + { + "objects": { + "oa": "19be028f-96cd-440e-bbcb-e151fbe00b45", + "sa": "0cf0ba32-01c8-41f5-a44c-ed31daa3ca92" + }, + "traces": [], + "drift": "Unreviewed", + "unreviewed": true + }, + { + "objects": { + "oa": "6a5930e2-0cfb-4aff-a4a4-8c4798f1cda5", + "sa": null + }, + "traces": [], + "drift": "Unreviewed", + "unreviewed": true + }, + { + "objects": { + "oa": "85eee860-b8ee-4c14-8551-ec4ce8b84aa8", + "sa": null + }, + "traces": [], + "drift": "Unreviewed", + "unreviewed": true + }, + { + "objects": { + "oa": null, + "sa": "715228b2-a573-47ad-bf53-0a9b968ded96" + }, + "traces": [], + "drift": "Unreviewed", + "unreviewed": true + }, + { + "objects": { + "oa": null, + "sa": "8dbaedfd-4a4a-4e5d-a9f9-d040d5f144d8" + }, + "traces": [], + "drift": "Unreviewed", + "unreviewed": true + }, + { + "objects": { + "oa": null, + "sa": "9c1085f2-c91d-4776-bb56-52c864a80e22" + }, + "traces": [], + "drift": "Unreviewed", + "unreviewed": true + } + ] + } + ], + "impacts": [], + "conflicts": [], + "commands": [ + { + "EditElement": [ + "d10ef3c1-479f-4780-9fd5-3b4d21dffe74", + { + "id": "d10ef3c1-479f-4780-9fd5-3b4d21dffe74", + "alias": "sa_navigation_message", + "name": "SA Navigation message", + "description": "", + "parent": "409c78a0-28bc-4810-86ba-300204a3513f", + "body": { + "Sa": { + "Shared": { + "Occurrence": { + "body": { + "Exchange": { + "exchange": "9c1085f2-c91d-4776-bb56-52c864a80e22", + "sender": null, + "receiver": null, + "message_kind": "Asynchronous", + "reply_to": null, + "values": [ + { + "path": [ + "0cf0ba32-01c8-41f5-a44c-ed31daa3ca92" + ], + "field": "ab1e449f-24f1-4bee-938c-b49510d19bf3", + "expression": "latitude_deg + 0.25" + }, + { + "path": [ + "8dbaedfd-4a4a-4e5d-a9f9-d040d5f144d8" + ], + "field": "ab1e449f-24f1-4bee-938c-b49510d19bf3", + "expression": "second independent" + } + ] + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + } + ] + }, + { + "EditTrace": [ + "b647f858-1a69-4292-8710-674b1692d54f", + { + "id": "b647f858-1a69-4292-8710-674b1692d54f", + "alias": "navigation_latitude_review", + "name": "Navigation origin latitude review", + "description": "", + "oa": "73de257c-d816-4f91-b507-36f79ca49f08", + "sa": "d10ef3c1-479f-4780-9fd5-3b4d21dffe74", + "kind": "Manual", + "baseline": { + "oa": { + "name": "OA Navigation message", + "description": "" + }, + "sa": { + "name": "SA Navigation message", + "description": "" + }, + "semantic": { + "facets": [ + { + "Annotation": { + "keys": { + "oa": { + "path": [ + "19be028f-96cd-440e-bbcb-e151fbe00b45" + ], + "field": "01d025e3-ee88-4449-a613-217b44a61ae2" + }, + "sa": { + "path": [ + "0cf0ba32-01c8-41f5-a44c-ed31daa3ca92" + ], + "field": "ab1e449f-24f1-4bee-938c-b49510d19bf3" + } + }, + "review": { + "oa": { + "Value": { + "path": [ + "19be028f-96cd-440e-bbcb-e151fbe00b45" + ], + "field": "01d025e3-ee88-4449-a613-217b44a61ae2", + "expression": "latitude_deg + 0.25" + } + }, + "sa": { + "Value": { + "path": [ + "0cf0ba32-01c8-41f5-a44c-ed31daa3ca92" + ], + "field": "ab1e449f-24f1-4bee-938c-b49510d19bf3", + "expression": "latitude_deg + 0.25" + } + } + } + } + } + ] + } + } + } + ] + } + ], + "prepared_fingerprint": "793cea8b7ddff23e" +} \ No newline at end of file diff --git a/harness/fixtures/release1-native/semantic-keyed.take-request.json b/harness/fixtures/release1-native/semantic-keyed.take-request.json new file mode 100644 index 00000000..782b499c --- /dev/null +++ b/harness/fixtures/release1-native/semantic-keyed.take-request.json @@ -0,0 +1,56 @@ +{ + "transition": { + "direction": "OaToSa", + "sources": [ + "73de257c-d816-4f91-b507-36f79ca49f08" + ], + "scope": "SelectedOnly", + "mappings": [ + { + "source": "73de257c-d816-4f91-b507-36f79ca49f08", + "target": "d10ef3c1-479f-4780-9fd5-3b4d21dffe74", + "target_kind": null + } + ], + "fields": [], + "resolutions": [], + "destination_diagram": null + }, + "traces": { + "73de257c-d816-4f91-b507-36f79ca49f08": "b647f858-1a69-4292-8710-674b1692d54f" + }, + "references": { + "01d025e3-ee88-4449-a613-217b44a61ae2": "ab1e449f-24f1-4bee-938c-b49510d19bf3", + "19be028f-96cd-440e-bbcb-e151fbe00b45": "0cf0ba32-01c8-41f5-a44c-ed31daa3ca92", + "73de257c-d816-4f91-b507-36f79ca49f08": "d10ef3c1-479f-4780-9fd5-3b4d21dffe74" + }, + "reviews": [ + { + "source": "73de257c-d816-4f91-b507-36f79ca49f08", + "resolutions": [ + { + "facet": { + "Annotation": { + "oa": { + "path": [ + "19be028f-96cd-440e-bbcb-e151fbe00b45" + ], + "field": "01d025e3-ee88-4449-a613-217b44a61ae2" + }, + "sa": { + "path": [ + "0cf0ba32-01c8-41f5-a44c-ed31daa3ca92" + ], + "field": "ab1e449f-24f1-4bee-938c-b49510d19bf3" + } + } + }, + "decision": "TakeSource" + } + ], + "reset": [] + } + ], + "candidates": [], + "accepted_impacts": [] +} \ No newline at end of file diff --git a/harness/fixtures/release1-native/semantic-ordered-branch-steps.after-order.arcadia b/harness/fixtures/release1-native/semantic-ordered-branch-steps.after-order.arcadia new file mode 100644 index 00000000..953e590a --- /dev/null +++ b/harness/fixtures/release1-native/semantic-ordered-branch-steps.after-order.arcadia @@ -0,0 +1,231 @@ +// Preserve the authored startup order rationale. +arcadia 5; +project mission "Startup branch steps order review" id "6d0b8162-c0bf-430f-b3f9-aa5396afee7d" { + description ""; + + sa_occurrence local_self_test_1d12e648 "SA Local self-test" id "1d12e648-66ba-4ce4-93fe-332792ecf67a" { + body { + Action { + action none; + lifeline none; + }; + }; + description ""; + parent @start_permitted_3342190a; + postcondition ""; + precondition ""; + } + + sa_occurrence verify_interlock_1e11083d "SA Verify interlock" id "1e11083d-6936-4730-b013-f2d8a827227d" { + body { + Action { + action none; + lifeline none; + }; + }; + description ""; + parent @start_permitted_3342190a; + postcondition ""; + precondition ""; + } + + sa_occurrence start_permitted_3342190a "SA Start permitted" id "3342190a-03fc-4f3c-b831-80b0f18a0bad" { + body { + Branch { + guard "permitted"; + occurrences [@request_start_e658f0ca, @local_self_test_1d12e648, @verify_interlock_1e11083d, @energize_d9d631f7]; + }; + }; + description ""; + parent @alternatives_601c0433; + postcondition ""; + precondition ""; + } + + sa_scenario startup_scenario_3eb4e8df "SA Startup scenario" id "3eb4e8df-a13a-492a-ab6f-1b36fb912d91" { + context none; + description ""; + parent none; + postcondition ""; + precondition ""; + steps [@alternatives_601c0433]; + viewpoint Functional; + } + + oa_occurrence alternatives "OA Alternatives" id "4c3d0157-6569-4c0a-93a2-b2467b00075a" { + body { + Fragment { + branches [@start_permitted]; + operator Alternative; + }; + }; + description ""; + parent @startup_scenario; + postcondition ""; + precondition ""; + } + + sa_occurrence alternatives_601c0433 "SA Alternatives" id "601c0433-8608-4ecf-b6bd-08179a8e16d5" { + body { + Fragment { + branches [@start_permitted_3342190a]; + operator Alternative; + }; + }; + description ""; + parent @startup_scenario_3eb4e8df; + postcondition ""; + precondition ""; + } + + oa_occurrence request_start "OA Request start" id "6e51da84-0808-46a2-863b-048083889f7a" { + body { + Action { + action none; + lifeline none; + }; + }; + description ""; + parent @start_permitted; + postcondition ""; + precondition ""; + } + + oa_occurrence energize "OA Energize" id "98a650aa-6d43-4d62-bc2f-45c9c2cc2f59" { + body { + Action { + action none; + lifeline none; + }; + }; + description ""; + parent @start_permitted; + postcondition ""; + precondition ""; + } + + oa_occurrence verify_interlock "OA Verify interlock" id "ac518f32-e9c2-4a6e-9afd-da1955ee7ea9" { + body { + Action { + action none; + lifeline none; + }; + }; + description ""; + parent @start_permitted; + postcondition ""; + precondition ""; + } + + oa_scenario startup_scenario "OA Startup scenario" id "b452dfca-e14e-46c6-ad23-4e1cdbc3a976" { + context none; + description ""; + parent none; + postcondition ""; + precondition ""; + steps [@alternatives]; + viewpoint OperationalActivity; + } + + oa_occurrence local_self_test "OA Local self-test" id "ba7d497c-bc43-42af-b77b-19d7b7be5f68" { + body { + Action { + action none; + lifeline none; + }; + }; + description ""; + parent @start_permitted; + postcondition ""; + precondition ""; + } + + sa_occurrence energize_d9d631f7 "SA Energize" id "d9d631f7-5f2c-47f7-a130-4151fd27dbfd" { + body { + Action { + action none; + lifeline none; + }; + }; + description ""; + parent @start_permitted_3342190a; + postcondition ""; + precondition ""; + } + + sa_occurrence request_start_e658f0ca "SA Request start" id "e658f0ca-014e-4043-980e-19a663b7ceea" { + body { + Action { + action none; + lifeline none; + }; + }; + description ""; + parent @start_permitted_3342190a; + postcondition ""; + precondition ""; + } + + oa_occurrence start_permitted "OA Start permitted" id "f6e150d3-d971-4572-a66d-9efe2beb2896" { + body { + Branch { + guard "permitted"; + occurrences [@request_start, @verify_interlock, @energize]; + }; + }; + description ""; + parent @alternatives; + postcondition ""; + precondition ""; + } + + trace startup_order_review "Startup branch steps order review" id "46195e23-864f-4df0-8e27-79da910fafd2" { + baseline { + oa { + description ""; + name "OA Start permitted"; + }; + sa { + description ""; + name "SA Start permitted"; + }; + semantic { + facets [{ + Member { + collection Steps; + keys { + oa uuid "ac518f32-e9c2-4a6e-9afd-da1955ee7ea9"; + sa uuid "1e11083d-6936-4730-b013-f2d8a827227d"; + }; + review { + oa { + Value true; + }; + sa { + Value true; + }; + }; + }; + }, + { + Order { + collection Steps; + review { + oa { + Value [uuid "6e51da84-0808-46a2-863b-048083889f7a", uuid "ac518f32-e9c2-4a6e-9afd-da1955ee7ea9", uuid "98a650aa-6d43-4d62-bc2f-45c9c2cc2f59"]; + }; + sa { + Value [uuid "e658f0ca-014e-4043-980e-19a663b7ceea", uuid "1d12e648-66ba-4ce4-93fe-332792ecf67a", uuid "1e11083d-6936-4730-b013-f2d8a827227d", uuid "d9d631f7-5f2c-47f7-a130-4151fd27dbfd"]; + }; + }; + }; + }]; + }; + }; + description ""; + kind Manual; + oa @start_permitted; + sa @start_permitted_3342190a; + } + +} diff --git a/harness/fixtures/release1-native/semantic-ordered-branch-steps.after-order.json b/harness/fixtures/release1-native/semantic-ordered-branch-steps.after-order.json new file mode 100644 index 00000000..ec5c0802 --- /dev/null +++ b/harness/fixtures/release1-native/semantic-ordered-branch-steps.after-order.json @@ -0,0 +1,409 @@ +{ + "schema_version": 5, + "id": "6d0b8162-c0bf-430f-b3f9-aa5396afee7d", + "alias": "mission", + "name": "Startup branch steps order review", + "description": "", + "elements": { + "1d12e648-66ba-4ce4-93fe-332792ecf67a": { + "id": "1d12e648-66ba-4ce4-93fe-332792ecf67a", + "alias": "local_self_test_1d12e648", + "name": "SA Local self-test", + "description": "", + "parent": "3342190a-03fc-4f3c-b831-80b0f18a0bad", + "body": { + "Sa": { + "Shared": { + "Occurrence": { + "body": { + "Action": { + "action": null, + "lifeline": null + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + }, + "1e11083d-6936-4730-b013-f2d8a827227d": { + "id": "1e11083d-6936-4730-b013-f2d8a827227d", + "alias": "verify_interlock_1e11083d", + "name": "SA Verify interlock", + "description": "", + "parent": "3342190a-03fc-4f3c-b831-80b0f18a0bad", + "body": { + "Sa": { + "Shared": { + "Occurrence": { + "body": { + "Action": { + "action": null, + "lifeline": null + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + }, + "3342190a-03fc-4f3c-b831-80b0f18a0bad": { + "id": "3342190a-03fc-4f3c-b831-80b0f18a0bad", + "alias": "start_permitted_3342190a", + "name": "SA Start permitted", + "description": "", + "parent": "601c0433-8608-4ecf-b6bd-08179a8e16d5", + "body": { + "Sa": { + "Shared": { + "Occurrence": { + "body": { + "Branch": { + "guard": "permitted", + "occurrences": [ + "e658f0ca-014e-4043-980e-19a663b7ceea", + "1d12e648-66ba-4ce4-93fe-332792ecf67a", + "1e11083d-6936-4730-b013-f2d8a827227d", + "d9d631f7-5f2c-47f7-a130-4151fd27dbfd" + ] + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + }, + "3eb4e8df-a13a-492a-ab6f-1b36fb912d91": { + "id": "3eb4e8df-a13a-492a-ab6f-1b36fb912d91", + "alias": "startup_scenario_3eb4e8df", + "name": "SA Startup scenario", + "description": "", + "parent": null, + "body": { + "Sa": { + "Shared": { + "Scenario": { + "viewpoint": "Functional", + "context": null, + "precondition": "", + "postcondition": "", + "steps": [ + "601c0433-8608-4ecf-b6bd-08179a8e16d5" + ] + } + } + } + } + }, + "4c3d0157-6569-4c0a-93a2-b2467b00075a": { + "id": "4c3d0157-6569-4c0a-93a2-b2467b00075a", + "alias": "alternatives", + "name": "OA Alternatives", + "description": "", + "parent": "b452dfca-e14e-46c6-ad23-4e1cdbc3a976", + "body": { + "Oa": { + "Shared": { + "Occurrence": { + "body": { + "Fragment": { + "operator": "Alternative", + "branches": [ + "f6e150d3-d971-4572-a66d-9efe2beb2896" + ] + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + }, + "601c0433-8608-4ecf-b6bd-08179a8e16d5": { + "id": "601c0433-8608-4ecf-b6bd-08179a8e16d5", + "alias": "alternatives_601c0433", + "name": "SA Alternatives", + "description": "", + "parent": "3eb4e8df-a13a-492a-ab6f-1b36fb912d91", + "body": { + "Sa": { + "Shared": { + "Occurrence": { + "body": { + "Fragment": { + "operator": "Alternative", + "branches": [ + "3342190a-03fc-4f3c-b831-80b0f18a0bad" + ] + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + }, + "6e51da84-0808-46a2-863b-048083889f7a": { + "id": "6e51da84-0808-46a2-863b-048083889f7a", + "alias": "request_start", + "name": "OA Request start", + "description": "", + "parent": "f6e150d3-d971-4572-a66d-9efe2beb2896", + "body": { + "Oa": { + "Shared": { + "Occurrence": { + "body": { + "Action": { + "action": null, + "lifeline": null + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + }, + "98a650aa-6d43-4d62-bc2f-45c9c2cc2f59": { + "id": "98a650aa-6d43-4d62-bc2f-45c9c2cc2f59", + "alias": "energize", + "name": "OA Energize", + "description": "", + "parent": "f6e150d3-d971-4572-a66d-9efe2beb2896", + "body": { + "Oa": { + "Shared": { + "Occurrence": { + "body": { + "Action": { + "action": null, + "lifeline": null + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + }, + "ac518f32-e9c2-4a6e-9afd-da1955ee7ea9": { + "id": "ac518f32-e9c2-4a6e-9afd-da1955ee7ea9", + "alias": "verify_interlock", + "name": "OA Verify interlock", + "description": "", + "parent": "f6e150d3-d971-4572-a66d-9efe2beb2896", + "body": { + "Oa": { + "Shared": { + "Occurrence": { + "body": { + "Action": { + "action": null, + "lifeline": null + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + }, + "b452dfca-e14e-46c6-ad23-4e1cdbc3a976": { + "id": "b452dfca-e14e-46c6-ad23-4e1cdbc3a976", + "alias": "startup_scenario", + "name": "OA Startup scenario", + "description": "", + "parent": null, + "body": { + "Oa": { + "Shared": { + "Scenario": { + "viewpoint": "OperationalActivity", + "context": null, + "precondition": "", + "postcondition": "", + "steps": [ + "4c3d0157-6569-4c0a-93a2-b2467b00075a" + ] + } + } + } + } + }, + "ba7d497c-bc43-42af-b77b-19d7b7be5f68": { + "id": "ba7d497c-bc43-42af-b77b-19d7b7be5f68", + "alias": "local_self_test", + "name": "OA Local self-test", + "description": "", + "parent": "f6e150d3-d971-4572-a66d-9efe2beb2896", + "body": { + "Oa": { + "Shared": { + "Occurrence": { + "body": { + "Action": { + "action": null, + "lifeline": null + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + }, + "d9d631f7-5f2c-47f7-a130-4151fd27dbfd": { + "id": "d9d631f7-5f2c-47f7-a130-4151fd27dbfd", + "alias": "energize_d9d631f7", + "name": "SA Energize", + "description": "", + "parent": "3342190a-03fc-4f3c-b831-80b0f18a0bad", + "body": { + "Sa": { + "Shared": { + "Occurrence": { + "body": { + "Action": { + "action": null, + "lifeline": null + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + }, + "e658f0ca-014e-4043-980e-19a663b7ceea": { + "id": "e658f0ca-014e-4043-980e-19a663b7ceea", + "alias": "request_start_e658f0ca", + "name": "SA Request start", + "description": "", + "parent": "3342190a-03fc-4f3c-b831-80b0f18a0bad", + "body": { + "Sa": { + "Shared": { + "Occurrence": { + "body": { + "Action": { + "action": null, + "lifeline": null + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + }, + "f6e150d3-d971-4572-a66d-9efe2beb2896": { + "id": "f6e150d3-d971-4572-a66d-9efe2beb2896", + "alias": "start_permitted", + "name": "OA Start permitted", + "description": "", + "parent": "4c3d0157-6569-4c0a-93a2-b2467b00075a", + "body": { + "Oa": { + "Shared": { + "Occurrence": { + "body": { + "Branch": { + "guard": "permitted", + "occurrences": [ + "6e51da84-0808-46a2-863b-048083889f7a", + "ac518f32-e9c2-4a6e-9afd-da1955ee7ea9", + "98a650aa-6d43-4d62-bc2f-45c9c2cc2f59" + ] + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + } + }, + "relations": {}, + "diagrams": {}, + "artifact_links": {}, + "traces": { + "46195e23-864f-4df0-8e27-79da910fafd2": { + "id": "46195e23-864f-4df0-8e27-79da910fafd2", + "alias": "startup_order_review", + "name": "Startup branch steps order review", + "description": "", + "oa": "f6e150d3-d971-4572-a66d-9efe2beb2896", + "sa": "3342190a-03fc-4f3c-b831-80b0f18a0bad", + "kind": "Manual", + "baseline": { + "oa": { + "name": "OA Start permitted", + "description": "" + }, + "sa": { + "name": "SA Start permitted", + "description": "" + }, + "semantic": { + "facets": [ + { + "Member": { + "collection": "Steps", + "keys": { + "oa": "ac518f32-e9c2-4a6e-9afd-da1955ee7ea9", + "sa": "1e11083d-6936-4730-b013-f2d8a827227d" + }, + "review": { + "oa": { + "Value": true + }, + "sa": { + "Value": true + } + } + } + }, + { + "Order": { + "collection": "Steps", + "review": { + "oa": { + "Value": [ + "6e51da84-0808-46a2-863b-048083889f7a", + "ac518f32-e9c2-4a6e-9afd-da1955ee7ea9", + "98a650aa-6d43-4d62-bc2f-45c9c2cc2f59" + ] + }, + "sa": { + "Value": [ + "e658f0ca-014e-4043-980e-19a663b7ceea", + "1d12e648-66ba-4ce4-93fe-332792ecf67a", + "1e11083d-6936-4730-b013-f2d8a827227d", + "d9d631f7-5f2c-47f7-a130-4151fd27dbfd" + ] + } + } + } + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/harness/fixtures/release1-native/semantic-ordered-branch-steps.ambiguous-request.json b/harness/fixtures/release1-native/semantic-ordered-branch-steps.ambiguous-request.json new file mode 100644 index 00000000..09e76412 --- /dev/null +++ b/harness/fixtures/release1-native/semantic-ordered-branch-steps.ambiguous-request.json @@ -0,0 +1,50 @@ +{ + "transition": { + "direction": "OaToSa", + "sources": [ + "f6e150d3-d971-4572-a66d-9efe2beb2896" + ], + "scope": "SelectedOnly", + "mappings": [ + { + "source": "f6e150d3-d971-4572-a66d-9efe2beb2896", + "target": "3342190a-03fc-4f3c-b831-80b0f18a0bad", + "target_kind": null + } + ], + "fields": [], + "resolutions": [], + "destination_diagram": null + }, + "traces": { + "f6e150d3-d971-4572-a66d-9efe2beb2896": "46195e23-864f-4df0-8e27-79da910fafd2" + }, + "references": { + "6e51da84-0808-46a2-863b-048083889f7a": "e658f0ca-014e-4043-980e-19a663b7ceea", + "98a650aa-6d43-4d62-bc2f-45c9c2cc2f59": "d9d631f7-5f2c-47f7-a130-4151fd27dbfd", + "ac518f32-e9c2-4a6e-9afd-da1955ee7ea9": "1e11083d-6936-4730-b013-f2d8a827227d", + "f6e150d3-d971-4572-a66d-9efe2beb2896": "3342190a-03fc-4f3c-b831-80b0f18a0bad" + }, + "reviews": [ + { + "source": "f6e150d3-d971-4572-a66d-9efe2beb2896", + "resolutions": [ + { + "facet": { + "Member": { + "collection": "Steps", + "keys": { + "oa": "ac518f32-e9c2-4a6e-9afd-da1955ee7ea9", + "sa": "1e11083d-6936-4730-b013-f2d8a827227d" + } + } + }, + "decision": "TakeSource" + } + ], + "reset": [] + } + ], + "candidates": [], + "accepted_impacts": [] +} \ No newline at end of file diff --git a/harness/fixtures/release1-native/semantic-ordered-branch-steps.ambiguous.txt b/harness/fixtures/release1-native/semantic-ordered-branch-steps.ambiguous.txt new file mode 100644 index 00000000..18fc5884 --- /dev/null +++ b/harness/fixtures/release1-native/semantic-ordered-branch-steps.ambiguous.txt @@ -0,0 +1 @@ +Ordered member insertion has no unique unchanged anchor; review an explicit final target order \ No newline at end of file diff --git a/harness/fixtures/release1-native/semantic-ordered-branch-steps.arcadia b/harness/fixtures/release1-native/semantic-ordered-branch-steps.arcadia new file mode 100644 index 00000000..8ad0b474 --- /dev/null +++ b/harness/fixtures/release1-native/semantic-ordered-branch-steps.arcadia @@ -0,0 +1,218 @@ +// Preserve the authored startup order rationale. +arcadia 5; +project mission "Startup branch steps order review" id "6d0b8162-c0bf-430f-b3f9-aa5396afee7d" { + description ""; + + sa_occurrence local_self_test_1d12e648 "SA Local self-test" id "1d12e648-66ba-4ce4-93fe-332792ecf67a" { + body { + Action { + action none; + lifeline none; + }; + }; + description ""; + parent @start_permitted_3342190a; + postcondition ""; + precondition ""; + } + + sa_occurrence verify_interlock_1e11083d "SA Verify interlock" id "1e11083d-6936-4730-b013-f2d8a827227d" { + body { + Action { + action none; + lifeline none; + }; + }; + description ""; + parent @start_permitted_3342190a; + postcondition ""; + precondition ""; + } + + sa_occurrence start_permitted_3342190a "SA Start permitted" id "3342190a-03fc-4f3c-b831-80b0f18a0bad" { + body { + Branch { + guard "permitted"; + occurrences [@request_start_e658f0ca, @local_self_test_1d12e648, @energize_d9d631f7]; + }; + }; + description ""; + parent @alternatives_601c0433; + postcondition ""; + precondition ""; + } + + sa_scenario startup_scenario_3eb4e8df "SA Startup scenario" id "3eb4e8df-a13a-492a-ab6f-1b36fb912d91" { + context none; + description ""; + parent none; + postcondition ""; + precondition ""; + steps [@alternatives_601c0433]; + viewpoint Functional; + } + + oa_occurrence alternatives "OA Alternatives" id "4c3d0157-6569-4c0a-93a2-b2467b00075a" { + body { + Fragment { + branches [@start_permitted]; + operator Alternative; + }; + }; + description ""; + parent @startup_scenario; + postcondition ""; + precondition ""; + } + + sa_occurrence alternatives_601c0433 "SA Alternatives" id "601c0433-8608-4ecf-b6bd-08179a8e16d5" { + body { + Fragment { + branches [@start_permitted_3342190a]; + operator Alternative; + }; + }; + description ""; + parent @startup_scenario_3eb4e8df; + postcondition ""; + precondition ""; + } + + oa_occurrence request_start "OA Request start" id "6e51da84-0808-46a2-863b-048083889f7a" { + body { + Action { + action none; + lifeline none; + }; + }; + description ""; + parent @start_permitted; + postcondition ""; + precondition ""; + } + + oa_occurrence energize "OA Energize" id "98a650aa-6d43-4d62-bc2f-45c9c2cc2f59" { + body { + Action { + action none; + lifeline none; + }; + }; + description ""; + parent @start_permitted; + postcondition ""; + precondition ""; + } + + oa_occurrence verify_interlock "OA Verify interlock" id "ac518f32-e9c2-4a6e-9afd-da1955ee7ea9" { + body { + Action { + action none; + lifeline none; + }; + }; + description ""; + parent @start_permitted; + postcondition ""; + precondition ""; + } + + oa_scenario startup_scenario "OA Startup scenario" id "b452dfca-e14e-46c6-ad23-4e1cdbc3a976" { + context none; + description ""; + parent none; + postcondition ""; + precondition ""; + steps [@alternatives]; + viewpoint OperationalActivity; + } + + oa_occurrence local_self_test "OA Local self-test" id "ba7d497c-bc43-42af-b77b-19d7b7be5f68" { + body { + Action { + action none; + lifeline none; + }; + }; + description ""; + parent @start_permitted; + postcondition ""; + precondition ""; + } + + sa_occurrence energize_d9d631f7 "SA Energize" id "d9d631f7-5f2c-47f7-a130-4151fd27dbfd" { + body { + Action { + action none; + lifeline none; + }; + }; + description ""; + parent @start_permitted_3342190a; + postcondition ""; + precondition ""; + } + + sa_occurrence request_start_e658f0ca "SA Request start" id "e658f0ca-014e-4043-980e-19a663b7ceea" { + body { + Action { + action none; + lifeline none; + }; + }; + description ""; + parent @start_permitted_3342190a; + postcondition ""; + precondition ""; + } + + oa_occurrence start_permitted "OA Start permitted" id "f6e150d3-d971-4572-a66d-9efe2beb2896" { + body { + Branch { + guard "permitted"; + occurrences [@request_start, @verify_interlock, @energize]; + }; + }; + description ""; + parent @alternatives; + postcondition ""; + precondition ""; + } + + trace startup_order_review "Startup branch steps order review" id "46195e23-864f-4df0-8e27-79da910fafd2" { + baseline { + oa { + description ""; + name "OA Start permitted"; + }; + sa { + description ""; + name "SA Start permitted"; + }; + semantic { + facets [{ + Member { + collection Steps; + keys { + oa uuid "ac518f32-e9c2-4a6e-9afd-da1955ee7ea9"; + sa uuid "1e11083d-6936-4730-b013-f2d8a827227d"; + }; + review { + oa { + Value true; + }; + sa { + Value false; + }; + }; + }; + }]; + }; + }; + description ""; + kind Manual; + oa @start_permitted; + sa @start_permitted_3342190a; + } + +} diff --git a/harness/fixtures/release1-native/semantic-ordered-branch-steps.final-plan.json b/harness/fixtures/release1-native/semantic-ordered-branch-steps.final-plan.json new file mode 100644 index 00000000..f83d0ca6 --- /dev/null +++ b/harness/fixtures/release1-native/semantic-ordered-branch-steps.final-plan.json @@ -0,0 +1,710 @@ +{ + "base_fingerprint": "00860542a85d3d15", + "request": { + "transition": { + "direction": "OaToSa", + "sources": [ + "f6e150d3-d971-4572-a66d-9efe2beb2896" + ], + "scope": "SelectedOnly", + "mappings": [ + { + "source": "f6e150d3-d971-4572-a66d-9efe2beb2896", + "target": "3342190a-03fc-4f3c-b831-80b0f18a0bad", + "target_kind": null + } + ], + "fields": [], + "resolutions": [], + "destination_diagram": null + }, + "traces": { + "f6e150d3-d971-4572-a66d-9efe2beb2896": "46195e23-864f-4df0-8e27-79da910fafd2" + }, + "references": { + "6e51da84-0808-46a2-863b-048083889f7a": "e658f0ca-014e-4043-980e-19a663b7ceea", + "98a650aa-6d43-4d62-bc2f-45c9c2cc2f59": "d9d631f7-5f2c-47f7-a130-4151fd27dbfd", + "ac518f32-e9c2-4a6e-9afd-da1955ee7ea9": "1e11083d-6936-4730-b013-f2d8a827227d", + "f6e150d3-d971-4572-a66d-9efe2beb2896": "3342190a-03fc-4f3c-b831-80b0f18a0bad" + }, + "reviews": [ + { + "source": "f6e150d3-d971-4572-a66d-9efe2beb2896", + "resolutions": [ + { + "facet": { + "Member": { + "collection": "Steps", + "keys": { + "oa": "ac518f32-e9c2-4a6e-9afd-da1955ee7ea9", + "sa": "1e11083d-6936-4730-b013-f2d8a827227d" + } + } + }, + "decision": "TakeSource" + }, + { + "facet": { + "Order": "Steps" + }, + "decision": "Candidate" + } + ], + "reset": [] + } + ], + "candidates": [ + { + "Element": { + "id": "3342190a-03fc-4f3c-b831-80b0f18a0bad", + "alias": "start_permitted_3342190a", + "name": "SA Start permitted", + "description": "", + "parent": "601c0433-8608-4ecf-b6bd-08179a8e16d5", + "body": { + "Sa": { + "Shared": { + "Occurrence": { + "body": { + "Branch": { + "guard": "permitted", + "occurrences": [ + "e658f0ca-014e-4043-980e-19a663b7ceea", + "1d12e648-66ba-4ce4-93fe-332792ecf67a", + "1e11083d-6936-4730-b013-f2d8a827227d", + "d9d631f7-5f2c-47f7-a130-4151fd27dbfd" + ] + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + } + } + ], + "accepted_impacts": [] + }, + "transition": { + "base_fingerprint": "00860542a85d3d15", + "request": { + "direction": "OaToSa", + "sources": [ + "f6e150d3-d971-4572-a66d-9efe2beb2896" + ], + "scope": "SelectedOnly", + "mappings": [ + { + "source": "f6e150d3-d971-4572-a66d-9efe2beb2896", + "target": "3342190a-03fc-4f3c-b831-80b0f18a0bad", + "target_kind": null + } + ], + "fields": [], + "resolutions": [], + "destination_diagram": null + }, + "changes": [ + { + "source": "f6e150d3-d971-4572-a66d-9efe2beb2896", + "target": "3342190a-03fc-4f3c-b831-80b0f18a0bad", + "action": "Reuse", + "fields": [] + } + ], + "conflicts": [], + "warnings": [ + "Identify the SA system of interest.", + "Choose action.", + "Choose lifeline.", + "Choose action.", + "Choose lifeline.", + "Add this occurrence to its owner's ordered steps.", + "Choose action.", + "Choose lifeline.", + "Choose action.", + "Choose lifeline.", + "Choose action.", + "Choose lifeline.", + "Choose action.", + "Choose lifeline.", + "Add this occurrence to its owner's ordered steps.", + "Choose action.", + "Choose lifeline.", + "Choose action.", + "Choose lifeline." + ], + "commands": [] + }, + "reviews": [ + { + "source": "f6e150d3-d971-4572-a66d-9efe2beb2896", + "target": "3342190a-03fc-4f3c-b831-80b0f18a0bad", + "facets": [ + { + "selector": { + "Header": "Name" + }, + "current": { + "Header": { + "Name": { + "oa": { + "Value": "OA Start permitted" + }, + "sa": { + "Value": "SA Start permitted" + } + } + } + }, + "drift": "Unreviewed" + }, + { + "selector": { + "Header": "Description" + }, + "current": { + "Header": { + "Description": { + "oa": { + "Value": "" + }, + "sa": { + "Value": "" + } + } + } + }, + "drift": "Unreviewed" + }, + { + "selector": { + "Header": "Parent" + }, + "current": { + "Header": { + "Parent": { + "oa": { + "Value": "4c3d0157-6569-4c0a-93a2-b2467b00075a" + }, + "sa": { + "Value": "601c0433-8608-4ecf-b6bd-08179a8e16d5" + } + } + } + }, + "drift": "Unreviewed" + }, + { + "selector": { + "Occurrence": "Constructor" + }, + "current": { + "Occurrence": { + "Constructor": { + "oa": { + "Value": "Branch" + }, + "sa": { + "Value": "Branch" + } + } + } + }, + "drift": "Unreviewed" + }, + { + "selector": { + "Occurrence": "Precondition" + }, + "current": { + "Occurrence": { + "Precondition": { + "oa": { + "Value": "" + }, + "sa": { + "Value": "" + } + } + } + }, + "drift": "Unreviewed" + }, + { + "selector": { + "Occurrence": "Postcondition" + }, + "current": { + "Occurrence": { + "Postcondition": { + "oa": { + "Value": "" + }, + "sa": { + "Value": "" + } + } + } + }, + "drift": "Unreviewed" + }, + { + "selector": { + "Occurrence": "BranchGuard" + }, + "current": { + "Occurrence": { + "BranchGuard": { + "oa": { + "Value": "permitted" + }, + "sa": { + "Value": "permitted" + } + } + } + }, + "drift": "Unreviewed" + }, + { + "selector": { + "Member": { + "collection": "Steps", + "keys": { + "oa": null, + "sa": "1d12e648-66ba-4ce4-93fe-332792ecf67a" + } + } + }, + "current": { + "Member": { + "collection": "Steps", + "keys": { + "oa": null, + "sa": "1d12e648-66ba-4ce4-93fe-332792ecf67a" + }, + "review": { + "oa": { + "Value": false + }, + "sa": { + "Value": true + } + } + } + }, + "drift": "Unreviewed" + }, + { + "selector": { + "Member": { + "collection": "Steps", + "keys": { + "oa": "6e51da84-0808-46a2-863b-048083889f7a", + "sa": "e658f0ca-014e-4043-980e-19a663b7ceea" + } + } + }, + "current": { + "Member": { + "collection": "Steps", + "keys": { + "oa": "6e51da84-0808-46a2-863b-048083889f7a", + "sa": "e658f0ca-014e-4043-980e-19a663b7ceea" + }, + "review": { + "oa": { + "Value": true + }, + "sa": { + "Value": true + } + } + } + }, + "drift": "Unreviewed" + }, + { + "selector": { + "Member": { + "collection": "Steps", + "keys": { + "oa": "98a650aa-6d43-4d62-bc2f-45c9c2cc2f59", + "sa": "d9d631f7-5f2c-47f7-a130-4151fd27dbfd" + } + } + }, + "current": { + "Member": { + "collection": "Steps", + "keys": { + "oa": "98a650aa-6d43-4d62-bc2f-45c9c2cc2f59", + "sa": "d9d631f7-5f2c-47f7-a130-4151fd27dbfd" + }, + "review": { + "oa": { + "Value": true + }, + "sa": { + "Value": true + } + } + } + }, + "drift": "Unreviewed" + }, + { + "selector": { + "Member": { + "collection": "Steps", + "keys": { + "oa": "ac518f32-e9c2-4a6e-9afd-da1955ee7ea9", + "sa": "1e11083d-6936-4730-b013-f2d8a827227d" + } + } + }, + "current": { + "Member": { + "collection": "Steps", + "keys": { + "oa": "ac518f32-e9c2-4a6e-9afd-da1955ee7ea9", + "sa": "1e11083d-6936-4730-b013-f2d8a827227d" + }, + "review": { + "oa": { + "Value": true + }, + "sa": { + "Value": false + } + } + } + }, + "drift": "Unchanged" + }, + { + "selector": { + "Order": "Steps" + }, + "current": { + "Order": { + "collection": "Steps", + "review": { + "oa": { + "Value": [ + "6e51da84-0808-46a2-863b-048083889f7a", + "ac518f32-e9c2-4a6e-9afd-da1955ee7ea9", + "98a650aa-6d43-4d62-bc2f-45c9c2cc2f59" + ] + }, + "sa": { + "Value": [ + "e658f0ca-014e-4043-980e-19a663b7ceea", + "1d12e648-66ba-4ce4-93fe-332792ecf67a", + "d9d631f7-5f2c-47f7-a130-4151fd27dbfd" + ] + } + } + } + }, + "drift": "Unreviewed" + }, + { + "selector": { + "Child": { + "oa": null, + "sa": "1d12e648-66ba-4ce4-93fe-332792ecf67a" + } + }, + "current": { + "Child": { + "keys": { + "oa": null, + "sa": "1d12e648-66ba-4ce4-93fe-332792ecf67a" + }, + "review": { + "oa": { + "Value": false + }, + "sa": { + "Value": true + } + } + } + }, + "drift": "Unreviewed" + }, + { + "selector": { + "Child": { + "oa": "6e51da84-0808-46a2-863b-048083889f7a", + "sa": "e658f0ca-014e-4043-980e-19a663b7ceea" + } + }, + "current": { + "Child": { + "keys": { + "oa": "6e51da84-0808-46a2-863b-048083889f7a", + "sa": "e658f0ca-014e-4043-980e-19a663b7ceea" + }, + "review": { + "oa": { + "Value": true + }, + "sa": { + "Value": true + } + } + } + }, + "drift": "Unreviewed" + }, + { + "selector": { + "Child": { + "oa": "98a650aa-6d43-4d62-bc2f-45c9c2cc2f59", + "sa": "d9d631f7-5f2c-47f7-a130-4151fd27dbfd" + } + }, + "current": { + "Child": { + "keys": { + "oa": "98a650aa-6d43-4d62-bc2f-45c9c2cc2f59", + "sa": "d9d631f7-5f2c-47f7-a130-4151fd27dbfd" + }, + "review": { + "oa": { + "Value": true + }, + "sa": { + "Value": true + } + } + } + }, + "drift": "Unreviewed" + }, + { + "selector": { + "Child": { + "oa": "ac518f32-e9c2-4a6e-9afd-da1955ee7ea9", + "sa": "1e11083d-6936-4730-b013-f2d8a827227d" + } + }, + "current": { + "Child": { + "keys": { + "oa": "ac518f32-e9c2-4a6e-9afd-da1955ee7ea9", + "sa": "1e11083d-6936-4730-b013-f2d8a827227d" + }, + "review": { + "oa": { + "Value": true + }, + "sa": { + "Value": true + } + } + } + }, + "drift": "Unreviewed" + }, + { + "selector": { + "Child": { + "oa": "ba7d497c-bc43-42af-b77b-19d7b7be5f68", + "sa": null + } + }, + "current": { + "Child": { + "keys": { + "oa": "ba7d497c-bc43-42af-b77b-19d7b7be5f68", + "sa": null + }, + "review": { + "oa": { + "Value": true + }, + "sa": { + "Value": false + } + } + } + }, + "drift": "Unreviewed" + } + ], + "acknowledged": [ + { + "Member": { + "collection": "Steps", + "keys": { + "oa": "ac518f32-e9c2-4a6e-9afd-da1955ee7ea9", + "sa": "1e11083d-6936-4730-b013-f2d8a827227d" + } + } + }, + { + "Order": "Steps" + } + ], + "retired": [], + "linked_impacts": [ + { + "objects": { + "oa": "6e51da84-0808-46a2-863b-048083889f7a", + "sa": "e658f0ca-014e-4043-980e-19a663b7ceea" + }, + "traces": [], + "drift": "Unreviewed", + "unreviewed": true + }, + { + "objects": { + "oa": "98a650aa-6d43-4d62-bc2f-45c9c2cc2f59", + "sa": "d9d631f7-5f2c-47f7-a130-4151fd27dbfd" + }, + "traces": [], + "drift": "Unreviewed", + "unreviewed": true + }, + { + "objects": { + "oa": "ac518f32-e9c2-4a6e-9afd-da1955ee7ea9", + "sa": "1e11083d-6936-4730-b013-f2d8a827227d" + }, + "traces": [], + "drift": "Unreviewed", + "unreviewed": true + }, + { + "objects": { + "oa": "ba7d497c-bc43-42af-b77b-19d7b7be5f68", + "sa": null + }, + "traces": [], + "drift": "Unreviewed", + "unreviewed": true + }, + { + "objects": { + "oa": null, + "sa": "1d12e648-66ba-4ce4-93fe-332792ecf67a" + }, + "traces": [], + "drift": "Unreviewed", + "unreviewed": true + } + ] + } + ], + "impacts": [], + "conflicts": [], + "commands": [ + { + "EditElement": [ + "3342190a-03fc-4f3c-b831-80b0f18a0bad", + { + "id": "3342190a-03fc-4f3c-b831-80b0f18a0bad", + "alias": "start_permitted_3342190a", + "name": "SA Start permitted", + "description": "", + "parent": "601c0433-8608-4ecf-b6bd-08179a8e16d5", + "body": { + "Sa": { + "Shared": { + "Occurrence": { + "body": { + "Branch": { + "guard": "permitted", + "occurrences": [ + "e658f0ca-014e-4043-980e-19a663b7ceea", + "1d12e648-66ba-4ce4-93fe-332792ecf67a", + "1e11083d-6936-4730-b013-f2d8a827227d", + "d9d631f7-5f2c-47f7-a130-4151fd27dbfd" + ] + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + } + ] + }, + { + "EditTrace": [ + "46195e23-864f-4df0-8e27-79da910fafd2", + { + "id": "46195e23-864f-4df0-8e27-79da910fafd2", + "alias": "startup_order_review", + "name": "Startup branch steps order review", + "description": "", + "oa": "f6e150d3-d971-4572-a66d-9efe2beb2896", + "sa": "3342190a-03fc-4f3c-b831-80b0f18a0bad", + "kind": "Manual", + "baseline": { + "oa": { + "name": "OA Start permitted", + "description": "" + }, + "sa": { + "name": "SA Start permitted", + "description": "" + }, + "semantic": { + "facets": [ + { + "Member": { + "collection": "Steps", + "keys": { + "oa": "ac518f32-e9c2-4a6e-9afd-da1955ee7ea9", + "sa": "1e11083d-6936-4730-b013-f2d8a827227d" + }, + "review": { + "oa": { + "Value": true + }, + "sa": { + "Value": true + } + } + } + }, + { + "Order": { + "collection": "Steps", + "review": { + "oa": { + "Value": [ + "6e51da84-0808-46a2-863b-048083889f7a", + "ac518f32-e9c2-4a6e-9afd-da1955ee7ea9", + "98a650aa-6d43-4d62-bc2f-45c9c2cc2f59" + ] + }, + "sa": { + "Value": [ + "e658f0ca-014e-4043-980e-19a663b7ceea", + "1d12e648-66ba-4ce4-93fe-332792ecf67a", + "1e11083d-6936-4730-b013-f2d8a827227d", + "d9d631f7-5f2c-47f7-a130-4151fd27dbfd" + ] + } + } + } + } + ] + } + } + } + ] + } + ], + "prepared_fingerprint": "eb2788fcfaacbfe1" +} \ No newline at end of file diff --git a/harness/fixtures/release1-native/semantic-ordered-branch-steps.final-request.json b/harness/fixtures/release1-native/semantic-ordered-branch-steps.final-request.json new file mode 100644 index 00000000..4561bb04 --- /dev/null +++ b/harness/fixtures/release1-native/semantic-ordered-branch-steps.final-request.json @@ -0,0 +1,87 @@ +{ + "transition": { + "direction": "OaToSa", + "sources": [ + "f6e150d3-d971-4572-a66d-9efe2beb2896" + ], + "scope": "SelectedOnly", + "mappings": [ + { + "source": "f6e150d3-d971-4572-a66d-9efe2beb2896", + "target": "3342190a-03fc-4f3c-b831-80b0f18a0bad", + "target_kind": null + } + ], + "fields": [], + "resolutions": [], + "destination_diagram": null + }, + "traces": { + "f6e150d3-d971-4572-a66d-9efe2beb2896": "46195e23-864f-4df0-8e27-79da910fafd2" + }, + "references": { + "6e51da84-0808-46a2-863b-048083889f7a": "e658f0ca-014e-4043-980e-19a663b7ceea", + "98a650aa-6d43-4d62-bc2f-45c9c2cc2f59": "d9d631f7-5f2c-47f7-a130-4151fd27dbfd", + "ac518f32-e9c2-4a6e-9afd-da1955ee7ea9": "1e11083d-6936-4730-b013-f2d8a827227d", + "f6e150d3-d971-4572-a66d-9efe2beb2896": "3342190a-03fc-4f3c-b831-80b0f18a0bad" + }, + "reviews": [ + { + "source": "f6e150d3-d971-4572-a66d-9efe2beb2896", + "resolutions": [ + { + "facet": { + "Member": { + "collection": "Steps", + "keys": { + "oa": "ac518f32-e9c2-4a6e-9afd-da1955ee7ea9", + "sa": "1e11083d-6936-4730-b013-f2d8a827227d" + } + } + }, + "decision": "TakeSource" + }, + { + "facet": { + "Order": "Steps" + }, + "decision": "Candidate" + } + ], + "reset": [] + } + ], + "candidates": [ + { + "Element": { + "id": "3342190a-03fc-4f3c-b831-80b0f18a0bad", + "alias": "start_permitted_3342190a", + "name": "SA Start permitted", + "description": "", + "parent": "601c0433-8608-4ecf-b6bd-08179a8e16d5", + "body": { + "Sa": { + "Shared": { + "Occurrence": { + "body": { + "Branch": { + "guard": "permitted", + "occurrences": [ + "e658f0ca-014e-4043-980e-19a663b7ceea", + "1d12e648-66ba-4ce4-93fe-332792ecf67a", + "1e11083d-6936-4730-b013-f2d8a827227d", + "d9d631f7-5f2c-47f7-a130-4151fd27dbfd" + ] + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + } + } + ], + "accepted_impacts": [] +} \ No newline at end of file diff --git a/harness/fixtures/release1-native/semantic-ordered-branch-steps.json b/harness/fixtures/release1-native/semantic-ordered-branch-steps.json new file mode 100644 index 00000000..b4105a97 --- /dev/null +++ b/harness/fixtures/release1-native/semantic-ordered-branch-steps.json @@ -0,0 +1,386 @@ +{ + "schema_version": 5, + "id": "6d0b8162-c0bf-430f-b3f9-aa5396afee7d", + "alias": "mission", + "name": "Startup branch steps order review", + "description": "", + "elements": { + "1d12e648-66ba-4ce4-93fe-332792ecf67a": { + "id": "1d12e648-66ba-4ce4-93fe-332792ecf67a", + "alias": "local_self_test_1d12e648", + "name": "SA Local self-test", + "description": "", + "parent": "3342190a-03fc-4f3c-b831-80b0f18a0bad", + "body": { + "Sa": { + "Shared": { + "Occurrence": { + "body": { + "Action": { + "action": null, + "lifeline": null + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + }, + "1e11083d-6936-4730-b013-f2d8a827227d": { + "id": "1e11083d-6936-4730-b013-f2d8a827227d", + "alias": "verify_interlock_1e11083d", + "name": "SA Verify interlock", + "description": "", + "parent": "3342190a-03fc-4f3c-b831-80b0f18a0bad", + "body": { + "Sa": { + "Shared": { + "Occurrence": { + "body": { + "Action": { + "action": null, + "lifeline": null + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + }, + "3342190a-03fc-4f3c-b831-80b0f18a0bad": { + "id": "3342190a-03fc-4f3c-b831-80b0f18a0bad", + "alias": "start_permitted_3342190a", + "name": "SA Start permitted", + "description": "", + "parent": "601c0433-8608-4ecf-b6bd-08179a8e16d5", + "body": { + "Sa": { + "Shared": { + "Occurrence": { + "body": { + "Branch": { + "guard": "permitted", + "occurrences": [ + "e658f0ca-014e-4043-980e-19a663b7ceea", + "1d12e648-66ba-4ce4-93fe-332792ecf67a", + "d9d631f7-5f2c-47f7-a130-4151fd27dbfd" + ] + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + }, + "3eb4e8df-a13a-492a-ab6f-1b36fb912d91": { + "id": "3eb4e8df-a13a-492a-ab6f-1b36fb912d91", + "alias": "startup_scenario_3eb4e8df", + "name": "SA Startup scenario", + "description": "", + "parent": null, + "body": { + "Sa": { + "Shared": { + "Scenario": { + "viewpoint": "Functional", + "context": null, + "precondition": "", + "postcondition": "", + "steps": [ + "601c0433-8608-4ecf-b6bd-08179a8e16d5" + ] + } + } + } + } + }, + "4c3d0157-6569-4c0a-93a2-b2467b00075a": { + "id": "4c3d0157-6569-4c0a-93a2-b2467b00075a", + "alias": "alternatives", + "name": "OA Alternatives", + "description": "", + "parent": "b452dfca-e14e-46c6-ad23-4e1cdbc3a976", + "body": { + "Oa": { + "Shared": { + "Occurrence": { + "body": { + "Fragment": { + "operator": "Alternative", + "branches": [ + "f6e150d3-d971-4572-a66d-9efe2beb2896" + ] + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + }, + "601c0433-8608-4ecf-b6bd-08179a8e16d5": { + "id": "601c0433-8608-4ecf-b6bd-08179a8e16d5", + "alias": "alternatives_601c0433", + "name": "SA Alternatives", + "description": "", + "parent": "3eb4e8df-a13a-492a-ab6f-1b36fb912d91", + "body": { + "Sa": { + "Shared": { + "Occurrence": { + "body": { + "Fragment": { + "operator": "Alternative", + "branches": [ + "3342190a-03fc-4f3c-b831-80b0f18a0bad" + ] + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + }, + "6e51da84-0808-46a2-863b-048083889f7a": { + "id": "6e51da84-0808-46a2-863b-048083889f7a", + "alias": "request_start", + "name": "OA Request start", + "description": "", + "parent": "f6e150d3-d971-4572-a66d-9efe2beb2896", + "body": { + "Oa": { + "Shared": { + "Occurrence": { + "body": { + "Action": { + "action": null, + "lifeline": null + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + }, + "98a650aa-6d43-4d62-bc2f-45c9c2cc2f59": { + "id": "98a650aa-6d43-4d62-bc2f-45c9c2cc2f59", + "alias": "energize", + "name": "OA Energize", + "description": "", + "parent": "f6e150d3-d971-4572-a66d-9efe2beb2896", + "body": { + "Oa": { + "Shared": { + "Occurrence": { + "body": { + "Action": { + "action": null, + "lifeline": null + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + }, + "ac518f32-e9c2-4a6e-9afd-da1955ee7ea9": { + "id": "ac518f32-e9c2-4a6e-9afd-da1955ee7ea9", + "alias": "verify_interlock", + "name": "OA Verify interlock", + "description": "", + "parent": "f6e150d3-d971-4572-a66d-9efe2beb2896", + "body": { + "Oa": { + "Shared": { + "Occurrence": { + "body": { + "Action": { + "action": null, + "lifeline": null + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + }, + "b452dfca-e14e-46c6-ad23-4e1cdbc3a976": { + "id": "b452dfca-e14e-46c6-ad23-4e1cdbc3a976", + "alias": "startup_scenario", + "name": "OA Startup scenario", + "description": "", + "parent": null, + "body": { + "Oa": { + "Shared": { + "Scenario": { + "viewpoint": "OperationalActivity", + "context": null, + "precondition": "", + "postcondition": "", + "steps": [ + "4c3d0157-6569-4c0a-93a2-b2467b00075a" + ] + } + } + } + } + }, + "ba7d497c-bc43-42af-b77b-19d7b7be5f68": { + "id": "ba7d497c-bc43-42af-b77b-19d7b7be5f68", + "alias": "local_self_test", + "name": "OA Local self-test", + "description": "", + "parent": "f6e150d3-d971-4572-a66d-9efe2beb2896", + "body": { + "Oa": { + "Shared": { + "Occurrence": { + "body": { + "Action": { + "action": null, + "lifeline": null + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + }, + "d9d631f7-5f2c-47f7-a130-4151fd27dbfd": { + "id": "d9d631f7-5f2c-47f7-a130-4151fd27dbfd", + "alias": "energize_d9d631f7", + "name": "SA Energize", + "description": "", + "parent": "3342190a-03fc-4f3c-b831-80b0f18a0bad", + "body": { + "Sa": { + "Shared": { + "Occurrence": { + "body": { + "Action": { + "action": null, + "lifeline": null + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + }, + "e658f0ca-014e-4043-980e-19a663b7ceea": { + "id": "e658f0ca-014e-4043-980e-19a663b7ceea", + "alias": "request_start_e658f0ca", + "name": "SA Request start", + "description": "", + "parent": "3342190a-03fc-4f3c-b831-80b0f18a0bad", + "body": { + "Sa": { + "Shared": { + "Occurrence": { + "body": { + "Action": { + "action": null, + "lifeline": null + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + }, + "f6e150d3-d971-4572-a66d-9efe2beb2896": { + "id": "f6e150d3-d971-4572-a66d-9efe2beb2896", + "alias": "start_permitted", + "name": "OA Start permitted", + "description": "", + "parent": "4c3d0157-6569-4c0a-93a2-b2467b00075a", + "body": { + "Oa": { + "Shared": { + "Occurrence": { + "body": { + "Branch": { + "guard": "permitted", + "occurrences": [ + "6e51da84-0808-46a2-863b-048083889f7a", + "ac518f32-e9c2-4a6e-9afd-da1955ee7ea9", + "98a650aa-6d43-4d62-bc2f-45c9c2cc2f59" + ] + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + } + }, + "relations": {}, + "diagrams": {}, + "artifact_links": {}, + "traces": { + "46195e23-864f-4df0-8e27-79da910fafd2": { + "id": "46195e23-864f-4df0-8e27-79da910fafd2", + "alias": "startup_order_review", + "name": "Startup branch steps order review", + "description": "", + "oa": "f6e150d3-d971-4572-a66d-9efe2beb2896", + "sa": "3342190a-03fc-4f3c-b831-80b0f18a0bad", + "kind": "Manual", + "baseline": { + "oa": { + "name": "OA Start permitted", + "description": "" + }, + "sa": { + "name": "SA Start permitted", + "description": "" + }, + "semantic": { + "facets": [ + { + "Member": { + "collection": "Steps", + "keys": { + "oa": "ac518f32-e9c2-4a6e-9afd-da1955ee7ea9", + "sa": "1e11083d-6936-4730-b013-f2d8a827227d" + }, + "review": { + "oa": { + "Value": true + }, + "sa": { + "Value": false + } + } + } + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/harness/fixtures/release1-native/semantic-ordered-branch-steps.manifest.json b/harness/fixtures/release1-native/semantic-ordered-branch-steps.manifest.json new file mode 100644 index 00000000..9eaa8bc4 --- /dev/null +++ b/harness/fixtures/release1-native/semantic-ordered-branch-steps.manifest.json @@ -0,0 +1,36 @@ +{ + "direction": "OaToSa", + "facet": { + "Member": { + "collection": "Steps", + "keys": { + "oa": "ac518f32-e9c2-4a6e-9afd-da1955ee7ea9", + "sa": "1e11083d-6936-4730-b013-f2d8a827227d" + } + } + }, + "final_order": [ + "e658f0ca-014e-4043-980e-19a663b7ceea", + "1d12e648-66ba-4ce4-93fe-332792ecf67a", + "1e11083d-6936-4730-b013-f2d8a827227d", + "d9d631f7-5f2c-47f7-a130-4151fd27dbfd" + ], + "rows": [ + [ + "6e51da84-0808-46a2-863b-048083889f7a", + "98a650aa-6d43-4d62-bc2f-45c9c2cc2f59", + "ac518f32-e9c2-4a6e-9afd-da1955ee7ea9", + "ba7d497c-bc43-42af-b77b-19d7b7be5f68" + ], + [ + "e658f0ca-014e-4043-980e-19a663b7ceea", + "d9d631f7-5f2c-47f7-a130-4151fd27dbfd", + "1e11083d-6936-4730-b013-f2d8a827227d", + "1d12e648-66ba-4ce4-93fe-332792ecf67a" + ] + ], + "source": "f6e150d3-d971-4572-a66d-9efe2beb2896", + "target": "3342190a-03fc-4f3c-b831-80b0f18a0bad", + "trace": "46195e23-864f-4df0-8e27-79da910fafd2", + "trace_name": "Startup branch steps order review" +} \ No newline at end of file diff --git a/harness/fixtures/release1-native/semantic-ordered-fragment.after-order.arcadia b/harness/fixtures/release1-native/semantic-ordered-fragment.after-order.arcadia new file mode 100644 index 00000000..1198ac6c --- /dev/null +++ b/harness/fixtures/release1-native/semantic-ordered-fragment.after-order.arcadia @@ -0,0 +1,205 @@ +// Preserve the authored startup order rationale. +arcadia 5; +project mission "Startup fragment branches order review" id "434e19a9-346b-4fdb-a624-fea060111454" { + description ""; + + sa_occurrence alternatives_0d05b2d3 "SA Alternatives" id "0d05b2d3-be70-41bb-917f-8c53f066b254" { + body { + Fragment { + branches [@request_start_d69b7042, @local_self_test_d4fe64ec, @verify_interlock_bbeca6cb, @energize_8dfa448e]; + operator Alternative; + }; + }; + description ""; + parent @startup_scenario_ed63e299; + postcondition ""; + precondition ""; + } + + oa_occurrence local_self_test "OA Local self-test" id "24cb59a8-f214-4a39-81d9-9c1c33398572" { + body { + Branch { + guard "Local self-test"; + occurrences []; + }; + }; + description ""; + parent @alternatives; + postcondition ""; + precondition ""; + } + + oa_scenario startup_scenario "OA Startup scenario" id "33cd48ac-c8fa-46bd-b953-a12d8fd42f55" { + context none; + description ""; + parent none; + postcondition ""; + precondition ""; + steps [@alternatives]; + viewpoint OperationalActivity; + } + + oa_occurrence verify_interlock "OA Verify interlock" id "5c1f1934-f363-4624-b7a3-84903c160ab0" { + body { + Branch { + guard "Verify interlock"; + occurrences []; + }; + }; + description ""; + parent @alternatives; + postcondition ""; + precondition ""; + } + + oa_occurrence energize "OA Energize" id "5d32fe3a-17c5-46cf-ace3-6f4a3258d2fd" { + body { + Branch { + guard "Energize"; + occurrences []; + }; + }; + description ""; + parent @alternatives; + postcondition ""; + precondition ""; + } + + sa_occurrence energize_8dfa448e "SA Energize" id "8dfa448e-ea97-4e5e-8b48-4d16b6c3f3c6" { + body { + Branch { + guard "Energize"; + occurrences []; + }; + }; + description ""; + parent @alternatives_0d05b2d3; + postcondition ""; + precondition ""; + } + + oa_occurrence alternatives "OA Alternatives" id "b0b25d32-db6f-47c2-86e4-36f1cc111c6a" { + body { + Fragment { + branches [@request_start, @verify_interlock, @energize]; + operator Alternative; + }; + }; + description ""; + parent @startup_scenario; + postcondition ""; + precondition ""; + } + + oa_occurrence request_start "OA Request start" id "ba27908d-3a78-4736-9909-f4f4b428d3cc" { + body { + Branch { + guard "Request start"; + occurrences []; + }; + }; + description ""; + parent @alternatives; + postcondition ""; + precondition ""; + } + + sa_occurrence verify_interlock_bbeca6cb "SA Verify interlock" id "bbeca6cb-27d0-4a35-8456-3424223e07c9" { + body { + Branch { + guard "Verify interlock"; + occurrences []; + }; + }; + description ""; + parent @alternatives_0d05b2d3; + postcondition ""; + precondition ""; + } + + sa_occurrence local_self_test_d4fe64ec "SA Local self-test" id "d4fe64ec-f03f-4aff-878f-7b59ee203e5d" { + body { + Branch { + guard "Local self-test"; + occurrences []; + }; + }; + description ""; + parent @alternatives_0d05b2d3; + postcondition ""; + precondition ""; + } + + sa_occurrence request_start_d69b7042 "SA Request start" id "d69b7042-4229-4c5e-96c7-dd54e5755a28" { + body { + Branch { + guard "Request start"; + occurrences []; + }; + }; + description ""; + parent @alternatives_0d05b2d3; + postcondition ""; + precondition ""; + } + + sa_scenario startup_scenario_ed63e299 "SA Startup scenario" id "ed63e299-a764-4716-ba14-a7c5662075e2" { + context none; + description ""; + parent none; + postcondition ""; + precondition ""; + steps [@alternatives_0d05b2d3]; + viewpoint Functional; + } + + trace startup_order_review "Startup fragment branches order review" id "4fdbe391-8ab5-4f03-b495-21c0e7289c15" { + baseline { + oa { + description ""; + name "OA Alternatives"; + }; + sa { + description ""; + name "SA Alternatives"; + }; + semantic { + facets [{ + Member { + collection FragmentBranches; + keys { + oa uuid "5c1f1934-f363-4624-b7a3-84903c160ab0"; + sa uuid "bbeca6cb-27d0-4a35-8456-3424223e07c9"; + }; + review { + oa { + Value true; + }; + sa { + Value true; + }; + }; + }; + }, + { + Order { + collection FragmentBranches; + review { + oa { + Value [uuid "ba27908d-3a78-4736-9909-f4f4b428d3cc", uuid "5c1f1934-f363-4624-b7a3-84903c160ab0", uuid "5d32fe3a-17c5-46cf-ace3-6f4a3258d2fd"]; + }; + sa { + Value [uuid "d69b7042-4229-4c5e-96c7-dd54e5755a28", uuid "d4fe64ec-f03f-4aff-878f-7b59ee203e5d", uuid "bbeca6cb-27d0-4a35-8456-3424223e07c9", uuid "8dfa448e-ea97-4e5e-8b48-4d16b6c3f3c6"]; + }; + }; + }; + }]; + }; + }; + description ""; + kind Manual; + oa @alternatives; + sa @alternatives_0d05b2d3; + } + +} diff --git a/harness/fixtures/release1-native/semantic-ordered-fragment.after-order.json b/harness/fixtures/release1-native/semantic-ordered-fragment.after-order.json new file mode 100644 index 00000000..29480a95 --- /dev/null +++ b/harness/fixtures/release1-native/semantic-ordered-fragment.after-order.json @@ -0,0 +1,359 @@ +{ + "schema_version": 5, + "id": "434e19a9-346b-4fdb-a624-fea060111454", + "alias": "mission", + "name": "Startup fragment branches order review", + "description": "", + "elements": { + "0d05b2d3-be70-41bb-917f-8c53f066b254": { + "id": "0d05b2d3-be70-41bb-917f-8c53f066b254", + "alias": "alternatives_0d05b2d3", + "name": "SA Alternatives", + "description": "", + "parent": "ed63e299-a764-4716-ba14-a7c5662075e2", + "body": { + "Sa": { + "Shared": { + "Occurrence": { + "body": { + "Fragment": { + "operator": "Alternative", + "branches": [ + "d69b7042-4229-4c5e-96c7-dd54e5755a28", + "d4fe64ec-f03f-4aff-878f-7b59ee203e5d", + "bbeca6cb-27d0-4a35-8456-3424223e07c9", + "8dfa448e-ea97-4e5e-8b48-4d16b6c3f3c6" + ] + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + }, + "24cb59a8-f214-4a39-81d9-9c1c33398572": { + "id": "24cb59a8-f214-4a39-81d9-9c1c33398572", + "alias": "local_self_test", + "name": "OA Local self-test", + "description": "", + "parent": "b0b25d32-db6f-47c2-86e4-36f1cc111c6a", + "body": { + "Oa": { + "Shared": { + "Occurrence": { + "body": { + "Branch": { + "guard": "Local self-test", + "occurrences": [] + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + }, + "33cd48ac-c8fa-46bd-b953-a12d8fd42f55": { + "id": "33cd48ac-c8fa-46bd-b953-a12d8fd42f55", + "alias": "startup_scenario", + "name": "OA Startup scenario", + "description": "", + "parent": null, + "body": { + "Oa": { + "Shared": { + "Scenario": { + "viewpoint": "OperationalActivity", + "context": null, + "precondition": "", + "postcondition": "", + "steps": [ + "b0b25d32-db6f-47c2-86e4-36f1cc111c6a" + ] + } + } + } + } + }, + "5c1f1934-f363-4624-b7a3-84903c160ab0": { + "id": "5c1f1934-f363-4624-b7a3-84903c160ab0", + "alias": "verify_interlock", + "name": "OA Verify interlock", + "description": "", + "parent": "b0b25d32-db6f-47c2-86e4-36f1cc111c6a", + "body": { + "Oa": { + "Shared": { + "Occurrence": { + "body": { + "Branch": { + "guard": "Verify interlock", + "occurrences": [] + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + }, + "5d32fe3a-17c5-46cf-ace3-6f4a3258d2fd": { + "id": "5d32fe3a-17c5-46cf-ace3-6f4a3258d2fd", + "alias": "energize", + "name": "OA Energize", + "description": "", + "parent": "b0b25d32-db6f-47c2-86e4-36f1cc111c6a", + "body": { + "Oa": { + "Shared": { + "Occurrence": { + "body": { + "Branch": { + "guard": "Energize", + "occurrences": [] + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + }, + "8dfa448e-ea97-4e5e-8b48-4d16b6c3f3c6": { + "id": "8dfa448e-ea97-4e5e-8b48-4d16b6c3f3c6", + "alias": "energize_8dfa448e", + "name": "SA Energize", + "description": "", + "parent": "0d05b2d3-be70-41bb-917f-8c53f066b254", + "body": { + "Sa": { + "Shared": { + "Occurrence": { + "body": { + "Branch": { + "guard": "Energize", + "occurrences": [] + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + }, + "b0b25d32-db6f-47c2-86e4-36f1cc111c6a": { + "id": "b0b25d32-db6f-47c2-86e4-36f1cc111c6a", + "alias": "alternatives", + "name": "OA Alternatives", + "description": "", + "parent": "33cd48ac-c8fa-46bd-b953-a12d8fd42f55", + "body": { + "Oa": { + "Shared": { + "Occurrence": { + "body": { + "Fragment": { + "operator": "Alternative", + "branches": [ + "ba27908d-3a78-4736-9909-f4f4b428d3cc", + "5c1f1934-f363-4624-b7a3-84903c160ab0", + "5d32fe3a-17c5-46cf-ace3-6f4a3258d2fd" + ] + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + }, + "ba27908d-3a78-4736-9909-f4f4b428d3cc": { + "id": "ba27908d-3a78-4736-9909-f4f4b428d3cc", + "alias": "request_start", + "name": "OA Request start", + "description": "", + "parent": "b0b25d32-db6f-47c2-86e4-36f1cc111c6a", + "body": { + "Oa": { + "Shared": { + "Occurrence": { + "body": { + "Branch": { + "guard": "Request start", + "occurrences": [] + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + }, + "bbeca6cb-27d0-4a35-8456-3424223e07c9": { + "id": "bbeca6cb-27d0-4a35-8456-3424223e07c9", + "alias": "verify_interlock_bbeca6cb", + "name": "SA Verify interlock", + "description": "", + "parent": "0d05b2d3-be70-41bb-917f-8c53f066b254", + "body": { + "Sa": { + "Shared": { + "Occurrence": { + "body": { + "Branch": { + "guard": "Verify interlock", + "occurrences": [] + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + }, + "d4fe64ec-f03f-4aff-878f-7b59ee203e5d": { + "id": "d4fe64ec-f03f-4aff-878f-7b59ee203e5d", + "alias": "local_self_test_d4fe64ec", + "name": "SA Local self-test", + "description": "", + "parent": "0d05b2d3-be70-41bb-917f-8c53f066b254", + "body": { + "Sa": { + "Shared": { + "Occurrence": { + "body": { + "Branch": { + "guard": "Local self-test", + "occurrences": [] + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + }, + "d69b7042-4229-4c5e-96c7-dd54e5755a28": { + "id": "d69b7042-4229-4c5e-96c7-dd54e5755a28", + "alias": "request_start_d69b7042", + "name": "SA Request start", + "description": "", + "parent": "0d05b2d3-be70-41bb-917f-8c53f066b254", + "body": { + "Sa": { + "Shared": { + "Occurrence": { + "body": { + "Branch": { + "guard": "Request start", + "occurrences": [] + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + }, + "ed63e299-a764-4716-ba14-a7c5662075e2": { + "id": "ed63e299-a764-4716-ba14-a7c5662075e2", + "alias": "startup_scenario_ed63e299", + "name": "SA Startup scenario", + "description": "", + "parent": null, + "body": { + "Sa": { + "Shared": { + "Scenario": { + "viewpoint": "Functional", + "context": null, + "precondition": "", + "postcondition": "", + "steps": [ + "0d05b2d3-be70-41bb-917f-8c53f066b254" + ] + } + } + } + } + } + }, + "relations": {}, + "diagrams": {}, + "artifact_links": {}, + "traces": { + "4fdbe391-8ab5-4f03-b495-21c0e7289c15": { + "id": "4fdbe391-8ab5-4f03-b495-21c0e7289c15", + "alias": "startup_order_review", + "name": "Startup fragment branches order review", + "description": "", + "oa": "b0b25d32-db6f-47c2-86e4-36f1cc111c6a", + "sa": "0d05b2d3-be70-41bb-917f-8c53f066b254", + "kind": "Manual", + "baseline": { + "oa": { + "name": "OA Alternatives", + "description": "" + }, + "sa": { + "name": "SA Alternatives", + "description": "" + }, + "semantic": { + "facets": [ + { + "Member": { + "collection": "FragmentBranches", + "keys": { + "oa": "5c1f1934-f363-4624-b7a3-84903c160ab0", + "sa": "bbeca6cb-27d0-4a35-8456-3424223e07c9" + }, + "review": { + "oa": { + "Value": true + }, + "sa": { + "Value": true + } + } + } + }, + { + "Order": { + "collection": "FragmentBranches", + "review": { + "oa": { + "Value": [ + "ba27908d-3a78-4736-9909-f4f4b428d3cc", + "5c1f1934-f363-4624-b7a3-84903c160ab0", + "5d32fe3a-17c5-46cf-ace3-6f4a3258d2fd" + ] + }, + "sa": { + "Value": [ + "d69b7042-4229-4c5e-96c7-dd54e5755a28", + "d4fe64ec-f03f-4aff-878f-7b59ee203e5d", + "bbeca6cb-27d0-4a35-8456-3424223e07c9", + "8dfa448e-ea97-4e5e-8b48-4d16b6c3f3c6" + ] + } + } + } + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/harness/fixtures/release1-native/semantic-ordered-fragment.ambiguous-request.json b/harness/fixtures/release1-native/semantic-ordered-fragment.ambiguous-request.json new file mode 100644 index 00000000..a0481f83 --- /dev/null +++ b/harness/fixtures/release1-native/semantic-ordered-fragment.ambiguous-request.json @@ -0,0 +1,50 @@ +{ + "transition": { + "direction": "OaToSa", + "sources": [ + "b0b25d32-db6f-47c2-86e4-36f1cc111c6a" + ], + "scope": "SelectedOnly", + "mappings": [ + { + "source": "b0b25d32-db6f-47c2-86e4-36f1cc111c6a", + "target": "0d05b2d3-be70-41bb-917f-8c53f066b254", + "target_kind": null + } + ], + "fields": [], + "resolutions": [], + "destination_diagram": null + }, + "traces": { + "b0b25d32-db6f-47c2-86e4-36f1cc111c6a": "4fdbe391-8ab5-4f03-b495-21c0e7289c15" + }, + "references": { + "5c1f1934-f363-4624-b7a3-84903c160ab0": "bbeca6cb-27d0-4a35-8456-3424223e07c9", + "5d32fe3a-17c5-46cf-ace3-6f4a3258d2fd": "8dfa448e-ea97-4e5e-8b48-4d16b6c3f3c6", + "b0b25d32-db6f-47c2-86e4-36f1cc111c6a": "0d05b2d3-be70-41bb-917f-8c53f066b254", + "ba27908d-3a78-4736-9909-f4f4b428d3cc": "d69b7042-4229-4c5e-96c7-dd54e5755a28" + }, + "reviews": [ + { + "source": "b0b25d32-db6f-47c2-86e4-36f1cc111c6a", + "resolutions": [ + { + "facet": { + "Member": { + "collection": "FragmentBranches", + "keys": { + "oa": "5c1f1934-f363-4624-b7a3-84903c160ab0", + "sa": "bbeca6cb-27d0-4a35-8456-3424223e07c9" + } + } + }, + "decision": "TakeSource" + } + ], + "reset": [] + } + ], + "candidates": [], + "accepted_impacts": [] +} \ No newline at end of file diff --git a/harness/fixtures/release1-native/semantic-ordered-fragment.ambiguous.txt b/harness/fixtures/release1-native/semantic-ordered-fragment.ambiguous.txt new file mode 100644 index 00000000..18fc5884 --- /dev/null +++ b/harness/fixtures/release1-native/semantic-ordered-fragment.ambiguous.txt @@ -0,0 +1 @@ +Ordered member insertion has no unique unchanged anchor; review an explicit final target order \ No newline at end of file diff --git a/harness/fixtures/release1-native/semantic-ordered-fragment.arcadia b/harness/fixtures/release1-native/semantic-ordered-fragment.arcadia new file mode 100644 index 00000000..48225d5d --- /dev/null +++ b/harness/fixtures/release1-native/semantic-ordered-fragment.arcadia @@ -0,0 +1,192 @@ +// Preserve the authored startup order rationale. +arcadia 5; +project mission "Startup fragment branches order review" id "434e19a9-346b-4fdb-a624-fea060111454" { + description ""; + + sa_occurrence alternatives_0d05b2d3 "SA Alternatives" id "0d05b2d3-be70-41bb-917f-8c53f066b254" { + body { + Fragment { + branches [@request_start_d69b7042, @local_self_test_d4fe64ec, @energize_8dfa448e]; + operator Alternative; + }; + }; + description ""; + parent @startup_scenario_ed63e299; + postcondition ""; + precondition ""; + } + + oa_occurrence local_self_test "OA Local self-test" id "24cb59a8-f214-4a39-81d9-9c1c33398572" { + body { + Branch { + guard "Local self-test"; + occurrences []; + }; + }; + description ""; + parent @alternatives; + postcondition ""; + precondition ""; + } + + oa_scenario startup_scenario "OA Startup scenario" id "33cd48ac-c8fa-46bd-b953-a12d8fd42f55" { + context none; + description ""; + parent none; + postcondition ""; + precondition ""; + steps [@alternatives]; + viewpoint OperationalActivity; + } + + oa_occurrence verify_interlock "OA Verify interlock" id "5c1f1934-f363-4624-b7a3-84903c160ab0" { + body { + Branch { + guard "Verify interlock"; + occurrences []; + }; + }; + description ""; + parent @alternatives; + postcondition ""; + precondition ""; + } + + oa_occurrence energize "OA Energize" id "5d32fe3a-17c5-46cf-ace3-6f4a3258d2fd" { + body { + Branch { + guard "Energize"; + occurrences []; + }; + }; + description ""; + parent @alternatives; + postcondition ""; + precondition ""; + } + + sa_occurrence energize_8dfa448e "SA Energize" id "8dfa448e-ea97-4e5e-8b48-4d16b6c3f3c6" { + body { + Branch { + guard "Energize"; + occurrences []; + }; + }; + description ""; + parent @alternatives_0d05b2d3; + postcondition ""; + precondition ""; + } + + oa_occurrence alternatives "OA Alternatives" id "b0b25d32-db6f-47c2-86e4-36f1cc111c6a" { + body { + Fragment { + branches [@request_start, @verify_interlock, @energize]; + operator Alternative; + }; + }; + description ""; + parent @startup_scenario; + postcondition ""; + precondition ""; + } + + oa_occurrence request_start "OA Request start" id "ba27908d-3a78-4736-9909-f4f4b428d3cc" { + body { + Branch { + guard "Request start"; + occurrences []; + }; + }; + description ""; + parent @alternatives; + postcondition ""; + precondition ""; + } + + sa_occurrence verify_interlock_bbeca6cb "SA Verify interlock" id "bbeca6cb-27d0-4a35-8456-3424223e07c9" { + body { + Branch { + guard "Verify interlock"; + occurrences []; + }; + }; + description ""; + parent @alternatives_0d05b2d3; + postcondition ""; + precondition ""; + } + + sa_occurrence local_self_test_d4fe64ec "SA Local self-test" id "d4fe64ec-f03f-4aff-878f-7b59ee203e5d" { + body { + Branch { + guard "Local self-test"; + occurrences []; + }; + }; + description ""; + parent @alternatives_0d05b2d3; + postcondition ""; + precondition ""; + } + + sa_occurrence request_start_d69b7042 "SA Request start" id "d69b7042-4229-4c5e-96c7-dd54e5755a28" { + body { + Branch { + guard "Request start"; + occurrences []; + }; + }; + description ""; + parent @alternatives_0d05b2d3; + postcondition ""; + precondition ""; + } + + sa_scenario startup_scenario_ed63e299 "SA Startup scenario" id "ed63e299-a764-4716-ba14-a7c5662075e2" { + context none; + description ""; + parent none; + postcondition ""; + precondition ""; + steps [@alternatives_0d05b2d3]; + viewpoint Functional; + } + + trace startup_order_review "Startup fragment branches order review" id "4fdbe391-8ab5-4f03-b495-21c0e7289c15" { + baseline { + oa { + description ""; + name "OA Alternatives"; + }; + sa { + description ""; + name "SA Alternatives"; + }; + semantic { + facets [{ + Member { + collection FragmentBranches; + keys { + oa uuid "5c1f1934-f363-4624-b7a3-84903c160ab0"; + sa uuid "bbeca6cb-27d0-4a35-8456-3424223e07c9"; + }; + review { + oa { + Value true; + }; + sa { + Value false; + }; + }; + }; + }]; + }; + }; + description ""; + kind Manual; + oa @alternatives; + sa @alternatives_0d05b2d3; + } + +} diff --git a/harness/fixtures/release1-native/semantic-ordered-fragment.final-plan.json b/harness/fixtures/release1-native/semantic-ordered-fragment.final-plan.json new file mode 100644 index 00000000..18964ce5 --- /dev/null +++ b/harness/fixtures/release1-native/semantic-ordered-fragment.final-plan.json @@ -0,0 +1,692 @@ +{ + "base_fingerprint": "25de14257e1b6081", + "request": { + "transition": { + "direction": "OaToSa", + "sources": [ + "b0b25d32-db6f-47c2-86e4-36f1cc111c6a" + ], + "scope": "SelectedOnly", + "mappings": [ + { + "source": "b0b25d32-db6f-47c2-86e4-36f1cc111c6a", + "target": "0d05b2d3-be70-41bb-917f-8c53f066b254", + "target_kind": null + } + ], + "fields": [], + "resolutions": [], + "destination_diagram": null + }, + "traces": { + "b0b25d32-db6f-47c2-86e4-36f1cc111c6a": "4fdbe391-8ab5-4f03-b495-21c0e7289c15" + }, + "references": { + "5c1f1934-f363-4624-b7a3-84903c160ab0": "bbeca6cb-27d0-4a35-8456-3424223e07c9", + "5d32fe3a-17c5-46cf-ace3-6f4a3258d2fd": "8dfa448e-ea97-4e5e-8b48-4d16b6c3f3c6", + "b0b25d32-db6f-47c2-86e4-36f1cc111c6a": "0d05b2d3-be70-41bb-917f-8c53f066b254", + "ba27908d-3a78-4736-9909-f4f4b428d3cc": "d69b7042-4229-4c5e-96c7-dd54e5755a28" + }, + "reviews": [ + { + "source": "b0b25d32-db6f-47c2-86e4-36f1cc111c6a", + "resolutions": [ + { + "facet": { + "Member": { + "collection": "FragmentBranches", + "keys": { + "oa": "5c1f1934-f363-4624-b7a3-84903c160ab0", + "sa": "bbeca6cb-27d0-4a35-8456-3424223e07c9" + } + } + }, + "decision": "TakeSource" + }, + { + "facet": { + "Order": "FragmentBranches" + }, + "decision": "Candidate" + } + ], + "reset": [] + } + ], + "candidates": [ + { + "Element": { + "id": "0d05b2d3-be70-41bb-917f-8c53f066b254", + "alias": "alternatives_0d05b2d3", + "name": "SA Alternatives", + "description": "", + "parent": "ed63e299-a764-4716-ba14-a7c5662075e2", + "body": { + "Sa": { + "Shared": { + "Occurrence": { + "body": { + "Fragment": { + "operator": "Alternative", + "branches": [ + "d69b7042-4229-4c5e-96c7-dd54e5755a28", + "d4fe64ec-f03f-4aff-878f-7b59ee203e5d", + "bbeca6cb-27d0-4a35-8456-3424223e07c9", + "8dfa448e-ea97-4e5e-8b48-4d16b6c3f3c6" + ] + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + } + } + ], + "accepted_impacts": [] + }, + "transition": { + "base_fingerprint": "25de14257e1b6081", + "request": { + "direction": "OaToSa", + "sources": [ + "b0b25d32-db6f-47c2-86e4-36f1cc111c6a" + ], + "scope": "SelectedOnly", + "mappings": [ + { + "source": "b0b25d32-db6f-47c2-86e4-36f1cc111c6a", + "target": "0d05b2d3-be70-41bb-917f-8c53f066b254", + "target_kind": null + } + ], + "fields": [], + "resolutions": [], + "destination_diagram": null + }, + "changes": [ + { + "source": "b0b25d32-db6f-47c2-86e4-36f1cc111c6a", + "target": "0d05b2d3-be70-41bb-917f-8c53f066b254", + "action": "Reuse", + "fields": [] + } + ], + "conflicts": [], + "warnings": [ + "Identify the SA system of interest." + ], + "commands": [] + }, + "reviews": [ + { + "source": "b0b25d32-db6f-47c2-86e4-36f1cc111c6a", + "target": "0d05b2d3-be70-41bb-917f-8c53f066b254", + "facets": [ + { + "selector": { + "Header": "Name" + }, + "current": { + "Header": { + "Name": { + "oa": { + "Value": "OA Alternatives" + }, + "sa": { + "Value": "SA Alternatives" + } + } + } + }, + "drift": "Unreviewed" + }, + { + "selector": { + "Header": "Description" + }, + "current": { + "Header": { + "Description": { + "oa": { + "Value": "" + }, + "sa": { + "Value": "" + } + } + } + }, + "drift": "Unreviewed" + }, + { + "selector": { + "Header": "Parent" + }, + "current": { + "Header": { + "Parent": { + "oa": { + "Value": "33cd48ac-c8fa-46bd-b953-a12d8fd42f55" + }, + "sa": { + "Value": "ed63e299-a764-4716-ba14-a7c5662075e2" + } + } + } + }, + "drift": "Unreviewed" + }, + { + "selector": { + "Occurrence": "Constructor" + }, + "current": { + "Occurrence": { + "Constructor": { + "oa": { + "Value": "Fragment" + }, + "sa": { + "Value": "Fragment" + } + } + } + }, + "drift": "Unreviewed" + }, + { + "selector": { + "Occurrence": "Precondition" + }, + "current": { + "Occurrence": { + "Precondition": { + "oa": { + "Value": "" + }, + "sa": { + "Value": "" + } + } + } + }, + "drift": "Unreviewed" + }, + { + "selector": { + "Occurrence": "Postcondition" + }, + "current": { + "Occurrence": { + "Postcondition": { + "oa": { + "Value": "" + }, + "sa": { + "Value": "" + } + } + } + }, + "drift": "Unreviewed" + }, + { + "selector": { + "Occurrence": "FragmentOperator" + }, + "current": { + "Occurrence": { + "FragmentOperator": { + "oa": { + "Value": "Alternative" + }, + "sa": { + "Value": "Alternative" + } + } + } + }, + "drift": "Unreviewed" + }, + { + "selector": { + "Member": { + "collection": "FragmentBranches", + "keys": { + "oa": null, + "sa": "d4fe64ec-f03f-4aff-878f-7b59ee203e5d" + } + } + }, + "current": { + "Member": { + "collection": "FragmentBranches", + "keys": { + "oa": null, + "sa": "d4fe64ec-f03f-4aff-878f-7b59ee203e5d" + }, + "review": { + "oa": { + "Value": false + }, + "sa": { + "Value": true + } + } + } + }, + "drift": "Unreviewed" + }, + { + "selector": { + "Member": { + "collection": "FragmentBranches", + "keys": { + "oa": "5c1f1934-f363-4624-b7a3-84903c160ab0", + "sa": "bbeca6cb-27d0-4a35-8456-3424223e07c9" + } + } + }, + "current": { + "Member": { + "collection": "FragmentBranches", + "keys": { + "oa": "5c1f1934-f363-4624-b7a3-84903c160ab0", + "sa": "bbeca6cb-27d0-4a35-8456-3424223e07c9" + }, + "review": { + "oa": { + "Value": true + }, + "sa": { + "Value": false + } + } + } + }, + "drift": "Unchanged" + }, + { + "selector": { + "Member": { + "collection": "FragmentBranches", + "keys": { + "oa": "5d32fe3a-17c5-46cf-ace3-6f4a3258d2fd", + "sa": "8dfa448e-ea97-4e5e-8b48-4d16b6c3f3c6" + } + } + }, + "current": { + "Member": { + "collection": "FragmentBranches", + "keys": { + "oa": "5d32fe3a-17c5-46cf-ace3-6f4a3258d2fd", + "sa": "8dfa448e-ea97-4e5e-8b48-4d16b6c3f3c6" + }, + "review": { + "oa": { + "Value": true + }, + "sa": { + "Value": true + } + } + } + }, + "drift": "Unreviewed" + }, + { + "selector": { + "Member": { + "collection": "FragmentBranches", + "keys": { + "oa": "ba27908d-3a78-4736-9909-f4f4b428d3cc", + "sa": "d69b7042-4229-4c5e-96c7-dd54e5755a28" + } + } + }, + "current": { + "Member": { + "collection": "FragmentBranches", + "keys": { + "oa": "ba27908d-3a78-4736-9909-f4f4b428d3cc", + "sa": "d69b7042-4229-4c5e-96c7-dd54e5755a28" + }, + "review": { + "oa": { + "Value": true + }, + "sa": { + "Value": true + } + } + } + }, + "drift": "Unreviewed" + }, + { + "selector": { + "Order": "FragmentBranches" + }, + "current": { + "Order": { + "collection": "FragmentBranches", + "review": { + "oa": { + "Value": [ + "ba27908d-3a78-4736-9909-f4f4b428d3cc", + "5c1f1934-f363-4624-b7a3-84903c160ab0", + "5d32fe3a-17c5-46cf-ace3-6f4a3258d2fd" + ] + }, + "sa": { + "Value": [ + "d69b7042-4229-4c5e-96c7-dd54e5755a28", + "d4fe64ec-f03f-4aff-878f-7b59ee203e5d", + "8dfa448e-ea97-4e5e-8b48-4d16b6c3f3c6" + ] + } + } + } + }, + "drift": "Unreviewed" + }, + { + "selector": { + "Child": { + "oa": null, + "sa": "d4fe64ec-f03f-4aff-878f-7b59ee203e5d" + } + }, + "current": { + "Child": { + "keys": { + "oa": null, + "sa": "d4fe64ec-f03f-4aff-878f-7b59ee203e5d" + }, + "review": { + "oa": { + "Value": false + }, + "sa": { + "Value": true + } + } + } + }, + "drift": "Unreviewed" + }, + { + "selector": { + "Child": { + "oa": "24cb59a8-f214-4a39-81d9-9c1c33398572", + "sa": null + } + }, + "current": { + "Child": { + "keys": { + "oa": "24cb59a8-f214-4a39-81d9-9c1c33398572", + "sa": null + }, + "review": { + "oa": { + "Value": true + }, + "sa": { + "Value": false + } + } + } + }, + "drift": "Unreviewed" + }, + { + "selector": { + "Child": { + "oa": "5c1f1934-f363-4624-b7a3-84903c160ab0", + "sa": "bbeca6cb-27d0-4a35-8456-3424223e07c9" + } + }, + "current": { + "Child": { + "keys": { + "oa": "5c1f1934-f363-4624-b7a3-84903c160ab0", + "sa": "bbeca6cb-27d0-4a35-8456-3424223e07c9" + }, + "review": { + "oa": { + "Value": true + }, + "sa": { + "Value": true + } + } + } + }, + "drift": "Unreviewed" + }, + { + "selector": { + "Child": { + "oa": "5d32fe3a-17c5-46cf-ace3-6f4a3258d2fd", + "sa": "8dfa448e-ea97-4e5e-8b48-4d16b6c3f3c6" + } + }, + "current": { + "Child": { + "keys": { + "oa": "5d32fe3a-17c5-46cf-ace3-6f4a3258d2fd", + "sa": "8dfa448e-ea97-4e5e-8b48-4d16b6c3f3c6" + }, + "review": { + "oa": { + "Value": true + }, + "sa": { + "Value": true + } + } + } + }, + "drift": "Unreviewed" + }, + { + "selector": { + "Child": { + "oa": "ba27908d-3a78-4736-9909-f4f4b428d3cc", + "sa": "d69b7042-4229-4c5e-96c7-dd54e5755a28" + } + }, + "current": { + "Child": { + "keys": { + "oa": "ba27908d-3a78-4736-9909-f4f4b428d3cc", + "sa": "d69b7042-4229-4c5e-96c7-dd54e5755a28" + }, + "review": { + "oa": { + "Value": true + }, + "sa": { + "Value": true + } + } + } + }, + "drift": "Unreviewed" + } + ], + "acknowledged": [ + { + "Member": { + "collection": "FragmentBranches", + "keys": { + "oa": "5c1f1934-f363-4624-b7a3-84903c160ab0", + "sa": "bbeca6cb-27d0-4a35-8456-3424223e07c9" + } + } + }, + { + "Order": "FragmentBranches" + } + ], + "retired": [], + "linked_impacts": [ + { + "objects": { + "oa": "24cb59a8-f214-4a39-81d9-9c1c33398572", + "sa": null + }, + "traces": [], + "drift": "Unreviewed", + "unreviewed": true + }, + { + "objects": { + "oa": "5c1f1934-f363-4624-b7a3-84903c160ab0", + "sa": "bbeca6cb-27d0-4a35-8456-3424223e07c9" + }, + "traces": [], + "drift": "Unreviewed", + "unreviewed": true + }, + { + "objects": { + "oa": "5d32fe3a-17c5-46cf-ace3-6f4a3258d2fd", + "sa": "8dfa448e-ea97-4e5e-8b48-4d16b6c3f3c6" + }, + "traces": [], + "drift": "Unreviewed", + "unreviewed": true + }, + { + "objects": { + "oa": "ba27908d-3a78-4736-9909-f4f4b428d3cc", + "sa": "d69b7042-4229-4c5e-96c7-dd54e5755a28" + }, + "traces": [], + "drift": "Unreviewed", + "unreviewed": true + }, + { + "objects": { + "oa": null, + "sa": "d4fe64ec-f03f-4aff-878f-7b59ee203e5d" + }, + "traces": [], + "drift": "Unreviewed", + "unreviewed": true + } + ] + } + ], + "impacts": [], + "conflicts": [], + "commands": [ + { + "EditElement": [ + "0d05b2d3-be70-41bb-917f-8c53f066b254", + { + "id": "0d05b2d3-be70-41bb-917f-8c53f066b254", + "alias": "alternatives_0d05b2d3", + "name": "SA Alternatives", + "description": "", + "parent": "ed63e299-a764-4716-ba14-a7c5662075e2", + "body": { + "Sa": { + "Shared": { + "Occurrence": { + "body": { + "Fragment": { + "operator": "Alternative", + "branches": [ + "d69b7042-4229-4c5e-96c7-dd54e5755a28", + "d4fe64ec-f03f-4aff-878f-7b59ee203e5d", + "bbeca6cb-27d0-4a35-8456-3424223e07c9", + "8dfa448e-ea97-4e5e-8b48-4d16b6c3f3c6" + ] + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + } + ] + }, + { + "EditTrace": [ + "4fdbe391-8ab5-4f03-b495-21c0e7289c15", + { + "id": "4fdbe391-8ab5-4f03-b495-21c0e7289c15", + "alias": "startup_order_review", + "name": "Startup fragment branches order review", + "description": "", + "oa": "b0b25d32-db6f-47c2-86e4-36f1cc111c6a", + "sa": "0d05b2d3-be70-41bb-917f-8c53f066b254", + "kind": "Manual", + "baseline": { + "oa": { + "name": "OA Alternatives", + "description": "" + }, + "sa": { + "name": "SA Alternatives", + "description": "" + }, + "semantic": { + "facets": [ + { + "Member": { + "collection": "FragmentBranches", + "keys": { + "oa": "5c1f1934-f363-4624-b7a3-84903c160ab0", + "sa": "bbeca6cb-27d0-4a35-8456-3424223e07c9" + }, + "review": { + "oa": { + "Value": true + }, + "sa": { + "Value": true + } + } + } + }, + { + "Order": { + "collection": "FragmentBranches", + "review": { + "oa": { + "Value": [ + "ba27908d-3a78-4736-9909-f4f4b428d3cc", + "5c1f1934-f363-4624-b7a3-84903c160ab0", + "5d32fe3a-17c5-46cf-ace3-6f4a3258d2fd" + ] + }, + "sa": { + "Value": [ + "d69b7042-4229-4c5e-96c7-dd54e5755a28", + "d4fe64ec-f03f-4aff-878f-7b59ee203e5d", + "bbeca6cb-27d0-4a35-8456-3424223e07c9", + "8dfa448e-ea97-4e5e-8b48-4d16b6c3f3c6" + ] + } + } + } + } + ] + } + } + } + ] + } + ], + "prepared_fingerprint": "3f71f2631dde9d35" +} \ No newline at end of file diff --git a/harness/fixtures/release1-native/semantic-ordered-fragment.final-request.json b/harness/fixtures/release1-native/semantic-ordered-fragment.final-request.json new file mode 100644 index 00000000..8a12b135 --- /dev/null +++ b/harness/fixtures/release1-native/semantic-ordered-fragment.final-request.json @@ -0,0 +1,87 @@ +{ + "transition": { + "direction": "OaToSa", + "sources": [ + "b0b25d32-db6f-47c2-86e4-36f1cc111c6a" + ], + "scope": "SelectedOnly", + "mappings": [ + { + "source": "b0b25d32-db6f-47c2-86e4-36f1cc111c6a", + "target": "0d05b2d3-be70-41bb-917f-8c53f066b254", + "target_kind": null + } + ], + "fields": [], + "resolutions": [], + "destination_diagram": null + }, + "traces": { + "b0b25d32-db6f-47c2-86e4-36f1cc111c6a": "4fdbe391-8ab5-4f03-b495-21c0e7289c15" + }, + "references": { + "5c1f1934-f363-4624-b7a3-84903c160ab0": "bbeca6cb-27d0-4a35-8456-3424223e07c9", + "5d32fe3a-17c5-46cf-ace3-6f4a3258d2fd": "8dfa448e-ea97-4e5e-8b48-4d16b6c3f3c6", + "b0b25d32-db6f-47c2-86e4-36f1cc111c6a": "0d05b2d3-be70-41bb-917f-8c53f066b254", + "ba27908d-3a78-4736-9909-f4f4b428d3cc": "d69b7042-4229-4c5e-96c7-dd54e5755a28" + }, + "reviews": [ + { + "source": "b0b25d32-db6f-47c2-86e4-36f1cc111c6a", + "resolutions": [ + { + "facet": { + "Member": { + "collection": "FragmentBranches", + "keys": { + "oa": "5c1f1934-f363-4624-b7a3-84903c160ab0", + "sa": "bbeca6cb-27d0-4a35-8456-3424223e07c9" + } + } + }, + "decision": "TakeSource" + }, + { + "facet": { + "Order": "FragmentBranches" + }, + "decision": "Candidate" + } + ], + "reset": [] + } + ], + "candidates": [ + { + "Element": { + "id": "0d05b2d3-be70-41bb-917f-8c53f066b254", + "alias": "alternatives_0d05b2d3", + "name": "SA Alternatives", + "description": "", + "parent": "ed63e299-a764-4716-ba14-a7c5662075e2", + "body": { + "Sa": { + "Shared": { + "Occurrence": { + "body": { + "Fragment": { + "operator": "Alternative", + "branches": [ + "d69b7042-4229-4c5e-96c7-dd54e5755a28", + "d4fe64ec-f03f-4aff-878f-7b59ee203e5d", + "bbeca6cb-27d0-4a35-8456-3424223e07c9", + "8dfa448e-ea97-4e5e-8b48-4d16b6c3f3c6" + ] + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + } + } + ], + "accepted_impacts": [] +} \ No newline at end of file diff --git a/harness/fixtures/release1-native/semantic-ordered-fragment.json b/harness/fixtures/release1-native/semantic-ordered-fragment.json new file mode 100644 index 00000000..7c290172 --- /dev/null +++ b/harness/fixtures/release1-native/semantic-ordered-fragment.json @@ -0,0 +1,336 @@ +{ + "schema_version": 5, + "id": "434e19a9-346b-4fdb-a624-fea060111454", + "alias": "mission", + "name": "Startup fragment branches order review", + "description": "", + "elements": { + "0d05b2d3-be70-41bb-917f-8c53f066b254": { + "id": "0d05b2d3-be70-41bb-917f-8c53f066b254", + "alias": "alternatives_0d05b2d3", + "name": "SA Alternatives", + "description": "", + "parent": "ed63e299-a764-4716-ba14-a7c5662075e2", + "body": { + "Sa": { + "Shared": { + "Occurrence": { + "body": { + "Fragment": { + "operator": "Alternative", + "branches": [ + "d69b7042-4229-4c5e-96c7-dd54e5755a28", + "d4fe64ec-f03f-4aff-878f-7b59ee203e5d", + "8dfa448e-ea97-4e5e-8b48-4d16b6c3f3c6" + ] + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + }, + "24cb59a8-f214-4a39-81d9-9c1c33398572": { + "id": "24cb59a8-f214-4a39-81d9-9c1c33398572", + "alias": "local_self_test", + "name": "OA Local self-test", + "description": "", + "parent": "b0b25d32-db6f-47c2-86e4-36f1cc111c6a", + "body": { + "Oa": { + "Shared": { + "Occurrence": { + "body": { + "Branch": { + "guard": "Local self-test", + "occurrences": [] + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + }, + "33cd48ac-c8fa-46bd-b953-a12d8fd42f55": { + "id": "33cd48ac-c8fa-46bd-b953-a12d8fd42f55", + "alias": "startup_scenario", + "name": "OA Startup scenario", + "description": "", + "parent": null, + "body": { + "Oa": { + "Shared": { + "Scenario": { + "viewpoint": "OperationalActivity", + "context": null, + "precondition": "", + "postcondition": "", + "steps": [ + "b0b25d32-db6f-47c2-86e4-36f1cc111c6a" + ] + } + } + } + } + }, + "5c1f1934-f363-4624-b7a3-84903c160ab0": { + "id": "5c1f1934-f363-4624-b7a3-84903c160ab0", + "alias": "verify_interlock", + "name": "OA Verify interlock", + "description": "", + "parent": "b0b25d32-db6f-47c2-86e4-36f1cc111c6a", + "body": { + "Oa": { + "Shared": { + "Occurrence": { + "body": { + "Branch": { + "guard": "Verify interlock", + "occurrences": [] + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + }, + "5d32fe3a-17c5-46cf-ace3-6f4a3258d2fd": { + "id": "5d32fe3a-17c5-46cf-ace3-6f4a3258d2fd", + "alias": "energize", + "name": "OA Energize", + "description": "", + "parent": "b0b25d32-db6f-47c2-86e4-36f1cc111c6a", + "body": { + "Oa": { + "Shared": { + "Occurrence": { + "body": { + "Branch": { + "guard": "Energize", + "occurrences": [] + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + }, + "8dfa448e-ea97-4e5e-8b48-4d16b6c3f3c6": { + "id": "8dfa448e-ea97-4e5e-8b48-4d16b6c3f3c6", + "alias": "energize_8dfa448e", + "name": "SA Energize", + "description": "", + "parent": "0d05b2d3-be70-41bb-917f-8c53f066b254", + "body": { + "Sa": { + "Shared": { + "Occurrence": { + "body": { + "Branch": { + "guard": "Energize", + "occurrences": [] + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + }, + "b0b25d32-db6f-47c2-86e4-36f1cc111c6a": { + "id": "b0b25d32-db6f-47c2-86e4-36f1cc111c6a", + "alias": "alternatives", + "name": "OA Alternatives", + "description": "", + "parent": "33cd48ac-c8fa-46bd-b953-a12d8fd42f55", + "body": { + "Oa": { + "Shared": { + "Occurrence": { + "body": { + "Fragment": { + "operator": "Alternative", + "branches": [ + "ba27908d-3a78-4736-9909-f4f4b428d3cc", + "5c1f1934-f363-4624-b7a3-84903c160ab0", + "5d32fe3a-17c5-46cf-ace3-6f4a3258d2fd" + ] + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + }, + "ba27908d-3a78-4736-9909-f4f4b428d3cc": { + "id": "ba27908d-3a78-4736-9909-f4f4b428d3cc", + "alias": "request_start", + "name": "OA Request start", + "description": "", + "parent": "b0b25d32-db6f-47c2-86e4-36f1cc111c6a", + "body": { + "Oa": { + "Shared": { + "Occurrence": { + "body": { + "Branch": { + "guard": "Request start", + "occurrences": [] + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + }, + "bbeca6cb-27d0-4a35-8456-3424223e07c9": { + "id": "bbeca6cb-27d0-4a35-8456-3424223e07c9", + "alias": "verify_interlock_bbeca6cb", + "name": "SA Verify interlock", + "description": "", + "parent": "0d05b2d3-be70-41bb-917f-8c53f066b254", + "body": { + "Sa": { + "Shared": { + "Occurrence": { + "body": { + "Branch": { + "guard": "Verify interlock", + "occurrences": [] + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + }, + "d4fe64ec-f03f-4aff-878f-7b59ee203e5d": { + "id": "d4fe64ec-f03f-4aff-878f-7b59ee203e5d", + "alias": "local_self_test_d4fe64ec", + "name": "SA Local self-test", + "description": "", + "parent": "0d05b2d3-be70-41bb-917f-8c53f066b254", + "body": { + "Sa": { + "Shared": { + "Occurrence": { + "body": { + "Branch": { + "guard": "Local self-test", + "occurrences": [] + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + }, + "d69b7042-4229-4c5e-96c7-dd54e5755a28": { + "id": "d69b7042-4229-4c5e-96c7-dd54e5755a28", + "alias": "request_start_d69b7042", + "name": "SA Request start", + "description": "", + "parent": "0d05b2d3-be70-41bb-917f-8c53f066b254", + "body": { + "Sa": { + "Shared": { + "Occurrence": { + "body": { + "Branch": { + "guard": "Request start", + "occurrences": [] + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + }, + "ed63e299-a764-4716-ba14-a7c5662075e2": { + "id": "ed63e299-a764-4716-ba14-a7c5662075e2", + "alias": "startup_scenario_ed63e299", + "name": "SA Startup scenario", + "description": "", + "parent": null, + "body": { + "Sa": { + "Shared": { + "Scenario": { + "viewpoint": "Functional", + "context": null, + "precondition": "", + "postcondition": "", + "steps": [ + "0d05b2d3-be70-41bb-917f-8c53f066b254" + ] + } + } + } + } + } + }, + "relations": {}, + "diagrams": {}, + "artifact_links": {}, + "traces": { + "4fdbe391-8ab5-4f03-b495-21c0e7289c15": { + "id": "4fdbe391-8ab5-4f03-b495-21c0e7289c15", + "alias": "startup_order_review", + "name": "Startup fragment branches order review", + "description": "", + "oa": "b0b25d32-db6f-47c2-86e4-36f1cc111c6a", + "sa": "0d05b2d3-be70-41bb-917f-8c53f066b254", + "kind": "Manual", + "baseline": { + "oa": { + "name": "OA Alternatives", + "description": "" + }, + "sa": { + "name": "SA Alternatives", + "description": "" + }, + "semantic": { + "facets": [ + { + "Member": { + "collection": "FragmentBranches", + "keys": { + "oa": "5c1f1934-f363-4624-b7a3-84903c160ab0", + "sa": "bbeca6cb-27d0-4a35-8456-3424223e07c9" + }, + "review": { + "oa": { + "Value": true + }, + "sa": { + "Value": false + } + } + } + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/harness/fixtures/release1-native/semantic-ordered-fragment.manifest.json b/harness/fixtures/release1-native/semantic-ordered-fragment.manifest.json new file mode 100644 index 00000000..f840592b --- /dev/null +++ b/harness/fixtures/release1-native/semantic-ordered-fragment.manifest.json @@ -0,0 +1,36 @@ +{ + "direction": "OaToSa", + "facet": { + "Member": { + "collection": "FragmentBranches", + "keys": { + "oa": "5c1f1934-f363-4624-b7a3-84903c160ab0", + "sa": "bbeca6cb-27d0-4a35-8456-3424223e07c9" + } + } + }, + "final_order": [ + "d69b7042-4229-4c5e-96c7-dd54e5755a28", + "d4fe64ec-f03f-4aff-878f-7b59ee203e5d", + "bbeca6cb-27d0-4a35-8456-3424223e07c9", + "8dfa448e-ea97-4e5e-8b48-4d16b6c3f3c6" + ], + "rows": [ + [ + "ba27908d-3a78-4736-9909-f4f4b428d3cc", + "5d32fe3a-17c5-46cf-ace3-6f4a3258d2fd", + "5c1f1934-f363-4624-b7a3-84903c160ab0", + "24cb59a8-f214-4a39-81d9-9c1c33398572" + ], + [ + "d69b7042-4229-4c5e-96c7-dd54e5755a28", + "8dfa448e-ea97-4e5e-8b48-4d16b6c3f3c6", + "bbeca6cb-27d0-4a35-8456-3424223e07c9", + "d4fe64ec-f03f-4aff-878f-7b59ee203e5d" + ] + ], + "source": "b0b25d32-db6f-47c2-86e4-36f1cc111c6a", + "target": "0d05b2d3-be70-41bb-917f-8c53f066b254", + "trace": "4fdbe391-8ab5-4f03-b495-21c0e7289c15", + "trace_name": "Startup fragment branches order review" +} \ No newline at end of file diff --git a/harness/fixtures/release1-native/semantic-ordered-reverse.after-order.arcadia b/harness/fixtures/release1-native/semantic-ordered-reverse.after-order.arcadia new file mode 100644 index 00000000..a0acc5a0 --- /dev/null +++ b/harness/fixtures/release1-native/semantic-ordered-reverse.after-order.arcadia @@ -0,0 +1,163 @@ +// Preserve the authored startup order rationale. +arcadia 5; +project mission "Reverse startup sequence order review" id "1fd1cbe4-f318-494f-8a6b-e8952d43bfff" { + description ""; + + sa_occurrence request_start_216722e5 "SA Request start" id "216722e5-9b11-41b1-ac7f-6134ca7f097f" { + body { + Control { + kind Choice; + }; + }; + description ""; + parent @startup_sequence_746208c4; + postcondition ""; + precondition ""; + } + + oa_occurrence request_start "OA Request start" id "4d7c8d97-6757-48d1-a4e7-aeef5f2d1df7" { + body { + Control { + kind Choice; + }; + }; + description ""; + parent @startup_sequence; + postcondition ""; + precondition ""; + } + + oa_occurrence local_self_test "OA Local self-test" id "52b121e3-b352-4167-9025-079a7418de09" { + body { + Control { + kind Choice; + }; + }; + description ""; + parent @startup_sequence; + postcondition ""; + precondition ""; + } + + oa_occurrence energize "OA Energize" id "5a03a9cc-b43c-4e0a-a657-c0a03c8cf6b5" { + body { + Control { + kind Choice; + }; + }; + description ""; + parent @startup_sequence; + postcondition ""; + precondition ""; + } + + sa_functional_chain startup_sequence_746208c4 "SA Startup sequence" id "746208c4-d2cf-4637-a15d-a1b0008be222" { + description ""; + parent none; + steps [@request_start_216722e5, @verify_interlock_853e4d13, @energize_bfe31d5e]; + } + + sa_occurrence local_self_test_8362cd43 "SA Local self-test" id "8362cd43-9b82-4bf5-b262-14c687219c3c" { + body { + Control { + kind Choice; + }; + }; + description ""; + parent @startup_sequence_746208c4; + postcondition ""; + precondition ""; + } + + sa_occurrence verify_interlock_853e4d13 "SA Verify interlock" id "853e4d13-013c-42f9-97e8-6e86df728e7b" { + body { + Control { + kind Choice; + }; + }; + description ""; + parent @startup_sequence_746208c4; + postcondition ""; + precondition ""; + } + + oa_occurrence verify_interlock "OA Verify interlock" id "b3645f15-0805-4886-ac00-cbc82cd5b15c" { + body { + Control { + kind Choice; + }; + }; + description ""; + parent @startup_sequence; + postcondition ""; + precondition ""; + } + + sa_occurrence energize_bfe31d5e "SA Energize" id "bfe31d5e-14c8-418b-a3f2-75482e70d2c5" { + body { + Control { + kind Choice; + }; + }; + description ""; + parent @startup_sequence_746208c4; + postcondition ""; + precondition ""; + } + + oa_process startup_sequence "OA Startup sequence" id "da61a4d8-cb1c-46c2-915e-69b35eccfde1" { + description ""; + parent none; + steps [@request_start, @local_self_test, @verify_interlock, @energize]; + } + + trace startup_order_review "Reverse startup sequence order review" id "ff1f880b-5723-486a-86d2-56ec64858d58" { + baseline { + oa { + description ""; + name "OA Startup sequence"; + }; + sa { + description ""; + name "SA Startup sequence"; + }; + semantic { + facets [{ + Member { + collection Steps; + keys { + oa uuid "b3645f15-0805-4886-ac00-cbc82cd5b15c"; + sa uuid "853e4d13-013c-42f9-97e8-6e86df728e7b"; + }; + review { + oa { + Value true; + }; + sa { + Value true; + }; + }; + }; + }, + { + Order { + collection Steps; + review { + oa { + Value [uuid "4d7c8d97-6757-48d1-a4e7-aeef5f2d1df7", uuid "52b121e3-b352-4167-9025-079a7418de09", uuid "b3645f15-0805-4886-ac00-cbc82cd5b15c", uuid "5a03a9cc-b43c-4e0a-a657-c0a03c8cf6b5"]; + }; + sa { + Value [uuid "216722e5-9b11-41b1-ac7f-6134ca7f097f", uuid "853e4d13-013c-42f9-97e8-6e86df728e7b", uuid "bfe31d5e-14c8-418b-a3f2-75482e70d2c5"]; + }; + }; + }; + }]; + }; + }; + description ""; + kind Manual; + oa @startup_sequence; + sa @startup_sequence_746208c4; + } + +} diff --git a/harness/fixtures/release1-native/semantic-ordered-reverse.after-order.json b/harness/fixtures/release1-native/semantic-ordered-reverse.after-order.json new file mode 100644 index 00000000..c6bc06c6 --- /dev/null +++ b/harness/fixtures/release1-native/semantic-ordered-reverse.after-order.json @@ -0,0 +1,289 @@ +{ + "schema_version": 5, + "id": "1fd1cbe4-f318-494f-8a6b-e8952d43bfff", + "alias": "mission", + "name": "Reverse startup sequence order review", + "description": "", + "elements": { + "216722e5-9b11-41b1-ac7f-6134ca7f097f": { + "id": "216722e5-9b11-41b1-ac7f-6134ca7f097f", + "alias": "request_start_216722e5", + "name": "SA Request start", + "description": "", + "parent": "746208c4-d2cf-4637-a15d-a1b0008be222", + "body": { + "Sa": { + "Shared": { + "Occurrence": { + "body": { + "Control": { + "kind": "Choice" + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + }, + "4d7c8d97-6757-48d1-a4e7-aeef5f2d1df7": { + "id": "4d7c8d97-6757-48d1-a4e7-aeef5f2d1df7", + "alias": "request_start", + "name": "OA Request start", + "description": "", + "parent": "da61a4d8-cb1c-46c2-915e-69b35eccfde1", + "body": { + "Oa": { + "Shared": { + "Occurrence": { + "body": { + "Control": { + "kind": "Choice" + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + }, + "52b121e3-b352-4167-9025-079a7418de09": { + "id": "52b121e3-b352-4167-9025-079a7418de09", + "alias": "local_self_test", + "name": "OA Local self-test", + "description": "", + "parent": "da61a4d8-cb1c-46c2-915e-69b35eccfde1", + "body": { + "Oa": { + "Shared": { + "Occurrence": { + "body": { + "Control": { + "kind": "Choice" + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + }, + "5a03a9cc-b43c-4e0a-a657-c0a03c8cf6b5": { + "id": "5a03a9cc-b43c-4e0a-a657-c0a03c8cf6b5", + "alias": "energize", + "name": "OA Energize", + "description": "", + "parent": "da61a4d8-cb1c-46c2-915e-69b35eccfde1", + "body": { + "Oa": { + "Shared": { + "Occurrence": { + "body": { + "Control": { + "kind": "Choice" + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + }, + "746208c4-d2cf-4637-a15d-a1b0008be222": { + "id": "746208c4-d2cf-4637-a15d-a1b0008be222", + "alias": "startup_sequence_746208c4", + "name": "SA Startup sequence", + "description": "", + "parent": null, + "body": { + "Sa": { + "FunctionalChain": { + "steps": [ + "216722e5-9b11-41b1-ac7f-6134ca7f097f", + "853e4d13-013c-42f9-97e8-6e86df728e7b", + "bfe31d5e-14c8-418b-a3f2-75482e70d2c5" + ] + } + } + } + }, + "8362cd43-9b82-4bf5-b262-14c687219c3c": { + "id": "8362cd43-9b82-4bf5-b262-14c687219c3c", + "alias": "local_self_test_8362cd43", + "name": "SA Local self-test", + "description": "", + "parent": "746208c4-d2cf-4637-a15d-a1b0008be222", + "body": { + "Sa": { + "Shared": { + "Occurrence": { + "body": { + "Control": { + "kind": "Choice" + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + }, + "853e4d13-013c-42f9-97e8-6e86df728e7b": { + "id": "853e4d13-013c-42f9-97e8-6e86df728e7b", + "alias": "verify_interlock_853e4d13", + "name": "SA Verify interlock", + "description": "", + "parent": "746208c4-d2cf-4637-a15d-a1b0008be222", + "body": { + "Sa": { + "Shared": { + "Occurrence": { + "body": { + "Control": { + "kind": "Choice" + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + }, + "b3645f15-0805-4886-ac00-cbc82cd5b15c": { + "id": "b3645f15-0805-4886-ac00-cbc82cd5b15c", + "alias": "verify_interlock", + "name": "OA Verify interlock", + "description": "", + "parent": "da61a4d8-cb1c-46c2-915e-69b35eccfde1", + "body": { + "Oa": { + "Shared": { + "Occurrence": { + "body": { + "Control": { + "kind": "Choice" + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + }, + "bfe31d5e-14c8-418b-a3f2-75482e70d2c5": { + "id": "bfe31d5e-14c8-418b-a3f2-75482e70d2c5", + "alias": "energize_bfe31d5e", + "name": "SA Energize", + "description": "", + "parent": "746208c4-d2cf-4637-a15d-a1b0008be222", + "body": { + "Sa": { + "Shared": { + "Occurrence": { + "body": { + "Control": { + "kind": "Choice" + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + }, + "da61a4d8-cb1c-46c2-915e-69b35eccfde1": { + "id": "da61a4d8-cb1c-46c2-915e-69b35eccfde1", + "alias": "startup_sequence", + "name": "OA Startup sequence", + "description": "", + "parent": null, + "body": { + "Oa": { + "Process": { + "steps": [ + "4d7c8d97-6757-48d1-a4e7-aeef5f2d1df7", + "52b121e3-b352-4167-9025-079a7418de09", + "b3645f15-0805-4886-ac00-cbc82cd5b15c", + "5a03a9cc-b43c-4e0a-a657-c0a03c8cf6b5" + ] + } + } + } + } + }, + "relations": {}, + "diagrams": {}, + "artifact_links": {}, + "traces": { + "ff1f880b-5723-486a-86d2-56ec64858d58": { + "id": "ff1f880b-5723-486a-86d2-56ec64858d58", + "alias": "startup_order_review", + "name": "Reverse startup sequence order review", + "description": "", + "oa": "da61a4d8-cb1c-46c2-915e-69b35eccfde1", + "sa": "746208c4-d2cf-4637-a15d-a1b0008be222", + "kind": "Manual", + "baseline": { + "oa": { + "name": "OA Startup sequence", + "description": "" + }, + "sa": { + "name": "SA Startup sequence", + "description": "" + }, + "semantic": { + "facets": [ + { + "Member": { + "collection": "Steps", + "keys": { + "oa": "b3645f15-0805-4886-ac00-cbc82cd5b15c", + "sa": "853e4d13-013c-42f9-97e8-6e86df728e7b" + }, + "review": { + "oa": { + "Value": true + }, + "sa": { + "Value": true + } + } + } + }, + { + "Order": { + "collection": "Steps", + "review": { + "oa": { + "Value": [ + "4d7c8d97-6757-48d1-a4e7-aeef5f2d1df7", + "52b121e3-b352-4167-9025-079a7418de09", + "b3645f15-0805-4886-ac00-cbc82cd5b15c", + "5a03a9cc-b43c-4e0a-a657-c0a03c8cf6b5" + ] + }, + "sa": { + "Value": [ + "216722e5-9b11-41b1-ac7f-6134ca7f097f", + "853e4d13-013c-42f9-97e8-6e86df728e7b", + "bfe31d5e-14c8-418b-a3f2-75482e70d2c5" + ] + } + } + } + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/harness/fixtures/release1-native/semantic-ordered-reverse.ambiguous-request.json b/harness/fixtures/release1-native/semantic-ordered-reverse.ambiguous-request.json new file mode 100644 index 00000000..4f0ffc1a --- /dev/null +++ b/harness/fixtures/release1-native/semantic-ordered-reverse.ambiguous-request.json @@ -0,0 +1,50 @@ +{ + "transition": { + "direction": "SaToOa", + "sources": [ + "746208c4-d2cf-4637-a15d-a1b0008be222" + ], + "scope": "SelectedOnly", + "mappings": [ + { + "source": "746208c4-d2cf-4637-a15d-a1b0008be222", + "target": "da61a4d8-cb1c-46c2-915e-69b35eccfde1", + "target_kind": null + } + ], + "fields": [], + "resolutions": [], + "destination_diagram": null + }, + "traces": { + "746208c4-d2cf-4637-a15d-a1b0008be222": "ff1f880b-5723-486a-86d2-56ec64858d58" + }, + "references": { + "4d7c8d97-6757-48d1-a4e7-aeef5f2d1df7": "216722e5-9b11-41b1-ac7f-6134ca7f097f", + "5a03a9cc-b43c-4e0a-a657-c0a03c8cf6b5": "bfe31d5e-14c8-418b-a3f2-75482e70d2c5", + "b3645f15-0805-4886-ac00-cbc82cd5b15c": "853e4d13-013c-42f9-97e8-6e86df728e7b", + "da61a4d8-cb1c-46c2-915e-69b35eccfde1": "746208c4-d2cf-4637-a15d-a1b0008be222" + }, + "reviews": [ + { + "source": "746208c4-d2cf-4637-a15d-a1b0008be222", + "resolutions": [ + { + "facet": { + "Member": { + "collection": "Steps", + "keys": { + "oa": "b3645f15-0805-4886-ac00-cbc82cd5b15c", + "sa": "853e4d13-013c-42f9-97e8-6e86df728e7b" + } + } + }, + "decision": "TakeSource" + } + ], + "reset": [] + } + ], + "candidates": [], + "accepted_impacts": [] +} \ No newline at end of file diff --git a/harness/fixtures/release1-native/semantic-ordered-reverse.ambiguous.txt b/harness/fixtures/release1-native/semantic-ordered-reverse.ambiguous.txt new file mode 100644 index 00000000..18fc5884 --- /dev/null +++ b/harness/fixtures/release1-native/semantic-ordered-reverse.ambiguous.txt @@ -0,0 +1 @@ +Ordered member insertion has no unique unchanged anchor; review an explicit final target order \ No newline at end of file diff --git a/harness/fixtures/release1-native/semantic-ordered-reverse.arcadia b/harness/fixtures/release1-native/semantic-ordered-reverse.arcadia new file mode 100644 index 00000000..c342bbfa --- /dev/null +++ b/harness/fixtures/release1-native/semantic-ordered-reverse.arcadia @@ -0,0 +1,150 @@ +// Preserve the authored startup order rationale. +arcadia 5; +project mission "Reverse startup sequence order review" id "1fd1cbe4-f318-494f-8a6b-e8952d43bfff" { + description ""; + + sa_occurrence request_start_216722e5 "SA Request start" id "216722e5-9b11-41b1-ac7f-6134ca7f097f" { + body { + Control { + kind Choice; + }; + }; + description ""; + parent @startup_sequence_746208c4; + postcondition ""; + precondition ""; + } + + oa_occurrence request_start "OA Request start" id "4d7c8d97-6757-48d1-a4e7-aeef5f2d1df7" { + body { + Control { + kind Choice; + }; + }; + description ""; + parent @startup_sequence; + postcondition ""; + precondition ""; + } + + oa_occurrence local_self_test "OA Local self-test" id "52b121e3-b352-4167-9025-079a7418de09" { + body { + Control { + kind Choice; + }; + }; + description ""; + parent @startup_sequence; + postcondition ""; + precondition ""; + } + + oa_occurrence energize "OA Energize" id "5a03a9cc-b43c-4e0a-a657-c0a03c8cf6b5" { + body { + Control { + kind Choice; + }; + }; + description ""; + parent @startup_sequence; + postcondition ""; + precondition ""; + } + + sa_functional_chain startup_sequence_746208c4 "SA Startup sequence" id "746208c4-d2cf-4637-a15d-a1b0008be222" { + description ""; + parent none; + steps [@request_start_216722e5, @verify_interlock_853e4d13, @energize_bfe31d5e]; + } + + sa_occurrence local_self_test_8362cd43 "SA Local self-test" id "8362cd43-9b82-4bf5-b262-14c687219c3c" { + body { + Control { + kind Choice; + }; + }; + description ""; + parent @startup_sequence_746208c4; + postcondition ""; + precondition ""; + } + + sa_occurrence verify_interlock_853e4d13 "SA Verify interlock" id "853e4d13-013c-42f9-97e8-6e86df728e7b" { + body { + Control { + kind Choice; + }; + }; + description ""; + parent @startup_sequence_746208c4; + postcondition ""; + precondition ""; + } + + oa_occurrence verify_interlock "OA Verify interlock" id "b3645f15-0805-4886-ac00-cbc82cd5b15c" { + body { + Control { + kind Choice; + }; + }; + description ""; + parent @startup_sequence; + postcondition ""; + precondition ""; + } + + sa_occurrence energize_bfe31d5e "SA Energize" id "bfe31d5e-14c8-418b-a3f2-75482e70d2c5" { + body { + Control { + kind Choice; + }; + }; + description ""; + parent @startup_sequence_746208c4; + postcondition ""; + precondition ""; + } + + oa_process startup_sequence "OA Startup sequence" id "da61a4d8-cb1c-46c2-915e-69b35eccfde1" { + description ""; + parent none; + steps [@request_start, @local_self_test, @energize]; + } + + trace startup_order_review "Reverse startup sequence order review" id "ff1f880b-5723-486a-86d2-56ec64858d58" { + baseline { + oa { + description ""; + name "OA Startup sequence"; + }; + sa { + description ""; + name "SA Startup sequence"; + }; + semantic { + facets [{ + Member { + collection Steps; + keys { + oa uuid "b3645f15-0805-4886-ac00-cbc82cd5b15c"; + sa uuid "853e4d13-013c-42f9-97e8-6e86df728e7b"; + }; + review { + oa { + Value false; + }; + sa { + Value true; + }; + }; + }; + }]; + }; + }; + description ""; + kind Manual; + oa @startup_sequence; + sa @startup_sequence_746208c4; + } + +} diff --git a/harness/fixtures/release1-native/semantic-ordered-reverse.final-plan.json b/harness/fixtures/release1-native/semantic-ordered-reverse.final-plan.json new file mode 100644 index 00000000..99dd95d3 --- /dev/null +++ b/harness/fixtures/release1-native/semantic-ordered-reverse.final-plan.json @@ -0,0 +1,620 @@ +{ + "base_fingerprint": "2601871eae7d5b65", + "request": { + "transition": { + "direction": "SaToOa", + "sources": [ + "746208c4-d2cf-4637-a15d-a1b0008be222" + ], + "scope": "SelectedOnly", + "mappings": [ + { + "source": "746208c4-d2cf-4637-a15d-a1b0008be222", + "target": "da61a4d8-cb1c-46c2-915e-69b35eccfde1", + "target_kind": null + } + ], + "fields": [], + "resolutions": [], + "destination_diagram": null + }, + "traces": { + "746208c4-d2cf-4637-a15d-a1b0008be222": "ff1f880b-5723-486a-86d2-56ec64858d58" + }, + "references": { + "4d7c8d97-6757-48d1-a4e7-aeef5f2d1df7": "216722e5-9b11-41b1-ac7f-6134ca7f097f", + "5a03a9cc-b43c-4e0a-a657-c0a03c8cf6b5": "bfe31d5e-14c8-418b-a3f2-75482e70d2c5", + "b3645f15-0805-4886-ac00-cbc82cd5b15c": "853e4d13-013c-42f9-97e8-6e86df728e7b", + "da61a4d8-cb1c-46c2-915e-69b35eccfde1": "746208c4-d2cf-4637-a15d-a1b0008be222" + }, + "reviews": [ + { + "source": "746208c4-d2cf-4637-a15d-a1b0008be222", + "resolutions": [ + { + "facet": { + "Member": { + "collection": "Steps", + "keys": { + "oa": "b3645f15-0805-4886-ac00-cbc82cd5b15c", + "sa": "853e4d13-013c-42f9-97e8-6e86df728e7b" + } + } + }, + "decision": "TakeSource" + }, + { + "facet": { + "Order": "Steps" + }, + "decision": "Candidate" + } + ], + "reset": [] + } + ], + "candidates": [ + { + "Element": { + "id": "da61a4d8-cb1c-46c2-915e-69b35eccfde1", + "alias": "startup_sequence", + "name": "OA Startup sequence", + "description": "", + "parent": null, + "body": { + "Oa": { + "Process": { + "steps": [ + "4d7c8d97-6757-48d1-a4e7-aeef5f2d1df7", + "52b121e3-b352-4167-9025-079a7418de09", + "b3645f15-0805-4886-ac00-cbc82cd5b15c", + "5a03a9cc-b43c-4e0a-a657-c0a03c8cf6b5" + ] + } + } + } + } + } + ], + "accepted_impacts": [] + }, + "transition": { + "base_fingerprint": "2601871eae7d5b65", + "request": { + "direction": "SaToOa", + "sources": [ + "746208c4-d2cf-4637-a15d-a1b0008be222" + ], + "scope": "SelectedOnly", + "mappings": [ + { + "source": "746208c4-d2cf-4637-a15d-a1b0008be222", + "target": "da61a4d8-cb1c-46c2-915e-69b35eccfde1", + "target_kind": null + } + ], + "fields": [], + "resolutions": [], + "destination_diagram": null + }, + "changes": [ + { + "source": "746208c4-d2cf-4637-a15d-a1b0008be222", + "target": "da61a4d8-cb1c-46c2-915e-69b35eccfde1", + "action": "Reuse", + "fields": [] + } + ], + "conflicts": [], + "warnings": [ + "Identify the SA system of interest.", + "Process/chain 'SA Startup sequence' has 3 start contributions; connect a clear entry/exit or review the intended independent paths.", + "Process/chain 'SA Startup sequence' has 3 end contributions; connect a clear entry/exit or review the intended independent paths.", + "Connect this contribution to its process/chain path.", + "Choice needs one incoming and at least two outgoing links; currently 0 incoming and 0 outgoing.", + "Connect this contribution to its process/chain path.", + "Choice needs one incoming and at least two outgoing links; currently 0 incoming and 0 outgoing.", + "Connect this contribution to its process/chain path.", + "Choice needs one incoming and at least two outgoing links; currently 0 incoming and 0 outgoing.", + "Add this occurrence to its owner's ordered steps.", + "Add this occurrence to its owner's ordered steps.", + "Process/chain 'OA Startup sequence' has 3 start contributions; connect a clear entry/exit or review the intended independent paths.", + "Process/chain 'OA Startup sequence' has 3 end contributions; connect a clear entry/exit or review the intended independent paths.", + "Connect this contribution to its process/chain path.", + "Choice needs one incoming and at least two outgoing links; currently 0 incoming and 0 outgoing.", + "Connect this contribution to its process/chain path.", + "Choice needs one incoming and at least two outgoing links; currently 0 incoming and 0 outgoing.", + "Connect this contribution to its process/chain path.", + "Choice needs one incoming and at least two outgoing links; currently 0 incoming and 0 outgoing." + ], + "commands": [] + }, + "reviews": [ + { + "source": "746208c4-d2cf-4637-a15d-a1b0008be222", + "target": "da61a4d8-cb1c-46c2-915e-69b35eccfde1", + "facets": [ + { + "selector": { + "Header": "Name" + }, + "current": { + "Header": { + "Name": { + "oa": { + "Value": "OA Startup sequence" + }, + "sa": { + "Value": "SA Startup sequence" + } + } + } + }, + "drift": "Unreviewed" + }, + { + "selector": { + "Header": "Description" + }, + "current": { + "Header": { + "Description": { + "oa": { + "Value": "" + }, + "sa": { + "Value": "" + } + } + } + }, + "drift": "Unreviewed" + }, + { + "selector": { + "Header": "Parent" + }, + "current": { + "Header": { + "Parent": { + "oa": { + "Value": null + }, + "sa": { + "Value": null + } + } + } + }, + "drift": "Unreviewed" + }, + { + "selector": { + "Member": { + "collection": "Steps", + "keys": { + "oa": "4d7c8d97-6757-48d1-a4e7-aeef5f2d1df7", + "sa": "216722e5-9b11-41b1-ac7f-6134ca7f097f" + } + } + }, + "current": { + "Member": { + "collection": "Steps", + "keys": { + "oa": "4d7c8d97-6757-48d1-a4e7-aeef5f2d1df7", + "sa": "216722e5-9b11-41b1-ac7f-6134ca7f097f" + }, + "review": { + "oa": { + "Value": true + }, + "sa": { + "Value": true + } + } + } + }, + "drift": "Unreviewed" + }, + { + "selector": { + "Member": { + "collection": "Steps", + "keys": { + "oa": "52b121e3-b352-4167-9025-079a7418de09", + "sa": null + } + } + }, + "current": { + "Member": { + "collection": "Steps", + "keys": { + "oa": "52b121e3-b352-4167-9025-079a7418de09", + "sa": null + }, + "review": { + "oa": { + "Value": true + }, + "sa": { + "Value": false + } + } + } + }, + "drift": "Unreviewed" + }, + { + "selector": { + "Member": { + "collection": "Steps", + "keys": { + "oa": "5a03a9cc-b43c-4e0a-a657-c0a03c8cf6b5", + "sa": "bfe31d5e-14c8-418b-a3f2-75482e70d2c5" + } + } + }, + "current": { + "Member": { + "collection": "Steps", + "keys": { + "oa": "5a03a9cc-b43c-4e0a-a657-c0a03c8cf6b5", + "sa": "bfe31d5e-14c8-418b-a3f2-75482e70d2c5" + }, + "review": { + "oa": { + "Value": true + }, + "sa": { + "Value": true + } + } + } + }, + "drift": "Unreviewed" + }, + { + "selector": { + "Member": { + "collection": "Steps", + "keys": { + "oa": "b3645f15-0805-4886-ac00-cbc82cd5b15c", + "sa": "853e4d13-013c-42f9-97e8-6e86df728e7b" + } + } + }, + "current": { + "Member": { + "collection": "Steps", + "keys": { + "oa": "b3645f15-0805-4886-ac00-cbc82cd5b15c", + "sa": "853e4d13-013c-42f9-97e8-6e86df728e7b" + }, + "review": { + "oa": { + "Value": false + }, + "sa": { + "Value": true + } + } + } + }, + "drift": "Unchanged" + }, + { + "selector": { + "Order": "Steps" + }, + "current": { + "Order": { + "collection": "Steps", + "review": { + "oa": { + "Value": [ + "4d7c8d97-6757-48d1-a4e7-aeef5f2d1df7", + "52b121e3-b352-4167-9025-079a7418de09", + "5a03a9cc-b43c-4e0a-a657-c0a03c8cf6b5" + ] + }, + "sa": { + "Value": [ + "216722e5-9b11-41b1-ac7f-6134ca7f097f", + "853e4d13-013c-42f9-97e8-6e86df728e7b", + "bfe31d5e-14c8-418b-a3f2-75482e70d2c5" + ] + } + } + } + }, + "drift": "Unreviewed" + }, + { + "selector": { + "Child": { + "oa": null, + "sa": "8362cd43-9b82-4bf5-b262-14c687219c3c" + } + }, + "current": { + "Child": { + "keys": { + "oa": null, + "sa": "8362cd43-9b82-4bf5-b262-14c687219c3c" + }, + "review": { + "oa": { + "Value": false + }, + "sa": { + "Value": true + } + } + } + }, + "drift": "Unreviewed" + }, + { + "selector": { + "Child": { + "oa": "4d7c8d97-6757-48d1-a4e7-aeef5f2d1df7", + "sa": "216722e5-9b11-41b1-ac7f-6134ca7f097f" + } + }, + "current": { + "Child": { + "keys": { + "oa": "4d7c8d97-6757-48d1-a4e7-aeef5f2d1df7", + "sa": "216722e5-9b11-41b1-ac7f-6134ca7f097f" + }, + "review": { + "oa": { + "Value": true + }, + "sa": { + "Value": true + } + } + } + }, + "drift": "Unreviewed" + }, + { + "selector": { + "Child": { + "oa": "52b121e3-b352-4167-9025-079a7418de09", + "sa": null + } + }, + "current": { + "Child": { + "keys": { + "oa": "52b121e3-b352-4167-9025-079a7418de09", + "sa": null + }, + "review": { + "oa": { + "Value": true + }, + "sa": { + "Value": false + } + } + } + }, + "drift": "Unreviewed" + }, + { + "selector": { + "Child": { + "oa": "5a03a9cc-b43c-4e0a-a657-c0a03c8cf6b5", + "sa": "bfe31d5e-14c8-418b-a3f2-75482e70d2c5" + } + }, + "current": { + "Child": { + "keys": { + "oa": "5a03a9cc-b43c-4e0a-a657-c0a03c8cf6b5", + "sa": "bfe31d5e-14c8-418b-a3f2-75482e70d2c5" + }, + "review": { + "oa": { + "Value": true + }, + "sa": { + "Value": true + } + } + } + }, + "drift": "Unreviewed" + }, + { + "selector": { + "Child": { + "oa": "b3645f15-0805-4886-ac00-cbc82cd5b15c", + "sa": "853e4d13-013c-42f9-97e8-6e86df728e7b" + } + }, + "current": { + "Child": { + "keys": { + "oa": "b3645f15-0805-4886-ac00-cbc82cd5b15c", + "sa": "853e4d13-013c-42f9-97e8-6e86df728e7b" + }, + "review": { + "oa": { + "Value": true + }, + "sa": { + "Value": true + } + } + } + }, + "drift": "Unreviewed" + } + ], + "acknowledged": [ + { + "Member": { + "collection": "Steps", + "keys": { + "oa": "b3645f15-0805-4886-ac00-cbc82cd5b15c", + "sa": "853e4d13-013c-42f9-97e8-6e86df728e7b" + } + } + }, + { + "Order": "Steps" + } + ], + "retired": [], + "linked_impacts": [ + { + "objects": { + "oa": "4d7c8d97-6757-48d1-a4e7-aeef5f2d1df7", + "sa": "216722e5-9b11-41b1-ac7f-6134ca7f097f" + }, + "traces": [], + "drift": "Unreviewed", + "unreviewed": true + }, + { + "objects": { + "oa": "52b121e3-b352-4167-9025-079a7418de09", + "sa": null + }, + "traces": [], + "drift": "Unreviewed", + "unreviewed": true + }, + { + "objects": { + "oa": "5a03a9cc-b43c-4e0a-a657-c0a03c8cf6b5", + "sa": "bfe31d5e-14c8-418b-a3f2-75482e70d2c5" + }, + "traces": [], + "drift": "Unreviewed", + "unreviewed": true + }, + { + "objects": { + "oa": "b3645f15-0805-4886-ac00-cbc82cd5b15c", + "sa": "853e4d13-013c-42f9-97e8-6e86df728e7b" + }, + "traces": [], + "drift": "Unreviewed", + "unreviewed": true + }, + { + "objects": { + "oa": null, + "sa": "8362cd43-9b82-4bf5-b262-14c687219c3c" + }, + "traces": [], + "drift": "Unreviewed", + "unreviewed": true + } + ] + } + ], + "impacts": [], + "conflicts": [], + "commands": [ + { + "EditElement": [ + "da61a4d8-cb1c-46c2-915e-69b35eccfde1", + { + "id": "da61a4d8-cb1c-46c2-915e-69b35eccfde1", + "alias": "startup_sequence", + "name": "OA Startup sequence", + "description": "", + "parent": null, + "body": { + "Oa": { + "Process": { + "steps": [ + "4d7c8d97-6757-48d1-a4e7-aeef5f2d1df7", + "52b121e3-b352-4167-9025-079a7418de09", + "b3645f15-0805-4886-ac00-cbc82cd5b15c", + "5a03a9cc-b43c-4e0a-a657-c0a03c8cf6b5" + ] + } + } + } + } + ] + }, + { + "EditTrace": [ + "ff1f880b-5723-486a-86d2-56ec64858d58", + { + "id": "ff1f880b-5723-486a-86d2-56ec64858d58", + "alias": "startup_order_review", + "name": "Reverse startup sequence order review", + "description": "", + "oa": "da61a4d8-cb1c-46c2-915e-69b35eccfde1", + "sa": "746208c4-d2cf-4637-a15d-a1b0008be222", + "kind": "Manual", + "baseline": { + "oa": { + "name": "OA Startup sequence", + "description": "" + }, + "sa": { + "name": "SA Startup sequence", + "description": "" + }, + "semantic": { + "facets": [ + { + "Member": { + "collection": "Steps", + "keys": { + "oa": "b3645f15-0805-4886-ac00-cbc82cd5b15c", + "sa": "853e4d13-013c-42f9-97e8-6e86df728e7b" + }, + "review": { + "oa": { + "Value": true + }, + "sa": { + "Value": true + } + } + } + }, + { + "Order": { + "collection": "Steps", + "review": { + "oa": { + "Value": [ + "4d7c8d97-6757-48d1-a4e7-aeef5f2d1df7", + "52b121e3-b352-4167-9025-079a7418de09", + "b3645f15-0805-4886-ac00-cbc82cd5b15c", + "5a03a9cc-b43c-4e0a-a657-c0a03c8cf6b5" + ] + }, + "sa": { + "Value": [ + "216722e5-9b11-41b1-ac7f-6134ca7f097f", + "853e4d13-013c-42f9-97e8-6e86df728e7b", + "bfe31d5e-14c8-418b-a3f2-75482e70d2c5" + ] + } + } + } + } + ] + } + } + } + ] + } + ], + "prepared_fingerprint": "5cf4abd1ecb52df5" +} \ No newline at end of file diff --git a/harness/fixtures/release1-native/semantic-ordered-reverse.final-request.json b/harness/fixtures/release1-native/semantic-ordered-reverse.final-request.json new file mode 100644 index 00000000..dccfea0f --- /dev/null +++ b/harness/fixtures/release1-native/semantic-ordered-reverse.final-request.json @@ -0,0 +1,78 @@ +{ + "transition": { + "direction": "SaToOa", + "sources": [ + "746208c4-d2cf-4637-a15d-a1b0008be222" + ], + "scope": "SelectedOnly", + "mappings": [ + { + "source": "746208c4-d2cf-4637-a15d-a1b0008be222", + "target": "da61a4d8-cb1c-46c2-915e-69b35eccfde1", + "target_kind": null + } + ], + "fields": [], + "resolutions": [], + "destination_diagram": null + }, + "traces": { + "746208c4-d2cf-4637-a15d-a1b0008be222": "ff1f880b-5723-486a-86d2-56ec64858d58" + }, + "references": { + "4d7c8d97-6757-48d1-a4e7-aeef5f2d1df7": "216722e5-9b11-41b1-ac7f-6134ca7f097f", + "5a03a9cc-b43c-4e0a-a657-c0a03c8cf6b5": "bfe31d5e-14c8-418b-a3f2-75482e70d2c5", + "b3645f15-0805-4886-ac00-cbc82cd5b15c": "853e4d13-013c-42f9-97e8-6e86df728e7b", + "da61a4d8-cb1c-46c2-915e-69b35eccfde1": "746208c4-d2cf-4637-a15d-a1b0008be222" + }, + "reviews": [ + { + "source": "746208c4-d2cf-4637-a15d-a1b0008be222", + "resolutions": [ + { + "facet": { + "Member": { + "collection": "Steps", + "keys": { + "oa": "b3645f15-0805-4886-ac00-cbc82cd5b15c", + "sa": "853e4d13-013c-42f9-97e8-6e86df728e7b" + } + } + }, + "decision": "TakeSource" + }, + { + "facet": { + "Order": "Steps" + }, + "decision": "Candidate" + } + ], + "reset": [] + } + ], + "candidates": [ + { + "Element": { + "id": "da61a4d8-cb1c-46c2-915e-69b35eccfde1", + "alias": "startup_sequence", + "name": "OA Startup sequence", + "description": "", + "parent": null, + "body": { + "Oa": { + "Process": { + "steps": [ + "4d7c8d97-6757-48d1-a4e7-aeef5f2d1df7", + "52b121e3-b352-4167-9025-079a7418de09", + "b3645f15-0805-4886-ac00-cbc82cd5b15c", + "5a03a9cc-b43c-4e0a-a657-c0a03c8cf6b5" + ] + } + } + } + } + } + ], + "accepted_impacts": [] +} \ No newline at end of file diff --git a/harness/fixtures/release1-native/semantic-ordered-reverse.json b/harness/fixtures/release1-native/semantic-ordered-reverse.json new file mode 100644 index 00000000..f2dabc8b --- /dev/null +++ b/harness/fixtures/release1-native/semantic-ordered-reverse.json @@ -0,0 +1,266 @@ +{ + "schema_version": 5, + "id": "1fd1cbe4-f318-494f-8a6b-e8952d43bfff", + "alias": "mission", + "name": "Reverse startup sequence order review", + "description": "", + "elements": { + "216722e5-9b11-41b1-ac7f-6134ca7f097f": { + "id": "216722e5-9b11-41b1-ac7f-6134ca7f097f", + "alias": "request_start_216722e5", + "name": "SA Request start", + "description": "", + "parent": "746208c4-d2cf-4637-a15d-a1b0008be222", + "body": { + "Sa": { + "Shared": { + "Occurrence": { + "body": { + "Control": { + "kind": "Choice" + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + }, + "4d7c8d97-6757-48d1-a4e7-aeef5f2d1df7": { + "id": "4d7c8d97-6757-48d1-a4e7-aeef5f2d1df7", + "alias": "request_start", + "name": "OA Request start", + "description": "", + "parent": "da61a4d8-cb1c-46c2-915e-69b35eccfde1", + "body": { + "Oa": { + "Shared": { + "Occurrence": { + "body": { + "Control": { + "kind": "Choice" + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + }, + "52b121e3-b352-4167-9025-079a7418de09": { + "id": "52b121e3-b352-4167-9025-079a7418de09", + "alias": "local_self_test", + "name": "OA Local self-test", + "description": "", + "parent": "da61a4d8-cb1c-46c2-915e-69b35eccfde1", + "body": { + "Oa": { + "Shared": { + "Occurrence": { + "body": { + "Control": { + "kind": "Choice" + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + }, + "5a03a9cc-b43c-4e0a-a657-c0a03c8cf6b5": { + "id": "5a03a9cc-b43c-4e0a-a657-c0a03c8cf6b5", + "alias": "energize", + "name": "OA Energize", + "description": "", + "parent": "da61a4d8-cb1c-46c2-915e-69b35eccfde1", + "body": { + "Oa": { + "Shared": { + "Occurrence": { + "body": { + "Control": { + "kind": "Choice" + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + }, + "746208c4-d2cf-4637-a15d-a1b0008be222": { + "id": "746208c4-d2cf-4637-a15d-a1b0008be222", + "alias": "startup_sequence_746208c4", + "name": "SA Startup sequence", + "description": "", + "parent": null, + "body": { + "Sa": { + "FunctionalChain": { + "steps": [ + "216722e5-9b11-41b1-ac7f-6134ca7f097f", + "853e4d13-013c-42f9-97e8-6e86df728e7b", + "bfe31d5e-14c8-418b-a3f2-75482e70d2c5" + ] + } + } + } + }, + "8362cd43-9b82-4bf5-b262-14c687219c3c": { + "id": "8362cd43-9b82-4bf5-b262-14c687219c3c", + "alias": "local_self_test_8362cd43", + "name": "SA Local self-test", + "description": "", + "parent": "746208c4-d2cf-4637-a15d-a1b0008be222", + "body": { + "Sa": { + "Shared": { + "Occurrence": { + "body": { + "Control": { + "kind": "Choice" + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + }, + "853e4d13-013c-42f9-97e8-6e86df728e7b": { + "id": "853e4d13-013c-42f9-97e8-6e86df728e7b", + "alias": "verify_interlock_853e4d13", + "name": "SA Verify interlock", + "description": "", + "parent": "746208c4-d2cf-4637-a15d-a1b0008be222", + "body": { + "Sa": { + "Shared": { + "Occurrence": { + "body": { + "Control": { + "kind": "Choice" + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + }, + "b3645f15-0805-4886-ac00-cbc82cd5b15c": { + "id": "b3645f15-0805-4886-ac00-cbc82cd5b15c", + "alias": "verify_interlock", + "name": "OA Verify interlock", + "description": "", + "parent": "da61a4d8-cb1c-46c2-915e-69b35eccfde1", + "body": { + "Oa": { + "Shared": { + "Occurrence": { + "body": { + "Control": { + "kind": "Choice" + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + }, + "bfe31d5e-14c8-418b-a3f2-75482e70d2c5": { + "id": "bfe31d5e-14c8-418b-a3f2-75482e70d2c5", + "alias": "energize_bfe31d5e", + "name": "SA Energize", + "description": "", + "parent": "746208c4-d2cf-4637-a15d-a1b0008be222", + "body": { + "Sa": { + "Shared": { + "Occurrence": { + "body": { + "Control": { + "kind": "Choice" + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + }, + "da61a4d8-cb1c-46c2-915e-69b35eccfde1": { + "id": "da61a4d8-cb1c-46c2-915e-69b35eccfde1", + "alias": "startup_sequence", + "name": "OA Startup sequence", + "description": "", + "parent": null, + "body": { + "Oa": { + "Process": { + "steps": [ + "4d7c8d97-6757-48d1-a4e7-aeef5f2d1df7", + "52b121e3-b352-4167-9025-079a7418de09", + "5a03a9cc-b43c-4e0a-a657-c0a03c8cf6b5" + ] + } + } + } + } + }, + "relations": {}, + "diagrams": {}, + "artifact_links": {}, + "traces": { + "ff1f880b-5723-486a-86d2-56ec64858d58": { + "id": "ff1f880b-5723-486a-86d2-56ec64858d58", + "alias": "startup_order_review", + "name": "Reverse startup sequence order review", + "description": "", + "oa": "da61a4d8-cb1c-46c2-915e-69b35eccfde1", + "sa": "746208c4-d2cf-4637-a15d-a1b0008be222", + "kind": "Manual", + "baseline": { + "oa": { + "name": "OA Startup sequence", + "description": "" + }, + "sa": { + "name": "SA Startup sequence", + "description": "" + }, + "semantic": { + "facets": [ + { + "Member": { + "collection": "Steps", + "keys": { + "oa": "b3645f15-0805-4886-ac00-cbc82cd5b15c", + "sa": "853e4d13-013c-42f9-97e8-6e86df728e7b" + }, + "review": { + "oa": { + "Value": false + }, + "sa": { + "Value": true + } + } + } + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/harness/fixtures/release1-native/semantic-ordered-reverse.manifest.json b/harness/fixtures/release1-native/semantic-ordered-reverse.manifest.json new file mode 100644 index 00000000..b21f3004 --- /dev/null +++ b/harness/fixtures/release1-native/semantic-ordered-reverse.manifest.json @@ -0,0 +1,36 @@ +{ + "direction": "SaToOa", + "facet": { + "Member": { + "collection": "Steps", + "keys": { + "oa": "b3645f15-0805-4886-ac00-cbc82cd5b15c", + "sa": "853e4d13-013c-42f9-97e8-6e86df728e7b" + } + } + }, + "final_order": [ + "4d7c8d97-6757-48d1-a4e7-aeef5f2d1df7", + "52b121e3-b352-4167-9025-079a7418de09", + "b3645f15-0805-4886-ac00-cbc82cd5b15c", + "5a03a9cc-b43c-4e0a-a657-c0a03c8cf6b5" + ], + "rows": [ + [ + "4d7c8d97-6757-48d1-a4e7-aeef5f2d1df7", + "5a03a9cc-b43c-4e0a-a657-c0a03c8cf6b5", + "b3645f15-0805-4886-ac00-cbc82cd5b15c", + "52b121e3-b352-4167-9025-079a7418de09" + ], + [ + "216722e5-9b11-41b1-ac7f-6134ca7f097f", + "bfe31d5e-14c8-418b-a3f2-75482e70d2c5", + "853e4d13-013c-42f9-97e8-6e86df728e7b", + "8362cd43-9b82-4bf5-b262-14c687219c3c" + ] + ], + "source": "746208c4-d2cf-4637-a15d-a1b0008be222", + "target": "da61a4d8-cb1c-46c2-915e-69b35eccfde1", + "trace": "ff1f880b-5723-486a-86d2-56ec64858d58", + "trace_name": "Reverse startup sequence order review" +} \ No newline at end of file diff --git a/harness/fixtures/release1-native/semantic-ordered-scenario.after-order.arcadia b/harness/fixtures/release1-native/semantic-ordered-scenario.after-order.arcadia new file mode 100644 index 00000000..d79d1fb6 --- /dev/null +++ b/harness/fixtures/release1-native/semantic-ordered-scenario.after-order.arcadia @@ -0,0 +1,179 @@ +// Preserve the authored startup order rationale. +arcadia 5; +project mission "Startup scenario order review" id "44ee1446-6c83-46da-a0c7-7780d3719881" { + description ""; + + sa_occurrence energize_10ca3141 "SA Energize" id "10ca3141-0fec-4158-9bb4-020cac14fdfc" { + body { + Action { + action none; + lifeline none; + }; + }; + description ""; + parent @startup_scenario_9fe4d605; + postcondition ""; + precondition ""; + } + + oa_occurrence request_start "OA Request start" id "1c0cb002-a7a5-4e6d-9e55-b37fa574e096" { + body { + Action { + action none; + lifeline none; + }; + }; + description ""; + parent @startup_scenario; + postcondition ""; + precondition ""; + } + + oa_occurrence local_self_test "OA Local self-test" id "30abc877-03ba-4c5b-9222-3c51222f197a" { + body { + Action { + action none; + lifeline none; + }; + }; + description ""; + parent @startup_scenario; + postcondition ""; + precondition ""; + } + + sa_occurrence request_start_8b82f67f "SA Request start" id "8b82f67f-c027-416d-9f8f-0d4057674b50" { + body { + Action { + action none; + lifeline none; + }; + }; + description ""; + parent @startup_scenario_9fe4d605; + postcondition ""; + precondition ""; + } + + sa_scenario startup_scenario_9fe4d605 "SA Startup scenario" id "9fe4d605-5e9d-4486-bc5d-f0ec660a8601" { + context none; + description ""; + parent none; + postcondition ""; + precondition ""; + steps [@request_start_8b82f67f, @local_self_test_de57a577, @verify_interlock_cedb45fb, @energize_10ca3141]; + viewpoint Functional; + } + + oa_occurrence energize "OA Energize" id "b3eaf78f-d793-433c-853b-4a27cdf0e809" { + body { + Action { + action none; + lifeline none; + }; + }; + description ""; + parent @startup_scenario; + postcondition ""; + precondition ""; + } + + oa_scenario startup_scenario "OA Startup scenario" id "b82aea81-8e09-4436-8877-0a097d86a10a" { + context none; + description ""; + parent none; + postcondition ""; + precondition ""; + steps [@request_start, @verify_interlock, @energize]; + viewpoint OperationalActivity; + } + + sa_occurrence verify_interlock_cedb45fb "SA Verify interlock" id "cedb45fb-83d2-4b6f-b812-51853b7dcaf7" { + body { + Action { + action none; + lifeline none; + }; + }; + description ""; + parent @startup_scenario_9fe4d605; + postcondition ""; + precondition ""; + } + + oa_occurrence verify_interlock "OA Verify interlock" id "cfaa7b0f-3823-4a9b-a456-7bb31c233b55" { + body { + Action { + action none; + lifeline none; + }; + }; + description ""; + parent @startup_scenario; + postcondition ""; + precondition ""; + } + + sa_occurrence local_self_test_de57a577 "SA Local self-test" id "de57a577-75f3-4bdc-a5d0-b09d44defde0" { + body { + Action { + action none; + lifeline none; + }; + }; + description ""; + parent @startup_scenario_9fe4d605; + postcondition ""; + precondition ""; + } + + trace startup_order_review "Startup scenario order review" id "2cd537f1-fef4-4593-a5e9-c639fcc9fd97" { + baseline { + oa { + description ""; + name "OA Startup scenario"; + }; + sa { + description ""; + name "SA Startup scenario"; + }; + semantic { + facets [{ + Member { + collection Steps; + keys { + oa uuid "cfaa7b0f-3823-4a9b-a456-7bb31c233b55"; + sa uuid "cedb45fb-83d2-4b6f-b812-51853b7dcaf7"; + }; + review { + oa { + Value true; + }; + sa { + Value true; + }; + }; + }; + }, + { + Order { + collection Steps; + review { + oa { + Value [uuid "1c0cb002-a7a5-4e6d-9e55-b37fa574e096", uuid "cfaa7b0f-3823-4a9b-a456-7bb31c233b55", uuid "b3eaf78f-d793-433c-853b-4a27cdf0e809"]; + }; + sa { + Value [uuid "8b82f67f-c027-416d-9f8f-0d4057674b50", uuid "de57a577-75f3-4bdc-a5d0-b09d44defde0", uuid "cedb45fb-83d2-4b6f-b812-51853b7dcaf7", uuid "10ca3141-0fec-4158-9bb4-020cac14fdfc"]; + }; + }; + }; + }]; + }; + }; + description ""; + kind Manual; + oa @startup_scenario; + sa @startup_scenario_9fe4d605; + } + +} diff --git a/harness/fixtures/release1-native/semantic-ordered-scenario.after-order.json b/harness/fixtures/release1-native/semantic-ordered-scenario.after-order.json new file mode 100644 index 00000000..6594c86e --- /dev/null +++ b/harness/fixtures/release1-native/semantic-ordered-scenario.after-order.json @@ -0,0 +1,309 @@ +{ + "schema_version": 5, + "id": "44ee1446-6c83-46da-a0c7-7780d3719881", + "alias": "mission", + "name": "Startup scenario order review", + "description": "", + "elements": { + "10ca3141-0fec-4158-9bb4-020cac14fdfc": { + "id": "10ca3141-0fec-4158-9bb4-020cac14fdfc", + "alias": "energize_10ca3141", + "name": "SA Energize", + "description": "", + "parent": "9fe4d605-5e9d-4486-bc5d-f0ec660a8601", + "body": { + "Sa": { + "Shared": { + "Occurrence": { + "body": { + "Action": { + "action": null, + "lifeline": null + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + }, + "1c0cb002-a7a5-4e6d-9e55-b37fa574e096": { + "id": "1c0cb002-a7a5-4e6d-9e55-b37fa574e096", + "alias": "request_start", + "name": "OA Request start", + "description": "", + "parent": "b82aea81-8e09-4436-8877-0a097d86a10a", + "body": { + "Oa": { + "Shared": { + "Occurrence": { + "body": { + "Action": { + "action": null, + "lifeline": null + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + }, + "30abc877-03ba-4c5b-9222-3c51222f197a": { + "id": "30abc877-03ba-4c5b-9222-3c51222f197a", + "alias": "local_self_test", + "name": "OA Local self-test", + "description": "", + "parent": "b82aea81-8e09-4436-8877-0a097d86a10a", + "body": { + "Oa": { + "Shared": { + "Occurrence": { + "body": { + "Action": { + "action": null, + "lifeline": null + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + }, + "8b82f67f-c027-416d-9f8f-0d4057674b50": { + "id": "8b82f67f-c027-416d-9f8f-0d4057674b50", + "alias": "request_start_8b82f67f", + "name": "SA Request start", + "description": "", + "parent": "9fe4d605-5e9d-4486-bc5d-f0ec660a8601", + "body": { + "Sa": { + "Shared": { + "Occurrence": { + "body": { + "Action": { + "action": null, + "lifeline": null + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + }, + "9fe4d605-5e9d-4486-bc5d-f0ec660a8601": { + "id": "9fe4d605-5e9d-4486-bc5d-f0ec660a8601", + "alias": "startup_scenario_9fe4d605", + "name": "SA Startup scenario", + "description": "", + "parent": null, + "body": { + "Sa": { + "Shared": { + "Scenario": { + "viewpoint": "Functional", + "context": null, + "precondition": "", + "postcondition": "", + "steps": [ + "8b82f67f-c027-416d-9f8f-0d4057674b50", + "de57a577-75f3-4bdc-a5d0-b09d44defde0", + "cedb45fb-83d2-4b6f-b812-51853b7dcaf7", + "10ca3141-0fec-4158-9bb4-020cac14fdfc" + ] + } + } + } + } + }, + "b3eaf78f-d793-433c-853b-4a27cdf0e809": { + "id": "b3eaf78f-d793-433c-853b-4a27cdf0e809", + "alias": "energize", + "name": "OA Energize", + "description": "", + "parent": "b82aea81-8e09-4436-8877-0a097d86a10a", + "body": { + "Oa": { + "Shared": { + "Occurrence": { + "body": { + "Action": { + "action": null, + "lifeline": null + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + }, + "b82aea81-8e09-4436-8877-0a097d86a10a": { + "id": "b82aea81-8e09-4436-8877-0a097d86a10a", + "alias": "startup_scenario", + "name": "OA Startup scenario", + "description": "", + "parent": null, + "body": { + "Oa": { + "Shared": { + "Scenario": { + "viewpoint": "OperationalActivity", + "context": null, + "precondition": "", + "postcondition": "", + "steps": [ + "1c0cb002-a7a5-4e6d-9e55-b37fa574e096", + "cfaa7b0f-3823-4a9b-a456-7bb31c233b55", + "b3eaf78f-d793-433c-853b-4a27cdf0e809" + ] + } + } + } + } + }, + "cedb45fb-83d2-4b6f-b812-51853b7dcaf7": { + "id": "cedb45fb-83d2-4b6f-b812-51853b7dcaf7", + "alias": "verify_interlock_cedb45fb", + "name": "SA Verify interlock", + "description": "", + "parent": "9fe4d605-5e9d-4486-bc5d-f0ec660a8601", + "body": { + "Sa": { + "Shared": { + "Occurrence": { + "body": { + "Action": { + "action": null, + "lifeline": null + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + }, + "cfaa7b0f-3823-4a9b-a456-7bb31c233b55": { + "id": "cfaa7b0f-3823-4a9b-a456-7bb31c233b55", + "alias": "verify_interlock", + "name": "OA Verify interlock", + "description": "", + "parent": "b82aea81-8e09-4436-8877-0a097d86a10a", + "body": { + "Oa": { + "Shared": { + "Occurrence": { + "body": { + "Action": { + "action": null, + "lifeline": null + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + }, + "de57a577-75f3-4bdc-a5d0-b09d44defde0": { + "id": "de57a577-75f3-4bdc-a5d0-b09d44defde0", + "alias": "local_self_test_de57a577", + "name": "SA Local self-test", + "description": "", + "parent": "9fe4d605-5e9d-4486-bc5d-f0ec660a8601", + "body": { + "Sa": { + "Shared": { + "Occurrence": { + "body": { + "Action": { + "action": null, + "lifeline": null + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + } + }, + "relations": {}, + "diagrams": {}, + "artifact_links": {}, + "traces": { + "2cd537f1-fef4-4593-a5e9-c639fcc9fd97": { + "id": "2cd537f1-fef4-4593-a5e9-c639fcc9fd97", + "alias": "startup_order_review", + "name": "Startup scenario order review", + "description": "", + "oa": "b82aea81-8e09-4436-8877-0a097d86a10a", + "sa": "9fe4d605-5e9d-4486-bc5d-f0ec660a8601", + "kind": "Manual", + "baseline": { + "oa": { + "name": "OA Startup scenario", + "description": "" + }, + "sa": { + "name": "SA Startup scenario", + "description": "" + }, + "semantic": { + "facets": [ + { + "Member": { + "collection": "Steps", + "keys": { + "oa": "cfaa7b0f-3823-4a9b-a456-7bb31c233b55", + "sa": "cedb45fb-83d2-4b6f-b812-51853b7dcaf7" + }, + "review": { + "oa": { + "Value": true + }, + "sa": { + "Value": true + } + } + } + }, + { + "Order": { + "collection": "Steps", + "review": { + "oa": { + "Value": [ + "1c0cb002-a7a5-4e6d-9e55-b37fa574e096", + "cfaa7b0f-3823-4a9b-a456-7bb31c233b55", + "b3eaf78f-d793-433c-853b-4a27cdf0e809" + ] + }, + "sa": { + "Value": [ + "8b82f67f-c027-416d-9f8f-0d4057674b50", + "de57a577-75f3-4bdc-a5d0-b09d44defde0", + "cedb45fb-83d2-4b6f-b812-51853b7dcaf7", + "10ca3141-0fec-4158-9bb4-020cac14fdfc" + ] + } + } + } + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/harness/fixtures/release1-native/semantic-ordered-scenario.ambiguous-request.json b/harness/fixtures/release1-native/semantic-ordered-scenario.ambiguous-request.json new file mode 100644 index 00000000..bd6f1458 --- /dev/null +++ b/harness/fixtures/release1-native/semantic-ordered-scenario.ambiguous-request.json @@ -0,0 +1,50 @@ +{ + "transition": { + "direction": "OaToSa", + "sources": [ + "b82aea81-8e09-4436-8877-0a097d86a10a" + ], + "scope": "SelectedOnly", + "mappings": [ + { + "source": "b82aea81-8e09-4436-8877-0a097d86a10a", + "target": "9fe4d605-5e9d-4486-bc5d-f0ec660a8601", + "target_kind": null + } + ], + "fields": [], + "resolutions": [], + "destination_diagram": null + }, + "traces": { + "b82aea81-8e09-4436-8877-0a097d86a10a": "2cd537f1-fef4-4593-a5e9-c639fcc9fd97" + }, + "references": { + "1c0cb002-a7a5-4e6d-9e55-b37fa574e096": "8b82f67f-c027-416d-9f8f-0d4057674b50", + "b3eaf78f-d793-433c-853b-4a27cdf0e809": "10ca3141-0fec-4158-9bb4-020cac14fdfc", + "b82aea81-8e09-4436-8877-0a097d86a10a": "9fe4d605-5e9d-4486-bc5d-f0ec660a8601", + "cfaa7b0f-3823-4a9b-a456-7bb31c233b55": "cedb45fb-83d2-4b6f-b812-51853b7dcaf7" + }, + "reviews": [ + { + "source": "b82aea81-8e09-4436-8877-0a097d86a10a", + "resolutions": [ + { + "facet": { + "Member": { + "collection": "Steps", + "keys": { + "oa": "cfaa7b0f-3823-4a9b-a456-7bb31c233b55", + "sa": "cedb45fb-83d2-4b6f-b812-51853b7dcaf7" + } + } + }, + "decision": "TakeSource" + } + ], + "reset": [] + } + ], + "candidates": [], + "accepted_impacts": [] +} \ No newline at end of file diff --git a/harness/fixtures/release1-native/semantic-ordered-scenario.ambiguous.txt b/harness/fixtures/release1-native/semantic-ordered-scenario.ambiguous.txt new file mode 100644 index 00000000..18fc5884 --- /dev/null +++ b/harness/fixtures/release1-native/semantic-ordered-scenario.ambiguous.txt @@ -0,0 +1 @@ +Ordered member insertion has no unique unchanged anchor; review an explicit final target order \ No newline at end of file diff --git a/harness/fixtures/release1-native/semantic-ordered-scenario.arcadia b/harness/fixtures/release1-native/semantic-ordered-scenario.arcadia new file mode 100644 index 00000000..fea4a0a6 --- /dev/null +++ b/harness/fixtures/release1-native/semantic-ordered-scenario.arcadia @@ -0,0 +1,166 @@ +// Preserve the authored startup order rationale. +arcadia 5; +project mission "Startup scenario order review" id "44ee1446-6c83-46da-a0c7-7780d3719881" { + description ""; + + sa_occurrence energize_10ca3141 "SA Energize" id "10ca3141-0fec-4158-9bb4-020cac14fdfc" { + body { + Action { + action none; + lifeline none; + }; + }; + description ""; + parent @startup_scenario_9fe4d605; + postcondition ""; + precondition ""; + } + + oa_occurrence request_start "OA Request start" id "1c0cb002-a7a5-4e6d-9e55-b37fa574e096" { + body { + Action { + action none; + lifeline none; + }; + }; + description ""; + parent @startup_scenario; + postcondition ""; + precondition ""; + } + + oa_occurrence local_self_test "OA Local self-test" id "30abc877-03ba-4c5b-9222-3c51222f197a" { + body { + Action { + action none; + lifeline none; + }; + }; + description ""; + parent @startup_scenario; + postcondition ""; + precondition ""; + } + + sa_occurrence request_start_8b82f67f "SA Request start" id "8b82f67f-c027-416d-9f8f-0d4057674b50" { + body { + Action { + action none; + lifeline none; + }; + }; + description ""; + parent @startup_scenario_9fe4d605; + postcondition ""; + precondition ""; + } + + sa_scenario startup_scenario_9fe4d605 "SA Startup scenario" id "9fe4d605-5e9d-4486-bc5d-f0ec660a8601" { + context none; + description ""; + parent none; + postcondition ""; + precondition ""; + steps [@request_start_8b82f67f, @local_self_test_de57a577, @energize_10ca3141]; + viewpoint Functional; + } + + oa_occurrence energize "OA Energize" id "b3eaf78f-d793-433c-853b-4a27cdf0e809" { + body { + Action { + action none; + lifeline none; + }; + }; + description ""; + parent @startup_scenario; + postcondition ""; + precondition ""; + } + + oa_scenario startup_scenario "OA Startup scenario" id "b82aea81-8e09-4436-8877-0a097d86a10a" { + context none; + description ""; + parent none; + postcondition ""; + precondition ""; + steps [@request_start, @verify_interlock, @energize]; + viewpoint OperationalActivity; + } + + sa_occurrence verify_interlock_cedb45fb "SA Verify interlock" id "cedb45fb-83d2-4b6f-b812-51853b7dcaf7" { + body { + Action { + action none; + lifeline none; + }; + }; + description ""; + parent @startup_scenario_9fe4d605; + postcondition ""; + precondition ""; + } + + oa_occurrence verify_interlock "OA Verify interlock" id "cfaa7b0f-3823-4a9b-a456-7bb31c233b55" { + body { + Action { + action none; + lifeline none; + }; + }; + description ""; + parent @startup_scenario; + postcondition ""; + precondition ""; + } + + sa_occurrence local_self_test_de57a577 "SA Local self-test" id "de57a577-75f3-4bdc-a5d0-b09d44defde0" { + body { + Action { + action none; + lifeline none; + }; + }; + description ""; + parent @startup_scenario_9fe4d605; + postcondition ""; + precondition ""; + } + + trace startup_order_review "Startup scenario order review" id "2cd537f1-fef4-4593-a5e9-c639fcc9fd97" { + baseline { + oa { + description ""; + name "OA Startup scenario"; + }; + sa { + description ""; + name "SA Startup scenario"; + }; + semantic { + facets [{ + Member { + collection Steps; + keys { + oa uuid "cfaa7b0f-3823-4a9b-a456-7bb31c233b55"; + sa uuid "cedb45fb-83d2-4b6f-b812-51853b7dcaf7"; + }; + review { + oa { + Value true; + }; + sa { + Value false; + }; + }; + }; + }]; + }; + }; + description ""; + kind Manual; + oa @startup_scenario; + sa @startup_scenario_9fe4d605; + } + +} diff --git a/harness/fixtures/release1-native/semantic-ordered-scenario.final-plan.json b/harness/fixtures/release1-native/semantic-ordered-scenario.final-plan.json new file mode 100644 index 00000000..64b44e26 --- /dev/null +++ b/harness/fixtures/release1-native/semantic-ordered-scenario.final-plan.json @@ -0,0 +1,704 @@ +{ + "base_fingerprint": "3af87e50a39fbaae", + "request": { + "transition": { + "direction": "OaToSa", + "sources": [ + "b82aea81-8e09-4436-8877-0a097d86a10a" + ], + "scope": "SelectedOnly", + "mappings": [ + { + "source": "b82aea81-8e09-4436-8877-0a097d86a10a", + "target": "9fe4d605-5e9d-4486-bc5d-f0ec660a8601", + "target_kind": null + } + ], + "fields": [], + "resolutions": [], + "destination_diagram": null + }, + "traces": { + "b82aea81-8e09-4436-8877-0a097d86a10a": "2cd537f1-fef4-4593-a5e9-c639fcc9fd97" + }, + "references": { + "1c0cb002-a7a5-4e6d-9e55-b37fa574e096": "8b82f67f-c027-416d-9f8f-0d4057674b50", + "b3eaf78f-d793-433c-853b-4a27cdf0e809": "10ca3141-0fec-4158-9bb4-020cac14fdfc", + "b82aea81-8e09-4436-8877-0a097d86a10a": "9fe4d605-5e9d-4486-bc5d-f0ec660a8601", + "cfaa7b0f-3823-4a9b-a456-7bb31c233b55": "cedb45fb-83d2-4b6f-b812-51853b7dcaf7" + }, + "reviews": [ + { + "source": "b82aea81-8e09-4436-8877-0a097d86a10a", + "resolutions": [ + { + "facet": { + "Member": { + "collection": "Steps", + "keys": { + "oa": "cfaa7b0f-3823-4a9b-a456-7bb31c233b55", + "sa": "cedb45fb-83d2-4b6f-b812-51853b7dcaf7" + } + } + }, + "decision": "TakeSource" + }, + { + "facet": { + "Order": "Steps" + }, + "decision": "Candidate" + } + ], + "reset": [] + } + ], + "candidates": [ + { + "Element": { + "id": "9fe4d605-5e9d-4486-bc5d-f0ec660a8601", + "alias": "startup_scenario_9fe4d605", + "name": "SA Startup scenario", + "description": "", + "parent": null, + "body": { + "Sa": { + "Shared": { + "Scenario": { + "viewpoint": "Functional", + "context": null, + "precondition": "", + "postcondition": "", + "steps": [ + "8b82f67f-c027-416d-9f8f-0d4057674b50", + "de57a577-75f3-4bdc-a5d0-b09d44defde0", + "cedb45fb-83d2-4b6f-b812-51853b7dcaf7", + "10ca3141-0fec-4158-9bb4-020cac14fdfc" + ] + } + } + } + } + } + } + ], + "accepted_impacts": [] + }, + "transition": { + "base_fingerprint": "3af87e50a39fbaae", + "request": { + "direction": "OaToSa", + "sources": [ + "b82aea81-8e09-4436-8877-0a097d86a10a" + ], + "scope": "SelectedOnly", + "mappings": [ + { + "source": "b82aea81-8e09-4436-8877-0a097d86a10a", + "target": "9fe4d605-5e9d-4486-bc5d-f0ec660a8601", + "target_kind": null + } + ], + "fields": [], + "resolutions": [], + "destination_diagram": null + }, + "changes": [ + { + "source": "b82aea81-8e09-4436-8877-0a097d86a10a", + "target": "9fe4d605-5e9d-4486-bc5d-f0ec660a8601", + "action": "Reuse", + "fields": [] + } + ], + "conflicts": [], + "warnings": [ + "Identify the SA system of interest.", + "Choose action.", + "Choose lifeline.", + "Choose action.", + "Choose lifeline.", + "Choose action.", + "Choose lifeline.", + "Add this occurrence to its owner's ordered steps.", + "Choose action.", + "Choose lifeline.", + "Choose action.", + "Choose lifeline.", + "Choose action.", + "Choose lifeline.", + "Add this occurrence to its owner's ordered steps.", + "Choose action.", + "Choose lifeline.", + "Choose action.", + "Choose lifeline." + ], + "commands": [] + }, + "reviews": [ + { + "source": "b82aea81-8e09-4436-8877-0a097d86a10a", + "target": "9fe4d605-5e9d-4486-bc5d-f0ec660a8601", + "facets": [ + { + "selector": { + "Header": "Name" + }, + "current": { + "Header": { + "Name": { + "oa": { + "Value": "OA Startup scenario" + }, + "sa": { + "Value": "SA Startup scenario" + } + } + } + }, + "drift": "Unreviewed" + }, + { + "selector": { + "Header": "Description" + }, + "current": { + "Header": { + "Description": { + "oa": { + "Value": "" + }, + "sa": { + "Value": "" + } + } + } + }, + "drift": "Unreviewed" + }, + { + "selector": { + "Header": "Parent" + }, + "current": { + "Header": { + "Parent": { + "oa": { + "Value": null + }, + "sa": { + "Value": null + } + } + } + }, + "drift": "Unreviewed" + }, + { + "selector": { + "Scenario": "Viewpoint" + }, + "current": { + "Scenario": { + "Viewpoint": { + "oa": { + "Value": "OperationalActivity" + }, + "sa": { + "Value": "Functional" + } + } + } + }, + "drift": "Unreviewed" + }, + { + "selector": { + "Scenario": "Context" + }, + "current": { + "Scenario": { + "Context": { + "oa": { + "Value": null + }, + "sa": { + "Value": null + } + } + } + }, + "drift": "Unreviewed" + }, + { + "selector": { + "Scenario": "Precondition" + }, + "current": { + "Scenario": { + "Precondition": { + "oa": { + "Value": "" + }, + "sa": { + "Value": "" + } + } + } + }, + "drift": "Unreviewed" + }, + { + "selector": { + "Scenario": "Postcondition" + }, + "current": { + "Scenario": { + "Postcondition": { + "oa": { + "Value": "" + }, + "sa": { + "Value": "" + } + } + } + }, + "drift": "Unreviewed" + }, + { + "selector": { + "Member": { + "collection": "Steps", + "keys": { + "oa": null, + "sa": "de57a577-75f3-4bdc-a5d0-b09d44defde0" + } + } + }, + "current": { + "Member": { + "collection": "Steps", + "keys": { + "oa": null, + "sa": "de57a577-75f3-4bdc-a5d0-b09d44defde0" + }, + "review": { + "oa": { + "Value": false + }, + "sa": { + "Value": true + } + } + } + }, + "drift": "Unreviewed" + }, + { + "selector": { + "Member": { + "collection": "Steps", + "keys": { + "oa": "1c0cb002-a7a5-4e6d-9e55-b37fa574e096", + "sa": "8b82f67f-c027-416d-9f8f-0d4057674b50" + } + } + }, + "current": { + "Member": { + "collection": "Steps", + "keys": { + "oa": "1c0cb002-a7a5-4e6d-9e55-b37fa574e096", + "sa": "8b82f67f-c027-416d-9f8f-0d4057674b50" + }, + "review": { + "oa": { + "Value": true + }, + "sa": { + "Value": true + } + } + } + }, + "drift": "Unreviewed" + }, + { + "selector": { + "Member": { + "collection": "Steps", + "keys": { + "oa": "b3eaf78f-d793-433c-853b-4a27cdf0e809", + "sa": "10ca3141-0fec-4158-9bb4-020cac14fdfc" + } + } + }, + "current": { + "Member": { + "collection": "Steps", + "keys": { + "oa": "b3eaf78f-d793-433c-853b-4a27cdf0e809", + "sa": "10ca3141-0fec-4158-9bb4-020cac14fdfc" + }, + "review": { + "oa": { + "Value": true + }, + "sa": { + "Value": true + } + } + } + }, + "drift": "Unreviewed" + }, + { + "selector": { + "Member": { + "collection": "Steps", + "keys": { + "oa": "cfaa7b0f-3823-4a9b-a456-7bb31c233b55", + "sa": "cedb45fb-83d2-4b6f-b812-51853b7dcaf7" + } + } + }, + "current": { + "Member": { + "collection": "Steps", + "keys": { + "oa": "cfaa7b0f-3823-4a9b-a456-7bb31c233b55", + "sa": "cedb45fb-83d2-4b6f-b812-51853b7dcaf7" + }, + "review": { + "oa": { + "Value": true + }, + "sa": { + "Value": false + } + } + } + }, + "drift": "Unchanged" + }, + { + "selector": { + "Order": "Steps" + }, + "current": { + "Order": { + "collection": "Steps", + "review": { + "oa": { + "Value": [ + "1c0cb002-a7a5-4e6d-9e55-b37fa574e096", + "cfaa7b0f-3823-4a9b-a456-7bb31c233b55", + "b3eaf78f-d793-433c-853b-4a27cdf0e809" + ] + }, + "sa": { + "Value": [ + "8b82f67f-c027-416d-9f8f-0d4057674b50", + "de57a577-75f3-4bdc-a5d0-b09d44defde0", + "10ca3141-0fec-4158-9bb4-020cac14fdfc" + ] + } + } + } + }, + "drift": "Unreviewed" + }, + { + "selector": { + "Child": { + "oa": null, + "sa": "de57a577-75f3-4bdc-a5d0-b09d44defde0" + } + }, + "current": { + "Child": { + "keys": { + "oa": null, + "sa": "de57a577-75f3-4bdc-a5d0-b09d44defde0" + }, + "review": { + "oa": { + "Value": false + }, + "sa": { + "Value": true + } + } + } + }, + "drift": "Unreviewed" + }, + { + "selector": { + "Child": { + "oa": "1c0cb002-a7a5-4e6d-9e55-b37fa574e096", + "sa": "8b82f67f-c027-416d-9f8f-0d4057674b50" + } + }, + "current": { + "Child": { + "keys": { + "oa": "1c0cb002-a7a5-4e6d-9e55-b37fa574e096", + "sa": "8b82f67f-c027-416d-9f8f-0d4057674b50" + }, + "review": { + "oa": { + "Value": true + }, + "sa": { + "Value": true + } + } + } + }, + "drift": "Unreviewed" + }, + { + "selector": { + "Child": { + "oa": "30abc877-03ba-4c5b-9222-3c51222f197a", + "sa": null + } + }, + "current": { + "Child": { + "keys": { + "oa": "30abc877-03ba-4c5b-9222-3c51222f197a", + "sa": null + }, + "review": { + "oa": { + "Value": true + }, + "sa": { + "Value": false + } + } + } + }, + "drift": "Unreviewed" + }, + { + "selector": { + "Child": { + "oa": "b3eaf78f-d793-433c-853b-4a27cdf0e809", + "sa": "10ca3141-0fec-4158-9bb4-020cac14fdfc" + } + }, + "current": { + "Child": { + "keys": { + "oa": "b3eaf78f-d793-433c-853b-4a27cdf0e809", + "sa": "10ca3141-0fec-4158-9bb4-020cac14fdfc" + }, + "review": { + "oa": { + "Value": true + }, + "sa": { + "Value": true + } + } + } + }, + "drift": "Unreviewed" + }, + { + "selector": { + "Child": { + "oa": "cfaa7b0f-3823-4a9b-a456-7bb31c233b55", + "sa": "cedb45fb-83d2-4b6f-b812-51853b7dcaf7" + } + }, + "current": { + "Child": { + "keys": { + "oa": "cfaa7b0f-3823-4a9b-a456-7bb31c233b55", + "sa": "cedb45fb-83d2-4b6f-b812-51853b7dcaf7" + }, + "review": { + "oa": { + "Value": true + }, + "sa": { + "Value": true + } + } + } + }, + "drift": "Unreviewed" + } + ], + "acknowledged": [ + { + "Member": { + "collection": "Steps", + "keys": { + "oa": "cfaa7b0f-3823-4a9b-a456-7bb31c233b55", + "sa": "cedb45fb-83d2-4b6f-b812-51853b7dcaf7" + } + } + }, + { + "Order": "Steps" + } + ], + "retired": [], + "linked_impacts": [ + { + "objects": { + "oa": "1c0cb002-a7a5-4e6d-9e55-b37fa574e096", + "sa": "8b82f67f-c027-416d-9f8f-0d4057674b50" + }, + "traces": [], + "drift": "Unreviewed", + "unreviewed": true + }, + { + "objects": { + "oa": "30abc877-03ba-4c5b-9222-3c51222f197a", + "sa": null + }, + "traces": [], + "drift": "Unreviewed", + "unreviewed": true + }, + { + "objects": { + "oa": "b3eaf78f-d793-433c-853b-4a27cdf0e809", + "sa": "10ca3141-0fec-4158-9bb4-020cac14fdfc" + }, + "traces": [], + "drift": "Unreviewed", + "unreviewed": true + }, + { + "objects": { + "oa": "cfaa7b0f-3823-4a9b-a456-7bb31c233b55", + "sa": "cedb45fb-83d2-4b6f-b812-51853b7dcaf7" + }, + "traces": [], + "drift": "Unreviewed", + "unreviewed": true + }, + { + "objects": { + "oa": null, + "sa": "de57a577-75f3-4bdc-a5d0-b09d44defde0" + }, + "traces": [], + "drift": "Unreviewed", + "unreviewed": true + } + ] + } + ], + "impacts": [], + "conflicts": [], + "commands": [ + { + "EditElement": [ + "9fe4d605-5e9d-4486-bc5d-f0ec660a8601", + { + "id": "9fe4d605-5e9d-4486-bc5d-f0ec660a8601", + "alias": "startup_scenario_9fe4d605", + "name": "SA Startup scenario", + "description": "", + "parent": null, + "body": { + "Sa": { + "Shared": { + "Scenario": { + "viewpoint": "Functional", + "context": null, + "precondition": "", + "postcondition": "", + "steps": [ + "8b82f67f-c027-416d-9f8f-0d4057674b50", + "de57a577-75f3-4bdc-a5d0-b09d44defde0", + "cedb45fb-83d2-4b6f-b812-51853b7dcaf7", + "10ca3141-0fec-4158-9bb4-020cac14fdfc" + ] + } + } + } + } + } + ] + }, + { + "EditTrace": [ + "2cd537f1-fef4-4593-a5e9-c639fcc9fd97", + { + "id": "2cd537f1-fef4-4593-a5e9-c639fcc9fd97", + "alias": "startup_order_review", + "name": "Startup scenario order review", + "description": "", + "oa": "b82aea81-8e09-4436-8877-0a097d86a10a", + "sa": "9fe4d605-5e9d-4486-bc5d-f0ec660a8601", + "kind": "Manual", + "baseline": { + "oa": { + "name": "OA Startup scenario", + "description": "" + }, + "sa": { + "name": "SA Startup scenario", + "description": "" + }, + "semantic": { + "facets": [ + { + "Member": { + "collection": "Steps", + "keys": { + "oa": "cfaa7b0f-3823-4a9b-a456-7bb31c233b55", + "sa": "cedb45fb-83d2-4b6f-b812-51853b7dcaf7" + }, + "review": { + "oa": { + "Value": true + }, + "sa": { + "Value": true + } + } + } + }, + { + "Order": { + "collection": "Steps", + "review": { + "oa": { + "Value": [ + "1c0cb002-a7a5-4e6d-9e55-b37fa574e096", + "cfaa7b0f-3823-4a9b-a456-7bb31c233b55", + "b3eaf78f-d793-433c-853b-4a27cdf0e809" + ] + }, + "sa": { + "Value": [ + "8b82f67f-c027-416d-9f8f-0d4057674b50", + "de57a577-75f3-4bdc-a5d0-b09d44defde0", + "cedb45fb-83d2-4b6f-b812-51853b7dcaf7", + "10ca3141-0fec-4158-9bb4-020cac14fdfc" + ] + } + } + } + } + ] + } + } + } + ] + } + ], + "prepared_fingerprint": "97cc6ac682b7d87b" +} \ No newline at end of file diff --git a/harness/fixtures/release1-native/semantic-ordered-scenario.final-request.json b/harness/fixtures/release1-native/semantic-ordered-scenario.final-request.json new file mode 100644 index 00000000..bff95d44 --- /dev/null +++ b/harness/fixtures/release1-native/semantic-ordered-scenario.final-request.json @@ -0,0 +1,84 @@ +{ + "transition": { + "direction": "OaToSa", + "sources": [ + "b82aea81-8e09-4436-8877-0a097d86a10a" + ], + "scope": "SelectedOnly", + "mappings": [ + { + "source": "b82aea81-8e09-4436-8877-0a097d86a10a", + "target": "9fe4d605-5e9d-4486-bc5d-f0ec660a8601", + "target_kind": null + } + ], + "fields": [], + "resolutions": [], + "destination_diagram": null + }, + "traces": { + "b82aea81-8e09-4436-8877-0a097d86a10a": "2cd537f1-fef4-4593-a5e9-c639fcc9fd97" + }, + "references": { + "1c0cb002-a7a5-4e6d-9e55-b37fa574e096": "8b82f67f-c027-416d-9f8f-0d4057674b50", + "b3eaf78f-d793-433c-853b-4a27cdf0e809": "10ca3141-0fec-4158-9bb4-020cac14fdfc", + "b82aea81-8e09-4436-8877-0a097d86a10a": "9fe4d605-5e9d-4486-bc5d-f0ec660a8601", + "cfaa7b0f-3823-4a9b-a456-7bb31c233b55": "cedb45fb-83d2-4b6f-b812-51853b7dcaf7" + }, + "reviews": [ + { + "source": "b82aea81-8e09-4436-8877-0a097d86a10a", + "resolutions": [ + { + "facet": { + "Member": { + "collection": "Steps", + "keys": { + "oa": "cfaa7b0f-3823-4a9b-a456-7bb31c233b55", + "sa": "cedb45fb-83d2-4b6f-b812-51853b7dcaf7" + } + } + }, + "decision": "TakeSource" + }, + { + "facet": { + "Order": "Steps" + }, + "decision": "Candidate" + } + ], + "reset": [] + } + ], + "candidates": [ + { + "Element": { + "id": "9fe4d605-5e9d-4486-bc5d-f0ec660a8601", + "alias": "startup_scenario_9fe4d605", + "name": "SA Startup scenario", + "description": "", + "parent": null, + "body": { + "Sa": { + "Shared": { + "Scenario": { + "viewpoint": "Functional", + "context": null, + "precondition": "", + "postcondition": "", + "steps": [ + "8b82f67f-c027-416d-9f8f-0d4057674b50", + "de57a577-75f3-4bdc-a5d0-b09d44defde0", + "cedb45fb-83d2-4b6f-b812-51853b7dcaf7", + "10ca3141-0fec-4158-9bb4-020cac14fdfc" + ] + } + } + } + } + } + } + ], + "accepted_impacts": [] +} \ No newline at end of file diff --git a/harness/fixtures/release1-native/semantic-ordered-scenario.json b/harness/fixtures/release1-native/semantic-ordered-scenario.json new file mode 100644 index 00000000..87b4c75c --- /dev/null +++ b/harness/fixtures/release1-native/semantic-ordered-scenario.json @@ -0,0 +1,286 @@ +{ + "schema_version": 5, + "id": "44ee1446-6c83-46da-a0c7-7780d3719881", + "alias": "mission", + "name": "Startup scenario order review", + "description": "", + "elements": { + "10ca3141-0fec-4158-9bb4-020cac14fdfc": { + "id": "10ca3141-0fec-4158-9bb4-020cac14fdfc", + "alias": "energize_10ca3141", + "name": "SA Energize", + "description": "", + "parent": "9fe4d605-5e9d-4486-bc5d-f0ec660a8601", + "body": { + "Sa": { + "Shared": { + "Occurrence": { + "body": { + "Action": { + "action": null, + "lifeline": null + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + }, + "1c0cb002-a7a5-4e6d-9e55-b37fa574e096": { + "id": "1c0cb002-a7a5-4e6d-9e55-b37fa574e096", + "alias": "request_start", + "name": "OA Request start", + "description": "", + "parent": "b82aea81-8e09-4436-8877-0a097d86a10a", + "body": { + "Oa": { + "Shared": { + "Occurrence": { + "body": { + "Action": { + "action": null, + "lifeline": null + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + }, + "30abc877-03ba-4c5b-9222-3c51222f197a": { + "id": "30abc877-03ba-4c5b-9222-3c51222f197a", + "alias": "local_self_test", + "name": "OA Local self-test", + "description": "", + "parent": "b82aea81-8e09-4436-8877-0a097d86a10a", + "body": { + "Oa": { + "Shared": { + "Occurrence": { + "body": { + "Action": { + "action": null, + "lifeline": null + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + }, + "8b82f67f-c027-416d-9f8f-0d4057674b50": { + "id": "8b82f67f-c027-416d-9f8f-0d4057674b50", + "alias": "request_start_8b82f67f", + "name": "SA Request start", + "description": "", + "parent": "9fe4d605-5e9d-4486-bc5d-f0ec660a8601", + "body": { + "Sa": { + "Shared": { + "Occurrence": { + "body": { + "Action": { + "action": null, + "lifeline": null + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + }, + "9fe4d605-5e9d-4486-bc5d-f0ec660a8601": { + "id": "9fe4d605-5e9d-4486-bc5d-f0ec660a8601", + "alias": "startup_scenario_9fe4d605", + "name": "SA Startup scenario", + "description": "", + "parent": null, + "body": { + "Sa": { + "Shared": { + "Scenario": { + "viewpoint": "Functional", + "context": null, + "precondition": "", + "postcondition": "", + "steps": [ + "8b82f67f-c027-416d-9f8f-0d4057674b50", + "de57a577-75f3-4bdc-a5d0-b09d44defde0", + "10ca3141-0fec-4158-9bb4-020cac14fdfc" + ] + } + } + } + } + }, + "b3eaf78f-d793-433c-853b-4a27cdf0e809": { + "id": "b3eaf78f-d793-433c-853b-4a27cdf0e809", + "alias": "energize", + "name": "OA Energize", + "description": "", + "parent": "b82aea81-8e09-4436-8877-0a097d86a10a", + "body": { + "Oa": { + "Shared": { + "Occurrence": { + "body": { + "Action": { + "action": null, + "lifeline": null + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + }, + "b82aea81-8e09-4436-8877-0a097d86a10a": { + "id": "b82aea81-8e09-4436-8877-0a097d86a10a", + "alias": "startup_scenario", + "name": "OA Startup scenario", + "description": "", + "parent": null, + "body": { + "Oa": { + "Shared": { + "Scenario": { + "viewpoint": "OperationalActivity", + "context": null, + "precondition": "", + "postcondition": "", + "steps": [ + "1c0cb002-a7a5-4e6d-9e55-b37fa574e096", + "cfaa7b0f-3823-4a9b-a456-7bb31c233b55", + "b3eaf78f-d793-433c-853b-4a27cdf0e809" + ] + } + } + } + } + }, + "cedb45fb-83d2-4b6f-b812-51853b7dcaf7": { + "id": "cedb45fb-83d2-4b6f-b812-51853b7dcaf7", + "alias": "verify_interlock_cedb45fb", + "name": "SA Verify interlock", + "description": "", + "parent": "9fe4d605-5e9d-4486-bc5d-f0ec660a8601", + "body": { + "Sa": { + "Shared": { + "Occurrence": { + "body": { + "Action": { + "action": null, + "lifeline": null + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + }, + "cfaa7b0f-3823-4a9b-a456-7bb31c233b55": { + "id": "cfaa7b0f-3823-4a9b-a456-7bb31c233b55", + "alias": "verify_interlock", + "name": "OA Verify interlock", + "description": "", + "parent": "b82aea81-8e09-4436-8877-0a097d86a10a", + "body": { + "Oa": { + "Shared": { + "Occurrence": { + "body": { + "Action": { + "action": null, + "lifeline": null + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + }, + "de57a577-75f3-4bdc-a5d0-b09d44defde0": { + "id": "de57a577-75f3-4bdc-a5d0-b09d44defde0", + "alias": "local_self_test_de57a577", + "name": "SA Local self-test", + "description": "", + "parent": "9fe4d605-5e9d-4486-bc5d-f0ec660a8601", + "body": { + "Sa": { + "Shared": { + "Occurrence": { + "body": { + "Action": { + "action": null, + "lifeline": null + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + } + }, + "relations": {}, + "diagrams": {}, + "artifact_links": {}, + "traces": { + "2cd537f1-fef4-4593-a5e9-c639fcc9fd97": { + "id": "2cd537f1-fef4-4593-a5e9-c639fcc9fd97", + "alias": "startup_order_review", + "name": "Startup scenario order review", + "description": "", + "oa": "b82aea81-8e09-4436-8877-0a097d86a10a", + "sa": "9fe4d605-5e9d-4486-bc5d-f0ec660a8601", + "kind": "Manual", + "baseline": { + "oa": { + "name": "OA Startup scenario", + "description": "" + }, + "sa": { + "name": "SA Startup scenario", + "description": "" + }, + "semantic": { + "facets": [ + { + "Member": { + "collection": "Steps", + "keys": { + "oa": "cfaa7b0f-3823-4a9b-a456-7bb31c233b55", + "sa": "cedb45fb-83d2-4b6f-b812-51853b7dcaf7" + }, + "review": { + "oa": { + "Value": true + }, + "sa": { + "Value": false + } + } + } + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/harness/fixtures/release1-native/semantic-ordered-scenario.manifest.json b/harness/fixtures/release1-native/semantic-ordered-scenario.manifest.json new file mode 100644 index 00000000..b7ba1964 --- /dev/null +++ b/harness/fixtures/release1-native/semantic-ordered-scenario.manifest.json @@ -0,0 +1,36 @@ +{ + "direction": "OaToSa", + "facet": { + "Member": { + "collection": "Steps", + "keys": { + "oa": "cfaa7b0f-3823-4a9b-a456-7bb31c233b55", + "sa": "cedb45fb-83d2-4b6f-b812-51853b7dcaf7" + } + } + }, + "final_order": [ + "8b82f67f-c027-416d-9f8f-0d4057674b50", + "de57a577-75f3-4bdc-a5d0-b09d44defde0", + "cedb45fb-83d2-4b6f-b812-51853b7dcaf7", + "10ca3141-0fec-4158-9bb4-020cac14fdfc" + ], + "rows": [ + [ + "1c0cb002-a7a5-4e6d-9e55-b37fa574e096", + "b3eaf78f-d793-433c-853b-4a27cdf0e809", + "cfaa7b0f-3823-4a9b-a456-7bb31c233b55", + "30abc877-03ba-4c5b-9222-3c51222f197a" + ], + [ + "8b82f67f-c027-416d-9f8f-0d4057674b50", + "10ca3141-0fec-4158-9bb4-020cac14fdfc", + "cedb45fb-83d2-4b6f-b812-51853b7dcaf7", + "de57a577-75f3-4bdc-a5d0-b09d44defde0" + ] + ], + "source": "b82aea81-8e09-4436-8877-0a097d86a10a", + "target": "9fe4d605-5e9d-4486-bc5d-f0ec660a8601", + "trace": "2cd537f1-fef4-4593-a5e9-c639fcc9fd97", + "trace_name": "Startup scenario order review" +} \ No newline at end of file diff --git a/harness/fixtures/release1-native/semantic-ordered.after-order.arcadia b/harness/fixtures/release1-native/semantic-ordered.after-order.arcadia new file mode 100644 index 00000000..65faabf9 --- /dev/null +++ b/harness/fixtures/release1-native/semantic-ordered.after-order.arcadia @@ -0,0 +1,163 @@ +// Preserve the authored startup order rationale. +arcadia 5; +project mission "Startup sequence order review" id "8a7844b3-69e7-49ec-9666-b63a588ed3c4" { + description ""; + + oa_occurrence verify_interlock "OA Verify interlock" id "068e2da6-5317-4b06-b0a0-ce8c1b5a26ca" { + body { + Control { + kind Choice; + }; + }; + description ""; + parent @startup_sequence; + postcondition ""; + precondition ""; + } + + sa_occurrence request_start_1b9b6f58 "SA Request start" id "1b9b6f58-0944-47d4-9d34-caa0d0d1561f" { + body { + Control { + kind Choice; + }; + }; + description ""; + parent @startup_sequence_e5dbb9b6; + postcondition ""; + precondition ""; + } + + sa_occurrence verify_interlock_80077b69 "SA Verify interlock" id "80077b69-a2e6-4858-96c3-88e98cbf06eb" { + body { + Control { + kind Choice; + }; + }; + description ""; + parent @startup_sequence_e5dbb9b6; + postcondition ""; + precondition ""; + } + + oa_occurrence request_start "OA Request start" id "9b50280a-881e-4d45-9645-947e71943f65" { + body { + Control { + kind Choice; + }; + }; + description ""; + parent @startup_sequence; + postcondition ""; + precondition ""; + } + + oa_process startup_sequence "OA Startup sequence" id "bd4e4ed5-a0a9-4394-bec0-85bb72434e0b" { + description ""; + parent none; + steps [@request_start, @verify_interlock, @energize]; + } + + oa_occurrence energize "OA Energize" id "c18de06b-2519-416c-b559-43f940d817b7" { + body { + Control { + kind Choice; + }; + }; + description ""; + parent @startup_sequence; + postcondition ""; + precondition ""; + } + + sa_occurrence energize_d2a66d07 "SA Energize" id "d2a66d07-6507-409b-a607-574684c6d146" { + body { + Control { + kind Choice; + }; + }; + description ""; + parent @startup_sequence_e5dbb9b6; + postcondition ""; + precondition ""; + } + + oa_occurrence local_self_test "OA Local self-test" id "db59b8b6-c002-4b26-bd32-87dcf2e40bcd" { + body { + Control { + kind Choice; + }; + }; + description ""; + parent @startup_sequence; + postcondition ""; + precondition ""; + } + + sa_functional_chain startup_sequence_e5dbb9b6 "SA Startup sequence" id "e5dbb9b6-0df5-482e-88eb-8b4f3fa3c90d" { + description ""; + parent none; + steps [@request_start_1b9b6f58, @local_self_test_fc5a0217, @verify_interlock_80077b69, @energize_d2a66d07]; + } + + sa_occurrence local_self_test_fc5a0217 "SA Local self-test" id "fc5a0217-9478-48c5-a83f-0c8edb6a81d1" { + body { + Control { + kind Choice; + }; + }; + description ""; + parent @startup_sequence_e5dbb9b6; + postcondition ""; + precondition ""; + } + + trace startup_order_review "Startup sequence order review" id "f6f83133-4199-4d39-b968-cb9d30c8e387" { + baseline { + oa { + description ""; + name "OA Startup sequence"; + }; + sa { + description ""; + name "SA Startup sequence"; + }; + semantic { + facets [{ + Member { + collection Steps; + keys { + oa uuid "068e2da6-5317-4b06-b0a0-ce8c1b5a26ca"; + sa uuid "80077b69-a2e6-4858-96c3-88e98cbf06eb"; + }; + review { + oa { + Value true; + }; + sa { + Value true; + }; + }; + }; + }, + { + Order { + collection Steps; + review { + oa { + Value [uuid "9b50280a-881e-4d45-9645-947e71943f65", uuid "068e2da6-5317-4b06-b0a0-ce8c1b5a26ca", uuid "c18de06b-2519-416c-b559-43f940d817b7"]; + }; + sa { + Value [uuid "1b9b6f58-0944-47d4-9d34-caa0d0d1561f", uuid "fc5a0217-9478-48c5-a83f-0c8edb6a81d1", uuid "80077b69-a2e6-4858-96c3-88e98cbf06eb", uuid "d2a66d07-6507-409b-a607-574684c6d146"]; + }; + }; + }; + }]; + }; + }; + description ""; + kind Manual; + oa @startup_sequence; + sa @startup_sequence_e5dbb9b6; + } + +} diff --git a/harness/fixtures/release1-native/semantic-ordered.after-order.json b/harness/fixtures/release1-native/semantic-ordered.after-order.json new file mode 100644 index 00000000..40015bb2 --- /dev/null +++ b/harness/fixtures/release1-native/semantic-ordered.after-order.json @@ -0,0 +1,289 @@ +{ + "schema_version": 5, + "id": "8a7844b3-69e7-49ec-9666-b63a588ed3c4", + "alias": "mission", + "name": "Startup sequence order review", + "description": "", + "elements": { + "068e2da6-5317-4b06-b0a0-ce8c1b5a26ca": { + "id": "068e2da6-5317-4b06-b0a0-ce8c1b5a26ca", + "alias": "verify_interlock", + "name": "OA Verify interlock", + "description": "", + "parent": "bd4e4ed5-a0a9-4394-bec0-85bb72434e0b", + "body": { + "Oa": { + "Shared": { + "Occurrence": { + "body": { + "Control": { + "kind": "Choice" + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + }, + "1b9b6f58-0944-47d4-9d34-caa0d0d1561f": { + "id": "1b9b6f58-0944-47d4-9d34-caa0d0d1561f", + "alias": "request_start_1b9b6f58", + "name": "SA Request start", + "description": "", + "parent": "e5dbb9b6-0df5-482e-88eb-8b4f3fa3c90d", + "body": { + "Sa": { + "Shared": { + "Occurrence": { + "body": { + "Control": { + "kind": "Choice" + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + }, + "80077b69-a2e6-4858-96c3-88e98cbf06eb": { + "id": "80077b69-a2e6-4858-96c3-88e98cbf06eb", + "alias": "verify_interlock_80077b69", + "name": "SA Verify interlock", + "description": "", + "parent": "e5dbb9b6-0df5-482e-88eb-8b4f3fa3c90d", + "body": { + "Sa": { + "Shared": { + "Occurrence": { + "body": { + "Control": { + "kind": "Choice" + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + }, + "9b50280a-881e-4d45-9645-947e71943f65": { + "id": "9b50280a-881e-4d45-9645-947e71943f65", + "alias": "request_start", + "name": "OA Request start", + "description": "", + "parent": "bd4e4ed5-a0a9-4394-bec0-85bb72434e0b", + "body": { + "Oa": { + "Shared": { + "Occurrence": { + "body": { + "Control": { + "kind": "Choice" + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + }, + "bd4e4ed5-a0a9-4394-bec0-85bb72434e0b": { + "id": "bd4e4ed5-a0a9-4394-bec0-85bb72434e0b", + "alias": "startup_sequence", + "name": "OA Startup sequence", + "description": "", + "parent": null, + "body": { + "Oa": { + "Process": { + "steps": [ + "9b50280a-881e-4d45-9645-947e71943f65", + "068e2da6-5317-4b06-b0a0-ce8c1b5a26ca", + "c18de06b-2519-416c-b559-43f940d817b7" + ] + } + } + } + }, + "c18de06b-2519-416c-b559-43f940d817b7": { + "id": "c18de06b-2519-416c-b559-43f940d817b7", + "alias": "energize", + "name": "OA Energize", + "description": "", + "parent": "bd4e4ed5-a0a9-4394-bec0-85bb72434e0b", + "body": { + "Oa": { + "Shared": { + "Occurrence": { + "body": { + "Control": { + "kind": "Choice" + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + }, + "d2a66d07-6507-409b-a607-574684c6d146": { + "id": "d2a66d07-6507-409b-a607-574684c6d146", + "alias": "energize_d2a66d07", + "name": "SA Energize", + "description": "", + "parent": "e5dbb9b6-0df5-482e-88eb-8b4f3fa3c90d", + "body": { + "Sa": { + "Shared": { + "Occurrence": { + "body": { + "Control": { + "kind": "Choice" + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + }, + "db59b8b6-c002-4b26-bd32-87dcf2e40bcd": { + "id": "db59b8b6-c002-4b26-bd32-87dcf2e40bcd", + "alias": "local_self_test", + "name": "OA Local self-test", + "description": "", + "parent": "bd4e4ed5-a0a9-4394-bec0-85bb72434e0b", + "body": { + "Oa": { + "Shared": { + "Occurrence": { + "body": { + "Control": { + "kind": "Choice" + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + }, + "e5dbb9b6-0df5-482e-88eb-8b4f3fa3c90d": { + "id": "e5dbb9b6-0df5-482e-88eb-8b4f3fa3c90d", + "alias": "startup_sequence_e5dbb9b6", + "name": "SA Startup sequence", + "description": "", + "parent": null, + "body": { + "Sa": { + "FunctionalChain": { + "steps": [ + "1b9b6f58-0944-47d4-9d34-caa0d0d1561f", + "fc5a0217-9478-48c5-a83f-0c8edb6a81d1", + "80077b69-a2e6-4858-96c3-88e98cbf06eb", + "d2a66d07-6507-409b-a607-574684c6d146" + ] + } + } + } + }, + "fc5a0217-9478-48c5-a83f-0c8edb6a81d1": { + "id": "fc5a0217-9478-48c5-a83f-0c8edb6a81d1", + "alias": "local_self_test_fc5a0217", + "name": "SA Local self-test", + "description": "", + "parent": "e5dbb9b6-0df5-482e-88eb-8b4f3fa3c90d", + "body": { + "Sa": { + "Shared": { + "Occurrence": { + "body": { + "Control": { + "kind": "Choice" + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + } + }, + "relations": {}, + "diagrams": {}, + "artifact_links": {}, + "traces": { + "f6f83133-4199-4d39-b968-cb9d30c8e387": { + "id": "f6f83133-4199-4d39-b968-cb9d30c8e387", + "alias": "startup_order_review", + "name": "Startup sequence order review", + "description": "", + "oa": "bd4e4ed5-a0a9-4394-bec0-85bb72434e0b", + "sa": "e5dbb9b6-0df5-482e-88eb-8b4f3fa3c90d", + "kind": "Manual", + "baseline": { + "oa": { + "name": "OA Startup sequence", + "description": "" + }, + "sa": { + "name": "SA Startup sequence", + "description": "" + }, + "semantic": { + "facets": [ + { + "Member": { + "collection": "Steps", + "keys": { + "oa": "068e2da6-5317-4b06-b0a0-ce8c1b5a26ca", + "sa": "80077b69-a2e6-4858-96c3-88e98cbf06eb" + }, + "review": { + "oa": { + "Value": true + }, + "sa": { + "Value": true + } + } + } + }, + { + "Order": { + "collection": "Steps", + "review": { + "oa": { + "Value": [ + "9b50280a-881e-4d45-9645-947e71943f65", + "068e2da6-5317-4b06-b0a0-ce8c1b5a26ca", + "c18de06b-2519-416c-b559-43f940d817b7" + ] + }, + "sa": { + "Value": [ + "1b9b6f58-0944-47d4-9d34-caa0d0d1561f", + "fc5a0217-9478-48c5-a83f-0c8edb6a81d1", + "80077b69-a2e6-4858-96c3-88e98cbf06eb", + "d2a66d07-6507-409b-a607-574684c6d146" + ] + } + } + } + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/harness/fixtures/release1-native/semantic-ordered.ambiguous-request.json b/harness/fixtures/release1-native/semantic-ordered.ambiguous-request.json new file mode 100644 index 00000000..a194a2d8 --- /dev/null +++ b/harness/fixtures/release1-native/semantic-ordered.ambiguous-request.json @@ -0,0 +1,50 @@ +{ + "transition": { + "direction": "OaToSa", + "sources": [ + "bd4e4ed5-a0a9-4394-bec0-85bb72434e0b" + ], + "scope": "SelectedOnly", + "mappings": [ + { + "source": "bd4e4ed5-a0a9-4394-bec0-85bb72434e0b", + "target": "e5dbb9b6-0df5-482e-88eb-8b4f3fa3c90d", + "target_kind": null + } + ], + "fields": [], + "resolutions": [], + "destination_diagram": null + }, + "traces": { + "bd4e4ed5-a0a9-4394-bec0-85bb72434e0b": "f6f83133-4199-4d39-b968-cb9d30c8e387" + }, + "references": { + "068e2da6-5317-4b06-b0a0-ce8c1b5a26ca": "80077b69-a2e6-4858-96c3-88e98cbf06eb", + "9b50280a-881e-4d45-9645-947e71943f65": "1b9b6f58-0944-47d4-9d34-caa0d0d1561f", + "bd4e4ed5-a0a9-4394-bec0-85bb72434e0b": "e5dbb9b6-0df5-482e-88eb-8b4f3fa3c90d", + "c18de06b-2519-416c-b559-43f940d817b7": "d2a66d07-6507-409b-a607-574684c6d146" + }, + "reviews": [ + { + "source": "bd4e4ed5-a0a9-4394-bec0-85bb72434e0b", + "resolutions": [ + { + "facet": { + "Member": { + "collection": "Steps", + "keys": { + "oa": "068e2da6-5317-4b06-b0a0-ce8c1b5a26ca", + "sa": "80077b69-a2e6-4858-96c3-88e98cbf06eb" + } + } + }, + "decision": "TakeSource" + } + ], + "reset": [] + } + ], + "candidates": [], + "accepted_impacts": [] +} \ No newline at end of file diff --git a/harness/fixtures/release1-native/semantic-ordered.ambiguous.txt b/harness/fixtures/release1-native/semantic-ordered.ambiguous.txt new file mode 100644 index 00000000..18fc5884 --- /dev/null +++ b/harness/fixtures/release1-native/semantic-ordered.ambiguous.txt @@ -0,0 +1 @@ +Ordered member insertion has no unique unchanged anchor; review an explicit final target order \ No newline at end of file diff --git a/harness/fixtures/release1-native/semantic-ordered.arcadia b/harness/fixtures/release1-native/semantic-ordered.arcadia new file mode 100644 index 00000000..02051110 --- /dev/null +++ b/harness/fixtures/release1-native/semantic-ordered.arcadia @@ -0,0 +1,150 @@ +// Preserve the authored startup order rationale. +arcadia 5; +project mission "Startup sequence order review" id "8a7844b3-69e7-49ec-9666-b63a588ed3c4" { + description ""; + + oa_occurrence verify_interlock "OA Verify interlock" id "068e2da6-5317-4b06-b0a0-ce8c1b5a26ca" { + body { + Control { + kind Choice; + }; + }; + description ""; + parent @startup_sequence; + postcondition ""; + precondition ""; + } + + sa_occurrence request_start_1b9b6f58 "SA Request start" id "1b9b6f58-0944-47d4-9d34-caa0d0d1561f" { + body { + Control { + kind Choice; + }; + }; + description ""; + parent @startup_sequence_e5dbb9b6; + postcondition ""; + precondition ""; + } + + sa_occurrence verify_interlock_80077b69 "SA Verify interlock" id "80077b69-a2e6-4858-96c3-88e98cbf06eb" { + body { + Control { + kind Choice; + }; + }; + description ""; + parent @startup_sequence_e5dbb9b6; + postcondition ""; + precondition ""; + } + + oa_occurrence request_start "OA Request start" id "9b50280a-881e-4d45-9645-947e71943f65" { + body { + Control { + kind Choice; + }; + }; + description ""; + parent @startup_sequence; + postcondition ""; + precondition ""; + } + + oa_process startup_sequence "OA Startup sequence" id "bd4e4ed5-a0a9-4394-bec0-85bb72434e0b" { + description ""; + parent none; + steps [@request_start, @verify_interlock, @energize]; + } + + oa_occurrence energize "OA Energize" id "c18de06b-2519-416c-b559-43f940d817b7" { + body { + Control { + kind Choice; + }; + }; + description ""; + parent @startup_sequence; + postcondition ""; + precondition ""; + } + + sa_occurrence energize_d2a66d07 "SA Energize" id "d2a66d07-6507-409b-a607-574684c6d146" { + body { + Control { + kind Choice; + }; + }; + description ""; + parent @startup_sequence_e5dbb9b6; + postcondition ""; + precondition ""; + } + + oa_occurrence local_self_test "OA Local self-test" id "db59b8b6-c002-4b26-bd32-87dcf2e40bcd" { + body { + Control { + kind Choice; + }; + }; + description ""; + parent @startup_sequence; + postcondition ""; + precondition ""; + } + + sa_functional_chain startup_sequence_e5dbb9b6 "SA Startup sequence" id "e5dbb9b6-0df5-482e-88eb-8b4f3fa3c90d" { + description ""; + parent none; + steps [@request_start_1b9b6f58, @local_self_test_fc5a0217, @energize_d2a66d07]; + } + + sa_occurrence local_self_test_fc5a0217 "SA Local self-test" id "fc5a0217-9478-48c5-a83f-0c8edb6a81d1" { + body { + Control { + kind Choice; + }; + }; + description ""; + parent @startup_sequence_e5dbb9b6; + postcondition ""; + precondition ""; + } + + trace startup_order_review "Startup sequence order review" id "f6f83133-4199-4d39-b968-cb9d30c8e387" { + baseline { + oa { + description ""; + name "OA Startup sequence"; + }; + sa { + description ""; + name "SA Startup sequence"; + }; + semantic { + facets [{ + Member { + collection Steps; + keys { + oa uuid "068e2da6-5317-4b06-b0a0-ce8c1b5a26ca"; + sa uuid "80077b69-a2e6-4858-96c3-88e98cbf06eb"; + }; + review { + oa { + Value true; + }; + sa { + Value false; + }; + }; + }; + }]; + }; + }; + description ""; + kind Manual; + oa @startup_sequence; + sa @startup_sequence_e5dbb9b6; + } + +} diff --git a/harness/fixtures/release1-native/semantic-ordered.final-plan.json b/harness/fixtures/release1-native/semantic-ordered.final-plan.json new file mode 100644 index 00000000..292cf156 --- /dev/null +++ b/harness/fixtures/release1-native/semantic-ordered.final-plan.json @@ -0,0 +1,620 @@ +{ + "base_fingerprint": "950c4cae7283ca98", + "request": { + "transition": { + "direction": "OaToSa", + "sources": [ + "bd4e4ed5-a0a9-4394-bec0-85bb72434e0b" + ], + "scope": "SelectedOnly", + "mappings": [ + { + "source": "bd4e4ed5-a0a9-4394-bec0-85bb72434e0b", + "target": "e5dbb9b6-0df5-482e-88eb-8b4f3fa3c90d", + "target_kind": null + } + ], + "fields": [], + "resolutions": [], + "destination_diagram": null + }, + "traces": { + "bd4e4ed5-a0a9-4394-bec0-85bb72434e0b": "f6f83133-4199-4d39-b968-cb9d30c8e387" + }, + "references": { + "068e2da6-5317-4b06-b0a0-ce8c1b5a26ca": "80077b69-a2e6-4858-96c3-88e98cbf06eb", + "9b50280a-881e-4d45-9645-947e71943f65": "1b9b6f58-0944-47d4-9d34-caa0d0d1561f", + "bd4e4ed5-a0a9-4394-bec0-85bb72434e0b": "e5dbb9b6-0df5-482e-88eb-8b4f3fa3c90d", + "c18de06b-2519-416c-b559-43f940d817b7": "d2a66d07-6507-409b-a607-574684c6d146" + }, + "reviews": [ + { + "source": "bd4e4ed5-a0a9-4394-bec0-85bb72434e0b", + "resolutions": [ + { + "facet": { + "Member": { + "collection": "Steps", + "keys": { + "oa": "068e2da6-5317-4b06-b0a0-ce8c1b5a26ca", + "sa": "80077b69-a2e6-4858-96c3-88e98cbf06eb" + } + } + }, + "decision": "TakeSource" + }, + { + "facet": { + "Order": "Steps" + }, + "decision": "Candidate" + } + ], + "reset": [] + } + ], + "candidates": [ + { + "Element": { + "id": "e5dbb9b6-0df5-482e-88eb-8b4f3fa3c90d", + "alias": "startup_sequence_e5dbb9b6", + "name": "SA Startup sequence", + "description": "", + "parent": null, + "body": { + "Sa": { + "FunctionalChain": { + "steps": [ + "1b9b6f58-0944-47d4-9d34-caa0d0d1561f", + "fc5a0217-9478-48c5-a83f-0c8edb6a81d1", + "80077b69-a2e6-4858-96c3-88e98cbf06eb", + "d2a66d07-6507-409b-a607-574684c6d146" + ] + } + } + } + } + } + ], + "accepted_impacts": [] + }, + "transition": { + "base_fingerprint": "950c4cae7283ca98", + "request": { + "direction": "OaToSa", + "sources": [ + "bd4e4ed5-a0a9-4394-bec0-85bb72434e0b" + ], + "scope": "SelectedOnly", + "mappings": [ + { + "source": "bd4e4ed5-a0a9-4394-bec0-85bb72434e0b", + "target": "e5dbb9b6-0df5-482e-88eb-8b4f3fa3c90d", + "target_kind": null + } + ], + "fields": [], + "resolutions": [], + "destination_diagram": null + }, + "changes": [ + { + "source": "bd4e4ed5-a0a9-4394-bec0-85bb72434e0b", + "target": "e5dbb9b6-0df5-482e-88eb-8b4f3fa3c90d", + "action": "Reuse", + "fields": [] + } + ], + "conflicts": [], + "warnings": [ + "Identify the SA system of interest.", + "Add this occurrence to its owner's ordered steps.", + "Process/chain 'OA Startup sequence' has 3 start contributions; connect a clear entry/exit or review the intended independent paths.", + "Process/chain 'OA Startup sequence' has 3 end contributions; connect a clear entry/exit or review the intended independent paths.", + "Connect this contribution to its process/chain path.", + "Choice needs one incoming and at least two outgoing links; currently 0 incoming and 0 outgoing.", + "Connect this contribution to its process/chain path.", + "Choice needs one incoming and at least two outgoing links; currently 0 incoming and 0 outgoing.", + "Connect this contribution to its process/chain path.", + "Choice needs one incoming and at least two outgoing links; currently 0 incoming and 0 outgoing.", + "Add this occurrence to its owner's ordered steps.", + "Process/chain 'SA Startup sequence' has 3 start contributions; connect a clear entry/exit or review the intended independent paths.", + "Process/chain 'SA Startup sequence' has 3 end contributions; connect a clear entry/exit or review the intended independent paths.", + "Connect this contribution to its process/chain path.", + "Choice needs one incoming and at least two outgoing links; currently 0 incoming and 0 outgoing.", + "Connect this contribution to its process/chain path.", + "Choice needs one incoming and at least two outgoing links; currently 0 incoming and 0 outgoing.", + "Connect this contribution to its process/chain path.", + "Choice needs one incoming and at least two outgoing links; currently 0 incoming and 0 outgoing." + ], + "commands": [] + }, + "reviews": [ + { + "source": "bd4e4ed5-a0a9-4394-bec0-85bb72434e0b", + "target": "e5dbb9b6-0df5-482e-88eb-8b4f3fa3c90d", + "facets": [ + { + "selector": { + "Header": "Name" + }, + "current": { + "Header": { + "Name": { + "oa": { + "Value": "OA Startup sequence" + }, + "sa": { + "Value": "SA Startup sequence" + } + } + } + }, + "drift": "Unreviewed" + }, + { + "selector": { + "Header": "Description" + }, + "current": { + "Header": { + "Description": { + "oa": { + "Value": "" + }, + "sa": { + "Value": "" + } + } + } + }, + "drift": "Unreviewed" + }, + { + "selector": { + "Header": "Parent" + }, + "current": { + "Header": { + "Parent": { + "oa": { + "Value": null + }, + "sa": { + "Value": null + } + } + } + }, + "drift": "Unreviewed" + }, + { + "selector": { + "Member": { + "collection": "Steps", + "keys": { + "oa": null, + "sa": "fc5a0217-9478-48c5-a83f-0c8edb6a81d1" + } + } + }, + "current": { + "Member": { + "collection": "Steps", + "keys": { + "oa": null, + "sa": "fc5a0217-9478-48c5-a83f-0c8edb6a81d1" + }, + "review": { + "oa": { + "Value": false + }, + "sa": { + "Value": true + } + } + } + }, + "drift": "Unreviewed" + }, + { + "selector": { + "Member": { + "collection": "Steps", + "keys": { + "oa": "068e2da6-5317-4b06-b0a0-ce8c1b5a26ca", + "sa": "80077b69-a2e6-4858-96c3-88e98cbf06eb" + } + } + }, + "current": { + "Member": { + "collection": "Steps", + "keys": { + "oa": "068e2da6-5317-4b06-b0a0-ce8c1b5a26ca", + "sa": "80077b69-a2e6-4858-96c3-88e98cbf06eb" + }, + "review": { + "oa": { + "Value": true + }, + "sa": { + "Value": false + } + } + } + }, + "drift": "Unchanged" + }, + { + "selector": { + "Member": { + "collection": "Steps", + "keys": { + "oa": "9b50280a-881e-4d45-9645-947e71943f65", + "sa": "1b9b6f58-0944-47d4-9d34-caa0d0d1561f" + } + } + }, + "current": { + "Member": { + "collection": "Steps", + "keys": { + "oa": "9b50280a-881e-4d45-9645-947e71943f65", + "sa": "1b9b6f58-0944-47d4-9d34-caa0d0d1561f" + }, + "review": { + "oa": { + "Value": true + }, + "sa": { + "Value": true + } + } + } + }, + "drift": "Unreviewed" + }, + { + "selector": { + "Member": { + "collection": "Steps", + "keys": { + "oa": "c18de06b-2519-416c-b559-43f940d817b7", + "sa": "d2a66d07-6507-409b-a607-574684c6d146" + } + } + }, + "current": { + "Member": { + "collection": "Steps", + "keys": { + "oa": "c18de06b-2519-416c-b559-43f940d817b7", + "sa": "d2a66d07-6507-409b-a607-574684c6d146" + }, + "review": { + "oa": { + "Value": true + }, + "sa": { + "Value": true + } + } + } + }, + "drift": "Unreviewed" + }, + { + "selector": { + "Order": "Steps" + }, + "current": { + "Order": { + "collection": "Steps", + "review": { + "oa": { + "Value": [ + "9b50280a-881e-4d45-9645-947e71943f65", + "068e2da6-5317-4b06-b0a0-ce8c1b5a26ca", + "c18de06b-2519-416c-b559-43f940d817b7" + ] + }, + "sa": { + "Value": [ + "1b9b6f58-0944-47d4-9d34-caa0d0d1561f", + "fc5a0217-9478-48c5-a83f-0c8edb6a81d1", + "d2a66d07-6507-409b-a607-574684c6d146" + ] + } + } + } + }, + "drift": "Unreviewed" + }, + { + "selector": { + "Child": { + "oa": null, + "sa": "fc5a0217-9478-48c5-a83f-0c8edb6a81d1" + } + }, + "current": { + "Child": { + "keys": { + "oa": null, + "sa": "fc5a0217-9478-48c5-a83f-0c8edb6a81d1" + }, + "review": { + "oa": { + "Value": false + }, + "sa": { + "Value": true + } + } + } + }, + "drift": "Unreviewed" + }, + { + "selector": { + "Child": { + "oa": "068e2da6-5317-4b06-b0a0-ce8c1b5a26ca", + "sa": "80077b69-a2e6-4858-96c3-88e98cbf06eb" + } + }, + "current": { + "Child": { + "keys": { + "oa": "068e2da6-5317-4b06-b0a0-ce8c1b5a26ca", + "sa": "80077b69-a2e6-4858-96c3-88e98cbf06eb" + }, + "review": { + "oa": { + "Value": true + }, + "sa": { + "Value": true + } + } + } + }, + "drift": "Unreviewed" + }, + { + "selector": { + "Child": { + "oa": "9b50280a-881e-4d45-9645-947e71943f65", + "sa": "1b9b6f58-0944-47d4-9d34-caa0d0d1561f" + } + }, + "current": { + "Child": { + "keys": { + "oa": "9b50280a-881e-4d45-9645-947e71943f65", + "sa": "1b9b6f58-0944-47d4-9d34-caa0d0d1561f" + }, + "review": { + "oa": { + "Value": true + }, + "sa": { + "Value": true + } + } + } + }, + "drift": "Unreviewed" + }, + { + "selector": { + "Child": { + "oa": "c18de06b-2519-416c-b559-43f940d817b7", + "sa": "d2a66d07-6507-409b-a607-574684c6d146" + } + }, + "current": { + "Child": { + "keys": { + "oa": "c18de06b-2519-416c-b559-43f940d817b7", + "sa": "d2a66d07-6507-409b-a607-574684c6d146" + }, + "review": { + "oa": { + "Value": true + }, + "sa": { + "Value": true + } + } + } + }, + "drift": "Unreviewed" + }, + { + "selector": { + "Child": { + "oa": "db59b8b6-c002-4b26-bd32-87dcf2e40bcd", + "sa": null + } + }, + "current": { + "Child": { + "keys": { + "oa": "db59b8b6-c002-4b26-bd32-87dcf2e40bcd", + "sa": null + }, + "review": { + "oa": { + "Value": true + }, + "sa": { + "Value": false + } + } + } + }, + "drift": "Unreviewed" + } + ], + "acknowledged": [ + { + "Member": { + "collection": "Steps", + "keys": { + "oa": "068e2da6-5317-4b06-b0a0-ce8c1b5a26ca", + "sa": "80077b69-a2e6-4858-96c3-88e98cbf06eb" + } + } + }, + { + "Order": "Steps" + } + ], + "retired": [], + "linked_impacts": [ + { + "objects": { + "oa": "068e2da6-5317-4b06-b0a0-ce8c1b5a26ca", + "sa": "80077b69-a2e6-4858-96c3-88e98cbf06eb" + }, + "traces": [], + "drift": "Unreviewed", + "unreviewed": true + }, + { + "objects": { + "oa": "9b50280a-881e-4d45-9645-947e71943f65", + "sa": "1b9b6f58-0944-47d4-9d34-caa0d0d1561f" + }, + "traces": [], + "drift": "Unreviewed", + "unreviewed": true + }, + { + "objects": { + "oa": "c18de06b-2519-416c-b559-43f940d817b7", + "sa": "d2a66d07-6507-409b-a607-574684c6d146" + }, + "traces": [], + "drift": "Unreviewed", + "unreviewed": true + }, + { + "objects": { + "oa": "db59b8b6-c002-4b26-bd32-87dcf2e40bcd", + "sa": null + }, + "traces": [], + "drift": "Unreviewed", + "unreviewed": true + }, + { + "objects": { + "oa": null, + "sa": "fc5a0217-9478-48c5-a83f-0c8edb6a81d1" + }, + "traces": [], + "drift": "Unreviewed", + "unreviewed": true + } + ] + } + ], + "impacts": [], + "conflicts": [], + "commands": [ + { + "EditElement": [ + "e5dbb9b6-0df5-482e-88eb-8b4f3fa3c90d", + { + "id": "e5dbb9b6-0df5-482e-88eb-8b4f3fa3c90d", + "alias": "startup_sequence_e5dbb9b6", + "name": "SA Startup sequence", + "description": "", + "parent": null, + "body": { + "Sa": { + "FunctionalChain": { + "steps": [ + "1b9b6f58-0944-47d4-9d34-caa0d0d1561f", + "fc5a0217-9478-48c5-a83f-0c8edb6a81d1", + "80077b69-a2e6-4858-96c3-88e98cbf06eb", + "d2a66d07-6507-409b-a607-574684c6d146" + ] + } + } + } + } + ] + }, + { + "EditTrace": [ + "f6f83133-4199-4d39-b968-cb9d30c8e387", + { + "id": "f6f83133-4199-4d39-b968-cb9d30c8e387", + "alias": "startup_order_review", + "name": "Startup sequence order review", + "description": "", + "oa": "bd4e4ed5-a0a9-4394-bec0-85bb72434e0b", + "sa": "e5dbb9b6-0df5-482e-88eb-8b4f3fa3c90d", + "kind": "Manual", + "baseline": { + "oa": { + "name": "OA Startup sequence", + "description": "" + }, + "sa": { + "name": "SA Startup sequence", + "description": "" + }, + "semantic": { + "facets": [ + { + "Member": { + "collection": "Steps", + "keys": { + "oa": "068e2da6-5317-4b06-b0a0-ce8c1b5a26ca", + "sa": "80077b69-a2e6-4858-96c3-88e98cbf06eb" + }, + "review": { + "oa": { + "Value": true + }, + "sa": { + "Value": true + } + } + } + }, + { + "Order": { + "collection": "Steps", + "review": { + "oa": { + "Value": [ + "9b50280a-881e-4d45-9645-947e71943f65", + "068e2da6-5317-4b06-b0a0-ce8c1b5a26ca", + "c18de06b-2519-416c-b559-43f940d817b7" + ] + }, + "sa": { + "Value": [ + "1b9b6f58-0944-47d4-9d34-caa0d0d1561f", + "fc5a0217-9478-48c5-a83f-0c8edb6a81d1", + "80077b69-a2e6-4858-96c3-88e98cbf06eb", + "d2a66d07-6507-409b-a607-574684c6d146" + ] + } + } + } + } + ] + } + } + } + ] + } + ], + "prepared_fingerprint": "b722b25357f48adb" +} \ No newline at end of file diff --git a/harness/fixtures/release1-native/semantic-ordered.final-request.json b/harness/fixtures/release1-native/semantic-ordered.final-request.json new file mode 100644 index 00000000..6decf574 --- /dev/null +++ b/harness/fixtures/release1-native/semantic-ordered.final-request.json @@ -0,0 +1,78 @@ +{ + "transition": { + "direction": "OaToSa", + "sources": [ + "bd4e4ed5-a0a9-4394-bec0-85bb72434e0b" + ], + "scope": "SelectedOnly", + "mappings": [ + { + "source": "bd4e4ed5-a0a9-4394-bec0-85bb72434e0b", + "target": "e5dbb9b6-0df5-482e-88eb-8b4f3fa3c90d", + "target_kind": null + } + ], + "fields": [], + "resolutions": [], + "destination_diagram": null + }, + "traces": { + "bd4e4ed5-a0a9-4394-bec0-85bb72434e0b": "f6f83133-4199-4d39-b968-cb9d30c8e387" + }, + "references": { + "068e2da6-5317-4b06-b0a0-ce8c1b5a26ca": "80077b69-a2e6-4858-96c3-88e98cbf06eb", + "9b50280a-881e-4d45-9645-947e71943f65": "1b9b6f58-0944-47d4-9d34-caa0d0d1561f", + "bd4e4ed5-a0a9-4394-bec0-85bb72434e0b": "e5dbb9b6-0df5-482e-88eb-8b4f3fa3c90d", + "c18de06b-2519-416c-b559-43f940d817b7": "d2a66d07-6507-409b-a607-574684c6d146" + }, + "reviews": [ + { + "source": "bd4e4ed5-a0a9-4394-bec0-85bb72434e0b", + "resolutions": [ + { + "facet": { + "Member": { + "collection": "Steps", + "keys": { + "oa": "068e2da6-5317-4b06-b0a0-ce8c1b5a26ca", + "sa": "80077b69-a2e6-4858-96c3-88e98cbf06eb" + } + } + }, + "decision": "TakeSource" + }, + { + "facet": { + "Order": "Steps" + }, + "decision": "Candidate" + } + ], + "reset": [] + } + ], + "candidates": [ + { + "Element": { + "id": "e5dbb9b6-0df5-482e-88eb-8b4f3fa3c90d", + "alias": "startup_sequence_e5dbb9b6", + "name": "SA Startup sequence", + "description": "", + "parent": null, + "body": { + "Sa": { + "FunctionalChain": { + "steps": [ + "1b9b6f58-0944-47d4-9d34-caa0d0d1561f", + "fc5a0217-9478-48c5-a83f-0c8edb6a81d1", + "80077b69-a2e6-4858-96c3-88e98cbf06eb", + "d2a66d07-6507-409b-a607-574684c6d146" + ] + } + } + } + } + } + ], + "accepted_impacts": [] +} \ No newline at end of file diff --git a/harness/fixtures/release1-native/semantic-ordered.json b/harness/fixtures/release1-native/semantic-ordered.json new file mode 100644 index 00000000..0962d7d8 --- /dev/null +++ b/harness/fixtures/release1-native/semantic-ordered.json @@ -0,0 +1,266 @@ +{ + "schema_version": 5, + "id": "8a7844b3-69e7-49ec-9666-b63a588ed3c4", + "alias": "mission", + "name": "Startup sequence order review", + "description": "", + "elements": { + "068e2da6-5317-4b06-b0a0-ce8c1b5a26ca": { + "id": "068e2da6-5317-4b06-b0a0-ce8c1b5a26ca", + "alias": "verify_interlock", + "name": "OA Verify interlock", + "description": "", + "parent": "bd4e4ed5-a0a9-4394-bec0-85bb72434e0b", + "body": { + "Oa": { + "Shared": { + "Occurrence": { + "body": { + "Control": { + "kind": "Choice" + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + }, + "1b9b6f58-0944-47d4-9d34-caa0d0d1561f": { + "id": "1b9b6f58-0944-47d4-9d34-caa0d0d1561f", + "alias": "request_start_1b9b6f58", + "name": "SA Request start", + "description": "", + "parent": "e5dbb9b6-0df5-482e-88eb-8b4f3fa3c90d", + "body": { + "Sa": { + "Shared": { + "Occurrence": { + "body": { + "Control": { + "kind": "Choice" + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + }, + "80077b69-a2e6-4858-96c3-88e98cbf06eb": { + "id": "80077b69-a2e6-4858-96c3-88e98cbf06eb", + "alias": "verify_interlock_80077b69", + "name": "SA Verify interlock", + "description": "", + "parent": "e5dbb9b6-0df5-482e-88eb-8b4f3fa3c90d", + "body": { + "Sa": { + "Shared": { + "Occurrence": { + "body": { + "Control": { + "kind": "Choice" + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + }, + "9b50280a-881e-4d45-9645-947e71943f65": { + "id": "9b50280a-881e-4d45-9645-947e71943f65", + "alias": "request_start", + "name": "OA Request start", + "description": "", + "parent": "bd4e4ed5-a0a9-4394-bec0-85bb72434e0b", + "body": { + "Oa": { + "Shared": { + "Occurrence": { + "body": { + "Control": { + "kind": "Choice" + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + }, + "bd4e4ed5-a0a9-4394-bec0-85bb72434e0b": { + "id": "bd4e4ed5-a0a9-4394-bec0-85bb72434e0b", + "alias": "startup_sequence", + "name": "OA Startup sequence", + "description": "", + "parent": null, + "body": { + "Oa": { + "Process": { + "steps": [ + "9b50280a-881e-4d45-9645-947e71943f65", + "068e2da6-5317-4b06-b0a0-ce8c1b5a26ca", + "c18de06b-2519-416c-b559-43f940d817b7" + ] + } + } + } + }, + "c18de06b-2519-416c-b559-43f940d817b7": { + "id": "c18de06b-2519-416c-b559-43f940d817b7", + "alias": "energize", + "name": "OA Energize", + "description": "", + "parent": "bd4e4ed5-a0a9-4394-bec0-85bb72434e0b", + "body": { + "Oa": { + "Shared": { + "Occurrence": { + "body": { + "Control": { + "kind": "Choice" + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + }, + "d2a66d07-6507-409b-a607-574684c6d146": { + "id": "d2a66d07-6507-409b-a607-574684c6d146", + "alias": "energize_d2a66d07", + "name": "SA Energize", + "description": "", + "parent": "e5dbb9b6-0df5-482e-88eb-8b4f3fa3c90d", + "body": { + "Sa": { + "Shared": { + "Occurrence": { + "body": { + "Control": { + "kind": "Choice" + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + }, + "db59b8b6-c002-4b26-bd32-87dcf2e40bcd": { + "id": "db59b8b6-c002-4b26-bd32-87dcf2e40bcd", + "alias": "local_self_test", + "name": "OA Local self-test", + "description": "", + "parent": "bd4e4ed5-a0a9-4394-bec0-85bb72434e0b", + "body": { + "Oa": { + "Shared": { + "Occurrence": { + "body": { + "Control": { + "kind": "Choice" + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + }, + "e5dbb9b6-0df5-482e-88eb-8b4f3fa3c90d": { + "id": "e5dbb9b6-0df5-482e-88eb-8b4f3fa3c90d", + "alias": "startup_sequence_e5dbb9b6", + "name": "SA Startup sequence", + "description": "", + "parent": null, + "body": { + "Sa": { + "FunctionalChain": { + "steps": [ + "1b9b6f58-0944-47d4-9d34-caa0d0d1561f", + "fc5a0217-9478-48c5-a83f-0c8edb6a81d1", + "d2a66d07-6507-409b-a607-574684c6d146" + ] + } + } + } + }, + "fc5a0217-9478-48c5-a83f-0c8edb6a81d1": { + "id": "fc5a0217-9478-48c5-a83f-0c8edb6a81d1", + "alias": "local_self_test_fc5a0217", + "name": "SA Local self-test", + "description": "", + "parent": "e5dbb9b6-0df5-482e-88eb-8b4f3fa3c90d", + "body": { + "Sa": { + "Shared": { + "Occurrence": { + "body": { + "Control": { + "kind": "Choice" + } + }, + "precondition": "", + "postcondition": "" + } + } + } + } + } + }, + "relations": {}, + "diagrams": {}, + "artifact_links": {}, + "traces": { + "f6f83133-4199-4d39-b968-cb9d30c8e387": { + "id": "f6f83133-4199-4d39-b968-cb9d30c8e387", + "alias": "startup_order_review", + "name": "Startup sequence order review", + "description": "", + "oa": "bd4e4ed5-a0a9-4394-bec0-85bb72434e0b", + "sa": "e5dbb9b6-0df5-482e-88eb-8b4f3fa3c90d", + "kind": "Manual", + "baseline": { + "oa": { + "name": "OA Startup sequence", + "description": "" + }, + "sa": { + "name": "SA Startup sequence", + "description": "" + }, + "semantic": { + "facets": [ + { + "Member": { + "collection": "Steps", + "keys": { + "oa": "068e2da6-5317-4b06-b0a0-ce8c1b5a26ca", + "sa": "80077b69-a2e6-4858-96c3-88e98cbf06eb" + }, + "review": { + "oa": { + "Value": true + }, + "sa": { + "Value": false + } + } + } + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/harness/fixtures/release1-native/semantic-ordered.manifest.json b/harness/fixtures/release1-native/semantic-ordered.manifest.json new file mode 100644 index 00000000..bb7095df --- /dev/null +++ b/harness/fixtures/release1-native/semantic-ordered.manifest.json @@ -0,0 +1,36 @@ +{ + "direction": "OaToSa", + "facet": { + "Member": { + "collection": "Steps", + "keys": { + "oa": "068e2da6-5317-4b06-b0a0-ce8c1b5a26ca", + "sa": "80077b69-a2e6-4858-96c3-88e98cbf06eb" + } + } + }, + "final_order": [ + "1b9b6f58-0944-47d4-9d34-caa0d0d1561f", + "fc5a0217-9478-48c5-a83f-0c8edb6a81d1", + "80077b69-a2e6-4858-96c3-88e98cbf06eb", + "d2a66d07-6507-409b-a607-574684c6d146" + ], + "rows": [ + [ + "9b50280a-881e-4d45-9645-947e71943f65", + "c18de06b-2519-416c-b559-43f940d817b7", + "068e2da6-5317-4b06-b0a0-ce8c1b5a26ca", + "db59b8b6-c002-4b26-bd32-87dcf2e40bcd" + ], + [ + "1b9b6f58-0944-47d4-9d34-caa0d0d1561f", + "d2a66d07-6507-409b-a607-574684c6d146", + "80077b69-a2e6-4858-96c3-88e98cbf06eb", + "fc5a0217-9478-48c5-a83f-0c8edb6a81d1" + ] + ], + "source": "bd4e4ed5-a0a9-4394-bec0-85bb72434e0b", + "target": "e5dbb9b6-0df5-482e-88eb-8b4f3fa3c90d", + "trace": "f6f83133-4199-4d39-b968-cb9d30c8e387", + "trace_name": "Startup sequence order review" +} \ No newline at end of file diff --git a/harness/fixtures/release1-native/semantic-ordered.steps.md b/harness/fixtures/release1-native/semantic-ordered.steps.md new file mode 100644 index 00000000..1a66966f --- /dev/null +++ b/harness/fixtures/release1-native/semantic-ordered.steps.md @@ -0,0 +1,73 @@ +# Ordered semantic native fixtures + +Open semantic-ordered.arcadia for the main OA→SA case. One trace is ready: +"Startup sequence order review" (alias startup_order_review). +Other validated variants use the same instructions: +- semantic-ordered-reverse.arcadia: "Reverse startup sequence order review", SA→OA. +- semantic-ordered-scenario.arcadia: "Startup scenario order review". +- semantic-ordered-branch-steps.arcadia: "Startup branch steps order review". +- semantic-ordered-fragment.arcadia: "Startup fragment branches order review". +The fragment variant uses Fragment branches in Members / rows; all others use +Ordered steps. In reverse, substitute Operational for destination/System for source. + +## Exact main flow + +1. Open the trace from Traceability with Review semantics. Direction Operational + to System. Reference pairs should include these three explicit OA→SA rows: + OA Request start → SA Request start + OA Energize → SA Energize + OA Verify interlock → SA Verify interlock + The trace endpoints are already paired; names do not infer the three step pairs. + Use Reference pairs to add them by their named picker choices. +2. In Properties select the paired Steps member for Verify interlock (enable Show + unchanged if necessary). It has pre-recorded exact paired history. Alternatively + choose Members / rows → Ordered steps, choose OA Verify interlock and SA Verify + interlock, then Use these addresses and review row. Click Take Operational. +3. Expect "Ordered member insertion has no unique unchanged anchor" and disabled + Apply. Source order is Request start, Verify interlock, Energize. Destination + order is Request start, Local self-test, Energize. The local step prevents a + uniquely anchored insertion; accepted source/project/history stay initial. +4. In Members / rows choose Review final order… (or select the Order Steps fact), + then Set final value. Initial draft: Request start, Local self-test, Energize, + Verify interlock. Press Up on Verify interlock once. Final draft: + Request start, Local self-test, Verify interlock, Energize. +5. Preview must succeed with zero Additional changes. Apply once. Compare accepted + project with semantic-ordered.after-order.json and single-file source with + semantic-ordered.after-order.arcadia. Only selected membership/order and its + typed review history change. One Undo restores the initial project/source; + Redo restores after-order. Save/reopen preserves the exact leading comment. + +## Rejected retained-draft/recovery check + +Before Apply, keep the explicit order Candidate selected but leave the Verify +member decision unselected. The saved order still contains Verify, which is no +longer in the proposed membership. Expect an exact-survivor error; the authored +order must remain visible. Changing tabs and recovering the workflow must retain +that rejected order. Re-select Take Operational for Verify: the original final +order becomes valid again without editing it. Reconcile with selected membership +is the explicit alternative that removes excluded entries and appends newly +included entries; it must never happen merely on Refresh or leaving a tab. + +At 1100x700 inspect the full scrollable order list and Up/Down controls, error, +Apply/Cancel and exact preview observations. Repeat at 1440x900, then reverse and +at least the Fragment branches variant. + +## Artifacts and validation + +Each stem has initial .arcadia/.json, .after-order.arcadia/.json, ambiguous request +and error, final request and plan, plus a manifest with exact IDs. Source oracles +use production source_project::update_bundle so the leading comment and unchanged +declarations retain exact bytes. semantic-ordered.validation.txt records all five +fixtures: both initial and final models validate, production source/JSON decode +round trips are exact, ambiguous Take rejects, successful final plan has 0 impacts. +Generator output and binaries stay under this ignored directory; no Cargo or +shared target output was used. + +## Minimum-width application navigation recheck + +The previous top page row hid Model checks and Engineering guide behind horizontal +scrolling at 1100x700. The source now wraps the existing page buttons. In a newly +built binary, resize 1440x900→1100x700: all nine labels/actions must stay visible, +with a second row as needed. Select Model checks and Engineering guide; ensure +content remains usable below the taller header. Resize back to 1440x900 and verify +buttons return to available space while selected-page styling remains correct. diff --git a/harness/fixtures/release1-native/state-actions.arcadia b/harness/fixtures/release1-native/state-actions.arcadia new file mode 100644 index 00000000..677f3053 --- /dev/null +++ b/harness/fixtures/release1-native/state-actions.arcadia @@ -0,0 +1,104 @@ +arcadia 5; +project native_state_actions "Native QA — state actions and recovery" id "f0060906-0000-4000-8000-000000000000" { + description "Isolated deterministic QA fixture. No user data. Preserve this pristine file; use a trial copy for edits."; + + sa_system controller "Pressure controller" id "f0060906-0000-4000-8000-000000000001" {} + sa_state_machine operating "Pressure operation" id "f0060906-0000-4000-8000-000000000002" { parent @controller; } + sa_region main_region "Main region" id "f0060906-0000-4000-8000-000000000003" { parent @operating; } + sa_state ready "Ready" id "f0060906-0000-4000-8000-000000000004" { + parent @main_region; + kind State; + entry "Retained entry notes"; + do_activity ""; + exit ""; + } + sa_region monitoring_region "Monitoring region" id "f0060906-0000-4000-8000-000000000005" { parent @ready; } + sa_state sampling "Sampling" id "f0060906-0000-4000-8000-000000000006" { parent @monitoring_region; kind State; } + sa_state idle "Idle" id "f0060906-0000-4000-8000-000000000007" { parent @main_region; kind State; } + sa_initial start "Start" id "f0060906-0000-4000-8000-000000000008" { parent @main_region; kind Initial; } + sa_initial start_monitoring "Start monitoring" id "f0060906-0000-4000-8000-000000000009" { parent @monitoring_region; kind Initial; } + sa_function restore_settings "Restore settings" id "f0060906-0000-4000-8000-000000000010" { + description "Referenced Entry work. This function is not contained in Ready."; + } + sa_function monitor_pressure "Monitor pressure" id "f0060906-0000-4000-8000-000000000011" { + description "Referenced Do work. This function is not contained in Ready."; + } + sa_function release_resources "Release resources" id "f0060906-0000-4000-8000-000000000012" { + description "Referenced Exit work. This function is not contained in Ready."; + } + sa_event sample_tick "Sample tick" id "f0060906-0000-4000-8000-000000000013" { + body { Time { kind After; expression "1 s"; }; }; + } + sa_data_type retry_budget "Retry budget" id "f0060906-0000-4000-8000-000000000014" { + definition { Integer { minimum 0; maximum 4; }; }; + default none; + unit "attempts"; + } + + allocation performs_restore "Performs restore" id "f0060906-0000-4000-8000-000000000020" { source @controller; target @restore_settings; } + allocation performs_monitor "Performs monitoring" id "f0060906-0000-4000-8000-000000000021" { source @controller; target @monitor_pressure; } + allocation performs_release "Performs release" id "f0060906-0000-4000-8000-000000000022" { source @controller; target @release_resources; } + state_action on_entry "Entry work link" id "f0060906-0000-4000-8000-000000000030" { source @ready; target @restore_settings; phase Entry; } + state_action while_ready "Do work link" id "f0060906-0000-4000-8000-000000000031" { source @ready; target @monitor_pressure; phase Do; } + state_action on_exit "Exit work link" id "f0060906-0000-4000-8000-000000000032" { source @ready; target @release_resources; phase Exit; } + state_transition refresh_status "Refresh status" id "f0060906-0000-4000-8000-000000000033" { + source @ready; target @ready; + transition_kind Internal; + triggers [@sample_tick]; trigger ""; + guard "pressure_valid"; + effects [@monitor_pressure]; effect "Update status display"; + } + state_transition initialize "Initialize" id "f0060906-0000-4000-8000-000000000034" { + source @start; target @ready; + transition_kind External; + triggers []; trigger ""; guard ""; effects []; effect ""; + } + state_transition initialize_monitoring "Initialize monitoring" id "f0060906-0000-4000-8000-000000000035" { + source @start_monitoring; target @sampling; + transition_kind External; + triggers []; trigger ""; guard ""; effects []; effect ""; + } + state_transition finish_monitoring "Finish monitoring" id "f0060906-0000-4000-8000-000000000036" { + source @ready; target @idle; + transition_kind External; + triggers []; trigger ""; guard "shutdown_requested"; effects []; effect ""; + } + + diagram state_view "Pressure operation — saved state view" id "f0060906-0000-4000-8000-000000000100" { + layer Sa; + projection { StateMachine { machine @operating; }; }; + description "Ready has prose, three typed action rows, an internal transition and a nested region. Work functions are deliberately not placed here."; + } + node view_machine id "f0060906-0000-4000-8000-000000000102" { + diagram @state_view; element @operating; + position { x 40; y 40; }; size { width 1030; height 800; }; collapsed false; + } + node view_main id "f0060906-0000-4000-8000-000000000103" { + diagram @state_view; element @main_region; + position { x 64; y 104; }; size { width 982; height 712; }; collapsed false; + } + node view_ready id "f0060906-0000-4000-8000-000000000104" { + diagram @state_view; element @ready; + position { x 160; y 180; }; size { width 590; height 570; }; collapsed false; + } + node view_monitoring id "f0060906-0000-4000-8000-000000000105" { + diagram @state_view; element @monitoring_region; + position { x 184; y 430; }; size { width 542; height 296; }; collapsed false; + } + node view_sampling id "f0060906-0000-4000-8000-000000000106" { + diagram @state_view; element @sampling; + position { x 286; y 520; }; size { width 224; height 94; }; collapsed false; + } + node view_idle id "f0060906-0000-4000-8000-000000000107" { + diagram @state_view; element @idle; + position { x 780; y 250; }; size { width 224; height 94; }; collapsed false; + } + node view_start id "f0060906-0000-4000-8000-000000000108" { + diagram @state_view; element @start; + position { x 105; y 210; }; size { width 16; height 16; }; collapsed false; + } + node view_monitor_start id "f0060906-0000-4000-8000-000000000109" { + diagram @state_view; element @start_monitoring; + position { x 220; y 550; }; size { width 16; height 16; }; collapsed false; + } +} diff --git a/harness/fixtures/rest-coverage/README.md b/harness/fixtures/rest-coverage/README.md new file mode 100644 index 00000000..aea19799 --- /dev/null +++ b/harness/fixtures/rest-coverage/README.md @@ -0,0 +1,32 @@ +# Real server coverage parity probe + +Run with PowerShell7 on Windows against a built/staged server. The script launches +its own hidden server on a free loopback port, creates synthetic data under a fresh +timestamped `.local/rest-coverage-*` directory, captures25 requests/responses and +asserts diagnostic parity, invalid-write rejection, source preservation and restart. +It stops only its own process, including on failure. It never attaches to an +existing server and does not use user credentials. The token it creates is fixed +synthetic probe data, not suitable for a deployed server. + +From the repository root, replace the executable path with the artifact under test: + +```powershell +$env:PATH = "$env:SystemRoot\System32;$env:SystemRoot;$env:SystemRoot\System32\Wbem" +& harness/fixtures/rest-coverage/run-coverage-probe.ps1 -ServerExe 'C:\path\to\staged\arcadia-server.exe' +``` + +For gnullvm builds, stage the required runtime DLL beside the executable. Clean +PATH is deliberate: success should not depend on a developer compiler directory. +The first launch and restart use the same isolated store; restart is a forced +process stop after an acknowledged saved revision. This tests local persistence, +not graceful-drain handling. Output directories and evidence are retained. + +`coverage-probe.json` has incomplete, repaired and invalid source bundles with +explicit synthetic UUIDs, plus five exact expected warning tuples. The repair +deliberately leaves an independent `sequence_gap` warning. The final reintroduction +of the incomplete bundle is an explicit replacement, not service Undo. No new API +or production code is required by the probe. + +Observed Windows execution and limitations are recorded in +`harness/release-rest-coverage-verification-20260906.md`. This launch wrapper is +Windows-specific; the fixture and HTTP oracles can be reused on other platforms. diff --git a/harness/fixtures/rest-coverage/coverage-probe.json b/harness/fixtures/rest-coverage/coverage-probe.json new file mode 100644 index 00000000..8fe6f6d9 --- /dev/null +++ b/harness/fixtures/rest-coverage/coverage-probe.json @@ -0,0 +1,58 @@ +{ + "incomplete": { + "entry": "project.arcadia", + "files": { + "work.arcadia": "// Synthetic OA fixture: known responsibilities, explicit incomplete contexts.\noa_entity crew \"Crew\" id \"f0660906-0000-4000-8000-000000000002\" {}\noa_activity activity_group \"Manage service\" id \"f0660906-0000-4000-8000-000000000010\" {\n oa_activity a \"Prepare\" id \"f0660906-0000-4000-8000-000000000011\" {}\n oa_activity b \"Deliver\" id \"f0660906-0000-4000-8000-000000000012\" {}\n}\noa_activity c \"Verify\" id \"f0660906-0000-4000-8000-000000000013\" {}\noa_capability q \"Reliable delivery\" id \"f0660906-0000-4000-8000-000000000020\" {}\noa_mission m \"Deliver reliably\" id \"f0660906-0000-4000-8000-000000000021\" {}\nallocation allocate_a \"Prepare responsibility\" id \"f0660906-0000-4000-8000-000000000041\" { source @crew; target @a; }\nallocation allocate_b \"Deliver responsibility\" id \"f0660906-0000-4000-8000-000000000042\" { source @crew; target @b; }\nallocation allocate_c \"Verify responsibility\" id \"f0660906-0000-4000-8000-000000000043\" { source @crew; target @c; }\ninteraction eab \"Prepared service\" id \"f0660906-0000-4000-8000-000000000050\" { source @a; target @b; }\ninteraction ep \"Grouped service\" id \"f0660906-0000-4000-8000-000000000051\" { source @activity_group; target @c; }\ninteraction eca \"Verified service\" id \"f0660906-0000-4000-8000-000000000052\" { source @c; target @a; }\ncapability_involvement support \"Crew support\" id \"f0660906-0000-4000-8000-000000000060\" { source @crew; target @q; }", + "behavior.arcadia": "oa_process delivery \"Delivery path\" id \"f0660906-0000-4000-8000-000000000030\" { steps [@ab_use]; }\noa_occurrence ab_use \"Use prepared service\" id \"f0660906-0000-4000-8000-000000000031\" {\n parent @delivery; body { Exchange { exchange @eab; sender none; receiver none; message_kind Asynchronous; reply_to none; values []; }; };\n}", + "project.arcadia": "arcadia 5; project coverage \"REST coverage parity\" id \"f0660906-0000-4000-8000-000000000001\" { include \"work.arcadia\"; include \"behavior.arcadia\"; }" + } + }, + "repaired": { + "entry": "project.arcadia", + "files": { + "work.arcadia": "// Synthetic OA fixture: known responsibilities, explicit incomplete contexts.\noa_entity crew \"Crew\" id \"f0660906-0000-4000-8000-000000000002\" {}\noa_activity activity_group \"Manage service\" id \"f0660906-0000-4000-8000-000000000010\" {\n oa_activity a \"Prepare\" id \"f0660906-0000-4000-8000-000000000011\" {}\n oa_activity b \"Deliver\" id \"f0660906-0000-4000-8000-000000000012\" {}\n}\noa_activity c \"Verify\" id \"f0660906-0000-4000-8000-000000000013\" {}\noa_capability q \"Reliable delivery\" id \"f0660906-0000-4000-8000-000000000020\" {}\noa_mission m \"Deliver reliably\" id \"f0660906-0000-4000-8000-000000000021\" {}\nallocation allocate_a \"Prepare responsibility\" id \"f0660906-0000-4000-8000-000000000041\" { source @crew; target @a; }\nallocation allocate_b \"Deliver responsibility\" id \"f0660906-0000-4000-8000-000000000042\" { source @crew; target @b; }\nallocation allocate_c \"Verify responsibility\" id \"f0660906-0000-4000-8000-000000000043\" { source @crew; target @c; }\ninteraction eab \"Prepared service\" id \"f0660906-0000-4000-8000-000000000050\" { source @a; target @b; }\ninteraction ep \"Grouped service\" id \"f0660906-0000-4000-8000-000000000051\" { source @a; target @c; }\ninteraction eca \"Verified service\" id \"f0660906-0000-4000-8000-000000000052\" { source @c; target @a; }\ncapability_involvement support \"Crew support\" id \"f0660906-0000-4000-8000-000000000060\" { source @crew; target @q; }", + "behavior.arcadia": "oa_process delivery \"Delivery path\" id \"f0660906-0000-4000-8000-000000000030\" { steps [@ca_use, @ab_use, @pc_use]; }\noa_occurrence ab_use \"Use prepared service\" id \"f0660906-0000-4000-8000-000000000031\" {\n parent @delivery; body { Exchange { exchange @eab; sender none; receiver none; message_kind Asynchronous; reply_to none; values []; }; };\n}\noa_occurrence ca_use \"Use verified service\" id \"f0660906-0000-4000-8000-000000000032\" {\n parent @delivery; body { Exchange { exchange @eca; sender none; receiver none; message_kind Asynchronous; reply_to none; values []; }; };\n}\noa_occurrence pc_use \"Use responsible leaf exchange\" id \"f0660906-0000-4000-8000-000000000033\" {\n parent @delivery; body { Exchange { exchange @ep; sender none; receiver none; message_kind Asynchronous; reply_to none; values []; }; };\n}\nmission_capability mission_delivery \"Mission delivery\" id \"f0660906-0000-4000-8000-000000000061\" { source @m; target @q; }\nchain_involvement context \"Explicit delivery context\" id \"f0660906-0000-4000-8000-000000000062\" { source @delivery; target @q; }", + "project.arcadia": "arcadia 5; project coverage \"REST coverage parity\" id \"f0660906-0000-4000-8000-000000000001\" { include \"work.arcadia\"; include \"behavior.arcadia\"; }" + } + }, + "invalid": { + "entry": "project.arcadia", + "files": { + "work.arcadia": "// Synthetic OA fixture: known responsibilities, explicit incomplete contexts.\noa_entity crew \"Crew\" id \"f0660906-0000-4000-8000-000000000002\" {}\noa_activity activity_group \"Manage service\" id \"f0660906-0000-4000-8000-000000000010\" {\n oa_activity a \"Prepare\" id \"f0660906-0000-4000-8000-000000000011\" {}\n oa_activity b \"Deliver\" id \"f0660906-0000-4000-8000-000000000012\" {}\n}\noa_activity c \"Verify\" id \"f0660906-0000-4000-8000-000000000013\" {}\noa_capability q \"Reliable delivery\" id \"f0660906-0000-4000-8000-000000000020\" {}\noa_mission m \"Deliver reliably\" id \"f0660906-0000-4000-8000-000000000021\" {}\nallocation allocate_a \"Prepare responsibility\" id \"f0660906-0000-4000-8000-000000000041\" { source @crew; target @a; }\nallocation allocate_b \"Deliver responsibility\" id \"f0660906-0000-4000-8000-000000000042\" { source @crew; target @b; }\nallocation allocate_c \"Verify responsibility\" id \"f0660906-0000-4000-8000-000000000043\" { source @crew; target @c; }\ninteraction eab \"Prepared service\" id \"f0660906-0000-4000-8000-000000000050\" { source @a; target @b; }\ninteraction ep \"Grouped service\" id \"f0660906-0000-4000-8000-000000000051\" { source @crew; target @c; }\ninteraction eca \"Verified service\" id \"f0660906-0000-4000-8000-000000000052\" { source @c; target @a; }\ncapability_involvement support \"Crew support\" id \"f0660906-0000-4000-8000-000000000060\" { source @crew; target @q; }", + "behavior.arcadia": "oa_process delivery \"Delivery path\" id \"f0660906-0000-4000-8000-000000000030\" { steps [@ab_use]; }\noa_occurrence ab_use \"Use prepared service\" id \"f0660906-0000-4000-8000-000000000031\" {\n parent @delivery; body { Exchange { exchange @eab; sender none; receiver none; message_kind Asynchronous; reply_to none; values []; }; };\n}", + "project.arcadia": "arcadia 5; project coverage \"REST coverage parity\" id \"f0660906-0000-4000-8000-000000000001\" { include \"work.arcadia\"; include \"behavior.arcadia\"; }" + } + }, + "expected": [ + { + "object_id": "f0660906-0000-4000-8000-000000000051", + "path": "work.arcadia", + "code": "non_leaf_work_exchange", + "field": "source" + }, + { + "object_id": "f0660906-0000-4000-8000-000000000013", + "path": "work.arcadia", + "code": "work_without_behavior_context", + "field": null + }, + { + "object_id": "f0660906-0000-4000-8000-000000000052", + "path": "work.arcadia", + "code": "exchange_without_behavior_context", + "field": null + }, + { + "object_id": "f0660906-0000-4000-8000-000000000021", + "path": "work.arcadia", + "code": "mission_without_capability", + "field": null + }, + { + "object_id": "f0660906-0000-4000-8000-000000000020", + "path": "work.arcadia", + "code": "capability_without_behavior_context", + "field": null + } + ] +} \ No newline at end of file diff --git a/harness/fixtures/rest-coverage/run-coverage-probe.ps1 b/harness/fixtures/rest-coverage/run-coverage-probe.ps1 new file mode 100644 index 00000000..380e67eb --- /dev/null +++ b/harness/fixtures/rest-coverage/run-coverage-probe.ps1 @@ -0,0 +1,137 @@ +param([Parameter(Mandatory=$true)][string]$ServerExe) +$ErrorActionPreference = 'Stop' +$taskRoot = (Resolve-Path (Join-Path $PSScriptRoot '../../..')).Path +$exe = (Resolve-Path -LiteralPath $ServerExe).Path +$runRoot = Join-Path $taskRoot ('.local/rest-coverage-' + (Get-Date -Format 'yyyyMMdd-HHmmss')) +$storePath = Join-Path $runRoot 'store' +New-Item -ItemType Directory -Path $runRoot,$storePath | Out-Null +$payload = Get-Content -LiteralPath (Join-Path $PSScriptRoot 'coverage-probe.json') -Raw | ConvertFrom-Json -AsHashtable +$testToken = 'release-coverage-probe-20260906-local-only' +$tokenPath = Join-Path $runRoot 'synthetic-token.txt' +[IO.File]::WriteAllText($tokenPath,$testToken) +$listener = [Net.Sockets.TcpListener]::new([Net.IPAddress]::Loopback,0) +$listener.Start() +$probePort = $listener.LocalEndpoint.Port +$listener.Stop() +$base = "http://127.0.0.1:$probePort" +$script:probeProcess = $null +$script:sequence = 0 +$script:launch = 0 +$codes = @($payload.expected | ForEach-Object { $_.code }) +function Assert-Probe($condition,[string]$message) { if (-not $condition) { throw $message } } +function Start-ProbeServer { + $script:launch++ + $script:probeProcess = Start-Process -FilePath $exe -ArgumentList @('--bind',"127.0.0.1:$probePort",'--data-dir',('"'+$storePath+'"'),'--token-file',('"'+$tokenPath+'"')) -WindowStyle Hidden -PassThru -RedirectStandardOutput (Join-Path $runRoot "server-$script:launch.stdout.log") -RedirectStandardError (Join-Path $runRoot "server-$script:launch.stderr.log") + for ($attempt = 0; $attempt -lt 40; $attempt++) { + if ($script:probeProcess.HasExited) { throw 'Probe server exited before readiness' } + try { $ready=Invoke-WebRequest "$base/health/ready" -TimeoutSec 1; if ($ready.StatusCode -eq 200) { return } } catch {} + Start-Sleep -Milliseconds 250 + } + throw 'Probe server did not become ready' +} +function Stop-ProbeServer { + if ($null -ne $script:probeProcess -and -not $script:probeProcess.HasExited) { + Stop-Process -InputObject $script:probeProcess + $script:probeProcess.WaitForExit() + } + $script:probeProcess = $null +} +function Call-Probe([string]$name,[string]$method,[string]$path,$body=$null,[string]$etag='',[int]$expectedStatus=200) { + $script:sequence++ + $stem = Join-Path $runRoot ('{0:D2}-{1}' -f $script:sequence,$name) + $headers = @{Authorization="Bearer $testToken"} + if ($etag) { $headers['If-Match']=$etag } + $request = @{Uri="$base$path";Method=$method;Headers=$headers;SkipHttpErrorCheck=$true;TimeoutSec=40} + if ($null -ne $body) { + $json=ConvertTo-Json -InputObject $body -Depth 100 -Compress + [IO.File]::WriteAllText("$stem.request.json",$json) + $request.ContentType='application/json' + $request.Body=[Text.Encoding]::UTF8.GetBytes($json) + } + $response=Invoke-WebRequest @request + $content=[string]$response.Content + [IO.File]::WriteAllText("$stem.response.txt",$content) + $tag=[string]($response.Headers.ETag | Select-Object -First 1) + @{method=$method;path=$path;status=[int]$response.StatusCode;etag=$tag} | ConvertTo-Json | Set-Content -LiteralPath "$stem.meta.json" + Assert-Probe ($response.StatusCode -eq $expectedStatus) "$name expected $expectedStatus; got $($response.StatusCode): $content" + $data=$null + try { $data=ConvertFrom-Json -InputObject $content -AsHashtable } catch {} + return @{content=$content;data=$data;etag=$tag;headers=$response.Headers} +} +function Tuples($issues) { + return (@($issues | Where-Object { $_.code -in $codes } | ForEach-Object { "$($_.code)|$($_.object_id)|$($_.field)" } | Sort-Object) -join "`n") +} +function Assert-Coverage($response,[bool]$located=$false) { + $found=@($response.data.issues | Where-Object { $_.code -in $codes }) + Assert-Probe ((Tuples $found) -ceq (Tuples $payload.expected)) 'Five exact coverage tuples differ' + Assert-Probe (@($response.data.issues | Where-Object severity -eq 'error').Count -eq 0) 'Unexpected structural error' + foreach ($issue in $found) { + Assert-Probe ($issue.severity -eq 'warning') 'Coverage is not warning severity' + if ($located) { + Assert-Probe ($issue.path -eq 'work.arcadia' -and $issue.line -ge 1 -and $issue.column -ge 1 -and $null -ne $issue.span) 'Missing submitted-source location' + } + } +} +function Assert-Bundle($actual,$expected) { + Assert-Probe ($actual.entry -ceq $expected.entry -and $actual.files.Count -eq $expected.files.Count) 'Bundle entry/file count changed' + foreach ($key in $expected.files.Keys) { Assert-Probe ($actual.files[$key] -ceq $expected.files[$key]) "Source bytes changed: $key" } +} +try { + @{exe=$exe;sha256=(Get-FileHash -LiteralPath $exe -Algorithm SHA256).Hash;port=$probePort;started=(Get-Date -Format o)} | ConvertTo-Json | Set-Content (Join-Path $runRoot 'run.json') + Start-ProbeServer + $created=Call-Probe 'create' 'POST' '/api/v1/projects' @{name='Coverage probe host'} '' 201 + $path=[string]($created.headers.Location | Select-Object -First 1) + Assert-Probe ($path -like '/api/v1/projects/*') 'Create omitted resource Location' + $before=Call-Probe 'before-model' 'GET' "$path/model" + $beforeBundle=Call-Probe 'before-bundle' 'GET' "$path/source-project" + $candidate=Call-Probe 'incomplete-candidate' 'POST' "$path/validation" @{format='source-project';source_project=$payload.incomplete} + Assert-Probe $candidate.data.valid 'Incomplete model should be valid' + Assert-Coverage $candidate $true + $after=Call-Probe 'candidate-no-commit-model' 'GET' "$path/model" + $afterBundle=Call-Probe 'candidate-no-commit-bundle' 'GET' "$path/source-project" + Assert-Probe ($after.content -ceq $before.content -and $after.etag -ceq $before.etag) 'Candidate changed accepted model/revision' + Assert-Bundle $afterBundle.data $beforeBundle.data + $committed=Call-Probe 'commit-incomplete' 'PUT' "$path/source-project" $payload.incomplete $before.etag + Assert-Coverage $committed $true + Assert-Probe ($committed.data.changed -and $committed.etag -cne $before.etag) 'Warning-only commit did not advance revision' + $model=Call-Probe 'incomplete-model' 'GET' "$path/model" + $bundle=Call-Probe 'incomplete-bundle' 'GET' "$path/source-project" + Assert-Bundle $bundle.data $payload.incomplete + $source=Call-Probe 'incomplete-canonical' 'GET' "$path/source" + $validation=Call-Probe 'incomplete-validation' 'GET' "$path/validation" + Assert-Coverage $validation + $jsonCheck=Call-Probe 'json-parity' 'POST' "$path/validation" @{format='json';model=$model.data} + $textCheck=Call-Probe 'text-parity' 'POST' "$path/validation" @{format='arcadia';source=$source.content} + Assert-Coverage $jsonCheck + Assert-Coverage $textCheck + Assert-Probe ($jsonCheck.data.valid -and $textCheck.data.valid) 'JSON/text parity candidate invalid' + $invalid=Call-Probe 'invalid-candidate' 'POST' "$path/validation" @{format='source-project';source_project=$payload.invalid} + Assert-Probe (-not $invalid.data.valid -and @($invalid.data.issues | Where-Object { $_.code -eq 'invalid_endpoints' -and $_.severity -eq 'error' }).Count -ge 1) 'Invalid endpoint not diagnosed' + Assert-Probe ((Tuples $invalid.data.issues) -eq '') 'Structural error did not gate coverage' + $null=Call-Probe 'invalid-write-rejected' 'PUT' "$path/source-project" $payload.invalid $model.etag 422 + $unchanged=Call-Probe 'rejection-model' 'GET' "$path/model" + $unchangedBundle=Call-Probe 'rejection-bundle' 'GET' "$path/source-project" + Assert-Probe ($unchanged.content -ceq $model.content -and $unchanged.etag -ceq $model.etag) 'Rejected write changed model/revision' + Assert-Bundle $unchangedBundle.data $bundle.data + $fixed=Call-Probe 'repair-write' 'PUT' "$path/source-project" $payload.repaired $model.etag + Assert-Probe ((Tuples $fixed.data.issues) -eq '') 'Repair left coverage warnings' + $fixedModel=Call-Probe 'repaired-model' 'GET' "$path/model" + $fixedBundle=Call-Probe 'repaired-bundle' 'GET' "$path/source-project" + Assert-Bundle $fixedBundle.data $payload.repaired + $fixedValidation=Call-Probe 'repaired-validation' 'GET' "$path/validation" + Stop-ProbeServer + Start-ProbeServer + $reopened=Call-Probe 'restart-model' 'GET' "$path/model" + $reopenedBundle=Call-Probe 'restart-bundle' 'GET' "$path/source-project" + $reopenedValidation=Call-Probe 'restart-validation' 'GET' "$path/validation" + Assert-Probe ($reopened.content -ceq $fixedModel.content -and $reopened.etag -ceq $fixedModel.etag) 'Restart changed model/revision' + Assert-Bundle $reopenedBundle.data $fixedBundle.data + Assert-Probe ($reopenedValidation.content -ceq $fixedValidation.content) 'Restart changed validation' + $again=Call-Probe 'restore-incomplete-by-replacement' 'PUT' "$path/source-project" $payload.incomplete $reopened.etag + Assert-Coverage $again $true + @{result='passed';requests=$script:sequence;resource=$path;initial_revision=$committed.data.revision;repaired_revision=$fixed.data.revision;final_revision=$again.data.revision;repair_issues=$fixed.data.issues;evidence=$runRoot} | ConvertTo-Json -Depth 20 | Set-Content (Join-Path $runRoot 'result.json') + Get-Content (Join-Path $runRoot 'result.json') +} catch { + $_ | Out-String | Set-Content (Join-Path $runRoot 'failure.txt') + throw +} finally { Stop-ProbeServer } diff --git a/harness/fixtures/run-timed-bundle-oracle.ps1 b/harness/fixtures/run-timed-bundle-oracle.ps1 new file mode 100644 index 00000000..96005f8a --- /dev/null +++ b/harness/fixtures/run-timed-bundle-oracle.ps1 @@ -0,0 +1,99 @@ +param( + [Parameter(Mandatory=$true)][string]$ServerExe, + [Parameter(Mandatory=$true)][string]$PristineFile, + [Parameter(Mandatory=$true)][string]$SavedEntry +) +$ErrorActionPreference='Stop' +$taskRoot=(Resolve-Path (Join-Path $PSScriptRoot '../..')).Path +$exe=(Resolve-Path -LiteralPath $ServerExe).Path +$pristine=(Resolve-Path -LiteralPath $PristineFile).Path +$saved=(Resolve-Path -LiteralPath $SavedEntry).Path +$include=(Resolve-Path -LiteralPath (Join-Path (Split-Path -Parent $saved) 'sa/model.arcadia')).Path +$runRoot=Join-Path $taskRoot ('.local/timed-bundle-oracle-'+(Get-Date -Format 'yyyyMMdd-HHmmss')) +$storePath=Join-Path $runRoot 'store' +New-Item -ItemType Directory -Path $runRoot,$storePath | Out-Null +$listener=[Net.Sockets.TcpListener]::new([Net.IPAddress]::Loopback,0) +$listener.Start(); $probePort=$listener.LocalEndpoint.Port; $listener.Stop() +$base="http://127.0.0.1:$probePort" +$testToken='timed-bundle-oracle-20260906-synthetic-only' +$tokenPath=Join-Path $runRoot 'synthetic-token.txt' +[IO.File]::WriteAllText($tokenPath,$testToken) +$script:ownedServer=$null +$script:launch=0 +function Start-Oracle { + $script:launch++ + $script:ownedServer=Start-Process -FilePath $exe -ArgumentList @('--bind',"127.0.0.1:$probePort",'--data-dir',('"'+$storePath+'"'),'--token-file',('"'+$tokenPath+'"')) -WindowStyle Hidden -PassThru -RedirectStandardOutput (Join-Path $runRoot "server-$script:launch.stdout.log") -RedirectStandardError (Join-Path $runRoot "server-$script:launch.stderr.log") + for($n=0;$n -lt 40;$n++) { + if($script:ownedServer.HasExited){throw 'Oracle server exited'} + try{$r=Invoke-WebRequest "$base/health/ready" -TimeoutSec 1;if($r.StatusCode -eq 200){return}}catch{} + Start-Sleep -Milliseconds 250 + } + throw 'Oracle readiness expired' +} +function Stop-Oracle { + if($null -ne $script:ownedServer -and -not $script:ownedServer.HasExited){Stop-Process -InputObject $script:ownedServer;$script:ownedServer.WaitForExit()} + $script:ownedServer=$null +} +function Request-Oracle([string]$label,[string]$method,[string]$path,$body=$null,[int]$expected=200) { + $args=@{Uri="$base$path";Method=$method;Headers=@{Authorization="Bearer $testToken"};SkipHttpErrorCheck=$true;TimeoutSec=40} + if($null -ne $body){$json=ConvertTo-Json -InputObject $body -Depth 100 -Compress;[IO.File]::WriteAllText((Join-Path $runRoot "$label.request.json"),$json);$args.ContentType='application/json';$args.Body=[Text.Encoding]::UTF8.GetBytes($json)} + $r=Invoke-WebRequest @args + [IO.File]::WriteAllText((Join-Path $runRoot "$label.response.json"),[string]$r.Content) + $tag=[string]($r.Headers.ETag | Select-Object -First 1) + @{status=[int]$r.StatusCode;etag=$tag;path=$path} | ConvertTo-Json | Set-Content (Join-Path $runRoot "$label.meta.json") + if($r.StatusCode -ne $expected){throw "$label expected $expected, got $($r.StatusCode): $($r.Content)"} + return @{data=(ConvertFrom-Json -InputObject $r.Content -AsHashtable);etag=$tag;location=[string]($r.Headers.Location | Select-Object -First 1)} +} +function Ordered-Json($value) { + if($null -eq $value){return $null} + if($value -is [Collections.IDictionary]){$map=[ordered]@{};foreach($key in @($value.Keys | Sort-Object)){$map[$key]=Ordered-Json $value[$key]};return $map} + if($value -is [Collections.IList]){$items=@(foreach($item in $value){Ordered-Json $item});return ,$items} + return $value +} +function Equal-Graph($left,$right,[string]$label) { + $a=ConvertTo-Json -InputObject (Ordered-Json $left) -Depth 100 -Compress + $b=ConvertTo-Json -InputObject (Ordered-Json $right) -Depth 100 -Compress + if($a -cne $b){[IO.File]::WriteAllText((Join-Path $runRoot "$label.expected.json"),$a);[IO.File]::WriteAllText((Join-Path $runRoot "$label.actual.json"),$b);throw "$label differs"} +} +try { + $hashes=@(Get-FileHash -LiteralPath $exe,$pristine,$saved,$include -Algorithm SHA256 | Select-Object Path,Hash) + $hashes | ConvertTo-Json | Set-Content (Join-Path $runRoot 'input-hashes.json') + $pristineBundle=@{entry=[IO.Path]::GetFileName($pristine);files=@{}} + $pristineBundle.files[$pristineBundle.entry]=[IO.File]::ReadAllText($pristine) + $savedBundle=@{entry=[IO.Path]::GetFileName($saved);files=@{}} + $savedBundle.files[$savedBundle.entry]=[IO.File]::ReadAllText($saved) + $savedBundle.files['sa/model.arcadia']=[IO.File]::ReadAllText($include) + Start-Oracle + $oldImport=Request-Oracle '01-pristine-import' 'POST' '/api/v1/projects/import-source-project' $pristineBundle 201 + $newImport=Request-Oracle '02-saved-import' 'POST' '/api/v1/projects/import-source-project' $savedBundle 201 + $old=Request-Oracle '03-pristine-model' 'GET' "$($oldImport.location)/model" + $new=Request-Oracle '04-saved-model' 'GET' "$($newImport.location)/model" + $bundle=Request-Oracle '05-saved-source-project' 'GET' "$($newImport.location)/source-project" + Equal-Graph $savedBundle $bundle.data 'saved-source-bytes' + $eventId='49044cb7-5f91-408a-86fc-1cc012eb2972' + $oldEventId='f0060906-0000-4000-8000-000000000013' + $transitionId='f0060906-0000-4000-8000-000000000033' + if($old.data.elements.Contains($eventId)){throw 'Event already exists in pristine model'} + $expectedModel=ConvertFrom-Json -InputObject (ConvertTo-Json -InputObject $old.data -Depth 100) -AsHashtable + $event=ConvertFrom-Json -InputObject (ConvertTo-Json -InputObject $expectedModel.elements[$oldEventId] -Depth 100) -AsHashtable + $event.id=$eventId;$event.alias='after_30_s';$event.name='After 30 s' + $event.body.Sa.Shared.Event.body.Time.expression='30 s' + $expectedModel.elements[$eventId]=$event + $expectedModel.relations[$transitionId].name='Unfinished timed refresh' + $expectedModel.relations[$transitionId].body.StateTransition.triggers=@($expectedModel.relations[$transitionId].body.StateTransition.triggers)+@($eventId) + Equal-Graph $expectedModel $new.data 'exact-three-deltas' + $validation=Request-Oracle '06-saved-validation' 'GET' "$($newImport.location)/validation" + if(-not $validation.data.valid -or @($validation.data.issues | Where-Object severity -eq 'error').Count){throw 'Saved graph invalid'} + Stop-Oracle + Start-Oracle + $reopened=Request-Oracle '07-reopened-model' 'GET' "$($newImport.location)/model" + $reopenedBundle=Request-Oracle '08-reopened-source-project' 'GET' "$($newImport.location)/source-project" + Equal-Graph $new.data $reopened.data 'reopened-model' + Equal-Graph $savedBundle $reopenedBundle.data 'reopened-source-bytes' + if($new.etag -cne $reopened.etag){throw 'Restart changed ETag'} + $afterHashes=@(Get-FileHash -LiteralPath $exe,$pristine,$saved,$include -Algorithm SHA256 | Select-Object Path,Hash) + Equal-Graph $hashes $afterHashes 'input-files-unchanged' + @{result='passed';evidence=$runRoot;pristine_elements=$old.data.elements.Count;saved_elements=$new.data.elements.Count;relations=$new.data.relations.Count;diagrams=$new.data.diagrams.Count;placements=@($new.data.diagrams.Values | ForEach-Object {$_.nodes.Count});new_event=$new.data.elements[$eventId];transition_before=$old.data.relations[$transitionId];transition_after=$new.data.relations[$transitionId];issues=$validation.data.issues} | ConvertTo-Json -Depth 40 | Set-Content (Join-Path $runRoot 'result.json') + Get-Content (Join-Path $runRoot 'result.json') +} catch {$_ | Out-String | Set-Content (Join-Path $runRoot 'failure.txt');throw} +finally {Stop-Oracle} diff --git a/harness/release-branch-integration-20260906.md b/harness/release-branch-integration-20260906.md new file mode 100644 index 00000000..e17367ce --- /dev/null +++ b/harness/release-branch-integration-20260906.md @@ -0,0 +1,53 @@ +# Cross-branch continuation — 2026-09-06 + +The current user objective is attachment +`10b0f701-1743-4de7-9ee1-225920e19649/goal-objective.md`, read in full. +The user explicitly requested accounting for completed work on other Git branches. + +## Verified repository state + +The initial checkout was `codex/behavior-draft-lifecycle` at `ea2e8e9`. +Its ledgers substantially predate the integrated implementation. Independent +ancestry, patch-equivalence and production-tree comparisons established: + +- `origin/codex/data-contract-integration` at `c091f80` is the integrated baseline. +- `process-direct` and `scenario-direct` are ancestors of that baseline. +- State core and state Windows/Linux integration are included, with later fixes. +- Every hosted-desktop contribution is patch-equivalent to integrated work. +- Semantic-review's core and review UI subtree match the integrated baseline; + apparently unique commits were adapted/cherry-picked equivalents. +- `data-linux-review` has identical production code; its evidence is historical. +- `transition-probe` adds isolated experiments, superseded by production schema 5. +- The initial behavior draft lifecycle commit is patch-equivalent to integrated work. + +Continue on `codex/release1-continuation`, created from `c091f80`. No remote push +was performed. The duplicate state implementation started before this discovery +was preserved in a named Git stash, not applied to the integrated baseline. +Do not revive its obsolete static-canvas audit or replace the established renderer. + +## Current environment, distinguished from historical evidence + +This checkout is `C:/Users/User/Documents/Git/ARCADIA-Studio`. Historical +`D:/CodexBuilds` artifacts, original Windows user paths and WSL distributions are +absent on this machine. Prior verification records remain scoped evidence; their +binaries and native windows cannot be assumed to exist here. + +The combined global build-tools installation command was rejected by automatic +approval review as blocked by policy. User-local Rust 1.98.1 gnullvm and portable +LLVM-MinGW 20260826 UCRT are installed under ignored `.local/toolchain`, without +changing global PATH or installing system components. `.local/toolchain/env.ps1` +selects that toolchain and `.local/target-gnullvm`. Workspace test compilation +succeeds. The first desktop run has 420 pass/9 fail; fixes and native acceptance +are in progress. A GNU normal-user executable manifest fixes Windows error 740 +on installer-like test filenames. New evidence is separate from old binaries. + +## Next work and ownership + +Root: synthesis, app lifecycle wiring, recoverable checkpoints, builds/native QA. +Home worker: Home/recents, navigation/view module; root owns app.rs. +Semantic worker: Annotation/Binding review in semantic_review modules. +Review worker: recovery design and verified missing typed StateAction card details. + +Release remains incomplete. Preserve the integrated state/scenario/process/data/ +hosted work and address remaining graphical parity, recovery and native acceptance +gates in PLAN.md rather than reimplementing accepted foundations. diff --git a/harness/release-coverage-diagnostics-design.md b/harness/release-coverage-diagnostics-design.md new file mode 100644 index 00000000..f4d6f6d8 --- /dev/null +++ b/harness/release-coverage-diagnostics-design.md @@ -0,0 +1,55 @@ +# Bounded coverage diagnostics — decision proposal, 2026-09-06 + +Read-only design for task 3 in [the current coverage audit](release-current-coverage-20260906.md). No model/schema changes or implementation acceptance. Scope is OA/SA only. + +## Recommended decision + +Add five advisory codes to shared validation, using existing identities and references. They describe missing explicit evidence, not proof of incorrect engineering. All remain `Severity::Warning`: no rejection of Apply, Save, REST transactions or source compilation solely because of these checks. + +The official [Arcadia Q&A, “Leaf functions, parent functions?” and “Functional exchanges only on leaf functions?”](https://mbse-capella.org/arcadia-qna.html) recommends leaf-level behavioral definition and contextual involvement in finalized models, while recognizing intermediate decomposition work. The exact warning policy below is a Studio design choice; it does not introduce a mandatory Capella conformance test. + +| Code / located record and field | Exact trigger and repair | +|---|---| +| `non_leaf_work_exchange` / Interaction or FunctionalExchange, `source` or `target` | The referenced endpoint is work with a direct child of the same work kind. Emit once per affected endpoint. “Choose the leaf activity/function responsible for this exchange.” SA uses the relation's function IDs, not its input/output port IDs; repair must keep port ownership consistent. | +| `work_without_behavior_context` / leaf Activity or Function, record location (`field = ""`) | No included Action names this work and no included Interaction/FE names it as an endpoint. “Include this leaf in a process/chain or scenario through a work action or exchange occurrence.” | +| `exchange_without_behavior_context` / Interaction or FunctionalExchange, record location | No included Exchange occurrence names this relation. “Use this exchange in a process/chain or scenario.” Suppress while it has a non-leaf endpoint: first repair the more specific endpoint warning. | +| `mission_without_capability` / OperationalMission or Mission, record location | No valid outgoing MissionCapability to a capability in its layer. “Connect this mission to a capability that delivers it.” MissionInvolvement is participant responsibility and does not satisfy this check. | +| `capability_without_behavior_context` / Capability or SystemCapability, record location | Capability has participant/work/role support but no explicit process/chain or scenario context. “Link a process/chain with Chain involvement, or set a scenario's Context to this capability.” Do not emit alongside existing `unsupported_capability` for the same capability. | + +The final code deliberately checks **presence of an explicit context**, not whether that context is complete. A linked empty chain/scenario has existing `empty_sequence` and missing-contribution diagnostics; repeating these on the capability would add noise. An isolated leaf capability with no support keeps the existing `unsupported_capability` warning. + +Capability context means either (a) incoming valid ChainInvolvement from a Process/FunctionalChain or (b) an existing same-layer Scenario whose `context` is this capability. A scenario whose context is a chain already linked to the capability is naturally covered by (a). Narrowly extend the existing `unsupported_capability` support predicate to recognize (b), so a scenario-only capability is not wrongly called unsupported. No new capability warning is needed on a pure grouping capability with same-kind child capabilities; inspect its children. Capability include/extend/generalization, parent containment, shared performers, mission context and similarly named records do not manufacture a direct context link. The message must say “explicit context,” not “no behavior exists.” + +## Exact traversal and exclusions + +Current seams: `model.rs` defines `OaBody::Process`, `SaBody::FunctionalChain`, `SharedBody::Scenario { context, steps, .. }`, `Element::steps()` and the closed `OccurrenceBody` variants. `validation.rs` already checks ownership, ordered membership, recursion and reference compatibility. The existing leaf-allocation predicate is `kind().is_work()` with no `p.children(id)` child of the same kind; reuse that definition. Build an indexed set of non-leaf work once rather than rescanning all children for every exchange. + +Build global covered-work and covered-exchange identity sets in one bounded iterative walk: + +1. Seed every Process, FunctionalChain and Scenario as a root, even if no capability references it. Coverage asks whether a context exists anywhere in the same project; it does not require every context to serve a capability. +2. Follow only identities listed in the owner's `steps()`, checking the occurrence's actual parent. For a Fragment follow its explicit `branches` whose parent is that fragment; for each Branch follow its `occurrences` through `steps()`. Do not walk arbitrary descendants: an unordered occurrence is not part of the authored behavior. +3. For `Exchange { exchange: Some(id), .. }`, mark the referenced Interaction/FE and its explicit work endpoints. For `Action { action: Some(id), .. }`, mark that referenced Activity/Function. Parent work does not cover its children. Mark identities, not occurrence counts; repeated messages and correlated replies cannot increase or double-count coverage. +4. Follow `SequenceReference { sequence: Some(id) }` only to the same process/chain kind and layer. Follow `ScenarioUse { scenario: Some(id), .. }` to the referenced scenario; ScenarioUse bindings preserve represented identity under current validation and do not substitute different work or exchanges. Visit each container/occurrence ID once. No recursion or per-root transitive-closure materialization. Every referenced sequence is also a root, so reuse does not change global coverage; this explicit treatment prevents later capability-scoped extensions from silently losing reused content. +5. An explicit work Action counts even when its scenario lifeline is unfinished: the work identity is known, and the existing missing-lifeline warning explains the remaining work. An exchange occurrence likewise counts its known exchange despite missing contextual details. Controls, timer-only occurrences, bare lifelines, State observations, StateAction/Availability links and Scenario.context alone do not mark work/exchanges covered. A state observation on a work lifeline observes state; it does not assert that work contributed. Participant/boundary messages never infer coverage of allocated Interaction/FE records: allocation of a transport is not evidence that every allocated exchange occurs in that scenario. + +No warnings for ports, BoundaryExchange, future CommunicationMean, data items, participants, roles, parent work coverage, diagrams or promoted/folded summary edges. Diagram visibility and layout are irrelevant. Allocation/role sharing does not propagate coverage to sibling work. Non-leaf endpoint warnings inspect authored relationships only. + +Recommended malformed-candidate policy: run this new completeness pass only when the existing structural validation pass has no Errors. This avoids cascades from dangling references, wrong layers, cycles or invalid ownership; accepted projects and saveable incomplete drafts still receive all new warnings. Keep the walk visited-set guarded regardless. Existing warning-only issues (`unordered_occurrence`, missing endpoints, empty sequences, topology gaps) do not disable the pass. Ignore incomplete edges with no usable referenced identity. + +Existing checks to retain rather than duplicate: `unallocated_leaf`, `unsupported_capability`, `empty_sequence`, `unordered_occurrence`, `incomplete_step`, `missing_exchange`, `sequence_gap`, `isolated_contribution`, `unreachable_contribution`, `nonterminating_contribution`, `sequence_starts/ends`, control/guard warnings and recursive-use errors. An unallocated leaf may also lack behavior context: these are distinct repairs. + +## User location and shared contract + +Use `Issue.element = Some(record_id)`, stable code and a real endpoint field or record-level location. Do not fabricate a `coverage` field absent from the source grammar. `source_project::model_diagnostics` already maps object/field to its owning file; source lint and REST consume shared validation. `views.rs::checks` already offers Open on the located identity. Endpoint warnings should open the exchange's properties; coverage warnings open the leaf/exchange or mission/capability and explain the Behavior/Actions repair. No automatic creation, allocation or reassignment is implied. + +Warnings must be deterministic by ID and endpoint, share codes/location across GUI/source/JSON/REST, disappear after the stated repair and reappear on Undo. The new helper should be linear in indexed records/references aside from ordered-map lookup costs. It should not call `validate()` recursively or perform a full graph walk separately for every leaf/capability. + +## Three acceptance fixtures + +1. **Valid covered SA, including reuse.** One System, one Actor, allocated leaf functions F/G, typed ports and FE E from F to G. Inner chain has an Exchange occurrence E; outer chain reuses inner and has ChainInvolvement to capability C. Mission M has MissionCapability to C. Add a functional scenario with F/G lifelines and repeated E call/reply plus a second scenario reusing it through a branch with identity-preserving bindings. Expected: none of the five new warnings; reuse/repetition does not duplicate facts. Remove E occurrences from every context: F/G and E become uncovered; add an Action F alone: only F's work warning clears, E/G remain. Run an OA counterpart variant with Interaction and Process to prove symmetric layer semantics. + +2. **Structurally invalid candidate.** Starting from fixture 1, make a ScenarioUse cycle, then independently try an FE referencing a missing function and an occurrence listed under the wrong owner. Expected: existing exact structural errors, no new completeness cascade, candidate rejected atomically and accepted model/coverage unchanged. A visited-set traversal must terminate even when invoked directly on these malformed graphs. This fixture does not reinterpret invalid structure as permissible incompleteness. + +3. **Saveable incomplete OA with exact repair targets.** Parent activity P has leaves A/B; independent leaf C. Allocate A/B/C to valid performers. Interaction Eab is A→B and appears in a Process; Ep is P→C and Eca is C→A, neither used in behavior. Mission M has no MissionCapability. Capability Q has only participant involvement. Add a participant scenario with only an observation of C's performer's state and no capability Context. Expected new warnings: Ep/source non-leaf; C uncovered; Eca uncovered; M missing capability; Q missing explicit behavior context. Ep's uncovered-exchange warning is suppressed until its endpoint is repaired. A/B are covered through Eab. Add an unordered occurrence Eca: existing unordered warning appears and coverage stays missing. Put it in ordered steps: Eca and C coverage clear. Repair Ep to A→C with matching source semantics, then include it in the process; link M→Q and process→Q. All five original warnings clear; Undo restores the relevant exact warnings. Repeated/reused Eab must never hide Eca's absence. Separately link an empty chain/scenario to Q: Q's context warning clears while the existing empty-sequence warning remains. + +For these fixtures compare code/record/field tuples through core validation, multi-file source lint and real REST; check Save success for fixture 3 and rejection atomicity for fixture 2. Native check Open navigation and repair/Undo at normal and minimum window sizes. These are proposed checks, not completed tests. diff --git a/harness/release-current-coverage-20260906.md b/harness/release-current-coverage-20260906.md new file mode 100644 index 00000000..9a1fb6d8 --- /dev/null +++ b/harness/release-current-coverage-20260906.md @@ -0,0 +1,161 @@ +# Current first-release coverage audit — 2026-09-06 + +Read-only production audit of `codex/release1-continuation`, HEAD `c091f80`, +including the current uncommitted Home, checkpoint, typed StateAction display and +semantic Annotation/Binding changes. This is a release acceptance map, not a claim +that the current working tree has passed its build or native gates. Root owns those +gates. The original objective was reread from the supplied goal-objective.md. + +The objective requires a usable, substantially complete OA/SA engineering tool, +strict graphical/text parity and a deployable REST service. It does not require +every Capella implementation class, LA/PA/deployment, execution, or native Capella +file compatibility. The current scope's explicitly unfinished requirements cannot +be silently reclassified as future work to obtain a release claim. + +## Evidence rules and reconciliation + +**Implemented** means a current typed contract and reachable code path exist. +**Verified** below identifies the actual bounded historical/native evidence; it +does not transfer an old executable's acceptance to changed production code. +**Missing** means a concrete code/contract/workflow gap, rather than an untested +feature being called absent. No new Cargo or native execution was performed here. + +The direct scenario/process/state branches are already incorporated in this +baseline; see release-branch-integration-20260906.md. Earlier notes saying these +surfaces are static/unimplemented are historical. The initial +release-semantics-audit.md R1–R6 findings are largely implemented now. Its selective +view and completeness-diagnostic recommendations remain materially open. + +## Measurable acceptance table + +| Engineering outcome | Implemented now | Verified evidence and remaining acceptance | +|---|---|---| +| OA responsibility and SA boundary modeling | Entities/actors/roles, independent work decomposition, leaf allocation, role assignment/realization; one SA System and external actors; nesting/summary distinction | Existing core/source/API and architecture native evidence. Final worked OA→SA fixture must preserve identities, allocations, independent semantic parents and view placements through save/reopen and undo. No new LA/PA structure is needed. | +| Missions and capabilities | OA/SA missions, participation, exploitation, capability involvement, include/extend/generalization with validation | R2 is implemented; do not reopen it as an absent concept. Final native proof should create the same mission map in each layer, reuse an included capability, reject a cycle and retain an incomplete extension condition as a warning. | +| OA entity communication means | Activity Interaction exists. Legacy OA ports plus BoundaryExchange and ExchangeAllocation→Interaction can preserve an approximation | **Missing ordinary authoring and an accurate OA semantic contract.** Capella's entity-level CommunicationMean is absent. New Port creation redirects users to SA typed port actions; OA permits only Legacy. Existing allocation support must be preserved, not called SA-only. See task 1. | +| SA boundary/data contracts | Typed input/output and component ports, item declarations, allocation, provided/required interfaces, protocols; data nature/mechanism, bounds/defaults, record/enum/inheritance/association and qualified message values | release-data-verification.md records Windows normal/compact advanced field work and Linux default bounds/recursive-field save/reopen. Linux association/protocol allocation and minimum layout remain unverified. Use the same fixtures to exercise data defaults, item inheritance and typed boundary allocation in desktop/source/REST. | +| Process/functional-chain contextual behavior | Direct creation/insertion, work/exchange contributions, Choice/Merge, Fork/Join, Loop, same-context links/guards, reconnect and sequence reuse | release-process-editing.md: Linux simple guarded insertion, Windows camera/owner behavior. The compact pending-form fix still needs native acceptance. Test one OA branched process and one SA chain with parallel/control/reuse contributions, reconnect/cancel, first-exchange insertion, repeated referenced work, undo/save/native reopen. Do not duplicate global work/exchange definitions. | +| Scenario authoring | Direct messages at captured sequence/operand slots, call/reply correlation, ordered occurrences, actions/timers/state observations, ALT/PAR/LOOP/OPT and bound reuse | release-scenario-editing.md: Windows ALT/default+minimum, order rejection and viewport preservation; Linux middle call/reply and save/reopen. Linux compact ALT/reuse/reorder/cancel and an operational-entity scenario remain a bounded acceptance set. New-selection auto-reveal is absent but is a usability improvement, not absent scenario semantics. | +| Modes/states and typed actions | Persisted state views, nesting/folding, state kinds/pseudostates, direct edges/reconnect, typed events and external/local/internal distinction. Current changes render/select typed Entry/Do/Exit actions without fabricating work containment | release-state-editing.md has scoped Windows/Linux acceptance. Current action rows/header changes await root's build/native checks: select all three phases, coexist with internal transition and nested region, fold, rename target, save/reopen. Linux minimum view and representative typed-event/local-transition paths still need coverage. | +| Readable saved engineering views | Multiple projection views, placements, nesting/folding, manual routes/labels and computed summary edges | **Missing persisted selective relation/category/coverage views.** Diagram currently stores only id/alias/name/description/layer/projection/nodes/routes. No named exchange category exists. Test two saved views of the same crowded model with different filters and identical semantic identities; see task 2. | +| Engineering completeness feedback | Unallocated leaf, unsupported capability, missing System, sequence/message/binding/trigger and interface-contract warnings already exist | **Missing** exchange-on-non-leaf and behavioral-context coverage diagnostics, plus mission-without-capability coverage. An allocated leaf with no process/scenario can currently look complete. Add actionable nonblocking warnings and navigable coverage; see task 3. Do not reimplement interface warnings already in ports.rs. | +| Bidirectional counterpart review | Source-v5 semantic facts, structural/scalar/constructor/member reconciliation, mapped references and historical decisions. Current Annotation/Binding row and recovery adapters present | Known open work remains ordered/attached/artifact/multi-source GUI facets, current-tree tests and native constructor/keyed-row acceptance. This audit does not duplicate that assigned slice. Literal expressions and candidate-parent lifeline choices had verified review findings, now fixed with tests awaiting integration. | +| Source/API/artifact equivalence | Normative language.rst v5; exact accepted multi-file source, identity/trivia-preserving updates, strict JSON/migration, artifacts, syntax/lint tooling and shared command validation; hosted REST transactions/source/model routes | Prior workspace, real HTTP/OpenAPI and migration evidence is recorded in STATE.md and data/semantic ledgers. Every *new* persisted category/filter/communication construct needs GUI edits, source+JSON roundtrip, REST create/edit/reject/reidentify/delete, unchanged state on reject, and restart persistence. Final gate must run on the actual integrated revision, including stale ETag and invalid-local-open destination isolation. | +| Learnability and safe reopening | Searchable offline help already exists. Current Home/recents, accepted-bundle/raw-draft checkpoints and explicit detached restore changes are present | Current Home/recovery is root-owned work, not accepted solely from code. **Documentation drift is verified:** scope.md says arcadia4 and names/descriptions-only review; guide.rs says existing counterpart review is prose-only and no unsaved recovery exists. Final instructions must match actual controls and accepted recovery semantics; see task 4. | + +## Prioritized next release tasks beyond already assigned review/recovery + +### 1. Close ordinary OA communication-means authoring (P1) + +Capella defines CommunicationMean as communication between operational entities, +with entity endpoints, rather than SA function ports. This is a real OA need +observed in the installed Capella OAB palette and defined in the authoritative +[OperationalAnalysis schema](https://raw.githubusercontent.com/eclipse-capella/capella/master/m2/plugins/org.polarsys.capella.core.data.def/model/OperationalAnalysis.ecore). + +Current seams: `model.rs::RelationBody/RelationKind`, +`validation.rs::validate_relation`, `ports.rs::validate_port` and +`validate_exchange_allocation`, `app.rs::add_element`, and +`workflow.rs::Relationship`. The current core accepts Interaction allocation, +but all OA ports are compatibility-only and warnings ask for typed SA resolution, +which is impossible in OA. `language.rst`'s allocation table incorrectly says +SA-only. A preserved legacy representation is not a usable new-project workflow. + +Freeze a small explicit contract first: named OA participant-to-participant +communication carrying items and allocating operational Interactions, with +same-layer ownership/performer validation. A direct CommunicationMean relation is +one candidate; extending BoundaryExchange with explicit endpoint semantics is +another. Avoid making OA engineers create fictitious typed SA ports. Preserve +legacy input as legacy and never infer conversion from names. + +Acceptance: start with an empty OA project; create two entities, their allocated +activities, an Interaction and a named communication means; allocate the +Interaction, edit carried items, navigate both levels, reject wrong-performer +allocation; save/reopen, undo/reidentify/delete safely and exercise equivalent +source/REST. Review its mapped SA boundary counterpart explicitly. This is a +bounded addition, not a demand to duplicate every OA package/configuration class. + +### 2. Persist selective views and named exchange sets (P2, release usability) + +Arcadia explicitly recommends representational synthesis and exchange categories; +Capella supports named categories with multiple exchange members. See the +[Arcadia Q&A on leaf functions and exchange synthesis](https://mbse-capella.org/arcadia-qna.html) +and [FunctionalAnalysis category definition](https://raw.githubusercontent.com/eclipse-capella/capella/master/m2/plugins/org.polarsys.capella.core.data.def/model/FunctionalAnalysis.ecore). +This directly addresses the user's clutter complaint and remains explicitly +unfinished in scope.md. Existing view projections/folding do not supply a saved +selection among multiple exchanges between the same visible endpoints. + +Bound the slice to named same-layer exchange sets, per-diagram relation-kind and +set membership filters, and a coverage view/filter for selected capability or +behavior context. Define combination semantics explicitly. Start with original +edges; optional aggregate rendering must expose its actual members and may never +create fake semantic exchanges. No hierarchical category subsystem is necessary. + +Seams: `model.rs::Diagram/ProjectionKind`, projection edge selection, typed forms, +source syntax/writer/updater, schema/API and reference lifecycle. Acceptance: +one interaction belongs to two named sets; two views show distinct subsets; +filtering hides no semantic content and deleting a set preserves its exchanges; +placement/route identities survive toggling; source/API/GUI persist identical +filter definitions and all members remain navigable. + +### 3. Add bounded engineering completeness checks (P2) + +The official Arcadia Q&A states that finalized behavior belongs on leaf functions +and ideally each leaf and exchange participates in a chain or scenario. This is +method guidance, so incomplete drafts must remain saveable. Current validation +already enforces leaf allocation and many local contracts; do not turn this slice +into a new general rules engine. + +Add located advisory checks for (a) work with children carrying an Interaction/FE, +(b) leaf work/exchanges not involved in a process/chain/scenario, and (c) a mission +without a MissionCapability. Report capability support separately: participant +involvement alone is already enough to remove unsupported_capability but is not +evidence that the capability's behavior is described. Decide/document whether a +contextual work action/state observation counts as behavioral coverage; traverse +reuse without double counting or recursion hazards. + +Seams: `validation.rs` and checks UI/context navigation, plus shared server lint +output. Acceptance fixture has one covered leaf, one uncovered leaf, a parent +exchange, an unsupported mission and repeated/reused occurrences. Each warning +locates the right record, disappears after the matching repair, reappears on Undo, +and never rejects Apply/Save or invents an allocation. + +### 4. Correct release-facing scope/help and provide one complete walkthrough (P1) + +Rewrite stale current claims in scope.md and guide.rs after the respective +integrations; append clear superseding state in PLAN/TASK_LEDGER rather than +letting an old narrative decide current acceptance. The normative reST should +continue to describe one contract; correct the OA ExchangeAllocation discrepancy. +Keep historical native evidence's revision/platform limits explicit. + +Use one worked engineering story: OA mission/roles/communication and alternative +process → reviewed SA responsibility boundary → typed boundary/data contracts → +scenario and degraded mode → artifact evidence → graphical edit reflected in a +commented source module → REST edit/reload and an intentionally rejected stale +update → local crash checkpoint restore. Help steps must name actual controls and +explain containment versus allocation versus occurrence reuse. Search should find +each topic, and contextual links must respect drafts. This is authoring/navigation +acceptance, not a request for another tutorial framework. + +### 5. Close current native and final release gates (P1) + +Use the bounded table fixtures on Windows and Linux at default and minimum size, +including the changed current actions, compact process form, keyed review and +recovery flows. Record exact build hash, model/source oracle, undo/reopen result +and serious runtime output; a prior test count is not fresh proof. Then run strict +workspace tests/clippy/fmt, optimized desktop/server, supported compiler/platform +checks, reST/OpenAPI/examples/migrations and real HTTP rejection/restart contracts. +Finish with fresh independent semantic, workflow and packaging review. Root's +current builds/native trials may discharge rows after this report was written. + +## Explicit bounds and unresolved interpretation + +Do not add LA/PA components, deployment, a simulator, native .capella/.aird import, +or the full SysML metamodel to close this audit. Generic constraints already carry +descriptive engineering constraints; this audit has not established that every +Capella duration/time-constraint class or capability-configuration class is a +necessary new first-release primitive. Reusable process references are implemented; +links into their internal contributions remain a separately recorded semantic +extension, not something current same-context SequenceLink should silently infer. +If an acceptance story requires that extension, freeze occurrence/path identity +and control-flow rules before implementing it. These boundaries preserve the +objective's OA/SA focus without calling known required work complete. diff --git a/harness/release-current-native-plan.md b/harness/release-current-native-plan.md new file mode 100644 index 00000000..752fcd50 --- /dev/null +++ b/harness/release-current-native-plan.md @@ -0,0 +1,134 @@ +# Current milestone native QA — deterministic fixture, 2026-09-06 + +Root is the sole native operator. This plan performs no native input and does not +declare UI acceptance. Fixture and validator are ignored local test artifacts, +not user data or production code. + +## Fixture and production oracle + +Pristine input: `.local/native-fixtures/state-actions.arcadia`. +Project UUID: `f0060906-0000-4000-8000-000000000000`. +Use a copied file under `.local/native-fixtures/runs/` for every destructive or +crash trial. Do not overwrite the pristine fixture or use a real project. + +Expected current-source5 graph: 14 elements, 10 relations, one saved state view, +8 placements, no artifacts or traces. The production validator lives at +`.local/native-fixtures/validate-state-actions.rs`; it decodes through +`persistence::decode_with_report`, validates, proves canonical parse/model +equality/idempotence, and asserts the counts and projection geometry. It writes +`state-actions.canonical.arcadia` as a model oracle. Its exact run output and +pristine SHA256 are recorded in `state-actions.validation.txt` and +`state-actions.sha256.txt` when validation completes. An old source3 example was +used only as syntax reference; the fixture itself is new source5 with fixed IDs. + +Production validation completed successfully using a separate direct-rustc helper +linked to the built core (no Cargo/shared-target writes): schema5, 14 elements, +10 relations, 1 diagram, 8 placements, 0 artifacts/traces, **0 issues**; +8 projected nodes and 3 routed edges; Ready y180 and nested region y430; +canonical parse equality and idempotence passed. Pristine SHA256: +`9CDE82608110E56AA9E14C9E33EB38976F53AED74AB013145D411D5743A0EC26`. + +Open SA → Behavior → **Pressure operation** and its existing **Pressure operation +— saved state view**. All IDs below share `f0060906-0000-4000-8000-` with the listed +12-digit suffix. The System owner is Pressure controller (`000000000001`), machine +`000000000002`, Ready `000000000004`, nested Monitoring region `000000000005`. + +| Visible Ready content, top to bottom | Selection / exact model oracle | +|---|---| +| `entry / Retained entry notes` | Existing prose on Ready, not a StateAction relation | +| `entry / Restore settings` | Entry work link `000000000030`, Entry, target function `000000000010` | +| `do / Monitor pressure` | Do work link `000000000031`, Do, target function `000000000011` | +| `exit / Release resources` | Exit work link `000000000032`, Exit, target function `000000000012` | +| `INTERNAL · Refresh status` plus its typed trigger/guard/effect summary | Internal transition `000000000033`, Ready→Ready, Sample tick Event `000000000013`, guard `pressure_valid`, effect work Monitor pressure | +| Monitoring region containing Start monitoring and Sampling | Actual semantic nested region; its top must be below all preceding rows | + +Ready reserves 232 world units of header (64 basic +28 prose +84 typed actions ++56 internal row). There are exactly three projected/routed transition edges; +neither typed actions nor the internal transition are graph edges. The referenced +functions have actual Allocation to Pressure controller and no state-view node +placements. They are not Ready's contained children. + +## Typed action and saved-view trial + +1. At 1440×900, use Fit, inspect all content above, then use readable zoom. Click + each typed action row: corresponding relation and phase/target appear in + Properties, with selected-row highlight. Right-click resolves the same action + identity. Click Internal: inspect the actual transition. Selection must not + dirty the model, move nodes or display a phantom contained Function. +2. Repeat at supported minimum 1100×700, including scrolling/pan and opening the + inspector. Rows must remain independently selectable; source/target/phase and + Create/Cancel/Apply controls must be reachable with no overlap or clipped + essential controls. Capture visible evidence at a readable scale, not only Fit. +3. Follow/select target Monitor pressure through normal model navigation and + rename it `Monitor pressure QA`. Return to the state view: Do row follows the + new name, its relation/target IDs remain `...031`/`...011`. Undo restores the + original text; Redo restores QA text. Other phases and legacy prose stay exact. +4. Fold Ready: all typed/internal content and nested region disappear without + semantic deletion. Unfold: content, phases, reference IDs and child order return. + Save to a trial source file and reopen the actual file through the native UI. + Verify stored collapse choice, current name and all fixed IDs/positions. Use + source/core parsing for the saved model, not an object-count-only oracle. +5. Restore the original fixture for other trials. Record binary hash, viewport, + platform, saved file and serious runtime output. Desktop tests passing does + not substitute for these gestures; Linux and Windows evidence are separate. + +## Explicit recovery trials + +Run each from a fresh trial copy unless a combined case is explicitly desired. +Wait for the application's checkpoint completion signal/storage evidence before +the root deliberately terminates the fixture app to simulate a crash. Reopen and +choose the offered snapshot explicitly. A normal discard/close has a different +meaning and is not a crash test. Restore must reopen a detached unsaved project; +the original source file bytes must remain unchanged until an explicit new Save As. + +| Draft surface | Authored state before crash | Restore oracle | +|---|---|---| +| Numeric property | Data → Retry budget (`...014`), change Integer minimum from `0` to raw `-`; attempt Apply to demonstrate rejection | Exact `-` survives in its field; accepted minimum remains 0, maximum4, default none, unit attempts. Repair to1 applies only after user action; undo returns accepted0. | +| Source | Textual model, append `// SOURCE_DRAFT_KEEP_ME` and an unterminated declaration/string; preserve an actual newline in raw text | Same raw bytes/lint location return, while graphics still show accepted fixture; no auto-Apply or generated-text replacement. Revert/repair is explicit. Include an additional dirty detached buffer in a separate trial if the UI offers it. | +| State behavior | Configure Ready→Idle transition; name `BEHAVIOR_DRAFT_KEEP_ME`, guard `pressure.pending`; enable timed trigger but leave its expression as three spaces. Submit rejects with time-expression guidance | Pending form remains on Pressure operation with name, guard, timed choice, whitespace and endpoints retained. No Event/transition created. Repair expression to `2 s` and explicitly submit only after confirming restored raw state. | +| General workflow | Start a relationship from Pressure controller; set name `WORKFLOW_DRAFT_KEEP_ME`, leave required target unset (or clear it), and demonstrate blocked submission | Same selected relationship kind, source, exact name and missing target remain. No relationship silently created by restore. Complete target/submit is an explicit new action. | +| Combined accepted+raw | Commit target rename `Monitor pressure QA`, then create invalid source draft with the marker above | Restored accepted graph has QA name with same IDs, plus exact unapplied invalid source bytes. It must not restore the pristine graph or apply the bad draft. | +| Semantic workflow | Use the root's separately validated OA/SA semantic fixture; stage a Candidate numeric `-`, a literal multiline Annotation expression and unfinished reference pair | Decisions, raw values and unfinished pair survive against the exact accepted model fingerprint; no restored commands execute. A snapshot with mismatched accepted model must reject visibly. This state-only fixture intentionally has no fake counterpart traces. | + +For every restore: selection/owner/perspective must expose the retained draft; +changing page must not erase it; Cancel returns to the accepted model; failure to +decode one version/path must not overwrite source files. Verify a successful Save +As writes only the chosen new destination and no hosted credentials/destination +were resurrected. Recovery checkpoints are user-local and separate from model +files; no auto-recovery into the original file is accepted. + +## Evidence completion + +Append actual results with exact scope after root operation: fixture decoder +output, Windows normal/minimum, Linux normal/minimum, saved core/source comparison, +property/source/behavior/workflow/semantic recovery, and unchanged original-file +hash. Leave unoperated scenarios marked pending rather than inferring them from +adjacent successful tests. + +## Native finding: a saved state view opened in Architecture + +Root observed the fixture open on Architecture with the generic renderer: Ready +reserved the 232-unit header but omitted typed/internal rows, and the toolbar +offered function/boundary exchange tools. The dedicated Behavior canvas already +rendered the correct content. This was a workspace-routing defect, not missing +StateAction semantics or a reason to create a second renderer. + +Bounded fix routes a StateMachine projection chosen during model open, diagram +selection, reveal or other navigation into the existing Behavior workspace, with +the exact saved view and its machine owner. Selection/raw property Draft survive; +the model/source are untouched. Existing pending-behavior guards remain in force. +Explicit Architecture navigation chooses a normal same-layer view, or an empty +Architecture workspace when none exists, so the user can create an architecture +view and is not bounced back to Behavior. The architecture chooser can still open +a saved state view explicitly. + +Regression coverage includes constructor and actual source Open; selecting a +second saved view; revealing a transition into a named state view; no history or +source mutation; explicit Architecture navigation with/without a normal view; +raw invalid properties and pending behavior retention. The three focused routing +tests passed, and the full current desktop suite passed **435/435** on 2026-09-06 +(`.local/state-routing-desktop.log`), including the latest restored-checkpoint +Home-list regression. The older fallback-owner first-move test now starts with +a deterministically preferred Allocation view; its captured-context and first +drag transaction assertions remain intact. Native recheck of the rebuilt binary +remains pending until recorded by the responsible operator. diff --git a/harness/release-current-native-verification.md b/harness/release-current-native-verification.md new file mode 100644 index 00000000..5195b740 --- /dev/null +++ b/harness/release-current-native-verification.md @@ -0,0 +1,168 @@ +# Current Windows lifecycle and keyed-review verification — 2026-09-06 + +This is current-machine evidence on `codex/release1-continuation`, integrated +baseline c091f80 plus the uncommitted continuation. It does not supersede unrelated +historical Linux checks or claim first-release acceptance. + +## Build and automated checks + +User-local Rust 1.98.1 `stable-x86_64-pc-windows-gnullvm`, LLVM-MinGW 20260826 UCRT. +`cargo build --locked -p oa-desktop -p oa-server` passed. Shared normal-user +manifests allow installer-like GNU test filenames to run without elevation. +`cargo test --locked --workspace` passes **840 tests**, with the existing routing +benchmark ignored; `.local/current-workspace-tests-final.log` is the full log. +Strict all-target workspace Clippy passed in `.local/current-clippy.log` (8m24s). + +Initial failures were corrected at their causes: typed action test double-applied +the camera transform; invalid Binding fixture reused one local lifeline; core +Annotation/Binding replacement reordered retained rows; a hosted timeout test +incorrectly assumed a 30ms deadline guaranteed server admission. The held-response +test now permits pre-admission timeout while preserving outcome uncertainty and +no-retry checks. Independent recovery review also found and fixed write/restore +workspace limit disagreement, preserving the prior usable checkpoint on rejection. + +## Isolated native lifecycle trial + +The actual debug GUI runs with `LOCALAPPDATA` redirected to +`.local/native-qa/appdata`; no user recents or recovery store was used. Captures and +input use the Computer Use skill's native Sky API. Root is sole native operator. +Default client size is 1440×900; minimum client size 1100×700 was reached through +native border resizing. No screenshot or unit test substitutes for this run. + +1. Home starts empty, offers New/Open/example/guide and clear recent-file guidance. + Loading the rescue example displays the existing OA architecture and 21-file + source project. This does not create a file destination. +2. Inserted ` unfinished_recovery_draft` after `arcadia 5;`. Apply rejected it at + `project.arcadia:1:12`, preserved the exact raw source and left the 41-element, + 26-relation accepted model intact. Architecture remained inspectable and + explicitly blocked property editing until source drafts were resolved. +3. Observed a completed private checkpoint containing that exact token, then + forcibly stopped only the trial's verified Studio process. After restart, + Home offered the retained project. Recover restored the source draft, located + error and unsaved-copy status. Undo history was absent as specified. +4. Reverted the intentional source error, then Save as chose + `.local/native-qa/recovered/project.arcadia`. All **21 saved files compare + exactly** with the checkpoint's accepted bundle (zero mismatches). Both retained + recovery entries were retired, no checkpoint remained, and stderr was empty. +5. Home displayed the saved project in Recents. At 1100×700 the action rows, + current-project card, recent Open/Remove and guide entry remained readable and + reachable. Recent Open loaded the source project with its identities intact. +6. Replaced the project UUID property with raw `-`. Apply rejected it without + changing the model. After observing its completed checkpoint, a second forced + trial-process stop/restart recovered exactly `-` in the identity field. Origin + is displayed as context on Home; restore still opens a copy without a save + destination. Save & continue rejected the raw UUID and retained the draft. + Open's discard decision then allowed moving to the next isolated fixture. + +Local forensic artifacts: `source-draft-before-crash.json`, +`property-before-crash.json`, the 21-file recovered directory and per-process +stdout/stderr logs under `.local/native-qa`. These contain synthetic example data. + +## State-action trial and open finding + +Production-parser-validated `.local/native-fixtures/state-actions.arcadia` has +14 elements, 10 relations, 1 saved state view, 8 placements and no issues. In +Behavior & scenarios, Ready shows legacy Entry prose, typed Entry/Do/Exit work +rows, an Internal transition and a nested region without overlap. Each of the +three typed rows selects its actual StateAction relation; the inspector confirms +the corresponding phase, Ready source and Restore settings / Monitor pressure / +Release resources target. Selection does not mutate the saved model. + +**Open native finding:** opening a file whose first diagram is a StateMachine +lands in Architecture's generic graph renderer, with blank action-header space +and functional/boundary exchange tools. The dedicated Behavior canvas renders +and selects correctly. A bounded routing correction is being investigated; do +not call the state-view entry workflow accepted before it is fixed and rerun. + +Still required for this milestone: updated-binary preflight/routing checks, +pending behavior/workflow crash recovery, state-row compact/fold/edit/save checks, +Annotation/Binding native Take/Candidate/history/source oracles, fresh independent +review closure and strict checks. Linux and complete release acceptance remain +separate required gates. + +## Semantic review in progress + +The validated `semantic-keyed.arcadia` fixture opens in Traceability and exposes +the Navigation message review. Members/rows can browse OA and SA origin Record +fields separately, choose their nested latitude fields and stage the exact pair. +Take Operational shows proposed `latitude_deg + 0.25`, one decision, three +reference pairs and zero additional changes. A completed checkpoint contains this +pending semantic workflow and Take decision; `.local/native-qa/semantic-before-crash.json` +preserves it. The trial process was then forcibly stopped, leaving the executable +unlocked for the routing build. Restore/Apply/source-oracle comparison is next. + +Several immediate captures during heavy compilation preceded the app's next paint; +fresh captures showed the intended picker menus. This was not an empty-choice +defect. Native dialogs also sometimes needed focus and input retried after a fresh +observation; folder navigation completed fixture opening. These observations do +not justify changes to model or field-picker semantics. + +## Recovered semantic review and Save-as history finding + +Updated routing binary SHA256 `70069D6CBBE1917E1597794B3E190FF9CF23664D458F794B3270DDBE6EE683A2` launched as isolated PID7580. Home recovered the pending Annotation review, retaining one Take decision and three explicit reference pairs. Regenerated validated preview showed `latitude_deg + 0.25`; Apply succeeded with zero additional changes. Save as `.local/native-qa/semantic-accepted.arcadia` exactly matched the independent after-take source oracle (SHA256 `9AB573C92BE66E3BAE659DD826923BA6066979B1651EDDA7BD469056632CD77E`), including comments, typed history, sibling value and order. Checkpoint retired after Save. + +Native history check found a production defect: changing the entry basename through Save as creates an extra Document transaction. First Undo only restores the prior entry name; saving it preserves after-take content and clears redo through another entry rename. Second Undo reached the real semantic transaction and saved exact initial source (SHA256 `49214BE2CA9C8BB6195C3B39737C053847021839F70F0BB2921D35A1FBD2AC83`). Root assigned non-history Save-as adoption/preflight correction and meaningful history tests to state_contract. One-step Undo/Redo after renamed Save remains unaccepted until rebuilt native recheck. Current Linux runtime is prepared and checksum verified but has not booted. + +## Binding rejection, crash recovery and repair accepted + +On routing binary70069D6C, the paired Rendezvous binding was explicitly staged. Candidate `SA Rendezvous extra local` rejected with `duplicate_lifeline_binding: Scenario-use lifeline bindings are one-to-one.` Apply was disabled. Switching Reference pairs / Properties retained the decision and error. A completed checkpoint preserved the exact after-Take accepted source and invalid local UUID; forensic copy `binding-invalid-before-crash.json`. + +Forced stop of isolated PID7580 and restart PID796 recovered the same candidate, presence and two reference pairs, regenerated the same error and kept Apply disabled. Repair to `SA Rendezvous alternate` produced a valid preview and zero additional changes. Native Apply + Save as into a new folder retaining the entry basename matched the complete after-repair oracle exactly (SHA256 `E3CE0347746584F503B825A8B6BD23A7EEBF0827940112EF8F36D500C1D0A043`). One Undo + Save matched after-Take exactly; Redo + Save matched after-repair. Saved copies are under `.local/native-qa/binding`. + +A new UI finding is clipped Use pair / Remove pair controls on Reference pairs at the normal1440x900 window. Semantic worker constrained the panel widths; rebuilt native inspection remains. The debug gnullvm executable needs `.local/toolchain/env.ps1` PATH during launch: a launch without it exited before a window with no app log. Packaged GNU runtime dependency closure must be checked explicitly; this does not change prior successful toolchain-environment runtime evidence. + +## State-view routing and pending behavior checkpoint + +Routing binary70069D6C opened the saved state fixture directly from Home/Recents into Behavior, exact machine and diagram selected with state tools. Explicit Architecture remained reachable with an empty architecture view, and selecting the saved state view there routed back correctly. At1100x700, toolbar/inspector remained usable, zoomed Exit action selected its StateAction relation, direct State placement into Main region created one state/placement, and one Undo restored the original14elements/10relations and clean document. The initial fixture file is unchanged. + +Right-clicking the Internal row opened Edit transition. Authored name `Unfinished timed refresh` with a checked new timed trigger and empty expression retained the existing trigger/effect/guard, showed the required-expression message and disabled Apply. The form scrolls at1100x700. Completed checkpoint `behavior-before-crash.json` contains exact unchanged accepted fixture plus the authored pending behavior draft. Isolated process796 was forcibly stopped for rebuild/recovery testing; no native GUI is currently running. +# Packaged clean-PATH smoke — 2026-09-06 continuation + +QA package `ArcadiaStudio-qa-debug-20260906-a-x86_64-pc-windows-gnullvm` +(older binary SHA25670069D6CBBE1917E1597794B3E190FF9CF23664D458F794B3270DDBE6EE683A2) +launched as process5452 with PATH exactly `C:\Windows\System32;C:\Windows`, +working directory `.local/package-clean-smoke`, and separate app metadata there. +The packaged libunwind.dll resolved the development-PATH dependency: actual Home +rendered, Explore rescue example showed41objects/26links with nested architecture, +and the offline Engineering guide rendered16topics. Alt+F4 closed the app normally; +the next native window inventory no longer contained it; process5452 was absent and +both stdout/stderr were zero bytes. This launch did not open +or modify the pending behavior checkpoint in the other QA appdata directory. + +The guide search Clear button was partially clipped at1440width. Root added explicit +Fill constraints to the search, content and step rows; updated native acceptance is +pending. The old package does not include complete Rust dependency notices, identified +by independent review; it remains QA evidence, not a distributable release. This smoke +does not establish final-binary portability or fresh package Save/reopen acceptance. +# Recovered timed transition and corrected Save-as history + +2026-09-06, process10896, staged desktop SHA256 +660BECC8DB96B218D12805B39661B46C9F48DF4F8F07CB3B7D42C1F803BE089F, +system-only PATH, isolated native-qa/appdata. Home Recover opened checkpoint +5a81ce27-e331-4165-9eab-8f190493f376 in Behavior with the exact raw name +`Unfinished timed refresh`, Internal Ready-to-Ready endpoints, Sample tick, +pressure_valid guard, Monitor pressure effect and Update status display notes. +Timed event remained enabled with empty expression and the time-expression error; +the accepted properties still said Refresh status. Enter30 s made Apply available. +One Apply created Event49044cb7-5f91-408a-86fc-1cc012eb2972 and edited only the +existing transition, leaving15elements/10relations and expected3coverage warnings. + +Native Save-as selected a different entry basename, state-recovered-renamed.arcadia. +Accepted entry SHA256AD8FD359CE7B0BE4F3AE0F55424EEB4EE032DEFB359F2F5AD89C00A79B62DADF +includes sa/model.arcadia, whose sole new Event body is Time(After,30 s), SHA256 +8FB649F8ABB93CF4C321F2D847A1A9F1A1551E0F713E938C46EF11E24DC58801. +One Ctrl+Z removed the Event and restored Refresh status. Ctrl+S produced entry +bytes exactly equal to pristine state-actions.arcadia, SHA256 +9CDE82608110E56AA9E14C9E33EB38976F53AED74AB013145D411D5743A0EC26. +Ctrl+Shift+Z remained available after that save, restored the same Event and +transition, and Ctrl+S reproduced the accepted entry exactly. Both saves retired +all private recovery JSON files. Oracles are retained under .local/native-qa as +state-after-timed-repair.arcadia, state-after-timed-repair-event.arcadia and +state-after-undo.arcadia. Full reopened bundle parsing remains a follow-up check; +entry byte equality alone is not claimed as independent whole-model comparison. + +Home Continue editing returned to the same Behavior owner/view and camera. +Native resize to1100x700 retained all9navigation pages. The Engineering guide at +that minimum size showed the entire Clear control and wrapped readable article +text, closing the observed clipping finding for that size. Ordered review and +reference-pair modal native acceptance remain pending. diff --git a/harness/release-home-recovery-review-20260906.md b/harness/release-home-recovery-review-20260906.md new file mode 100644 index 00000000..439e73a7 --- /dev/null +++ b/harness/release-home-recovery-review-20260906.md @@ -0,0 +1,63 @@ +# Current Home/recovery cross-feature review — 2026-09-06 + +Bounded read-only review of current `home.rs`, `home/tests.rs`, `recovery.rs`, +`app/checkpoints.rs`, app open/save/result handlers, `app/save_as_tests.rs`, and +semantic ordered/recovery adapters. No native input or Cargo was used for this +review. Existing native evidence is `release-current-native-verification.md`; +it records actual older-binary source/property/Annotation/Binding recovery and +state routing, not acceptance of every current source change. + +## Finding and authorized correction + +**P2: Home Resume discarded the actual workspace choice.** `home_event` sent +`Page::Diagram`, which is deliberately explicit Architecture navigation. Open +the state-only saved fixture (Behavior), visit Home, then Resume: that navigation +clears the active state view to an empty Architecture page. Text/Data/Traceability +also returned to Architecture. This is navigation loss, not model loss. + +Root authorized a bounded correction after this finding: `App` now remembers its +last actual non-Home page; Home Resume requests that page. Constructor state-view +routing initializes the remembered page correctly; explicit Architecture keeps +its existing behavior. Three focused `home_resume` tests cover all eight pages, +repeated Home navigation, exact state view and explicit Architecture, raw invalid +source/property drafts, and accepted-document identity. Source edits are limited +to `app.rs` and `app/checkpoints.rs`. Formatting/diff checks passed. Tests and +native recheck are pending root's current build queue; earlier compiled test +binaries must not be claimed to include this correction. + +## Other reviewed boundaries + +* **Old v1 semantic checkpoints:** the actual synthetic + `.local/native-qa/semantic-before-crash.json` has checkpoint version 1, semantic + version 1, one decision, and **no `orders` field**. The current closed `Saved` + schema adds `orders` with `#[serde(default)]`; it restores an empty order-draft + map while retaining existing decisions/rows/references. Its exact accepted-model + fingerprint is still checked, and `refresh` recomputes a plan rather than + executing saved commands. No incompatibility found by inspection. A current + deserialize/replan regression now removes that field from valid and rejected + raw v1 candidates, checks exact intent retention and rejects a changed accepted + model. It is authored but awaits the root test run; the prior native restore + predates the ordered addition. +* **Current restored Home card:** `checkpoint_loaded` removes the restored ID + from the displayed entries immediately, and Refresh filters both current and + restored IDs. Save retires checkpoints only when all accepted/raw/workflow work + is clean. The current focused test checks immediate removal. Distinct retained + generations after a second crash are not proof of duplicate identity; the old + snapshot intentionally remains until clean save/discard. +* **Failed saves:** current result handlers leave the accepted document, raw + workspace, recents, save destination, and recovery checkpoints in place on + failure; pending multi-file save recovery is surfaced separately. Successful + renamed Save-as adoption is revision checked and does not add semantic history. + No new failure-path defect found. Previously closed late-write races were not + rerun or counted as new review evidence. +* **Externally changed includes:** ordinary Save retains the loaded physical + expectations; a recovered checkpoint opens detached and retains no old disk + binding. Save-as uses the destination's admission/expectation path and refuses + collisions rather than silently overwriting an original include. No new + cross-feature overwrite path found. This is code review plus existing focused + tests, not a new fault-injected disk or native run. + +Limits: no current-binary crash execution, filesystem permission failure, Linux +runtime, execution of the new old-v1 deserialize test, or post-fix Home Resume native acceptance was +performed here. One concrete navigation finding was reported and implemented; +the remaining reviewed paths produced no additional concrete correctness defect. diff --git a/harness/release-home.md b/harness/release-home.md new file mode 100644 index 00000000..0c717db4 --- /dev/null +++ b/harness/release-home.md @@ -0,0 +1,76 @@ +# Home and recent models — implementation, 2026-09-06 + +The DATA continuation baseline did not contain Home/start or recent-model code. +This slice adds `oa-desktop/src/home.rs`, its storage tests, `mod home`, and the +Home view/navigation in `views.rs`. The lead has added App lifecycle and recovery +integration; integrated tests and native acceptance remain pending below. + +## Behavior and interface + +`Page::Home` presents New, Open, the rescue example, the engineering guide, +Continue editing, recent local model files and available recovery snapshots. +Home hides the modeling sidebar/inspector. Horizontal navigation remains reachable +at the existing minimum window width. Recovery rows use the lead's +`recovery::State` entries and warning, and emit `Event::Recover(Id)`. + +The module emits typed `home::Event` values. New/Open/Example/OpenRecent/Recover +must pass through the existing App Pending/save/discard guards; Guide/Resume use +existing Page navigation. Home does not modify or read model contents. Refresh +checks file availability. Remove only forgets recent-file metadata. Missing or +unavailable files remain visible with a Locate action using the normal Open picker. + +`home::State::default()` is memory-only for deterministic tests and constructors. +`load_default()` loads metadata without creating directories. `remember(&Path, +&str)` is called only after successful accepted local Open/Save; `forget(&Path)` +and `refresh()` handle the corresponding Home actions. Preserve the Home state +across App reset; do not reload it on each document replacement. JSON export, +failed writes, rejected/stale save receipts and canceled opens are not recents. + +`home::data_directory()` returns LOCALAPPDATA/ArcadiaStudio on Windows, otherwise +XDG_DATA_HOME/arcadia-studio or HOME/.local/share/arcadia-studio. There is no +current-directory fallback. Recent metadata is `recent-models.json` under it. +Recovery may reuse this helper with a separate subdirectory. + +## Storage guarantees + +At most 12 canonical local file paths and bounded display names, most recent +first; no model content, source text or credentials. Writes use a same-directory +temporary file, sync and atomic replacement; Unix also syncs the parent directory. +The existing `tempfile` dependency moved from dev-only to runtime. + +The reader bounds input to 256 KiB, rejects unknown fields/versions and invalid +entries, and keeps malformed/unsupported metadata unchanged. Failures are visible +and leave session history usable. Missing model files never disappear from history +merely because a drive is disconnected. Metadata removal does not remove files. + +## Verification status + +Eight focused storage tests cover side-effect-free first load, bounded order/deduplication +and durable replacement, missing/forgotten files, restored availability, malformed/ +future/oversized history preservation, failure/retry, malformed entries and absence +of model content from the store. They are written but not yet executed while the +lead bootstraps the new local Windows gnullvm toolchain. `git diff --check` passed. +Three App integration regressions additionally cover newer unsaved work surviving +a successful older Save receipt, checkpoint completion while a context menu is +open, and deferred discard/close cleanup of a late write before closing. Four +context-menu tests now explicitly use the demo fixture instead of startup. +README and the in-app Files/Recovery guide describe Home, recents, ten-second +private checkpoints, raw buffers, detached Save As and the missing undo history. +Owned Rust files were formatted successfully using the already installed GNU +toolchain's rustfmt executable (gnullvm initially lacked that component). + +Still required: compile/tests after lead App/recovery wiring; App lifecycle tests +for dirty model/drafts and stale file/save completions; actual default/minimum +window Home/recent/missing-path/recovery operation. No native input was issued. + +## Independent integration review + +Read-only review identified exact accepted-source comparison, unsupported property +buffer paths, source-path admission, completion events blocked by context menus, +stale-save checkpoint retirement, in-flight discard retirement and JSON-import +recent-history gaps. Findings were sent to the lead; several corrections are +already present but have not been exercised. Missing include paths legitimately +exist in Workspace.reachable without a loaded buffer, as source_files inserts +them before I/O. Recovery must preserve these repair states rather than require +reachable to be a subset of files. App::new test fixtures that require the example +must explicitly use with_project(Project::demo()), avoiding user-local state. diff --git a/harness/release-http-completion-review-20260906.md b/harness/release-http-completion-review-20260906.md new file mode 100644 index 00000000..6d4df6ba --- /dev/null +++ b/harness/release-http-completion-review-20260906.md @@ -0,0 +1,34 @@ +# HTTP concurrency fixture completion review + +The integrated run exposed completion watchdog assumptions in two existing real +HTTP tests, not a demonstrated catalog-locking or revision-commit defect. +The slow-source test's two-second catalog read and winning PATCH assertions +completed before its old 25-second candidate-completion watchdog expired. +The oversized test's five-second candidate-validation watchdog also expired; +its separate catalog responsiveness assertions had passed. + +A focused experiment extending the slow-source completion watchdog exposed the +actual response: the 2,000-deep accepted fixture returned HTTP **504** after +31.05 seconds. The service has a real 30-second request deadline. Waiting longer +cannot turn that response into the intended 412 conflict, and accepting 504 here +would lose this test's revision-conflict proof. + +The concurrency fixture now uses 1,000 nested activities. It retains the +100-millisecond unfinished-request assertion, two-second catalog/PATCH bounds, +exact 412 `revision_conflict`, revision 2, winning model content, and persisted +disk equality checks. Its completion watchdog is 45 seconds, enough to observe +the real service deadline. The oversized import/replacement/candidate checks +use 30-second completion watchdogs while retaining their separate five-second +catalog bounds, exact `model_limit` rejection and no-mutation checks. Only test +fixtures/watchdogs changed; production service behavior is unchanged. + +Focused verification on the same `-p oa-server --test http` feature graph: + +| Test | Result | Test duration | +|---|---|---| +| `accepted_slow_source_does_not_lock_catalog_and_loses_to_a_newer_http_revision` | 1 passed | 13.13 s | +| `oversized_source_and_json_are_rejected_without_stalling_the_catalog` | 1 passed | 25.71 s | + +Logs: `.local/server-completion-focused.log` and +`.local/server-oversized-focused.log`. These focused passes do not establish the +result of the next integrated workspace run under concurrent test load. diff --git a/harness/release-linux-validation-options.md b/harness/release-linux-validation-options.md new file mode 100644 index 00000000..786e2323 --- /dev/null +++ b/harness/release-linux-validation-options.md @@ -0,0 +1,133 @@ +# Linux release verification options — 2026-09-06 + +This note records an initial read-only feasibility inspection, subsequent portable QEMU preparation and pilot, and the later user-authorized switch to WSL. The chronological sections distinguish preparation, actual Linux execution, and outstanding release acceptance. No Windows feature/security setting was changed by these investigations. + +## Observed environment + +- Windows 11 Pro, 2 logical CPUs, 8 GiB physical RAM; approximately 3.6 GiB free physical memory at inspection. `C:` had approximately 29.3 GiB free. `D:` is the mounted Windows installation DVD, not historical `D:/CodexBuilds`; that historical directory is absent. `Z:` is a network/shared-folder drive with approximately 17.7 GiB free, not a suitable assumed local VM/build disk. +- `wsl.exe` exists but both `wsl --status` and `wsl --list --quiet` report that Windows Subsystem for Linux is not installed. No Docker, Podman, QEMU, VirtualBox, Multipass or GitHub CLI executable was found through normal command lookup or their conventional installation directories. No WSL/vmcompute service was found by the bounded service-name query. +- `WinHvPlatform.dll` exists. A read-only `WHvGetCapability(WHvCapabilityCodeHypervisorPresent)` query returned HRESULT `0`, 4 bytes written and **false**. The Windows system-information flag `HypervisorPresent=true` does not establish usable nested acceleration for a new guest. QEMU WHPX should therefore be treated as unavailable now. Microsoft's [capability API](https://learn.microsoft.com/en-us/virtualization/api/hypervisor-platform/funcs/whvgetcapability) and [QEMU WHPX prerequisites](https://www.qemu.org/docs/master/system/whpx.html) distinguish API availability from the required hypervisor platform. +- Git's bundled SSH executable and 7-Zip are installed. No known Linux remote host is supplied by the current environment; no SSH configurations, credential stores or account sessions were inspected. An SSH client alone is not a remote Linux resource. + +## Repository routes and one corrected regression + +The configured origin is `https://gitlab.com/unbleaklessness/ARCADIA-Studio`. The tracked CI definition is `.github/workflows/ci.yml`, with Windows/Ubuntu jobs for fmt, strict clippy, workspace tests, release builds and executable artifacts. There is no tracked `.gitlab-ci.yml`. The GitHub workflow cannot be assumed to execute on the GitLab origin; no running pipeline or accessible GitHub mirror was established. It also has no interactive Linux desktop acceptance stage, full distribution assembly, restart/deployment probe or final package checksums. + +`deployment/Dockerfile` builds only the server on Rust 1.90/Bookworm and packages it in Debian Bookworm slim; `deployment/arcadia-server.service` describes a real installed Linux service with a restricted service account. Neither recipe supplies a local Linux kernel or proves deployment. Inspection found that the newly included `build/windows_manifest.rs` was missing from the Docker source context, even though its runtime body skips Linux. Root corrected this with `COPY build ./build`. This is a corrected code-inspection finding, not a successful Docker build. + +## Concrete options + +Times and working-disk figures below are planning estimates, not benchmarks. They include this Rust/Iced/WGPU project rather than only the guest OS. + +| Route | What it can establish | Expected resources / elapsed time | Constraint or risk | +|---|---|---|---| +| Known Linux workstation/VM or authenticated CI runner supplied later | Native Linux builds, tests, MSRV, real HTTP/restart and packages; interactive desktop if a supported display/control route is provided | Typically 10–25 GiB build workspace; 1–4 hours initial automated work on a modest accelerated host, plus acceptance work | No such host/access is currently established. Do not invent endpoints or initiate authentication. CI logs alone do not establish GUI behavior. | +| User-local QEMU TCG + Ubuntu 24.04 minimal cloud image | A real Linux kernel/userspace executing Linux binaries and tests; a real Linux graphical session with a lightweight desktop and Mesa software Vulkan | About 0.45 GiB initial QEMU installer + image downloads, then guest packages/toolchain/crates. Budget a 20–24 GiB sparse guest disk, 2–3 GiB guest RAM plus 2 GiB guest swap. Provisioning could take 1–3 hours; full cold workspace build/test/release under TCG may take 8–24+ hours and must be measured | No hardware acceleration presently available. CPU emulation plus software Vulkan may make interactive acceptance slow. Remaining Windows RAM/disk is tight; pause heavy Windows builds before starting. This is emulated Linux acceptance, not Linux hardware/GPU-performance evidence. | +| User-local QEMU TCG + Debian amd64 netinst, lightweight Xfce session | Same real Linux execution categories as above | Netinst downloads only needed packages; roughly 4 GiB OS + Xfce before Rust/build outputs, with the same 20–24 GiB working-disk budget and TCG timing risk | Installer is more interactive than a seeded cloud image. Debian coverage does not replace the repository's documented Ubuntu 24.04 package-runtime claim. [Debian requirements](https://www.debian.org/releases/stable/amd64/ch03s04.en.html), [task sizes](https://www.debian.org/releases/stable/amd64/apds02.en.html). | +| Full Ubuntu Desktop installer under TCG | Ubuntu desktop environment without manually adding a session | Official Ubuntu 24.04 guidance asks for at least 25 GB storage before this project's build workspace | Poor fit for ~29 GiB currently free and 8 GiB total host RAM. Prefer minimal Ubuntu plus only needed GUI/runtime packages. [Ubuntu installation guide](https://ubuntu.com/desktop/docs/en/24.04/tutorial/install-ubuntu-desktop/). | +| WSL2/WSLg, Docker Desktop, Hyper-V, VirtualBox or WHPX installation | Potentially faster real Linux environments after setup | Likely needs Windows feature/driver installation and possibly reboot; nested virtualization also matters on this already virtualized host | Not a user-local equivalent of portable QEMU TCG, and outside this inspection's allowed actions. The earlier rejected build-tools installer is not authorization to modify these features. | +| Windows cross-build/MSYS2/Cygwin or downloaded Linux artifacts only | Compilation or artifact inspection | Potentially smaller/faster than a guest, but desktop Linux linking still needs a target sysroot/native dependencies | Does not execute Linux tests, filesystem behavior, service restart or Linux GUI. Cannot close the remaining Linux release gates. | + +## Recommended feasible local pilot + +Use a bounded **Ubuntu 24.04 minimal QEMU/TCG pilot** if no actual Linux host becomes available. This keeps the documented runtime family and avoids system feature changes. Do not commit to a full emulated build before measuring its speed. + +1. Obtain a pinned Windows QEMU build from the Windows provider linked by [QEMU's download page](https://www.qemu.org/download/). The linked [Stefan Weil distribution](https://qemu.weilnetz.de/w64/) currently lists approximately 197 MB installers and matching SHA-512 files. Prefer extracting the payload under ignored `.local` with the available 7-Zip, then verify the binaries and firmware are runnable without installer execution; portable extraction has **not** been tested here. Do not silently substitute an installer requiring elevation if extraction fails. +2. Use Canonical's [Ubuntu 24.04 minimal amd64 QCow2 image](https://cloud-images.ubuntu.com/minimal/releases/noble/release/), currently listed as 252 MB with SHA256SUMS and signatures. Pin the dated release and checksum in the evidence ledger instead of relying on a moving URL. It is a UEFI/GPT bootable disk: configure matching x86 UEFI firmware from the QEMU bundle and an independent writable variables file, and create a separate sparse overlay rather than modify the downloaded base. +3. Seed a fresh guest account using cloud-init NoCloud. A tiny CIDATA ISO/FAT seed is one option; [NoCloud also supports SMBIOS configuration with an HTTP seed directory](https://docs.cloud-init.io/en/latest/reference/datasources/nocloud.html), avoiding a host ISO-building dependency. The seed can contain new disposable guest credentials/keys, never existing user credentials. Keep the seed and source transfer server scoped to a task-specific directory. Use QEMU's unprivileged user-mode networking and loopback-only host forwarding, not TAP/bridges; [QEMU documents this backend as requiring no administrator privilege](https://www.qemu.org/docs/master/system/invocation.html). +4. Start at 2 GiB guest RAM, 1–2 emulated CPUs and `-accel tcg`; add 2 GiB guest swap. First verify boot, ext4 write/reopen, package networking, Rust installation and one small core compile/test. Record wall time, available host memory and real allocated disk after 30–60 minutes. If the pilot cannot make useful progress without sustained host paging, prefer an actual accelerated Linux host rather than consuming the Windows acceptance environment. +5. Copy a snapshot of the exact source revision plus any explicitly recorded uncommitted patch into the guest filesystem; do not build on the shared `Z:` drive or use Windows target output as a Linux cache. Build with one Cargo job and incremental compilation disabled if disk pressure requires it; the repository already sets dev debug info to zero. Reserve at least 5 GiB free host disk for Windows, logs and final evidence. Sparse virtual capacity is a maximum, not a guarantee of low physical use; measure growth before each major stage. +6. Provision only required Linux build libraries and a lightweight Xorg/desktop session initially. Add Mesa Vulkan software rendering and inspect `vulkaninfo` before launching Studio. Mesa documents the software-driver ICD naming, including `lvp_icd.x86_64.json`, in its [installation guide](https://docs.mesa3d.org/install.html); discover the actual installed path. Set the recorded WGPU/Vulkan environment intentionally. A displayed Linux window must be exercised through the root's permitted GUI-control surface. An Xvfb screenshot or successful process launch is only a smoke test. Add a real Wayland session separately if both display-backend acceptance claims are required. + +The milestone output should include Linux kernel/distribution, Rust versions, emulator/CPU/RAM/rendering mode, exact source revision, automated command results, real HTTP/restart results, native workflow evidence, fresh package hashes and known limits. TCG does run the Linux binary on a Linux kernel; it does not justify claims about physical Linux hardware acceleration or performance. No current Linux release gate is closed by this feasibility report. + +## Preparation completed later on 2026-09-06 — VM not booted + +Root authorized download, verification and local extraction after the read-only feasibility pass. All pilot files are in ignored `.local/linux-qa`; no installer was executed, Windows feature changed or existing credential read. There is no extraction blocker. + +| Prepared input | Pinned URL / verified result | +|---|---| +| QEMU Windows payload | `https://qemu.weilnetz.de/w64/qemu-w64-setup-20260811.exe`, 206,615,928 bytes. SHA-512 matches `https://qemu.weilnetz.de/w64/qemu-w64-setup-20260811.sha512`: `5bcf9eed634e8575a37b74f445af41a2fe4106da512d0c30c368301d4c105037fdfab40a5287367a28a957624cddebbc8c07e16c88ab6634f554cdf3d16bf543`. | +| Ubuntu disk | `https://cloud-images.ubuntu.com/minimal/releases/noble/release-20260905/ubuntu-24.04-minimal-cloudimg-amd64.img`, 264,372,224 bytes. SHA-256 matches the same dated directory's `SHA256SUMS`: `46b0dbaffa6950a7da5ff2dc5ed34c46084610b3b6d1fae8f1ec2d7e953984a3`. | + +These are matches against the published checksum files fetched over HTTPS; no independent OpenPGP signature verification is claimed. QEMU was extracted with the installed 7-Zip into `.local/linux-qa/qemu`. Its console executable reports **11.1.0 (v11.1.0-12130-ge470268ff4)**. `--version`, `-display help` and `qemu-img info` exited successfully as the normal user; GTK and SDL display backends are listed. No guest was launched by those probes. + +QEMU's x86_64 firmware descriptor specifies `share/edk2-x86_64-code.fd` (3,653,632 bytes) and `share/edk2-i386-vars.fd` (540,672 bytes). The latter was copied to the task's independent `runtime/uefi-vars.fd`. `qemu-img info` recognizes the downloaded image as clean QCow2, with 3.5 GiB virtual capacity. `runtime/pilot.qcow2` is a separate **20 GiB virtual** overlay backed by that unchanged image; its initial physical file size is 197,120 bytes. Prepared files occupy approximately **1.61 GiB** in total; `C:` retained approximately **27.4 GiB** free at preparation completion. No unrelated caches were removed. + +`seed/user-data` and `seed/meta-data` configure only the disposable guest account `arcadia`, its new Ed25519 public key, password-disabled SSH, hostname and no package update/upgrade. The private key exists only under `.local/linux-qa/keys`; it is not embedded in the seed, report or tracked files. Public-key fingerprint: `SHA256:36NIvdOXSeBW4ifQFCqQD6Tgfi6PU0aFc1r+mLA9yCk`. The pinned Ubuntu package manifest includes cloud-init and openssh-server, so first-boot SSH does not depend on a package-install command. + +`serve-seed.cjs` binds **127.0.0.1:18080** and serves only `/user-data` and `/meta-data`. A brief host-only smoke test verified both responses and 404 rejection of private-key/path-traversal requests; the server then stopped. `launch-pilot.cjs` syntax and `--describe` passed. It waits for seed-server readiness, then would launch QEMU with: + +- `q35`, `tcg,thread=multi`, CPU `max`, 2 vCPUs and 2 GiB RAM; +- read-only UEFI code, independent writable variables and the 20 GiB overlay; +- Virtio disk/network and user-mode networking with **only 127.0.0.1:2222 → guest:22** inbound forwarding; +- NoCloud SMBIOS seed URL `http://10.0.2.2:18080/`, no monitor listener, serial output to `runtime/serial.log`, headless display by default; +- hidden helper processes with logs/PIDs confined to `runtime`; `--gui` selects GTK when interactive verification is authorized. + +The complete resolved argument vector is in `.local/linux-qa/ready-command.json`. **The following command is ready but has not been run; root retains the boot/resource gate while Windows work and the optional existing-Linux-host question are pending:** + +```powershell +& 'C:\Users\User\.cache\codex-runtimes\codex-primary-runtime\dependencies\node\bin\node.exe' .local/linux-qa/launch-pilot.cjs +``` + +After an authorized boot completes, the intended guest connection uses the disposable key and a separate task-local host-key file: + +```powershell +& 'C:\Program Files\Git\usr\bin\ssh.exe' -i .local/linux-qa/keys/guest_ed25519 -p 2222 -o IdentitiesOnly=yes -o UserKnownHostsFile=.local/linux-qa/keys/known_hosts -o StrictHostKeyChecking=accept-new arcadia@127.0.0.1 +``` + +The key connection, guest boot, cloud-init, network-to-host seed access, guest filesystem growth, Rust build speed and Linux GUI remain untested. No swap or toolchain packages have been provisioned in the guest. Preparation creates a reproducible pilot starting point, not Linux release acceptance. + +## Bounded QEMU pilot — actual guest execution + +Root authorized a headless pilot with **one vCPU and 2 GiB RAM**, initially bounded to 30 minutes, then allowed one 10-minute recovery extension after network/account setup made useful progress. Processes were hidden; networking remained user-mode NAT with loopback-only host listeners. A task-local watchdog sampled memory/disk every 20 seconds, with stop thresholds of 512 MiB free host RAM and 5 GiB free disk. Concurrent Windows work reached 100% host CPU, so timings below describe a contended two-CPU machine, not an isolated emulator benchmark. + +| Attempt (2026-09-06 PDT) | Observed result | +|---|---| +| 15:37:47–15:47:10, original headless display | Ubuntu kernel/root filesystem/systemd started, but BOOT/UEFI/ttyS0 device jobs timed out before udev coldplug completed. Numerous framebuffer slow-work warnings. No SSH. Disk and logs retained as `runtime/attempt1-*`. | +| 15:47:51–15:54:14, fresh overlay with `-vga none` | Framebuffer warnings disappeared. The same device-job timeouts caused emergency mode; VGA removal alone did not fix startup. Logs and pre-recovery disk retained as `runtime/attempt2-*`. | +| From 15:55:03, retained second disk with loopback TCP serial | GRUB automatically retried with initrd after previous initrdless failure. Device jobs still initially timed out. Coldplug, cloud-init network setup and mount recovery eventually completed. SSH with the disposable `arcadia` key succeeded; cloud-init finished at 16:08:17, about **13m14s** after this launch. | +| 16:10:51–16:14:10, after guest timeout adjustment | Relaunched following a clean guest reboot request. Startup reached serial-getty/user sessions without the earlier recorded device timeout at that point. The user then supplied WSL and root directed stopping QEMU; post-reboot SSH/file verification was not completed. | + +The third attempt established **real Linux execution**: Ubuntu 24.04.4 LTS, kernel `6.8.0-139-generic`, x86_64 under QEMU TCG. `cloud-init status --long` returned `done`, `errors: []`, and `recoverable_errors: {}` with `DataSourceNoCloudNet [seed=dmi,http://10.0.2.2:18080/]`. The root filesystem expanded to 19 GiB, with 514 MiB used and 18 GiB available. `lsblk` confirmed `vda15` labeled UEFI mounted on `/boot/efi` and `vda16` labeled BOOT mounted on `/boot`. + +The guest obtained `10.0.2.15` with gateway `10.0.2.2`; DNS resolved `archive.ubuntu.com`. An explicit write, `sync`, and reread of `~/pilot-check.txt` returned the exact `arcadia-linux-pilot` content. The guest reported 1961 MiB RAM, 281 MiB used, 1679 MiB available and no swap. `systemctl --failed` listed zero units after bootstrap recovery. No existing credentials were inspected or tried. The maintenance prompt never yielded a root shell; access used only the seeded disposable SSH identity. No package installation, Rust toolchain, source transfer, Cargo build/test, GUI session, or Linux release gate was completed. + +To accommodate observed late coldplug under TCG, the authorized guest-only file `/etc/systemd/system.conf.d/90-tcg-pilot.conf` contains: + +```ini +[Manager] +DefaultDeviceTimeoutSec=600s +DefaultTimeoutStartSec=600s +``` + +`systemctl daemon-reload` followed by `systemctl show` confirmed both defaults at 10 minutes. This is an emulator bootstrap allowance, not an application performance relaxation. These settings are supported by [systemd 255's documented manager configuration](https://raw.githubusercontent.com/systemd/systemd/v255/man/systemd-system.conf.xml). A clean `sudo systemctl reboot` completed, after which QEMU exited as configured by `-no-reboot`. The subsequent cold startup was interrupted by the authorized WSL switch; a fully verified clean reboot remains outstanding. + +At the 16:12 resource summary, 91 samples across attempts showed minimum free host memory **2026459136 bytes (1.89 GiB)** and minimum free disk **24309592064 bytes (22.64 GiB)**. The current overlay file length was **36831232 bytes**, plus preserved failed-attempt overlays of 7995392 and 6619136 bytes. These are QCow2 file lengths, not the advertised 20 GiB virtual capacity. Windows build output also consumed host disk during the pilot. + +All pilot processes were stopped on the WSL switch after verifying their identities: QEMU 9008, seed 8780, launcher 10840, watchdog 1244 and serial client 2544. `.local/linux-qa/runtime/guest-timeout-reboot.log`, attempt logs, resource samples, saved overlays and task-local known-hosts remain available. The launch helper now uses `-vga none` and loopback TCP serial `127.0.0.1:4444` with serial logging; `serial-console.cjs` sends only explicitly written task-local command messages. The watchdog still records this pilot's fixed start/budget and must be assigned a **new explicit budget before any future pilot**. Do not reuse its expired deadline accidentally. + +This experiment does not establish that Linux is unavailable. It established usable Linux SSH/filesystem/network behavior and a concrete boot-timing issue with a guest-only adaptation. If WSL cannot run, the retained booted guest is a valid fallback for a further measured pilot; plan roughly 15–20 minutes for a bounded cold-boot/recovery check before committing to Rust provisioning. Full emulated-build and GUI timings remain unmeasured. + +## User-installed WSL — distro attempt and pending host restart + +The user subsequently reported installing WSL and explicitly authorized creating a Linux test distro. The QEMU pilot was stopped as directed, preserving its disk and evidence. WSL inspection now reports **WSL 2.7.13.0**, kernel **6.18.33.2-2**, WSLg **1.0.73.2**, and default version 2. No existing distributions were registered; none were modified. `Ubuntu-24.04` is listed by `--list --online`. + +The current runtime is not yet usable: `wsl --status` reports that WSL2 cannot start because virtualization is unavailable. Read-only component/service checks found VirtualMachinePlatform enabled (CIM InstallState 1), WSL1's optional component disabled (2), WslService running, and no vmcompute or hns service. **Component Based Servicing reports RebootPending=true.** CPU inventory reports virtualization firmware/extensions/SLAT false while HypervisorPresent is true. These observations do not distinguish an outstanding Windows reboot from every nested-virtualization constraint; complete the pending user-controlled reboot and recheck first. No restart, Windows feature change, service-start override, registry edit, WSL global setting or existing credential access was attempted by the agent. + +Prepared the official [Ubuntu 24.04.4 WSL image](https://releases.ubuntu.com/24.04.4/ubuntu-24.04.4-wsl-amd64.wsl) under `.local/linux-wsl/ubuntu-24.04.4-wsl-amd64.wsl`: **391541571 bytes**, SHA-256 **`9b2f7730dc68227dd04a9f3e5eab86ad85caf556b8606ad94f1f29ff5c4fd3f5`**, matching the release directory's HTTPS `SHA256SUMS`. No independent OpenPGP verification is claimed. This is the supported Ubuntu WSL root-filesystem image, not the earlier QEMU QCow2. + +The distro-only attempt deliberately avoided an install command that might enable Windows components: + +```powershell +wsl.exe --import ArcadiaLinuxQA ` + C:/Users/User/Documents/Git/ARCADIA-Studio/.local/linux-wsl/ArcadiaLinuxQA ` + C:/Users/User/Documents/Git/ARCADIA-Studio/.local/linux-wsl/ubuntu-24.04.4-wsl-amd64.wsl ` + --version 2 +``` + +It failed after **524 ms**, before registration, with `Wsl/Service/RegisterDistro/CreateVm/HCS/HCS_E_SERVICE_NOT_AVAILABLE` and a required-feature-unavailable message. A following distro list remained empty; WSL removed the attempted empty install directory. Exact decoded outputs are retained in `.local/linux-wsl/{status,version,list,help,import-result,post-import-list}.json`. Microsoft documents the distro-import operation and separate platform installation in [WSL commands](https://learn.microsoft.com/en-us/windows/wsl/basic-commands) and [installation guidance](https://learn.microsoft.com/en-us/windows/wsl/install). + +There is **no WSL guest session, initialized Linux workspace or installed Linux toolchain yet**. Host resources after QEMU shutdown and image download were approximately 3.97 GiB free RAM and 21.82 GiB free disk. After the host prerequisite is resolved, recheck `wsl --status` and `wsl --list --verbose`; if the task name remains absent, the verified image and `.local/linux-wsl/import.cjs` can retry the same isolated import without another download. The helper refuses an existing install directory, so inspect any partial state rather than overwriting it. + +Once that distro actually starts, create its task-local non-root build account/workspace, provision only the required Rust/native prerequisites, and coordinate source snapshot identity with the Windows owner. Build inside the distro filesystem, not `/mnt/c` and not the Windows Cargo target. Start with one Cargo job and an isolated Linux target directory; the first proposed check, after prerequisites and source transfer, is `CARGO_BUILD_JOBS=1 CARGO_INCREMENTAL=0 cargo test --locked -p oa-core --lib`. Measure its wall time, peak memory and disk growth before broad workspace/GUI provisioning. Reserve at least 5 GiB free host disk. WSL installation by itself does not close any Linux build, runtime, GUI or package acceptance gate. diff --git a/harness/release-msrv-readiness-20260906.md b/harness/release-msrv-readiness-20260906.md new file mode 100644 index 00000000..8b711bdf --- /dev/null +++ b/harness/release-msrv-readiness-20260906.md @@ -0,0 +1,64 @@ +# Rust 1.90 Windows readiness audit + +The locked Windows runtime/build dependency declarations do not rule out the +workspace's Rust1.90 minimum. This is a declaration audit and toolchain preparation, +not a successful MSRV compilation or runtime claim. + +Fresh `cargo metadata --locked --offline --format-version 1 --filter-platform +x86_64-pc-windows-gnullvm` was collected with the existing task-local stable toolchain. +Starting at `oa-desktop` and `oa-server`, traversal of selected normal/build dependency +edges yields **266 packages**:183 declare `rust_version`,83 do not. None declares a +minimum above1.90. Highest external declaration is `ordered-float 5.5.0:1.90`; +Iced0.14, wgpu27.0.1 and the selected ICU2.3 packages declare1.88. +Missing or inaccurate declarations and project source compatibility still require +an actual compiler check. Dev-only and non-Windows target dependencies are outside +this runtime declaration count. + +Provenance is retained under `.local/msrv-readiness/`: target-filtered metadata, +selected package names/versions/manifests/minima, summary, download manifest and +installation logs. Cargo.lock SHA256: +`E7B95AFAC40A11823FA68D615FCAD97DEEB878C8FA1E83B28561BE5A81896112`. +No dependency, source, workspace target or global configuration was changed. + +## The exact requested host triple is unavailable in Rust1.90 + +`rustup toolchain install 1.90.0-x86_64-pc-windows-gnullvm --profile minimal +--no-self-update` failed with “target ... not found in channel”. The official +[Rust1.90 distribution manifest](https://static.rust-lang.org/dist/channel-rust-1.90.0.toml) +contains gnullvm target standard libraries, but no gnullvm-host rustc/cargo packages. +The [Rust1.91 release announcement](https://blog.rust-lang.org/2025/10/30/Rust-1.91.0/) +records promotion of Windows gnullvm to host-tools support. Raising the declared +MSRV merely to use this host triple would confuse host packaging with language/ +library compatibility. + +The viable preparation uses `1.90.0-x86_64-pc-windows-gnu` as compiler host and adds +`x86_64-pc-windows-gnullvm` target libraries. Installation was launched using the +existing task-local Rustup homes under `.local/toolchain`, minimal profile, +`--no-self-update`, and explicit target. Installation completed successfully. +`rustup run` reports rustc1.90.0 (`1159e78c4`, LLVM20.1.8) and cargo1.90.0 +(`840b83a10`). Installed targets include both GNU and gnullvm. Exact version output +and compiler hashes are in `installed-provenance.txt` and `compiler-hashes.json` +in the ignored audit directory. The task environment still selects stable gnullvm, +and Rustup's pre-existing GNU default/settings remain unchanged. + +## Subsequent verification, owned by the root build operator + +After installation, dot-source `.local/toolchain/env.ps1`, invoke the compiler using +an explicit `+1.90.0-x86_64-pc-windows-gnu`, and pass the explicit gnullvm target. +Use a separate task-local target directory to avoid invalidating the current +stable/native acceptance artifacts. For example, once target ownership is granted: + +```powershell +. .local/toolchain/env.ps1 +$env:CARGO_TARGET_DIR = Join-Path (Get-Location) '.local/target-msrv190' +cargo +1.90.0-x86_64-pc-windows-gnu check --locked --workspace --all-targets --target x86_64-pc-windows-gnullvm +``` + +That check includes test code/dependencies and can reveal gaps beyond this runtime +declaration audit. An actual build/link and appropriate tests are still needed +before asserting MSRV support. The existing clang linker configuration applies to +the gnullvm target; GNU host build scripts/proc macros use the installed GNU host +toolchain. Linux/Docker Rust1.90 remains a separate platform verification gate. +After this preparation, the user enabled WSL; the root operator may prefer a +Linux1.90 check before allocating a second Windows compilation tree. No MSRV Cargo +compilation was started by this audit. diff --git a/harness/release-oa-communication-critique-20260906.md b/harness/release-oa-communication-critique-20260906.md new file mode 100644 index 00000000..bbf605bf --- /dev/null +++ b/harness/release-oa-communication-critique-20260906.md @@ -0,0 +1,109 @@ +# OA communication means — independent design critique + +Read-only production review, 2026-09-06. Recommendation: add a first-class +`CommunicationMean` relation over OA Entity/Actor endpoints and reuse the existing +`ExchangeAllocation` relation. Do not widen the meaning of existing boundary-port +records silently. Root retains the contract decision; no production edits made. + +## Primary evidence + +Capella's [OperationalAnalysis.ecore](https://raw.githubusercontent.com/eclipse-capella/capella/master/m2/plugins/org.polarsys.capella.core.data.def/model/OperationalAnalysis.ecore) +defines CommunicationMean as a specialization of ComponentExchange and exposes +sourceEntity/targetEntity as direct aliases of its endpoints (lines 1597–1632). +Entity includes actors in its documented operational meaning. The inherited +[FunctionalAnalysis.ecore](https://raw.githubusercontent.com/eclipse-capella/capella/master/m2/plugins/org.polarsys.capella.core.data.def/model/FunctionalAnalysis.ecore) +defines optional orientation, default false, and allocation of functional +exchanges (lines 2522–2545). ComponentExchange also participates in the event/ +operation abstraction. These establish a useful shared exchange family, not a +requirement to force operational entities through SA typed ports. + +## Why the smaller-looking BoundaryExchange change is risky + +Current `RelationKind::permits` requires Port→Port. `ports::validate_relation` +checks participant ownership through each endpoint's parent, Legacy/Standard/Flow +contracts and carried interfaces. `validate_relation_context` rejects endpoints +with equal parents: applied directly to nested OA sibling entities it would reject +valid communication. `ports::validate_exchange_allocation` again treats endpoint +parents as performers and classifies Interaction allocation as legacy. Its legacy +branch warns OA users to introduce typed ports they cannot create, and checks only +the forward pairing even for an unoriented boundary. + +Projection identifies BoundaryExchange endpoints as ports; endpoint picking, +reconnection, connection creation, interface checks and scenario participant +resolution all contain related assumptions. Changing only `permits` yields an +inconsistent product. A proper tagged endpoint sum could represent both semantics, +but it requires migrating every BoundaryExchange and all these consumers. A new +relation variant exposes each required exhaustive integration explicitly while +leaving old records, identities and authored source intact. + +## Small explicit contract to freeze + +- `CommunicationMean { source, target, oriented, items }`; distinct same-layer OA + Entity or Actor endpoints, optional carried OA ExchangeItems, no child ports. + Initial `oriented=false` follows the primary model; users can choose direction. + Multiple named means between the same pair remain valid engineering alternatives. +- `ExchangeAllocation { source: mean, target: interaction }` is relation-to-relation + allocation. Do not duplicate allocated-interaction IDs inside the mean. Preserve + old BoundaryExchange→Interaction and BoundaryExchange→FunctionalExchange records. +- Match actual direct Activity Allocation owners. Missing/ambiguous allocation is + an actionable incomplete-allocation warning; a known contradictory owner is an + error. Role realization alone does not silently establish an actual allocation. + For unoriented means consider both complete pairings; when one owner is unknown, + the other known owner must still belong to at least one admissible pairing. +- The mean carries every allocated Interaction item, using existing item-type + satisfaction rules. Editing either item list revalidates its allocations + atomically. Means may be created before their Interactions; incomplete work must + remain possible without inventing responsibilities or allocating parents. +- ParticipantInteraction scenarios may reference means; OperationalActivity + scenarios/process exchange contributions continue to reference Interactions. + An unoriented ordinary message resolves direction from explicit sender/receiver + lifelines. A correlated same-mean Reply reverses the actual Call binding, including + a reverse Call on an unoriented mean. Keep the existing operation-response + convention rather than invent simplex hardware constraints from `oriented`. + This direction convention is a Studio decision to specify, not established by + the primary schema's orientation sentence alone. + +## Compatibility and transition traps + +`transitions::mapped_relation` currently clones BoundaryExchange into the other +layer. Direct OA participant endpoints mapped to SA participants are not component +ports. Two OA entities may also map to one System: their operational communication +need not become an external boundary exchange. Never auto-generate SA ports or +self-boundaries from that fact. A bounded first implementation should explicitly +report an unsupported/omitted counterpart until a user chooses the corresponding +SA boundary and its ports; preserve the OA mean and any existing manual trace. + +Schema 5 is current. `migration_v4::Project4` embeds live Relation/Diagram types; +adding a live variant can accidentally make a schema-4 envelope accept a new +construct. Freeze a deliberate version policy and add negative old-version +source/JSON admission tests. Do not rewrite frozen migration fixtures or reinterpret +legacy ports as entities. An explicit future conversion must account for port-level +artifacts, interface roles, diagrams and endpoint references before removal. + +Generic reference traversal must visit items; endpoint visitors alone are +insufficient. Deleting a mean removes its allocations, routes, trace links and +dependent occurrences, while preserving their Interaction definitions and +cross-layer counterparts. Deleting/reparenting an actual work allocation must +revalidate the new OA allocation contract without accidentally invoking the SA +port-allocation cascade. + +## Required user journey and falsification tests + +Start empty: place two OA entities/actors; create allocated activities and an +Interaction; draw/name a Communication means edge between participant cards; +select carried items and allocate existing Interactions using a filtered picker. +The inspector exposes both communication and activity layers with direct navigation. +Render the mean on participant boundaries, keep work allocation nested, and show +allocated Interactions as selectable rows rather than extra allocation arrows. +Endpoint reconnect preserves UUID/alias/prose/items/routes/artifact owners and +retains rejected edits. No OA Port form should appear in this journey. + +Acceptance must falsify: mixed-layer/Role/Port/Activity endpoints; same endpoint; +wrong/unknown/role-only performer; forward/reverse/unoriented and partial-owner +allocation; missing carried items and later item changes; multiple means per pair; +reverse unoriented Call and correctly correlated Reply; forbidden process/viewpoint +use; folded containers and missing placements; sibling nested entities; exact +reconnect/undo/redo; remap/delete/reparent; no automatic SA boundary after participant +collapse. Source+JSON+REST must agree, reject atomically, and retain identity and +comments through reidentify, include movement and server restart. A standalone OA +mean plus later allocation must be usable without SA or LA/PA work. diff --git a/harness/release-oa-communication-design.md b/harness/release-oa-communication-design.md new file mode 100644 index 00000000..9e892043 --- /dev/null +++ b/harness/release-oa-communication-design.md @@ -0,0 +1,284 @@ +# OA communication means — bounded contract decision, 2026-09-06 + +Status: proposed for root synthesis. Production was read only. Investigation uses +HEAD c091f80 plus current integrated changes. No implementation, tests or native +acceptance are claimed. This closes the representation question raised by +release-current-coverage-20260906.md, not the feature itself. + +## Recommendation and evidence + +Add a first-class `CommunicationMean { source, target, oriented, items }` relation +between OA participants, retaining existing identified `ExchangeAllocation` +relations to its allocated `Interaction`s. Keep `BoundaryExchange`'s port endpoint +contract intact. Share exchange-item/allocation helpers where their semantics +agree, and use a dedicated OA validation branch where participant endpoints differ. + +The official Arcadia activities reference describes operational Interactions +between activities and their allocation to communication means between +stakeholders. It also connects stakeholder scenarios to these exchanges. This +establishes the engineering need independently of a Capella UI convenience. +[Arcadia reference, activities 5.1.1.2.4–8, PDF page 21](https://www.eclipse.org/capella/resources/arcadia-reference/Arcadia%20Reference%20-%20Activities.pdf). + +Capella's OA schema makes CommunicationMean a named relation specializing +ComponentExchange and gives it direct sourceEntity/targetEntity aliases. Its +inherited exchange contract includes orientation (default false) and allocations +to functional exchanges. These are conceptual evidence, not a requirement to copy +the whole EMF inheritance structure. [OperationalAnalysis schema](https://raw.githubusercontent.com/eclipse-capella/capella/master/m2/plugins/org.polarsys.capella.core.data.def/model/OperationalAnalysis.ecore), +[FunctionalAnalysis schema, ComponentExchange](https://raw.githubusercontent.com/eclipse-capella/capella/master/m2/plugins/org.polarsys.capella.core.data.def/model/FunctionalAnalysis.ecore). +The installed Capella benchmark also observed an OAB communication-means tool; +see release-capella-workflow-benchmark.md. + +## Current facts that constrain the decision + +- `BoundaryExchange` has port source/target, orientation and items. + `RelationKind::permits` accepts Port→Port; `ports.rs` uses each endpoint's + semantic parent as its participant. `projection.rs` treats these endpoint IDs + as ports, and validation rejects same participant-owner connections. +- OA ports admit only `Legacy`, which ordinary creation does not offer. + `app.rs::add_element` redirects all Port creation to SA typed port actions. + Thus new OA entity communication has no ordinary authoring path. +- Core `ExchangeAllocation` already accepts BoundaryExchange→Interaction in OA + (the language.rst table's SA-only statement is inaccurate). Its legacy branch + checks actual work Allocation, checks carried items, and emits a typed-port + resolution warning. That warning is inappropriate as guidance for OA. +- `ports.rs::actual_performer` uses an actual Allocation. Broader + `validation::effective_performer` can resolve role-based responsibility; those + functions must not be casually interchanged in a transport-allocation check. +- ParticipantInteraction scenarios permit Interaction and BoundaryExchange today. + `message_endpoints` mostly returns the stored relation tuple, with the tuple + reversed for a same-exchange Reply. It does not handle ordinary reverse messages + on an unoriented declaration. +- `transitions.rs::mapped_relation` clones BoundaryExchange, while + `sa_port_omissions` omits typed SA ports and dependent boundary relations on the + reverse transition. Merely adding another enum arm cannot provide a valid + bidirectional communication transition. +- `migration_v4::Project4` shares current `Relation` rather than freezing its + constructor set. Adding a live enum variant without old-version admission + checks would accidentally admit the new meaning under schema4. + +## Alternatives compared + +| Criterion | A. New CommunicationMean relation | B. BoundaryExchange with direct OA endpoints | C. First-class OA port contract | +|---|---|---|---| +| Engineering meaning | Constructor explicitly means OA participant communication | Same tag means participant→participant in OA and port→port otherwise; readers must inspect referenced records | Adds named OA endpoint objects beyond the established participant relation need | +| Existing source/API behavior | Additive tagged variant; old boundary shape/meaning stays fixed | Shape unchanged but endpoint semantics broaden; old tools cannot distinguish it from a malformed boundary | New port constructor and new endpoint workflow, plus existing boundary semantics | +| Allocation | Dedicated OA branch reuses ExchangeAllocation IDs and item validation | Allocation must branch on layer and endpoint kind before every owner/Legacy check | Reuses port parents, but needs new ownership/interface/direction rules | +| UI and nesting | Draw entity→entity; no extra boxes or hidden semantic records | Same visual result, but labels/tools/inspectors need contextual relabeling everywhere | Users create ports before communicating, or UI invents extra identities | +| Traceability | Independent means identity owns artifacts; explicit typed boundary counterpart | Existing relation tag is reusable, but actual counterpart conversion still changes endpoint families | OA port→SA port mapping appears tempting and can falsely imply technical design equivalence | +| Migration | Old data preserved; optional reviewed conversion is explicit | Old relation remains valid, but its constructor no longer identifies one endpoint family | Existing Legacy cannot be silently promoted; interface/direction meaning still needs review | +| Maintainability | More exhaustive matches initially; compiler exposes missing integration seams | Fewer enum edits but more hidden assumptions in port/scenario/projection/transition code | Largest permanent semantic surface and authoring burden | + +A wins because it makes a real semantic distinction explicit without inventing +OA port design. B is viable only with a shared tagged endpoint abstraction used +throughout projection, validation, scenarios, transition and API documentation; +that is a larger architectural change than the apparent one-line permits edit. +C becomes attractive only if user studies establish independently named OA +communication endpoints, their distinct contracts and artifact references as a +primary modeling need. Current evidence establishes participants and means. + +## Proposed minimum contract + +The following are **Studio design choices** unless explicitly sourced above. + +1. `CommunicationMean` is a Relation with ordinary immutable identity, alias, + exact name/description and body fields `source: Id`, `target: Id`, + `oriented: bool` and `items: Vec`. Source keyword: `communication_mean`. + Both endpoints must be different OA Entity or Actor elements. No System, + SystemActor, Activity, Role or Port endpoint is admitted. Nesting is not an + endpoint, allocation or deployment relationship. +2. Default `oriented false`, shown as “Bidirectional” in creation. `true` means + ordinary directed use follows source→target. Stored source/target order is + stable even when unoriented; reversing display does not rewrite identities. + Parallel means are valid: “Radio” and “Telephone” can join the same entities. + No physical medium, protocol, capacity or connection kind is inferred. +3. Items are distinct same-layer ExchangeItem references, using the existing + item-nature/mechanism/type contracts. `[]` is an explicit empty carried set, + not a wildcard for allocated interactions. Referenced external interface or + medium specifications can remain ordinary ArtifactLinks/Constraints; do not + add a protocol execution engine or fictitious contained physical equipment. +4. `ExchangeAllocation { source: means_id, target: interaction_id }` is a + separately identified same-layer relation. The target must be Interaction; + a CommunicationMean cannot allocate an SA FE or another communication means. + One means may allocate many Interactions; an Interaction may have several + explicitly modeled means (alternatives/redundancy). Do not collapse those + links or infer routing/execution exclusivity. Duplicate exact endpoint pairs + can be diagnosed as redundant but must not change historical admissibility + of other ExchangeAllocation kinds in this slice. +5. Let `(a,b)` be the actual allocated participants of an Interaction's activity + endpoints and `(s,t)` the means endpoints. For oriented means the only + admissible pair is `(s,t)`; for unoriented means either `(s,t)` or `(t,s)`. + Compare every known actual participant against these whole pair candidates. + If none survives, reject the allocation. If a participant is missing, retain + the allocation with a located incompleteness warning; do not guess through + a role or parent entity. A role-only assignment is intentionally unresolved + for this concrete allocation even if `effective_performer` can compute a + unique candidate. Help should offer adding/reviewing actual Allocation. +6. Every allocated Interaction item must satisfy at least one declared means + item, using the existing inheritance-aware `items_satisfy` direction. A + specialized carried item may satisfy its declared base, not conversely. + An allocation action may offer an explicit preview to add the missing item + declarations atomically; it must not silently widen the means contract. +7. Reconnect/reorient/edit of a means validates all dependent allocations and + scenario occurrences atomically. Invalid raw form edits remain recoverable. + Removing a means removes its incident allocation relations and follows normal + occurrence/reference deletion policy; it never deletes the global Interaction, + activities, participants or their unrelated ports. Removing an allocation + removes only that identified link. Reidentification updates every live + reference while preserving historical review semantics under existing rules. + +### Participant scenarios and direction + +Admit CommunicationMean for ParticipantInteraction and compatible MixedLegacy +scenarios. OperationalActivity scenarios and process contributions continue using +Interaction; SA BoundaryInterface scenarios use SA boundary/functional exchanges. +Messages refer to the existing means, not copied declarations. Each occurrence +retains its own ID, name, annotations and actual sender/receiver lifelines. + +Provide a shared pure endpoint resolver used by core validation, direct-drawing +eligibility, form choices and renderer. For an unoriented means, test both ordered +endpoint pairs against explicit sender/receiver semantic representations. Exactly +one compatible pair resolves direction; missing bindings remain incomplete, +conflicting bindings reject, and two viable role-based pairings require explicit +participant binding rather than arbitrary tie breaking. All consumers must use +the same answer. Ordinary oriented messages allow only the stored ordered pair. + +A correlated Reply follows the existing operation-response convention: when it +uses its Call's means, it reverses that Call's **resolved** endpoints, including +a Call traveling opposite the stored tuple on an unoriented means. It must not +blindly reverse the means tuple. Keep this response exception for oriented means +as well; orientation is not evidence of a physical simplex link. A separate reverse +means can also be selected and must match the correlation. No physical full-duplex +guarantee follows. This choice was criticized independently: a transport-level +Reply prohibition would impose hardware semantics absent from the primary OA +contract and conflict with current FE/Boundary operation conventions. + +### Bidirectional OA↔SA transition and reviewed semantics + +CommunicationMean↔BoundaryExchange is an explicit exchange correspondence, using +the existing `TraceKind::Exchange` broadened to this pair (or an explicitly named +new trace kind if root prefers); ordinary source/target/item/orientation facets +must participate in source-v5-style historical semantic review. A Manual trace +alone is insufficient if the UI claims meaningful counterpart updates. + +OA→SA creation first requires reviewed participant counterparts. The communication +request then has **per-relation endpoint decisions** to reuse or explicitly create +compatible SA component ports on those selected owners. Port kinds, orientation, +items/interfaces and PortAllocations must be chosen/validated, never inferred from +“Radio” or from an OA entity. Keep entity→participant correspondence separate from +means-endpoint→port decisions; putting entity→port in the global reference map can +corrupt other participant mappings. Proposed defaults are inspectable drafts. + +SA→OA maps the owners of selected boundary ports to OA participants and creates +or reuses the CommunicationMean with mapped items/orientation. Typed SA ports and +PortAllocations have no fabricated OA port counterparts. The reverse omission +closure must explicitly recognize this valid projection before removing the +boundary relation and its dependent ExchangeAllocations. If both owners map to +one OA participant, or a System-boundary decision remains unresolved, report a +conflict rather than making a self means or dropping the relation silently. + +Endpoint review must show “OA participant” versus “SA port — participant owner”. +Keep/Candidate can retain or choose valid technical endpoints. Take of an OA +endpoint needs the explicit per-relation endpoint decision; it cannot paste the +mapped participant ID into the SA port field. Existing Source/Target baseline +facts remain exact identities, not silently normalized owner facts. Reusing the +current scalar facets is possible, but their transfer adapter must be explicit. +Orientation facet applicability and ExchangeItems membership must cover the new +constructor, including unknown/deleted historical references and exact resets. + +## Concrete examples + +Illustrative new-version source fragment, assuming OA declarations `rescue_boat`, +`control_centre`, `distress_report` and Interaction `report_distress` already exist: + +```text +communication_mean rescue_radio "Rescue radio" id "00000000-0000-4000-8000-000000000601" { + source @rescue_boat; + target @control_centre; + oriented false; + items [@distress_report]; +} +exchange_allocation radio_carries_report "Radio carries the distress report" id "00000000-0000-4000-8000-000000000602" { + source @rescue_radio; + target @report_distress; +} +``` + +Valid: report activity allocated to rescue_boat, receive activity allocated to +control_centre; both participants visible in one view. A second return Interaction +can share the means when its carried items are declared. Nested entities remain +their real semantic parents; a means to a nested rescue team attaches to that team, +even if folding later promotes its visual endpoint to the outer entity. + +Invalid: replacing control_centre with an SA System; allocating an Interaction +whose known receiver performer is a third entity; allocating a nonempty item set +to an empty means contract; reversing an ordinary use after setting oriented true. +Incomplete but saveable: an allocated Interaction whose receiving activity has no +actual performer yet. A missing referenced entity is a structural error, not that +kind of incompleteness. + +## Compatibility and implementation seams + +Recommend one new explicit schema/source version for the upcoming persistent +OA/view additions (currently next would be 6), rather than accepting new contracts +under an old frozen grammar. New decoder reads existing v1–v5 with reports and no +disk writes; new exports advertise the new version. The frozen v5 reader can reuse +unchanged values only after an explicit old-constructor/field gate. Also fix the +shared live Relation admission in v4 and verify v3's closed constructor check. +If root elects an additive current-v5 evolution, it must explicitly version the +normative contract/API capability and document that old clients reject new +models; it cannot claim unchanged v5 semantics. A deliberate version bump is +cleaner and matches previous repository migrations. + +Do not automatically convert old OA Legacy boundaries: port aliases, contracts, +interfaces, artifacts and historical references contain user data. An optional +reviewed “Convert to communication means” operation can retarget a boundary to +its OA participant owners while preserving the relation's UUID, alias, name, +items, orientation, routes, artifacts and incident allocations. Retain the ports +as independent legacy records; show obsolete references and invalid dependent +scenario/trace effects in the preview. No port deletion or equivalence assertion +is implicit. OA legacy warnings must explain preservation/conversion rather than +demanding impossible typed SA contracts. + +| Integration seam | Required handling | +|---|---| +| model.rs, validation.rs and ports.rs | New kind/body/constructor helpers and OA endpoint validation; shared item logic, separate participant-allocation branch; no widening SA port contracts | +| references.rs, commands/delete/reidentify, artifacts | Live endpoints/items/dependent allocations, located references, exact identity/history behavior; failed batch remains unchanged | +| projection.rs, canvas hit/reconnect/context tools | Participant anchors, orientation, full identity/selectability; Allocation and suitable overview/capability views; FunctionalDataflow remains work-level; folding is presentation only | +| workflow.rs, forms.rs, source/API command forms | Draw/configure/edit/allocate means and items, valid owner choices, explicit reject messages; no raw JSON-only feature | +| validation.rs::message_endpoints, scenario.rs and direct scenario selection | Shared orientation resolver, actual-call reply reversal, same declaration used in participant scenario and architecture | +| transitions.rs and semantic catalog/mutate/review | Explicit new mapping, reverse omission exemption, owner/port endpoint adapter, complete orientation/item/source/target drift and review | +| language parser/writer/updater, persistence adapters, OpenAPI/API docs | Closed constructor, new version and accurate examples; current and old-version rejection; source trivia preservation | +| guide.rs, scope.md, engineering example | OA activity Interaction versus participant CommunicationMean, allocation, bidirectional use, no fake ports or deployment | + +## Acceptance and falsification + +Use one three-participant fixture with nested entities, two different means between +the same pair, direct work allocations, role-only unfinished work, forward/reverse +Interactions and inherited data items. Exercise the following through GUI, source +and REST, then native Windows/Linux default and minimum size: + +1. Create/edit/allocate/reconnect means with explicit invalid raw draft retention; + reverse allocation only for unoriented; partial known wrong performer rejects; + role-only warns; the correct repair clears only its warning. +2. Two views/folding retain relation and allocation identities; line selection, + nested member navigation, item/member editing and artifact attachment work. +3. A reverse Call on an unoriented means and correlated Reply show opposite actual + lifeline directions; repeated/reused occurrence context remains independent; + ambiguous role bindings reject rather than silently reverse. +4. Source+JSON roundtrip, commented multi-file graphical update, Undo/Redo, Save + All/reopen, raw invalid recovery, REST dry-run/reject/ETag/restart; edits to the + means never duplicate or delete the underlying Interaction definitions. +5. OA→SA choices generate/reuse valid explicit typed endpoints; SA→OA retains the + communication correspondence without OA ports. Owner collapse and unavailable + mappings reject. Endpoint drift requires a genuine reviewed choice. +6. Old v1–v5 inputs remain exact under migration. New constructor under an old + version rejects. A reviewed legacy conversion preserves references/artifacts + and never executes from merely opening a file. + +Revisit the design if representative engineering work needs independently named +OA endpoints with distinct contracts that this relation cannot express, if a +Capella/native comparison demonstrates that direct participants lose essential +role semantics, or if scoped endpoint decisions cannot represent a realistic +one-to-many system realization without corrupting correspondence. These would +justify a new endpoint or realization abstraction. They do not justify making +current port-based BoundaryExchange silently accept several incompatible meanings. diff --git a/harness/release-recovery-design.md b/harness/release-recovery-design.md new file mode 100644 index 00000000..e5708bc4 --- /dev/null +++ b/harness/release-recovery-design.md @@ -0,0 +1,57 @@ +# Recoverable local checkpoints — bounded design, 2026-09-06 + +Read-only investigation of current data-contract-integration baseline c091f80. Root owns implementation. No native recovery acceptance is claimed. This supersedes the initial behavior review's obsolete static-canvas baseline: current state, scenario and process direct editing is already integrated; process/scenario branches need no cherry-pick, and state branch differences are patch-equivalent (316dc55→9118218 and a8f451e→c5276b1). + +## Recommended contract + +Checkpoint the accepted source bundle and the unapplied source workspace separately in user-local application state. A checkpoint is a recoverable local copy, never a model save and never authorization to overwrite a prior local/hosted destination. Startup lists recoverable sessions; Restore is explicit and creates a detached unsaved document. Original path is display-only provenance. Save after restore uses Save As. A failed restore retains both the current application and the original recovery file. + +Candidates considered: (1) periodically save the actual model files; rejected because invalid drafts cannot compile, external edits can be overwritten and this conflates recovery with Save. (2) persist the entire App; rejected because it includes tokens, stale async context, caches and modal state. (3) serialize a small explicit recovery DTO with source/workspace payload; selected because the canonical compiler can reconstitute the model while preserving exact comments/includes and invalid raw text. + +## Checkpoint content + +- Closed `format_version`, random session/checkpoint ID, monotonically increasing generation, timestamp, product/source schema metadata, human title and optional original local path label. +- Exact `Document::bundle()` accepted entry and file map. Do not format Project back to text: that loses lexical/source ownership history. No serialized compiled index is trusted on restore. +- Workspace `entry`, `active`, `applied`, `reachable`, and every loaded file buffer's **text and baseline**. This includes dirty detached files. `Workspace::bundle()` alone is insufficient: it loses the baseline used by dirty/Revert and cannot distinguish a new invalid draft from a clean loaded file. Optional cursors may be encoded as line/column integers and clamped on restore. Undo/redo stacks need not be persisted for this bounded slice, but say so in the recovery UI/help. +- Unsaved state must survive: `Document::from_bundle` calls the restored bundle saved. Use an explicit recovered-unsaved flag at App level (cleared only by successful local source save), or a dedicated Document restore constructor that also restores its saved source baseline. Prefer the flag if it avoids duplicating a second potentially large accepted bundle. Merely setting path=None does not make `App::dirty()` true. +- Do not include hosted State/session/tokens/URLs with credentials, reqwest clients, network receipts, DiskProject write expectations, pending save-journal rollback handles, compiled caches, queued commands, interaction gestures or model-menu state. Never serialize App wholesale. + +**Important invalid-open case:** `open_source_project` keeps the previously accepted graphical Document when an invalid new file opens, and replaces Workspace with `applied=false`. The bundles may describe different projects. Preserve both independently; do not attempt to compile draft text to replace the accepted document during checkpoint capture or restoration. State clearly that graphics show the last accepted model while the recovered source needs repair. + +Property/behavior/semantic-review drafts are separate mutable state. Accepted+per-file source coverage must not be described as recovery of every unsaved edit. For full release recovery, add explicit versioned DTOs for these drafts, preserving raw invalid fields and avoiding command replay. Until then, indicate their exclusion in the checkpoint UI and keep their existing navigation/close guards. A source-only checkpoint made while such a form is pending must not call Apply automatically. + +## Capture and write integration + +`App::subscription` can emit a short periodic RecoveryTick. Handle recovery ticks/completions before ordinary busy/modal gates in `App::update`, like the existing passive scroll observation: saving or a modal must not lose edits already in memory. A dirty generation is driven by accepted document revision and source workspace edits, not just document revision (unapplied text does not change the latter). Capture an immutable DTO on the UI thread, then bounded serialization/filesystem work on `spawn_blocking`; no sleep/blocking fs on UI thread. + +If using a wrapper after dispatch, `App::update` has many early returns; wrapping only the tail misses SourceEdit, local/hosted loads, source include changes and prepared semantic Apply paths. A periodic snapshot comparison plus a coalesced single writer is simpler to reason about. Avoid cloning all 64MiB bundles for every mouse-move event. One in-flight write per session; if newer edits arrive, schedule the newest snapshot after completion. Completion includes session ID/generation and cannot clear/acknowledge another document's checkpoint. + +Persist before cleanly leaving a recoverable session, or retain the already durable checkpoint. `reset` rebuilds the whole App; preserve the recovery coordinator and start a fresh session identity. An explicit Discard should retire only that session's checkpoint, but coordinate with in-flight writes so an old completion cannot resurrect it. A cancelled Open or Save must not retire recovery. A successful JSON export must not clear recovery: current Save deliberately leaves source dirty. A successful source save retires only the captured generation if no newer edits/drafts remain. Hosted Save likewise cannot clear a checkpoint that contains unapplied local drafts. + +Windows directory: LocalAppData/ArcadiaStudio/recovery. Linux: XDG_STATE_HOME/arcadia-studio/recovery, falling back to HOME/.local/state/arcadia-studio/recovery. Resolve a stable absolute app-owned root; no fallback to repository or model directory. Use per-session immutable generation files or two generation slots plus a safe manifest. Write a unique temp file, flush/sync, then publish only the complete file. Keep the last valid generation until the new generation is durable. Handle Windows rename-over-existing behavior explicitly; do not delete the only durable snapshot before replacement. Startup ignores temp files and falls back to a previous valid generation after a truncated newest file. + +The payload paths are virtual SourceBundle paths, not filesystem extraction targets. Apply existing relative/source path validation; cap checkpoint file bytes before parsing, number of files, per-file and total text+baseline bytes, and nesting before allocation. Normal draft bytes may be invalid Arcadia but still must obey resource limits. Reject absolute/traversing virtual paths, missing entry/active buffers and invalid reachable keys without deleting evidence. Unknown checkpoint versions stay listed with a clear incompatible-version message. Storage failure is nonmodal, reports that recent work is not protected, and retries without dropping model/drafts; never claim saved success from a queued write. + +## Restore integration order + +1. Read/validate DTO off thread and compile the **accepted** bundle through `Document::from_bundle(..., LIMITS)`; reject altered/incompatible accepted source without changing App. Require exact accepted source equality after compilation, or expose an explicit migration step; UUID generation during silent restore is unacceptable. +2. Construct Workspace from persisted text+baseline through a narrow restore API in `source_workspace.rs`. Preserve applied=false, detached buffers and dirty flags; never `Workspace::new(draft_bundle,true)`. +3. On explicit user Restore, pass existing dirty-navigation handling. Build replacement state only after preparation succeeds. Preserve current window size, guide/Home state and recovery coordinator as `reset` already does for selected persistent UI state. +4. Install prepared Document, then recovered Workspace; clear local path, DiskProject, pending save recovery and hosted document binding; assign new canvas_document/epoch and source_version. Reset gestures/forms/caches and selection to valid identities. Do not call `sync_source()` after installing drafts, because `Workspace::accept` replaces reachable dirty buffers. +5. Call `show_source_file(true)`, refresh accepted model diagnostics/index separately, and request draft lint without Apply. Select Text when unapplied/dirty draft files exist; retain explicit recovery status and unsaved marker. Keep original checkpoint until recovered work is saved or explicitly discarded. + +## Falsification tests + +1. Accepted nested source with comments plus two invalid unsaved file edits: checkpoint/crash/restore yields identical accepted Project and accepted bytes, exact raw drafts and correct dirty/Revert baselines. No file in the original model directory changes. +2. Dirty detached file after include removal: restore keeps it discoverable and dirty; applying reachable text retains detached draft and save guard. +3. Invalid Open from hosted A into broken local B: recovered accepted graphics remain A, draft workspace is B with applied=false, path/session/token/ETag absent; repair and Save cannot overwrite hosted A or local B without explicit destination selection. +4. Rapid edits during asynchronous write: old completion never marks newer generation protected; restart recovers latest completed generation. Change project while a write is in flight; no session collision. Discard while writing cannot recreate a discarded snapshot. +5. Restore invalid text in active included file with stale cursor: buffer text is exact, cursor clamps, lint reports the right path, Apply rejection preserves draft and accepted model. +6. Save cancelled/failed, JSON export, stale local-save receipt and stale hosted response all retain needed recovery. Successful source save with no later edits retires only its session. New edits after saved snapshot remain recoverable. +7. Truncated JSON, unsupported version, oversized bytes/map, invalid path, missing entry, invalid accepted source and read-only recovery directory each fail visibly without changing current document or deleting the last valid checkpoint. Two app instances use independent session files. +8. Explicit Restore while another model is dirty goes through Save/Discard/Cancel; Cancel leaves both models/checkpoints untouched. Source restore does not inadvertently apply property/behavior drafts. +9. Real Windows/Linux native trial: type invalid included source, wait for confirmed checkpoint, terminate process abruptly, relaunch, inspect recovery choice, Restore, fix, Save As, reopen and compare exact model/source. Repeat at 1100×700 and inspect failure/retry UI. Unit success is not a substitute for this crash/reopen workflow. + +## Relevant current seams + +`document.rs::{bundle,snapshot,dirty,mark_saved,from_bundle}`; `source_workspace.rs::{Buffer,Workspace,accept,discovered,dirty,detached_dirty}`; `app.rs::{update,subscription,reset,sync_source,show_source_file,open_source_project,save_to,proceed}` and Message::{SourceEdit,SourceUndo,IncludesLoaded,Loaded,Saved,Discard}. Existing pending_recovery/save-journal rollback concerns interrupted explicit model writes; autosave recovery must remain a separate mechanism and separate UI wording. diff --git a/harness/release-rest-coverage-probe-plan.md b/harness/release-rest-coverage-probe-plan.md new file mode 100644 index 00000000..e7deb14f --- /dev/null +++ b/harness/release-rest-coverage-probe-plan.md @@ -0,0 +1,85 @@ +# Real REST coverage parity probe + +Executed successfully against the rebuilt Windows server; see +`release-rest-coverage-verification-20260906.md` for the actual 25-request evidence. +The procedure below remains the reproducible plan, rather than a claim about other +platforms or future source6 features. + +The isolated probe executes this against the rebuilt current server, using only synthetic data. +The tracked payload is `harness/fixtures/rest-coverage/coverage-probe.json`: three complete +source bundles (`incomplete`, `repaired`, `invalid`) and an `expected` array of five +exact code/object/field/file tuples. The reserved fixture alias `parent` was corrected +to `activity_group` before the successful complete HTTP run. Its semantics +mirror the core coverage fixtures that passed in the full 367-test core run. + +Use a fresh store under `.local/rest-coverage-store`, a verified free loopback +port such as 18787, and a dedicated synthetic token file if authentication is +enabled. Launch only this project's `arcadia-server.exe` with explicit `--bind`, +`--data-dir`, and `--token-file`; start hidden on Windows. Do not use a user's +server/store. Retain request/response JSON and ETags under `.local/rest-coverage`. +All JSON calls use `Content-Type: application/json` and the known test token. + +## Exact expected incomplete result + +All five are severity `warning`; all locate `work.arcadia` on source-project +validation. The UUID prefix is `f0660906-0000-4000-8000-`. + +| Code | UUID suffix | Field | +|---|---|---| +| non_leaf_work_exchange | 000000000051 | source | +| work_without_behavior_context | 000000000013 | null | +| exchange_without_behavior_context | 000000000052 | null | +| mission_without_capability | 000000000021 | null | +| capability_without_behavior_context | 000000000020 | null | + +The parent exchange's uncovered-exchange warning is intentionally suppressed. +A/B are covered through their included exchange; C is merely allocated. No +complete-model rejection is expected. Compare **code/object_id/field** across +representations, plus path and valid source span where supplied. Compare codes, +not human message wording or array order. + +## Probe sequence and oracles + +1. GET `/health/ready`; POST `/api/v1/projects` with + `{"name":"Coverage probe host"}`. Retain its Location, resource ID and opaque + ETag. GET its model/source-project and retain exact responses and revision. +2. POST `/api/v1/projects/{id}/validation` with + `{"format":"source-project","source_project":payload.incomplete}`. + Expect 200, `valid:true`, exactly the five new warning tuples above, real + `work.arcadia` locations, no `error`. GET the accepted resource and verify + model/source/ETag/revision unchanged: candidate validation does not commit. +3. PUT `/api/v1/projects/{id}/source-project` with raw `payload.incomplete` and + `If-Match` equal to the current ETag. Expect 200, changed revision/new ETag, + with warning-only admission. GET `/validation`, `/model`, `/source`, and + `/source-project`. The accepted source bundle must exactly equal the submitted + three files (all UUIDs are explicit). Normalize the five code/ID/field tuples + from GET validation and compare to step 2. +4. POST candidate validation twice more: `{"format":"json","model":model}` + and `{"format":"arcadia","source":canonical_source}` using those exact GET + representations. Both return 200/valid:true and the identical five tuples. + Canonical source/JSON need not preserve source-project file paths. Verify + accepted ETag/revision unchanged after both calls. +5. POST source-project candidate validation using `payload.invalid`. It changes + only the grouped Interaction's source to existing Entity `crew`, an invalid + work-exchange endpoint. Expect 200/valid:false, an `invalid_endpoints` Error + on that relation, and **none of the five new completeness codes**. PUT the + same invalid source-project with current `If-Match`: expect 422. GET current + model/source-project/ETag/revision and compare byte/content identity to step 3. +6. PUT `payload.repaired` with current `If-Match`. Expect acceptance and all five + new codes cleared. The repair names leaf A as the parent exchange's source, + includes both missing exchanges, connects mission→capability and adds explicit + process→capability context. Other existing topology warnings, such as the + deliberately simple sequence's exchange gap, may remain; do not misreport the + five-code assertion as zero total diagnostics. +7. Stop/restart only the probe server with the same isolated store, then GET + model/source-project/validation: persisted repaired data and ETag/revision + must match. Optionally PUT the original incomplete bundle under current + If-Match and verify the same five tuples reappear. This is an explicit + replacement, **not server Undo**; core transaction Undo already has its own test. +8. Stop the verified probe process. Preserve the synthetic store and evidence; + cleanup is optional and must remain scoped to these named probe paths. + +The server's actual DTOs are in `oa-server/src/routes.rs`: source-project +validation returns submitted-byte locations; successful bundle writes return +accepted-bundle locations; current validation uses core object/field diagnostics. +No new API route, schema, command type, or service undo is required. diff --git a/harness/release-rest-coverage-verification-20260906.md b/harness/release-rest-coverage-verification-20260906.md new file mode 100644 index 00000000..bb71aad2 --- /dev/null +++ b/harness/release-rest-coverage-verification-20260906.md @@ -0,0 +1,37 @@ +# Real REST coverage parity acceptance + +Executed 2026-09-06 against staged `arcadia-server.exe` SHA256 +`BB775876741FE4A7B07CDE08940AEC8E23F545FDAFEC024FC4DC3BA6DD1AB9FC`, with +only Windows system directories on PATH and the staged `libunwind.dll` beside the +binary. A fresh synthetic store and an ephemeral loopback port were used; no user +server/model was touched. The probe started and stopped only its own hidden server. + +All **25 HTTP requests passed**. Evidence is retained under +`.local/rest-coverage-20260906-160857`: request payloads, response bodies, status/ +ETag metadata, binary provenance, server logs and `result.json`. The prepared runner +is now tracked at `harness/fixtures/rest-coverage/run-coverage-probe.ps1`; the +three-bundle payload is adjacent as `coverage-probe.json`. The tracked copy was +rerun successfully with another fresh store and the same clean PATH: all25 requests +passed again under `.local/rest-coverage-20260906-161345`. See the adjacent README +for reproducible invocation. + +| Observable behavior | Observed result | +|---|---| +| Incomplete bundle candidate | HTTP200, valid:true; exact five advisory code/object/field tuples from the plan, all warnings with submitted `work.arcadia` locations. Accepted model/source/ETag unchanged. | +| Warning-only bundle commit | HTTP200, changed:true, revision2. Exact three submitted files retained, including comments and explicit IDs. | +| Current validation, native JSON and canonical Arcadia candidate parity | Same five code/object/field tuples, no structural errors. | +| Invalid Entity endpoint on Interaction | Candidate200/valid:false with `invalid_endpoints`; all five new completeness codes gated. PUT422; accepted model, source bundle and ETag unchanged. | +| Repair | HTTP200, revision3; all five completeness codes clear. The independent existing `sequence_gap` warning remains on Process steps2–3, as expected. | +| Forced server stop and restart, same isolated store | Repaired model/source/validation and opaque ETag identical. | +| Explicit replacement with incomplete bundle | HTTP200, revision4; exact five warnings reappear. This operation is replacement, not server Undo. | + +The first attempt correctly rejected the synthetic alias `parent` as reserved. +The fixture now uses `activity_group`, retaining all UUIDs and expected semantics; +the complete probe was rerun from a fresh store. This was fixture repair, not a +production parser change. The failed attempt remains in +`.local/rest-coverage-20260906-160838` for provenance. + +This verifies the current Windows service and diagnostics parity, admission and +restart behavior for the representative OA fixture. It does not establish Linux +runtime, container deployment, production-scale throughput or category/source6 +features. No probe process is intentionally left running. diff --git a/harness/release-save-as-runtime-packaging.md b/harness/release-save-as-runtime-packaging.md new file mode 100644 index 00000000..d49c4eab --- /dev/null +++ b/harness/release-save-as-runtime-packaging.md @@ -0,0 +1,38 @@ +# Save-as history and Windows runtime packaging — 2026-09-06 + +## Save-as decision and evidence + +Native investigation reproduced an extra Undo step after saving to a different entry basename: the first Undo reversed only the file-key rename, while the second reversed the semantic edit. Saving the undone state also cleared Redo. `Message::Saved` had committed the rebased receipt through `Document::apply_bundle`. + +The fix adopts a verified save receipt as a source-location change, without adding history or clearing Redo. Uniform entry names across retained history are rebased together, preserving exact source text, model meaning, and stack lengths. If history contains explicitly authored entry-name changes, historical snapshots retain those names; adoption changes only the current snapshot. Revision and receipt-content checks reject stale or altered receipts, and raw drafts also prevent a late receipt from changing their source location. + +Before disk I/O, source Save-as rejects a chosen basename that collides (case-insensitively) with a non-entry file in any retained current/past/future snapshot. This conservative restriction avoids swapping historical files or rewriting includes. JSON export is exempt. All planned snapshots must compile to the same models and exact relocated source before adoption; failure leaves history unchanged. The bounded history may be compiled during both preflight and receipt adoption. Revisit that cost only with measured latency evidence. + +The focused `cargo test --locked -p oa-desktop save_as` run passed **11 tests, 0 failures** (`.local/save-as-focused-test.log`). Seven new regressions cover uniform history and existing Redo, explicit entry-name history, past/future filename collisions, stale/changed receipts, actual disk write receipts followed by Undo/save/Redo, rejection before disk I/O, and preservation of a newer raw draft. Implementation: `document.rs`, `document_save_as_tests.rs`, `app.rs`, and `app/save_as_tests.rs` under `crates/oa-desktop/src`. + +**Fixed-binary native Save-as acceptance remains pending.** Recheck with a recorded binary hash: apply a semantic change; Save-as to a new basename; one model Undo must restore the original semantics and exact original source text; save that undone state; Redo must remain available and restore the edited text. Reopen the saved file to confirm disk still contains the explicitly saved undone state until another Save. Repeat Save-as with available Redo and verify a historical filename collision is refused before creating the destination. Record source hashes before edit, after edit, and after Undo. A separate successful state-view routing check does not establish this result. + +## Observed GNU/LLVM runtime closure + +Read-only PE import inspection used LLVM-MinGW 20260826 UCRT `llvm-readobj --coff-imports`; no builds, application launches, system changes, or runtime copying were performed for this inspection. These are the inspected **debug** outputs, not release packages: + +| File under `.local/target-gnullvm/debug` | Bytes | SHA-256 | +|---|---:|---| +| `arcadia-studio.exe` | 101734912 | `70069d6cbbe1917e1597794b3e190ff9cf23664d458f794b3270ddbe6ee683a2` | +| `arcadia-server.exe` | 37165056 | `fc942ecf222a2032eb5e28c68d1125ef09fd15cc81864dd77eeb119af8fdae48` | + +Both import **`libunwind.dll`** directly. It is the only non-Windows dependency in their observed static import closure. The x86_64 runtime is at `.local/toolchain/llvm-mingw-20260826-ucrt-x86_64/x86_64-w64-mingw32/bin/libunwind.dll`: **190976 bytes**, SHA-256 **`f45604a12e8bcd839fd86f667c1be1cc7c4f9da2b3c69ff381ac708f0e1ae7d4`**. The toolchain's top-level `bin/libunwind.dll` is byte-identical. + +That DLL recursively imports only KERNEL32 and UCRT API sets (stdio, runtime, environment, private, filesystem, locale, convert, string, heap). Neither binary nor libunwind imports libc++, libwinpthread, libomp, LLVM, Clang, or sanitizer DLLs. The remaining executable imports are Windows API sets and OS DLLs: kernel32, user32, ole32/oleaut32, bcrypt/bcryptprimitives, advapi32, dwmapi, gdi32, shell32, imm32, uxtheme, ws2_32, ntdll, and opengl32 (case-insensitive union). + +The root's observed failure without toolchain PATH and successful launch with it is consistent with the missing libunwind import. Staging the required runtime next to both executables uses the ordinary application-directory DLL lookup; no registry or machine PATH change is needed. See Microsoft's [DLL search order](https://learn.microsoft.com/en-us/windows/win32/dlls/dynamic-link-library-search-order). Include the LLVM-MinGW distribution's `LICENSE.TXT` (LLVM Apache-2.0 with LLVM Exceptions and included notices) alongside runtime provenance. Do not copy OS DLLs from System32 or the whole compiler directory. + +Static imports are not the complete graphics-runtime test: the resolved WGPU/ash implementation can load backend DLLs dynamically (for example `vulkan-1.dll`, and optional DXC paths). Import inspection cannot establish driver compatibility. Repeat inspection against the actual final binaries and perform the clean-environment staged smoke in [Windows packaging](../docs/windows-packaging.md). + +## Packaging baseline and remaining gates + +At inspection, no tracked packaging builder existed. `harness/verification-v2-packages.md` describes historical staging/archive checks, but its D: evidence and dist outputs are absent here. `.github/workflows/ci.yml` builds a Windows/Linux matrix and uploads executable-only artifacts; it is not a GNU/LLVM dependency staging recipe. The repository origin is GitLab and no GitLab pipeline is present. CI is unchanged. The Docker build-context regression was separately corrected with `COPY build ./build`; no Docker execution is claimed. + +The new `scripts/package-windows.ps1` provides a repeatable packaging operation for explicitly selected, already-built x86_64 MSVC or gnullvm inputs. It must not imply a release build or acceptance simply because packaging succeeds. Current native Save-as recheck, clean-PATH staged desktop/server operation, final release-build import inspection, and release acceptance remain separate gates. + +Script verification on this machine packaged the existing debug gnullvm inputs into ignored `.local/package-script-qa/ArcadiaStudio-qa-debug-20260906-a-x86_64-pc-windows-gnullvm.zip`: **42 files**, **27747149 bytes**, SHA-256 **`8935db6c0fb1e02366b2354139bc1bb5ac111ec438f319fad32121e73cf19559`**. Staged-source and archive-entry hash/count verification passed. Separate refusal checks passed for an existing output name, missing executable inputs, and selecting MSVC while supplying binaries that import the unstaged LLVM runtime. No application was launched by this verification, and no actual MSVC binary package was available to test. The script's final additional check binds staging to the binary hash inspected before import scanning, rejecting binaries changed during packaging. diff --git a/harness/release-selective-views-critique-20260906.md b/harness/release-selective-views-critique-20260906.md new file mode 100644 index 00000000..bcf73231 --- /dev/null +++ b/harness/release-selective-views-critique-20260906.md @@ -0,0 +1,83 @@ +# Selective views candidate: independent bounded critique + +Read-only review of `release-selective-views-design.md`, current core projection, +port layout, coverage traversal and schema admission. No implementation or native +acceptance is claimed. The closed persisted-options design is viable; freeze the +following meanings before schema6 implementation. + +Primary evidence supports distinct work/participant category membership, not a +particular filtering UI: Capella's [FunctionalAnalysis schema](https://raw.githubusercontent.com/eclipse-capella/capella/master/m2/plugins/org.polarsys.capella.core.data.def/model/FunctionalAnalysis.ecore) +has `ExchangeCategory.exchanges: FunctionalExchange` and +`ComponentExchangeCategory.exchanges: ComponentExchange` (lines461–493,2879–2910). +The [official Arcadia Q&A](https://mbse-capella.org/arcadia-qna.html), questions15–16, +describes presentation synthesis, simple/multiple categories and context-specific +dataflow usage. It explicitly distinguishes existing simple categories from +hierarchical categories. A single typed Studio record with a family discriminator +is compatible with those needs; hierarchy is not required merely to copy Capella. + +## Findings and decisions needed + +| Priority | Concrete gap / falsifier | Recommended bounded contract | +|---|---|---| +| P1 | One `categories: Any(ids)` plus “when the constraint applies to that family” is ambiguous. Select only a Work category in a mixed architecture: Participant edges might either disappear or remain. `Any([])` has no family from which to infer applicability. | Persist independent Work and Participant selectors, each `All` or `Any(ids)`. Require selected category IDs to match that family/layer. `All` includes uncategorized exchanges; `Any` admits only membership in at least one selected category, so `Any([])` admits none of that family. Other family and non-exchange edges remain independent. Show family-specific counts/labels. | +| P1 | Global `InBehavior` does not answer “show exchanges used by Process P”, even with correct diagnostic reachability. Two processes with disjoint exchanges both pass the global filter. | Expose global “Used in any behavior” separately from an explicit selected Process/Chain/Scenario scope. Refactor the shared traversal to accept root IDs; union selected roots and their valid included reuse. Define selected scope with no IDs as empty evidence, and define its complement only over same-layer Work exchanges. Do not infer context from performer, state actions or transport allocation. | +| P1 | Filtering before the existing port-layout call can move ports and alter edge attachment geometry even without a layout command. `projection.rs:438` passes rendered edges to `port_layout::layout`; `port_layout.rs:163–230` derives automatic facing/sides from those peers. Hide one of two opposite peers to reproduce. | Compute baseline eligible edges and automatic port geometry first; apply visibility filters to the drawable edge set afterward, or explicitly preserve equivalent baseline peer input. Retain manual anchors/routes verbatim. Test automatic and manual ports while hide/category/coverage options toggle; no unintended owner resize or anchor flip. | +| P1 gate | Forty same-direction exchanges between one promoted pair in one selected category still produce forty lines. A sparse three-exchange fixture cannot establish the required readability benefit. | Run a dense40 same-pair task before declaring category complexity management complete. If users cannot count/find/select a requested member reliably at normal zoom, add summary presentation for compatible endpoint pair/orientation/family, with actual member IDs and count. Membership overlap must partition deterministically without duplicates. Keep summaries non-semantic and support drilling into actual edges. | +| P2 | “No relationships” is inaccurate if the filter only affects arrows. Allocation nesting, interface-item rows and internal state/action rows still express relationships; current `projection.rs:337–349` excludes several of these from edges entirely. | Label the control “Relationship lines” / “No relationship lines”; scope options to actual graph edges. Do not erase allocation nesting or typed state rows. Either restrict the first controls to architecture diagrams or explicitly explain ignored options for dedicated state views. | + +## Lifecycle and schema constraints + +Accept legacy OA BoundaryExchange as Participant-category members when those +records are already valid in the current compatibility model. This avoids making +legacy user diagrams unfilterable and does not reinterpret them as CommunicationMean. +New OA authoring should prefer the separately agreed CommunicationMean contract. +Wrong-family/wrong-layer members reject atomically; no silent conversion of ports, +items, allocation, direction or endpoint ownership. A family change with incompatible +members must be an explicit combined edit or rejected, never clear members silently. + +The source6 boundary needs a frozen v5 diagram/body vocabulary and an audit of +all older envelopes. In particular, `migration_v4.rs:50–67` currently embeds live +Element/Relation/Diagram types because those were unchanged in schema5. Adding +current category bodies/options without closing that seam would let schema4 files +admit future constructs. Probe v4 and v5 JSON and text containing each new field/ +variant: rejection must precede mutation; legitimate old files must preserve default +projection, placement IDs and routes exactly after migration. Preserve unknown-value +rejection, source lexical edits, source-bundle locations and REST restart round trips. + +Deleting a selected category must retain `Any([])` for its own family and preserve +the other selector. Deleting a member exchange must prune memberships, hidden IDs +and only its routes, with exact Undo restoring all three. Reidentification must +visit category members, both selectors, context roots, hidden edge/trace IDs and +route references. A remove-and-recreate exchange has a new identity and must not +inherit a dead hide reference. Empty categories and same-name categories remain +distinct real records; inspectors/pickers should show family and identity context. + +## Minimum acceptance additions + +1. Mixed Work/Participant/uncategorized/non-exchange fixture: verify every family + selector's All/Any(empty)/Any(overlap) truth table under kind/hide intersections. +2. Two disjoint processes plus shared reused scenario: global and selected-context + filters differ exactly; unlisted owned occurrences do not count. A behavior edit + changes visible edges without editing saved options/history. Deleting the last + chosen context remains an explicit empty selection, not global scope. +3. Forty parallel same-pair members, overlapping categories, one hidden member and + folded endpoints: perform readable count/find/select/export; test summary member + partition if required. Restore a filtered member without widening other filters. +4. Toggle options on routed typed SA exchanges with opposite peers: compare owner + geometry, automatic/manual anchors, saved waypoints and independent second view. +5. Duplicate view produces new diagram/placement IDs while retaining semantic IDs, + options, routes and no accidental model transition. Native/SVG/REST projection + and label text must agree, including empty Items labels. +6. Source/API rejection, v4/v5 admission, deletion/Undo/Redo/ID swap and restart + preserve exact semantics and source provenance. Pending raw drafts keep current + guards; a filter panel must not bypass them. + +The shared traversal is currently private `validation/coverage.rs::included_evidence` +and starts from every behavior container. Reuse a pure explicit-root evidence helper +for both diagnostics and projection, rather than duplicate its semantics. Avoid +using missing warning codes as a coverage oracle: structural-error gating suppresses +diagnostics, and non-leaf exchange warnings intentionally suppress uncovered ones. + +The dense readability gate and context-specific workflow are required evidence; +category hierarchy, arbitrary queries and transport coverage inference are not +justified additions for this bounded first contract. diff --git a/harness/release-selective-views-design.md b/harness/release-selective-views-design.md new file mode 100644 index 00000000..1e5ee5cb --- /dev/null +++ b/harness/release-selective-views-design.md @@ -0,0 +1,180 @@ +# Selective views and exchange categories — candidate contract + +2026-09-06, root design investigation while current native acceptance is rebuilding. +This is a proposal awaiting independent criticism and implementation, not an +implemented schema or release claim. Keep the existing source-v5 contract unchanged +until the combined source/schema6 admission boundary is deliberately frozen. + +## Engineering need and primary evidence + +Engineers need several readable views of one architecture, selected by an explicit +engineering concern, without changing its exchanges or losing their authored layout. +The official [Arcadia Q&A](https://mbse-capella.org/arcadia-qna.html) describes +representation-only synthesis when leaf functions are hidden and grouping exchanges +by category. It distinguishes the possible dataflow from the particular uses of +that dataflow in behavior. Category display must therefore not imply a new flow, +execution path, or semantic allocation. + +Capella's [FunctionalAnalysis schema](https://raw.githubusercontent.com/eclipse-capella/capella/master/m2/plugins/org.polarsys.capella.core.data.def/model/FunctionalAnalysis.ecore) +defines named ExchangeCategory and ComponentExchangeCategory records, each referring +to existing exchanges. This supports reusable explicit category membership. It does +not establish the Studio filter-combination policy proposed below. Eclipse's +[Sirius/Capella description](https://www.eclipse.org/community/eclipse_newsletter/2017/december/article2.php) +identifies contextual diagrams, filters and layers as complexity-management tools. + +Current repository evidence: Diagram stores projection, placements and per-edge +routes. project_diagram derives nodes/edges from the whole model and visible +placements; ProjectedEdge retains one actual semantic ID. These are useful seams. +Do not invent a second graph model to implement selection or categories. + +## Alternatives and proposed choice + +| Candidate | Assessment | +| --- | --- | +| Ephemeral checkboxes plus manual hiding | Small but lost on reopen, absent from source/REST and unsuitable for shared engineering views. | +| Arbitrary query language per diagram | Flexible but adds a second language, difficult reproducibility and opaque hidden content. No demonstrated first-release need. | +| Closed persisted view options plus reusable named categories | Clear meanings, typed graphical/source/API parity and bounded computation. Recommended. | + +Use explicit typed options, not arbitrary JSON paths or user-authored predicates. +Separate the semantic category membership from a view's chosen visibility and label +settings. A view filter never edits a category or any engineering relation. + +## Proposed model contract + +One shared `ExchangeCategory` element body has `family` (Work or Participant) and +an unordered distinct list of same-layer exchange relation IDs. Work admits OA +Interaction or SA FunctionalExchange. Participant admits OA CommunicationMean or +SA BoundaryExchange. Already-valid legacy OA BoundaryExchange is also an eligible +Participant member; that does not convert its ports or reinterpret it as a means. +New OA authoring follows the CommunicationMean contract. Category membership conveys a named concern, +not ownership, transport allocation, item compatibility, mutual exclusion or order. +The same exchange may belong to several categories. Empty categories are valid. +Keep categories as layer-root records initially; hierarchy needs separate evidence +and cycle/transition semantics. Artifact links can address categories normally. + +A Diagram acquires a `ViewOptions` value with legacy-equivalent defaults: + +- `hidden_edges`: distinct relation/trace IDs belonging to the view layer (including + applicable cross-layer realization traces). This is an authored hide set, not + deletion; routes remain retained. Missing references reject on source/API input + and ordinary Delete cleans live references through the core transaction. +- `relation_kinds`: All or Only(explicit distinct kinds). Only([]) means no relation + kinds, not All. Label this "No relationship lines": allocation nesting and typed + state/action rows still express relationships independently of these graph edges. +- `work_categories` and `participant_categories`: independently All or + Any(explicit distinct category IDs of that family). All includes categorized + and uncategorized exchanges of its family. Any includes an exchange exactly + when it belongs to at least one selected category; uncategorized exchanges + fail that predicate. Any([]) includes none of that family. Neither selector + constrains the other family or non-exchange relationships. +- `coverage`: All, InBehavior, or OutsideBehavior, applying to Work exchanges using + the same actual occurrence/reuse reachability as coverage diagnostics. Participant + exchanges must not inherit coverage from unrelated allocated exchanges silently; + a later explicit transport-coverage option can define that quantifier separately. +- `work_contexts`: All or Any(explicit Process/Chain/Scenario context IDs from the + same layer). Any requires an actual Work exchange occurrence reachable from at + least one selected context through the same explicit reuse walk as coverage. + Any([]) matches no Work exchanges. This is distinct from global coverage: + "used anywhere" does not mean "used in the selected startup scenario". + Participant exchanges and non-exchange relations are unaffected. Contexts do + not include descendants merely because they share a semantic container. +- `exchange_labels`: Name, Items, or NameAndItems. Labels refer to declared carried + item names; an empty set remains visibly empty when Items is selected. Label + choice changes no selection, hide set, route, category or engineering content. + +Saved node selection remains the existing explicit placement bank. Do not combine +filters with automatic semantic deletion, automatic layout or automatic insertion of +new placements. A duplicate-view command creates a new diagram and placement IDs, +copies options/routes, and retains the same semantic element/relation references. + +## Exact combination and projection order + +First project placement eligibility and parent/fold promotion using current rules. +Compute automatic port placement from the eligible pre-filter edge set, then retain +that baseline geometry while filtering the drawable edge set. Current port_layout +uses peer edges, so feeding it already-filtered edges could move automatic ports +when a user only changes visibility. Manual anchors remain exact. Verify this with +opposite-side peer edges and independent per-view routes; filtering must not silently +become a layout action. +For each actual eligible relationship, require its kind to be allowed, require +category membership under that exchange family's selector, require both the explicit +Work coverage and chosen-context predicates when applicable, and require absence +from hidden_edges. These restrictions combine by intersection. Category Any combines +selected categories by union. Apply these predicates to actual IDs before any +optional visual grouping. A category change never clears hidden_edges. + +An edge whose endpoint has no visible placement/ancestor remains absent by ordinary +projection rules. Filters never fabricate endpoint placement. A folded internal edge +whose endpoints resolve to the same visible node stays suppressed under the existing +rule; its original semantic record remains navigable from a contextual list. + +Expose a compact "View content" panel with current restrictions and a searchable +list of omitted records with the reason (explicit hide, kind, category, coverage, +or endpoint not shown). Display all contributing reasons when useful. "Show" removes +only explicit hide; if another restriction still suppresses it, show that reason and +offer an explicit filter edit. "Reset filters" is one undoable view edit, never a +semantic edit. Preserve pending property/source guards and exact undo history. + +Initially display separate real edges with category membership in the inspector and +filter controls. If grouped category lines are needed to achieve readable acceptance, +add a distinct presentation result with `members: Vec` and an explicitly marked +summary label. Never assign a category ID as though it were an exchange. Picking a +summary opens its actual members; it cannot create/reconnect/delete a synthetic +exchange. Group only compatible promoted endpoint pairs/orientation/family and show +the member count. A member in multiple selected categories needs a deterministic +nonduplicating policy before this optional grouping can be accepted. Do not claim +grouped-summary functionality from filtering alone. + +## Editing, lifecycle and parity + +View options use EditDiagram and the current Document transaction. Source syntax +must distinguish All from empty selected sets, retain explicit options during +lexical edits, and reject unknown values. Schema6 migration adds default options +without changing old projection results or routes. Older envelopes must reject new +constructs rather than using current live Diagram/Element types accidentally. + +Category edits use normal typed element creation/properties. Core reference visitors +cover all members and selected categories; reidentification changes every live +reference. Delete category removes the ID from selected category sets but leaves +Any([]) explicitly empty rather than widening to All. Deleting a selected context +similarly removes only that context ID and retains Any([]). Delete an exchange removes +only its membership and live view references, preserving independent exchanges and +routes. Preview the resulting effects through the existing deletion contract. + +Category transition maps explicit members through reviewed exchange counterparts; +it cannot imply that a missing exchange is unnecessary or fabricate one merely to +fill the category. Members/order/source facets and artifact links need closed +semantic-review support. A view is presentation data, not a realization-trace target. +Duplicating across layers requires explicit counterpart placement decisions and +cannot reuse old layer IDs blindly. + +The same pure filter/projection implementation feeds native Canvas, SVG, REST view +queries and inspectable diagnostics. GUI controls expose every persisted option. +No per-pixel model-wide coverage scan: build the reachability index once per accepted +revision and reuse it across requested views. Establish an actual sparse/dense timing +measurement before optimizing beyond that boundary. + +## Falsification and next action + +Use a fixture with two OA participants, three activities, two processes sharing one +Interaction, an uncovered Interaction, two overlapping categories, a non-exchange +relation and a folded owner. Save two views with different restrictions and routes. +Hide one shared-category edge, change labels/categories, reopen, undo/redo and compare +exact source/projection. It must stay hidden. Verify Only([]), Any([]), category +deletion, ID swaps, edge deletion/recreation, same-name categories, hidden endpoints, +two unrelated view geometries, source migration and atomic REST rejection/restart. +Add a context truth table with one globally covered exchange outside the selected +process, one shared by both processes, and one uncovered exchange. Verify category +selectors independently across both families, including uncategorized members. +Add forty exchanges between the same visible endpoint pair in one category. If +filtered separate edges remain unreadable, grouped presentation is required before +claiming the category complexity-management workflow accepted; the sparse fixture +cannot establish that result. Keep original member navigation and hide persistence +as mandatory acceptance in any grouped alternative. +Behavior coverage changes should update visibility without silently altering saved +options or emitting view-history transactions. + +Independent review should challenge the category family shape, legacy admission, +meaning of uncategorized exchanges and non-Work coverage, omitted-record UX, and +whether separate edges plus filtering satisfy the initial user task. Freeze those +answers alongside CommunicationMean/source6 before production edits. diff --git a/harness/release-semantic-batch-foundation-design.md b/harness/release-semantic-batch-foundation-design.md new file mode 100644 index 00000000..c0d6c694 --- /dev/null +++ b/harness/release-semantic-batch-foundation-design.md @@ -0,0 +1,197 @@ +# Multi-source semantic review foundation: concrete design + +Read-only design against the current DATA working tree, after keyed/ordered review +and workflow recovery. This adds implementation detail beyond +`release-semantic-remaining-plan.md`; no production edits, Cargo run or acceptance +claim accompanies it. + +## Current seams and two constraints + +`Workflow::Semantic(Box)` delegates view/update/command and +recovery to State. App's dedicated semantic handler applies its single prepared +Command batch through Document. Keep that external interface and atomic apply path. +Current State contains one anchor/trace/direction, decisions/resets, references, +scalar/group/keyed/order drafts, and one computed plan. `load_trace` clears drafts; +`request` creates one source/mapping/review and at most one complete target candidate. +Family adapters compose unvalidated typed records before the core validates them. +They need not produce an individually valid Project to retain or edit a row. + +The core already accepts multiple review requests derived from one frozen initial +Project, chosen trace per source, a shared canonical OA→SA reference map and one +complete `SemanticRecord` candidate per target ID. It rejects duplicate sources, +duplicate candidate records, actual-slot disagreement and unselected candidate +changes. Two constraints matter to the GUI design: + +1. **The core cannot see two independently authored Candidate values once the GUI + has collapsed them into one record.** If row A authors minimum 5 and row B authors + minimum 7, both Candidate resolutions read whichever minimum the single candidate + contains. The GUI must compare the two authored intentions before generating that + record. Last-row-wins deduplication would silently erase user intent. +2. **Convergence is directionally constrained today.** Two OA sources can map to one + SA target. Two SA sources cannot map to one OA target under the canonical + `BTreeMap` contract: `review.rs::build_review` inserts `(targetOA,sourceSA)` + for each reverse transition, and rejects the second differing value as + “Transition mapping disagrees with frozen correspondence.” Reverse inversion also + rejects ambiguous maps. Support reverse batches with representable mappings; + retain/display the core conflict for reverse convergence. Do not silently drop + mappings or promise symmetric many-to-one support. A future relaxation requires + a separate demonstrated core contract change, outside this foundation. + +## Recommended structure + +Keep public `State` as the session facade. Extract its current row-specific state +into `ReviewRow`, with an explicit `RowAddress { source, target, trace }` and stable +draft identity. Store a row bank plus included flags, active row and display order. +Switching the active row changes focus only. Excluding a row removes its request +contribution while retaining all raw drafts. Changing its mapping/selected trace +opens or restores the draft for that exact address; it must not repurpose existing +raw values under a new identity. Only one included row per source is admissible; +multiple historical traces for the same pair are deliberate alternatives, not two +review requests for the same source. + +Session owns direction, one frozen accepted Project/fingerprint, canonical reference +pairs, one merged preview, accepted impacts and errors. An optional draft bank per +direction retains work when switching direction; require explicit address selection +where the reversed mapping is ambiguous. No direction switch commits model changes. +Check the supplied current Project against the session's accepted fingerprint +before planning and Apply; a changed accepted model leaves drafts intact and reports +staleness. All proposals and observed history read the same frozen base. + +Split Event into session actions and `Row { address, event: RowEvent }`. Tag row +events with their originating draft identity so delayed text-editor messages cannot +edit whichever row happens to be selected after a switch. Existing constructor, +keyed, order and numeric form code remains the row editor. Project contexts can +show the provisional combined destination, but never overwrite authored raw values. +Raw incomplete numbers or conflicting constructor alternatives block inclusion; +an excluded invalid row remains recoverable without blocking the included batch. + +## Candidate composition without sequential Apply + +Add a closed `compose.rs` adapter, extracting reusable composition from today's +`combined_value`. Its product is target contributions with row provenance, not +Commands. For every included row: + +1. Materialize only selected raw Candidate scopes and existing typed Take payloads + against the frozen base and shared explicit references. Do not require its own + final Project to validate: e.g. one row proposes a minimum above the old maximum, + while another proposes a sufficient new maximum. Validate their joint result. +2. Produce typed desired observations indexed by actual destination slot: + `(target, scalar selector)`; `(target, collection, destination member ID)`; + exact literal; full Annotation `(path,field)`; Binding referenced ID; and Order + collection. Use the destination identity, never the full source/target pair as + an agreement key. A row with no source identity and Take contributes frozen + target preservation, distinct from explicit absent-source removal. Keep is also + an explicit frozen-value constraint and cannot be dropped merely because its + JSON diff is empty. +3. Compare all authored observations for the same actual slot. Equal intentions + can share the final slot; differing ones retain both drafts and block preview + with source names, facet/key and each proposed value. Offer explicit actions to + edit a row, leave its decision unselected, or exclude it. Do not automatically + synchronize raw Candidate drafts to whichever row was edited last. +4. Constructor proposals have coverage over their descendant slots, including + slots introduced by another row and retired NotApplicable branches. Compare that + coverage against every other proposal. Preserve the core's first-equal-kind-only + acknowledgment exception; a replacing group remains a complete scoped proposal. + Never apply the same-row “untouched candidate field” convenience across sources + to silently accept another source's changed value. Existing `constructors::roots`, + `contains`, typed `capture_facet` and `semantic_facets` are the seams for this closed + adapter. Family-specific equality/reference semantics remain explicit. +5. Assemble one target record from the agreed contributions: constructor groups + first, independent scalar/keyed changes next, complete order last. Preserve + surviving unordered row positions and use canonical destination-key order for + genuinely new rows. Include a complete candidate only where a Candidate decision + requires one; its record must also contain the agreed Keep/Take effects on that + target. Finally call `plan_semantic_transition` once with all source mappings, + trace selections, row resolutions/resets, shared references and unique candidates. + The core still controls validation, exact scope/retirement, induced impacts, + preparation fingerprint and the only executable Command batch. + +The session's disagreement checks protect independent raw intent; they do not +replace the core's agreement check. Compare GUI requests/results to direct core +calls and retain any core conflict that the GUI did not anticipate. Do not merge +whole records, concatenate per-row candidate vectors, execute previews sequentially, +or use successful per-row plans as a prerequisite for joint validity. + +Ordered collections require particular care. Core `build_review` validates each +row's explicit Order against that row's expected surviving membership before its +final cross-source merge. An Order containing a member introduced only by another +row can therefore require that membership to be explicitly reviewed in the order's +row as well (a destination-only Candidate member address can express agreement). +Expose the involved membership/Order decisions for explicit repair; do not secretly +add acknowledgments. Likewise retain core “Contradictory anchored orders” and +“Combined ordered membership needs an explicit final target order” conflicts. +Prove any convenience to share a final order with a direct core request before +offering it. This is a contract edge to test, not permission to invent a merge order. + +## Alternatives considered + +| Approach | Assessment | +| --- | --- | +| Wrap several complete current States and concatenate requests/candidates | Fast for distinct targets, but duplicates candidates for convergent targets and cannot preserve conflicting raw Candidate values when deduplicated. Reject as the final architecture. | +| Give all rows one mutable target Draft | Avoids duplicate records but makes one source edit silently change another row's authored proposal. Useful only as an explicitly accepted resolution surface, not draft storage. | +| Independent row drafts plus provenance-bearing typed contributions | Recommended. Reuses existing closed adapters, protects Keep and raw conflicts, admits jointly valid values, and leaves the core as the final planner. | + +## Files, dependencies and bounded implementation order + +One owner should edit `semantic_review.rs`, `semantic_review/**` for this foundation: +new `batch.rs` for row selection/session orchestration, `compose.rs` for closed +contributions, `batch_tests.rs` for core-oracle cases, and focused changes in +`view.rs`, `recovery.rs` and the family adapters. Keep `Workflow::Semantic(State)` +and its public methods intact so App/Workflow recovery wrappers need no changes. +Coordinate any unavoidable wrapper adjustment with root; do not touch App source +writeback, Save handling or native input. Core/source/API production edits are not +part of this design. + +Implement incrementally inside this structure: row bank and recovery with distinct +targets; convergent scalar/keyed agreement and single candidate assembly; then +constructor/order overlap and full same-target edge tests. The first checkpoint +must visibly identify unsupported convergence instead of silently serializing it. +Do not call the foundation complete until same-target disagreement and agreement +cases pass. Attachments/artifact creation remain subsequent adapters. + +Recovery v2 stores one base identity/fingerprint, direction and shared references, +row addresses/inclusion/order, all row scalar/group/keyed/order raw drafts, row +resets, active identity and exact accepted impacts. No proposed projects/commands +are persisted. Restore checks the base once, rejects duplicate included source +addresses and duplicate draft keys, restores excluded invalid rows, then recomposes +and replans. Decode existing v1 snapshots as a one-row session; never silently drop +a supported old workflow. Apply limits across all rows rather than independently +allowing each row the full model budget. + +## Engineering workflow and decisive oracles + +Two OA operating modes review their constraints against one SA Integer data type. +Base range is 0..100. Normal operation row proposes minimum 120; emergency operation +row proposes maximum 150. Retain both source rows and allow the jointly valid +120..150 result although the first row alone is invalid. If emergency also proposes +minimum 130, show both authored minima and block. Leaving that conflicting minimum +unselected restores the earlier 120 draft and the valid joint result. Exclude and +restore the emergency row, recover the workflow, then apply once and undo once. +Permuting display/review order must never change the outcome or erase a draft. + +Add a second same-target fixture where one row Takes an Integer constructor while +another Keeps a Real bound; both source orders must reject retirement of the kept +fact. Ordered startup fixtures must preserve the local self-test and expose explicit +membership/order repair when source anchors disagree. Reverse distinct-target +batches must succeed; reverse convergence must retain the current core refusal. + +Exact existing oracles in `crates/oa-core/tests/r3_semantic.rs`: + +- `independent_bounds_keep_take_and_stale_prepare` for scoped bounds/stale plans; + extend its pattern to the jointly valid two-source min/max case above. +- `constructor_retirement_cannot_override_other_sources_kept_bound_in_either_order`. +- `constructor_union_conflicts_with_another_sources_new_member_in_either_order`. +- `combined_ordered_membership_commutes_or_requires_agreed_anchors`. +- `semantic_batch_is_one_undo_and_replanning_acknowledged_values_is_noop`. +- `explicit_trace_selection_keeps_independent_trace_baselines`. +- `same_target_member_keys_cannot_last_write_win` validates historical slot uniqueness; + add live GUI candidate disagreement tests rather than treating that baseline-only + test as sufficient proof of batch candidate composition. + +Add source/comment/history equality through App Apply, Undo/Redo and reopen; +stale receipt/model rejection; exclusion and recovery of invalid drafts; explicit +per-row reset with two histories; equal versus conflicting exact Annotation/Binding +keys; kept slots versus mutations; first-equal constructor exception; and duplicate +source/trace choices. Native acceptance must inspect row switching and retained +text, conflicting values, the one combined preview and one transaction at normal +and minimum window sizes. diff --git a/harness/release-semantic-keyed2.md b/harness/release-semantic-keyed2.md new file mode 100644 index 00000000..e825c694 --- /dev/null +++ b/harness/release-semantic-keyed2.md @@ -0,0 +1,104 @@ +# Independent Annotation / Binding review and semantic recovery + +Implemented on integrated DATA baseline `c091f80`; current working-tree changes +remain under integration and are not release acceptance evidence. + +## Contract and implementation + +The existing semantic core planner remains the authority for scopes, reference +mapping, validation, historical observations, induced changes and atomic commands. +The desktop now exposes independent `Annotation` and `Binding` facet selectors +alongside Members/Literals. A message annotation key is the complete `(path,field)`; +a scenario binding key is its reused scenario's `referenced` lifeline. An explicit +OA/SA pair stages a row and its key reference correspondence. It creates no model +objects. Changing a key requires separate removal/addition and explicit resets of +overlapping historical observations. + +Keep retains the target row; Take maps typed source references and preserves +expression prose literally; Candidate sets exact presence plus expression or local +lifeline. A missing source key preserves a target-local row. An explicit source key +whose row is absent can remove its mapped target row. Full candidates compose only +the selected row, leaving sibling rows intact. The core validates the result again. + +`semantic_review/keyed_rows.rs` owns the closed row adapter, raw multiline +expression/local-lifeline drafts, complete path pairs and context-specific choices. +`view.rs` exposes paged carried-field browsing, recursive descent, source/destination +key choice, exact history scope and candidate controls. Drafts survive decision +changes, temporary exclusion, staging removal and invalid values. Binding choices +distinguish reused keys from local scenario lifelines and follow candidate ownership. + +## Independent findings + +The behavior-review agent found two concrete issues by read-only review: + +- Local binding choices followed original ownership during a Header Parent + candidate. Fixed by walking from the combined candidate's parent. +- Generic display substituted a UUID-shaped expression when its text matched a + referenced field UUID. Fixed with typed annotation rendering: names apply only + to path/field; expression text is shown literally. + +Both have focused regression tests. The reviewer inspected the corrections and +reported no additional material finding in the bounded keyed-row review. + +## Semantic checkpoint extension + +Independent review of root's recovery integration found that pending semantic +workflow drafts were absent from both dirty detection and checkpoint snapshots. +The root assigned the semantic adapter extension here and owns App/checkpoint wiring. + +`State::recovery_snapshot` / `State::from_recovery` preserve authored decisions, +reference mappings, exact resets, staged keys, presence, scalar and constructor +raw inputs, cached constructor alternatives, row drafts and unfinished pair picks. +Plans and proposed models are recomputed. Restore verifies the accepted Project UUID +and complete core project fingerprint (the persistent model revision, not a transient +Document counter); changed models reject. Invalid numeric inputs remain recoverable +and cannot apply until repaired. Constructor contexts are recalculated and group +editing mode restored. No GUI pointer state or credentials are serialized. + +`Workflow` wraps all adapters in a strict externally tagged record. Relationship +drafts retain names, multiline conditions, kind, direction, endpoints and item +choices; manual traces retain exact names and counterpart picks. Transition review +stores its typed request and recomputes the core plan. Port workflows retain kind, +orientation, incoming/outgoing items, endpoints, selected boundary, split-side +choice and generated identity; their plans are rebuilt too. Every adapter checks +the original accepted model fingerprint. No saved computed command is replayed. +App dirty/restore integration remains root-owned. + +## Core ordering correction + +Focused GUI tests exposed a core mutation defect: replacing or merely retaining +an Annotation/Binding removed the existing row and appended it. A Keep decision +alongside an unrelated Header candidate therefore changed authored row order and +could fail the planner's unselected-facet check. The bounded `semantic/mutate.rs` +fix replaces a changed row in place, skips identical rows, removes only requested +rows, and appends only genuinely new rows. New GUI rows compose in destination-key +order, matching the core's canonical new-slot application. Existing rows retain +their authored positions. Core regressions cover both row families and directions. + +## Verification status and next gates + +Owned rustfmt completed successfully. With the user-local Rust 1.98.1 GNU LLVM +toolchain, the full desktop test suite passed **431 tests, 0 failures, 0 ignored** +(`.local/keyed2-desktop-test.log`, 31.72 seconds). The core `r3_semantic` integration +suite passed **35 tests, 0 failures** (`.local/keyed2-core-test.log`). This includes +the narrow core correction, GUI row composition, all workflow recovery adapters, +and the independently corrected typed StateAction canvas test. The earlier focused +desktop semantic run passed 58 tests before the last new-row composition test was +added; the complete 431-test result supersedes that focused checkpoint. + +The added tests cover both OA→SA and SA→OA, Keep/Take/Candidate combinations, +coexisting scalar candidates, sibling preservation, literal UUID expressions, +raw draft retention, recursive qualified-field selection, local/reused binding +choice separation, candidate ownership, exact reset requirements, missing-key versus +absent-row behavior, unresolved reference rejection/repair, invalid local binding +rejection/repair, exact source/model/history Undo/Redo and reopening, and recovery +against unchanged/changed models. Native operation remains root-owned and pending. + +The shared Cargo target has been released to root for native binary builds. +Workspace formatting, strict Clippy, broader workspace checks and native acceptance +remain root-owned gates; the test results above do not claim release acceptance. + +Run native message-value and scenario-use reviews at normal and minimum size, +including long qualified paths, reverse review, absent/new rows, overlapping +history reset, multiline expressions, Save/reopen and recovery of invalid input. +Other ordered/attached/artifact/multi-source GUI facets remain outside this slice. diff --git a/harness/release-semantic-ordered.md b/harness/release-semantic-ordered.md new file mode 100644 index 00000000..1d72456d --- /dev/null +++ b/harness/release-semantic-ordered.md @@ -0,0 +1,96 @@ +# Independent ordered semantic review + +Working-tree slice on DATA `c091f80`. This does not claim full semantic GUI or +release acceptance. Owned production files are `semantic_review.rs` and its subtree; +the public Workflow interface, core, language and REST contract remain unchanged. + +## Implemented contract + +Members / rows now exposes ordered Steps and Fragment branches using the existing +explicit OA/SA pair and presence workflow. Paths cover Process / FunctionalChain, +Scenario, Branch occurrences and Fragment branches. Membership does not create or +reparent a referenced object. Keep, Take and Candidate presence affect only the +selected entry; the existing exact pairing/reset and constructor-overlap checks +remain applicable. + +Order is an independent fact. Keep retains the frozen target order. Take follows +typed explicit correspondences; destination-only members retain their positions +when the mapped source already agrees with their surrounding order. Taking an +incoming member uses the core's unique adjacent-anchor rules. Ambiguous placement +blocks until an explicit final Order is selected. + +Set final value shows named positions with Up/Down controls. Its complete order +must include every surviving destination member once. The order draft is initialized +from the proposed membership, then remains authored state: changing membership, +leaving decisions unselected, preview errors and recovery do not silently replace +it. The explicit Reconcile with selected membership action removes excluded rows, +retains surviving authored positions, and appends newly included rows for deliberate +placement. Invalid or obsolete order drafts remain visible and block Apply until +repaired. A provisional membership list exists only for these editing controls; +the actual request always receives complete validation. + +The GUI composes members before order, submits one complete candidate record, and +uses the unchanged core for exact history, scope overlap, impacts and atomic +Document/source application. Recovery adds typed ordered draft rows with the same +accepted-model fingerprint check and no persisted computed commands. + +## Review and verification + +The independent behavior-review agent compared the anchor algorithm, target-local +Take exception, final member-set checks and recovery path against the core and +reported no concrete defect. Their suggested multi-insertion falsifier was added: +two incoming members must retain source order despite independent UUID ordering. + +Eight focused tests cover both directions, all four owner shapes, single and +multiple uniquely anchored insertions, ambiguous insertion with a destination-local +step, independent Keep/Take/Candidate removal, order-only scope, membership picker +ownership, rejected/stale/restored draft retention and explicit reconciliation, +direct core preview/result comparisons, and exact source/history Undo/Redo/reopen. +The first test run found an invalid scenario fixture using sequence-only Controls; +the fixture was corrected to scenario Actions without changing production behavior. +The focused run passed **8 tests, 0 failures, 0 ignored** (4.70 seconds; +`.local/semantic-ordered-test.log`). Owned-file rustfmt passed. Full desktop +regression, strict checks and native acceptance remain root-owned gates. + +## Native acceptance still required + +Validated standalone fixtures and exact core request/result/source oracles are +available in ignored `.local/native-fixtures/semantic-ordered*`, with main steps in +`semantic-ordered.steps.md`. Five fixtures cover the main sequence, reverse sequence, +Scenario, Branch steps and Fragment branches. Each initial/final model validates and +round-trips through source and JSON; ambiguous Take rejects and the explicit final +order has zero additional impacts. These are programmatic oracles, not native evidence. + +Use an OA Process traced to an SA FunctionalChain, with explicit corresponding +occurrences Request start, Energize and Verify interlock. OA order is Request, +Verify, Energize. SA order is Request, Local self-test, Energize, with the mapped +Verify occurrence owned by the chain but not included. Supply explicit reference +pairs for the three mapped occurrences. + +1. In Members / rows choose Ordered steps, stage the Verify pair and Take + Operational. Expect a unique-anchor error; Local self-test makes placement + ambiguous. No accepted model/source/history change occurs. +2. Use Review final order, then Set final value. The initial named draft retains + Request, Local self-test, Energize and appends Verify. Move Verify Up once to + choose Request, Local self-test, Verify, Energize. Preview must preserve the + independent local occurrence and show only the explicit member/order facts. +3. Toggle membership of another paired occurrence while retaining the order draft. + Expect an exact-member-set error. Reconcile explicitly, inspect the retained + positions, then move the newly appended entry. Cancel/reopen recovery must retain + the rejected raw order; stale accepted models must reject recovery. +4. Apply, inspect the unchanged unrelated source/comment text and exact history, + Undo once, Redo, Save and reopen. Repeat in reverse and for Scenario, Branch and + Fragment branch order at normal and minimum window sizes. + +Root also observed Reference pairs buttons clipped at the right edge in the +previous native binary. This slice explicitly bounds that panel's scrollable, +content column and rows to available width. Recheck Use pair and every Remove pair +with long identity labels at 1440x900 and 1100x700; the layout change has not yet +received native acceptance. The footer now accurately leaves attached-record and +multi-source review pending. + +Root also observed top-level Model checks and Engineering guide disappearing at +1100x700 behind the previous horizontal navigation. The exact navigation container +in `views.rs::view` now uses `task_nav.wrap()` with unchanged buttons/actions/styles. +No `app.rs` handler was changed. Owned rustfmt passed; verify all nine pages remain +visible and usable at 1100x700 and 1440x900 in the rebuilt native binary. diff --git a/harness/release-semantic-remaining-plan.md b/harness/release-semantic-remaining-plan.md new file mode 100644 index 00000000..a89ca706 --- /dev/null +++ b/harness/release-semantic-remaining-plan.md @@ -0,0 +1,87 @@ +# Remaining semantic review GUI: bounded implementation plan + +Read-only review on DATA `c091f80e6ecf90e9aa6f13ec6fd34924c5ef55de` +plus the current keyed-row/recovery working tree. No production edits or new tests +were performed for this plan. Existing Annotation/Binding and unordered members +are complete within their documented slice and must be reused. + +## Exact remaining contract + +| Area | Core selector and semantics | Current desktop boundary | +| --- | --- | --- | +| Ordered lists | `Member { collection: Steps / FragmentBranches, keys }` independently decides presence; `Order(Steps / FragmentBranches)` separately decides the complete surviving order. Steps covers sequences and Branch occurrences. Every survivor occurs exactly once. Source anchors may place an insertion, but ambiguous placement requires an explicit final order. Target-local members survive Take. | `keyed::supported` excludes these collections; complete lists are editable only inside replacing occurrence groups. | +| Ownership | `Child(KeyPair)` attaches an existing mapped child by changing its Parent; removal detaches only from this owner. A new child needs an explicit transition mapping. Child and Header.Parent decisions must agree on the same actual child slot. | No independent Child editor. Candidate composition currently emits only the selected trace's target record. | +| Incident relation | `Incident { keys, end: Source / Target }` attaches an existing mapped relationship endpoint. Detachment requires an explicit final endpoint reviewed through the relationship trace; a boolean false alone is insufficient. | No endpoint attachment editor or linked relationship review in this modal. | +| Artifacts | `Artifact { keys, facet }`: Presence, Name, Description, Owner, Relation, Uri, Revision, Fragment. Creation needs a complete typed artifact candidate and explicit review of all seven introduced metadata facets. Deletion retires exact metadata observations. Metadata is reviewed from its initially attached owner trace; moves must reconcile attachment and Owner. | Artifact observations are visible; editing and exact artifact-history reset are disabled. No artifact candidate collection is emitted. | +| Multiple sources | One `SemanticTransitionRequest` carries explicit mappings, selected trace per source, canonical references, independent resolutions/resets per source, and one final candidate per record ID. All source proposals are derived from the same frozen project. Equal actual-slot proposals agree; contradictions reject regardless of source iteration order. | `State::request` constructs one source/mapping/review/target candidate. Switching trace clears its current drafts. | + +## Smallest coherent sequence and ownership + +1. **Ordered membership and order.** Add `semantic_review/ordered.rs` and focused + tests; extend existing member choices rather than replacing keyed rows. Show + explicit OA/SA member pairs, independent presence decisions, and a named final + order with Up/Down controls. Preserve raw order across deselection and rejected + previews. Initial defaults may retain the destination order but must not silently + acknowledge an ambiguous insertion. Extend `semantic_review.rs`, `view.rs`, and + `recovery.rs` only. This is independently shippable using current single-record + request construction; no core change is demonstrated as necessary. +2. **Batch review foundation with existing facets.** Introduce a session containing + per-source drafts and one shared references table, accepted base and preview. + Add/remove an explicit source-target-trace row; select a row to use the existing + facts/forms, retaining other rows' raw drafts. Compose one candidate per record + ID, then invoke the core once for the full batch. Display contradictions beside + every involved source, retaining all decisions. Extend recovery to all rows and + shared draft state. Own `semantic_review/**`; coordinate the Workflow wrapper + and root's App event integration only if its public State interface changes. +3. **Child and incident attachments.** Add `attachments.rs` with typed existing + object/relationship pair choices and Source/Target endpoint labels. Child + Candidate edits a separate child record. For incident removal offer a linked + relationship-review row and explicit replacement endpoint, using the batch + foundation; do not offer a misleading standalone unlink checkbox. Include + creation only via explicit transition mapping. Generalize candidate assembly + to a map of typed record IDs. Keep full induced-impact review and one Apply. +4. **Complete artifact lifecycle.** Add `artifacts.rs`: select existing attached + links or deliberately reserve a new destination identity, stage its explicit + pair, and edit typed raw metadata with separate None versus empty optional text. + Creation shows every required metadata decision together. Move identifies the + new owner and linked review rows; deletion shows retired history before Apply. + Enable exact artifact history reset only with lifecycle support. Preserve + provisional identity and invalid text in recovery. Own semantic subtree; reuse + ordinary artifact form helpers only with their owner's coordination. + +No core contract extension is presently a verified blocker. Multi-record candidate +composition and the single-source State shape are desktop dependencies, not evidence +that planner scope must be broadened. Do not implement batch review as sequential +Apply operations: later sources must never observe earlier proposed writes. + +## Engineering example and oracle + +Review an OA vehicle-start process into an SA startup chain. OA adds “verify +interlock” between “request start” and “energize”; SA already retains a local +“self-test” between those anchors. Stage the explicit occurrence pair and Take +presence. Ambiguous placement must block until the user selects a final sequence, +for example request, self-test, verify interlock, energize. Review a second OA +maintenance-start process against the same SA chain: contradictory placement of +the same mapped occurrence must block in either source order. Agreeing explicit +final orders should yield one candidate and one transaction. + +Then attach an already mapped “verify interlock” function to the selected subsystem, +move its incoming functional exchange through an explicitly reviewed endpoint, and +attach a requirement link with URI, revision and fragment. A conflicting kept Parent +or attachment must block the whole batch. Creating the requirement link with only +Presence selected must block until every introduced metadata facet is reviewed. + +Compare GUI-produced requests and prepared results against direct core calls on +the same frozen fixture. Existing `r3_semantic.rs` oracles include +`ordered_member_requires_anchor_or_explicit_order_and_preserves_target_only`, +`combined_ordered_membership_commutes_or_requires_agreed_anchors`, +`child_membership_and_parent_resolutions_agree_by_actual_child_slot`, +`artifact_creation_requires_reviewed_metadata_and_deletion_retires_exact_facts`, +`artifact_owner_candidate_moves_link_and_conflicts_with_kept_attachment`, and +`target_only_artifact_move_records_owner_and_metadata_in_either_order`. + +For every slice, compare both directions, source iteration permutations, exact +acknowledged/retired scopes and accepted impacts. Apply must preserve unrelated +records and comments, round-trip source/model/history, and produce one Undo/Redo. +Invalid raw drafts, rejected/stale previews and workflow recovery must retain all +authored values. Native review remains required at normal and minimum window size. diff --git a/harness/release-timed-bundle-oracle-20260906.md b/harness/release-timed-bundle-oracle-20260906.md new file mode 100644 index 00000000..3be4701c --- /dev/null +++ b/harness/release-timed-bundle-oracle-20260906.md @@ -0,0 +1,67 @@ +# Timed transition: actual saved bundle oracle + +The actual two-file native save independently parsed, validated and reopened with +exactly the intended graph changes. The entry file alone was not treated as the +accepted model: the oracle read both `state-recovered-renamed.arcadia` and its +`sa/model.arcadia` include from `.local/native-qa`. + +The staged production service was launched on an isolated loopback port with fresh +synthetic storage and a system-only PATH. Its SHA256 was +`BB775876741FE4A7B07CDE08940AEC8E23F545FDAFEC024FC4DC3BA6DD1AB9FC`. +Both pristine and saved bundles passed `/api/v1/projects/import-source-project`; +the returned native Projects were compared structurally, with object-key ordering +normalized and sequence ordering retained. + +## Exact accepted delta + +The expected entire Project was constructed from the pristine parsed Project by +only these changes, then compared for full equality to the saved parsed Project: + +1. Add SA Event `49044cb7-5f91-408a-86fc-1cc012eb2972`, alias `after_30_s`, name + `After 30 s`, empty description, no parent, typed `Time { kind: After, + expression: "30 s" }`. +2. Rename existing StateTransition `f0060906-0000-4000-8000-000000000033` from + `Refresh status` to `Unfinished timed refresh`. +3. Append the new Event ID after the original Sample tick Event + `f0060906-0000-4000-8000-000000000013` in that transition's typed trigger list. + +**Full Project equality passed.** The original trigger Event remains Time/After +`1 s`. The transition retains alias `refresh_status`, Internal kind, Ready→Ready +endpoints, empty free-text trigger, guard `pressure_valid`, typed effect +`f0060906-0000-4000-8000-000000000011` (Monitor pressure) and free-text effect +`Update status display`. All other elements, relations, IDs, source-independent +model metadata, state actions, containment, diagram placements, sizes, collapse +states, routes, traces and artifact links remain equal. + +Counts are pristine14→saved15 elements,10 relations,1 diagram and8 placements. +The saved graph has no structural errors. Its three existing +`work_without_behavior_context` warnings address the three work functions; typed +state actions do not constitute process/scenario evidence under the coverage policy. + +GET source-project also matched the exact two submitted file contents and entry +name. After force-stopping and restarting only the oracle server, model, two-file +source bundle and ETag still matched. Before/after hashes of all original input +files were identical; the oracle did not rewrite native artifacts. + +## Inputs and reproducibility + +| Source file | SHA256 | +|---|---| +| `.local/native-fixtures/state-actions.arcadia` | `9CDE82608110E56AA9E14C9E33EB38976F53AED74AB013145D411D5743A0EC26` | +| `.local/native-qa/state-recovered-renamed.arcadia` | `AD8FD359CE7B0BE4F3AE0F55424EEB4EE032DEFB359F2F5AD89C00A79B62DADF` | +| `.local/native-qa/sa/model.arcadia` | `8FB649F8ABB93CF4C321F2D847A1A9F1A1551E0F713E938C46EF11E24DC58801` | + +Tracked runner: `harness/fixtures/run-timed-bundle-oracle.ps1`. From the repository +root, using PowerShell7 and the actual saved artifacts: + +```powershell +$env:PATH = "$env:SystemRoot\System32;$env:SystemRoot;$env:SystemRoot\System32\Wbem" +& harness/fixtures/run-timed-bundle-oracle.ps1 -ServerExe .local/native-qa/bin-20260906-corrected/arcadia-server.exe -PristineFile .local/native-fixtures/state-actions.arcadia -SavedEntry .local/native-qa/state-recovered-renamed.arcadia +``` + +Evidence: `.local/timed-bundle-oracle-20260906-162848/` contains exact bundle request +payloads, parsed models, accepted/reopened bundles, response status/ETags, validation, +input hashes and `result.json` with the before/after transition. The response pairs +are sufficient to repeat the graph comparison even if the native files later move. +All owned oracle processes were stopped. This is independent parser/persistence +proof of the native save, not an additional native interaction or Linux claim. diff --git a/harness/release-windows-package-review.md b/harness/release-windows-package-review.md new file mode 100644 index 00000000..5821abda --- /dev/null +++ b/harness/release-windows-package-review.md @@ -0,0 +1,38 @@ +# Independent Windows package review + +Reviewed the current packaging script, guide, selected Cargo metadata, installed toolchain notices, and `.local/package-script-qa/ArcadiaStudio-qa-debug-20260906-a-x86_64-pc-windows-gnullvm` contents on 2026-09-06. No application was launched and no Cargo build/test was run for this review. + +## Release blocker: third-party notices missing + +The inspected QA bundle contains the application MIT license and only `licenses/llvm-mingw-LICENSE.TXT` under `licenses/`. It omits dependency notices, including Iced 0.14.0 (Hector Ramon and Iced contributors) and Tokio 1.53.1 (Tokio contributors). Their exact downloaded crate license files require their copyright and permission notices in copies or substantial portions. The selected locked Windows normal/build dependency graph contains 266 packages; a top-level dependency list would miss transitive notices. Several published workspace crates omit license texts even though their manifests declare license expressions, so collecting only existing root LICENSE files is insufficient. + +Iced graphics embeds `fonts/Iced-Icons.ttf`; its optional Fira Sans font is gated by `fira-sans` (disabled in the observed metadata). Embedded assets need an explicit provenance record, not an assumption that every font in dependency examples/tests ships. Rust's installed toolchain supplies `share/doc/rust/COPYRIGHT-library.html` and `COPYRIGHT.html`; these are absent from the QA bundle too. LLVM-MinGW's root LICENSE.TXT alone is not evidence that every component-specific notice was resolved. + +Smallest correction: select the two binary packages' transitive normal/build graph using locked target-filtered Cargo metadata; stage available notices, exact version/license/source information and local hashes; require reviewed, hash-bound supplements where publications omit required texts or contain embedded assets; include installed Rust library notices and the exact runtime distribution's notices. Refuse unresolved sources/notices before publishing an archive. This provides auditable inputs, not a universal license audit or proof that a prebuilt binary matches that graph. + +## Runtime and integrity + +No missing ordinary runtime DLL was established: current evidence identifies `libunwind.dll` as the sole non-Windows static import and it is staged beside both executables. The script inspects its imports too. Dynamic graphics and delay-loaded dependencies remain a clean-environment runtime acceptance gate, as the guide already states. The inspected bundle uses debug binaries; package-label version and PE architecture cannot prove release profile/source revision. + +Fresh-output refusal, no recursive removal, create-new claim, copy hash checks, and ZIP readback are useful protections. Optional hardening: leaf reparse checks do not establish physical containment through ancestor junctions. This was not a demonstrated failure for the actual ordinary workspace/output paths and is distinct from the concrete missing-notice release blocker. + +## Correction evidence + +Implemented `scripts/windows-notices.ps1`, integrated it into `scripts/package-windows.ps1`, documented the workflow, and added hash-bound local supplements under `scripts/windows-notices/`. The collector uses locked offline target-filtered metadata, verifies cached registry archives against Cargo.lock, hashes actual local source files, stages nested notices and exact asset mappings, and includes the installed Rust copyright reports/compiler identity/target-library hashes. Git sources, missing declared sources, unknown font mappings, missing texts, path traversal and altered supplements are explicit refusals. Supplemental notice downloads were performed during this review at the recorded source URLs; packaging itself does not download. + +Actual current graph preflight passes: **266 selected packages, 484 staged notice inputs, 58 Rust target library files hashed**. Twenty-seven published-crate notice mappings cover omitted texts. Iced graphics has two conservative font mappings: Fira Sans (disabled current feature, OFL statement and copyright extracted from its exact metadata plus upstream OFL text) and Iced Icons (declared distributed MIT plus its exact separate copyright/name metadata). LLVM-MinGW's exact libunwind hash is mapped to the component license at LLVM commit `ea7d852a70e8bdfaf601d6626a760f9771b2c4b4`, reported by the installed distribution's clang. + +The icon font's different copyright owner was investigated, then distinguished from a proven conflicting license. Its pinned crate declares MIT; no explicit contrary asset license/exception was found in the pinned repository or inspected history. The conservative distribution record therefore retains Iced's MIT plus the font's own Fontello copyright metadata and a candid provenance limitation. Missing original glyph configuration alone is not treated as proof that the distributed license excludes the asset. No font replacement or dependency modification was made. Exact name/cmap inspection confirmed the seven renderer codepoints (F00C, E800 through E805). See `scripts/windows-notices/Iced-Icons-NOTICE.txt` for the evidence boundary. + +`scripts/test-windows-notices.ps1` passes **8 contract checks on Windows PowerShell 5.1 and PowerShell 7**: selected normal/build graph excludes dev-only and disconnected packages; both Rust copyright reports are included; actual source changes alter source hashes; tampered archives, unmapped fonts, unsupported sources, escaping supplement paths and altered supplements are refused. All three scripts parse successfully. Tests use synthetic metadata and temporary fixtures; no build, Cargo target mutation or UI occurs. This does not test every license's legal requirements or indirect/generated asset discovery. + +Fresh staging/archive verification against the root's newly rebuilt debug binaries **passed** as `qa-debug-20260906-b` under `.local/package-script-qa/`. The archive contains **528 files**, including **485 license files** (484 collected inputs plus the LLVM-MinGW distribution root notice). Every staged input was hash-checked and every ZIP entry was read back and compared. The archive is 30,615,400 bytes. Log: `.local/package-notices-qa-b.log`. + +| Input/output | SHA-256 | Bytes | +| --- | --- | ---: | +| arcadia-studio.exe | `821f702f37f902955d63d230d6d7522935d09348931c36a45b24d70a72434553` | 102,017,024 | +| arcadia-server.exe | `dd6b7a6a9725558954f4f29219ebf6e773b30843766fe738c365f1ced3df9e8e` | 37,202,432 | +| libunwind.dll | `f45604a12e8bcd839fd86f667c1be1cc7c4f9da2b3c69ff381ac708f0e1ae7d4` | 190,976 | +| QA b ZIP | `237b30ee0c704efad89ed5c371d2c965f9e7d11ea318c97ba16ee34282456f73` | 30,615,400 | + +The collected Cargo.lock hash is `e7b95afac40a11823fa68d615fcad97deeb878c8fa1e83b28561be5a81896112`. Spot inspection of the actual stage confirms the Tokio, Iced and separate font copyright notices are present; the machine-readable inventory contains both font mappings, all selected package declarations and source hashes, and Rust distribution evidence. Native acceptance remains owned by the release operator; this package is QA and does not imply the full release objective is complete. Script/docs ownership was returned to root after this successful run, with no Cargo build or target mutation by this work. diff --git a/scripts/package-windows.ps1 b/scripts/package-windows.ps1 new file mode 100644 index 00000000..20eead8c --- /dev/null +++ b/scripts/package-windows.ps1 @@ -0,0 +1,250 @@ +#Requires -Version 5.1 +# Does not build or launch applications. +[CmdletBinding()] +param( + [Parameter(Mandatory = $true)] + [ValidateSet('x86_64-pc-windows-msvc', 'x86_64-pc-windows-gnullvm')] + [string]$Target, + [Parameter(Mandatory = $true)] + [ValidatePattern('^[A-Za-z0-9][A-Za-z0-9._-]{0,79}$')] + [string]$Version, + [Parameter(Mandatory = $true)] + [string]$BinaryDirectory, + [string]$LlvmMinGWRoot, + [string]$NoticeManifest = (Join-Path $PSScriptRoot 'windows-notices/manifest.json'), + [string]$OutputRoot = (Join-Path $PSScriptRoot '../dist') +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' +$repository = [IO.Path]::GetFullPath((Join-Path $PSScriptRoot '..')) +$utf8 = New-Object Text.UTF8Encoding($false) + +function Require-File([string]$Path) { + if (-not (Test-Path -LiteralPath $Path -PathType Leaf)) { throw "Required file missing: $Path" } + return (Get-Item -LiteralPath $Path).FullName +} + +# Read ordinary PE32+ imports without invoking a compiler or trusting PATH tools. +# Dynamically loaded libraries and delay imports remain a separate runtime smoke gate. +function Read-PeImports([string]$Path) { + $stream = [IO.File]::OpenRead($Path) + $reader = New-Object IO.BinaryReader($stream) + try { + if ($reader.ReadUInt16() -ne 0x5a4d) { throw "Not a PE executable: $Path" } + $stream.Position = 0x3c + $pe = $reader.ReadUInt32() + $stream.Position = $pe + if ($reader.ReadUInt32() -ne 0x4550) { throw "Invalid PE signature: $Path" } + if ($reader.ReadUInt16() -ne 0x8664) { throw "Expected x86_64 PE input: $Path" } + $sectionCount = $reader.ReadUInt16() + $stream.Position = $pe + 20 + $optionalSize = $reader.ReadUInt16() + $optional = $pe + 24 + $stream.Position = $optional + if ($reader.ReadUInt16() -ne 0x20b) { throw "Expected PE32+ input: $Path" } + $stream.Position = $optional + 120 + $importRva = $reader.ReadUInt32() + $sections = @() + for ($index = 0; $index -lt $sectionCount; $index++) { + $stream.Position = $optional + $optionalSize + $index * 40 + 8 + $virtualSize = $reader.ReadUInt32() + $virtualAddress = $reader.ReadUInt32() + $rawSize = $reader.ReadUInt32() + $rawAddress = $reader.ReadUInt32() + $sections += [pscustomobject]@{ Start = $virtualAddress; Size = [Math]::Max($virtualSize, $rawSize); Raw = $rawAddress } + } + function Rva-Offset([uint32]$Rva) { + foreach ($section in $sections) { + if ($Rva -ge $section.Start -and $Rva -lt ($section.Start + $section.Size)) { + return [long]($section.Raw + $Rva - $section.Start) + } + } + throw "Unmapped PE RVA in $Path" + } + if ($importRva -eq 0) { return } + $descriptors = Rva-Offset $importRva + for ($index = 0; $index -lt 4096; $index++) { + $stream.Position = $descriptors + $index * 20 + $fields = @(0..4 | ForEach-Object { $reader.ReadUInt32() }) + if (@($fields | Where-Object { $_ -ne 0 }).Count -eq 0) { return } + $stream.Position = Rva-Offset $fields[3] + $name = New-Object Text.StringBuilder + for ($character = 0; $character -lt 256; $character++) { + $value = $reader.ReadByte() + if ($value -eq 0) { break } + [void]$name.Append([char]$value) + } + if ($character -eq 256) { throw "Unterminated PE import name: $Path" } + $name.ToString().ToLowerInvariant() + } + throw "Unterminated PE import table: $Path" + } finally { + $reader.Dispose() + } +} + +# Windows system components only. Unknown imports fail, including VC redistributable DLLs. +# Add a redistributable only with explicit provenance, license, and recursive import checks. +$systemDlls = @( + 'kernel32.dll', 'kernelbase.dll', 'user32.dll', 'ole32.dll', 'oleaut32.dll', + 'bcrypt.dll', 'bcryptprimitives.dll', 'advapi32.dll', 'dwmapi.dll', 'gdi32.dll', + 'shell32.dll', 'imm32.dll', 'uxtheme.dll', 'ws2_32.dll', 'ntdll.dll', 'opengl32.dll', + 'ucrtbase.dll', 'msvcrt.dll', 'crypt32.dll', 'secur32.dll', 'shlwapi.dll', + 'comdlg32.dll', 'comctl32.dll', 'version.dll', 'winmm.dll', 'winhttp.dll', + 'iphlpapi.dll', 'userenv.dll', 'dbghelp.dll', 'dwrite.dll', 'd2d1.dll', + 'd3d11.dll', 'd3d12.dll', 'dxgi.dll', 'd3dcompiler_47.dll', 'propsys.dll', + 'powrprof.dll', 'setupapi.dll', 'cfgmgr32.dll', 'winspool.drv', 'wtsapi32.dll' +) +$inputs = [ordered]@{} +foreach ($name in @('arcadia-studio.exe', 'arcadia-server.exe')) { + $inputs[$name] = Require-File (Join-Path $BinaryDirectory $name) +} +if ($Target -eq 'x86_64-pc-windows-gnullvm') { + if (-not $LlvmMinGWRoot) { throw 'gnullvm requires -LlvmMinGWRoot for its exact runtime and license.' } + $inputs['libunwind.dll'] = Require-File (Join-Path $LlvmMinGWRoot 'x86_64-w64-mingw32/bin/libunwind.dll') + $inputs['licenses/llvm-mingw-LICENSE.TXT'] = Require-File (Join-Path $LlvmMinGWRoot 'LICENSE.TXT') +} elseif ($LlvmMinGWRoot) { + throw 'MSVC packaging does not use LLVM-MinGW; omit -LlvmMinGWRoot.' +} + +$imports = [ordered]@{} +$inspectedHashes = @{} +foreach ($name in @($inputs.Keys | Where-Object { $_ -match '\.(exe|dll)$' })) { + $inspectedHashes[$name] = (Get-FileHash -LiteralPath $inputs[$name] -Algorithm SHA256).Hash.ToLowerInvariant() + $imports[$name] = @(Read-PeImports $inputs[$name] | Sort-Object -Unique) + foreach ($dependency in $imports[$name]) { + $isSystem = $dependency -match '^(api-ms-win-|ext-ms-win-)[a-z0-9-]+\.dll$' -or $systemDlls -contains $dependency + if (-not $isSystem -and -not $inputs.Contains($dependency)) { + throw "Unpackaged runtime import '$dependency' in '$name'. Resolve its provenance and dependency closure before packaging." + } + } +} + +foreach ($name in @('README.md', 'LICENSE')) { $inputs[$name] = Require-File (Join-Path $repository $name) } +$tracked = @(& git -C $repository -c core.quotepath=false ls-files -- docs examples deployment) +if ($LASTEXITCODE -ne 0 -or $tracked.Count -eq 0) { throw 'A Git checkout and tracked docs/examples/deployment inputs are required.' } +foreach ($relative in $tracked) { + $source = Require-File (Join-Path $repository $relative) + if (((Get-Item -LiteralPath $source).Attributes -band [IO.FileAttributes]::ReparsePoint) -ne 0) { + throw "Package input must not be a symlink: $source" + } + $inputs[$relative] = $source +} +# Explicitly require the packaging guide, including before its first commit. +$inputs['docs/windows-packaging.md'] = Require-File (Join-Path $repository 'docs/windows-packaging.md') +foreach ($name in @('release-current-coverage-20260906.md', 'release-save-as-runtime-packaging.md')) { + $inputs["harness/$name"] = Require-File (Join-Path $repository "harness/$name") +} + +# Resolve notice/source inputs before creating an output claim. No Cargo build occurs. +. (Join-Path $PSScriptRoot 'windows-notices.ps1') +$notices = Get-WindowsNotices $repository $Target $NoticeManifest $LlvmMinGWRoot +foreach ($name in $notices.Inputs.Keys) { + if ($inputs.Contains($name)) { throw "Notice collides with package input: $name" } + $inputs[$name] = $notices.Inputs[$name] + $inspectedHashes[$name] = $notices.Hashes[$name] +} + +$output = [IO.Path]::GetFullPath($OutputRoot) +$packageName = "ArcadiaStudio-$Version-$Target" +$stage = Join-Path $output $packageName +$archive = "$stage.zip" +$checksum = "$archive.sha256" +$lockPath = "$stage.packaging.lock" +foreach ($path in @($stage, $archive, $checksum, $lockPath)) { + if (Test-Path -LiteralPath $path) { throw "Output already exists; choose a fresh version or output root: $path" } +} +foreach ($directory in @('docs', 'examples', 'deployment')) { + $sourceRoot = [IO.Path]::GetFullPath((Join-Path $repository $directory)) + [IO.Path]::DirectorySeparatorChar + if (($stage + [IO.Path]::DirectorySeparatorChar).StartsWith($sourceRoot, [StringComparison]::OrdinalIgnoreCase)) { + throw 'Output must not be inside a packaged source directory.' + } +} +[void][IO.Directory]::CreateDirectory($output) +$lock = [IO.File]::Open($lockPath, [IO.FileMode]::CreateNew, [IO.FileAccess]::Write, [IO.FileShare]::None) +try { + [void][IO.Directory]::CreateDirectory($stage) + $inputHashes = [ordered]@{} + foreach ($name in $inputs.Keys) { + $destination = Join-Path $stage $name + [void][IO.Directory]::CreateDirectory([IO.Path]::GetDirectoryName($destination)) + $before = (Get-FileHash -LiteralPath $inputs[$name] -Algorithm SHA256).Hash.ToLowerInvariant() + if ($inspectedHashes.ContainsKey($name) -and $inspectedHashes[$name] -ne $before) { + throw "Input changed after dependency/notice inspection: $name" + } + [IO.File]::Copy($inputs[$name], $destination, $false) + if ((Get-FileHash -LiteralPath $destination -Algorithm SHA256).Hash.ToLowerInvariant() -ne $before) { + throw "Input changed during staging: $name" + } + $inputHashes[$name] = $before + } + $metadata = [ordered]@{ + version = $Version; target = $Target + acceptance = 'Packaging integrity only; native clean-environment verification required.' + build = 'Prebuilt inputs supplied by caller; this script does not establish build profile or source revision.' + imports = $imports; input_sha256 = $inputHashes + } + [IO.File]::WriteAllText((Join-Path $stage 'package.json'), ($metadata | ConvertTo-Json -Depth 6) + "`n", $utf8) + [IO.File]::WriteAllText((Join-Path $stage 'dependency-notices.json'), ($notices.Inventory | ConvertTo-Json -Depth 14) + "`n", $utf8) + $noticeIndex = @('Third-party notice inventory', '', 'Exact package versions, license expressions, source hashes and notice paths:', 'dependency-notices.json', '', 'License texts and embedded-asset notices are retained under licenses/.', 'Rust distribution library/copyright reports are retained under licenses/rust/.', 'Collection is conservative; it does not establish binary/source equivalence or constitute a universal license audit.', '') + foreach ($package in $notices.Inventory.packages) { $noticeIndex += "$($package.name) $($package.version) -- $($package.license) -- licenses/dependencies/$($package.name)-$($package.version)/" } + [IO.File]::WriteAllText((Join-Path $stage 'THIRD-PARTY-NOTICES.txt'), ($noticeIndex -join "`n") + "`n", $utf8) + $start = @' +Arcadia Studio portable Windows bundle + +Open arcadia-studio.exe. See docs/windows-packaging.md for runtime prerequisites +and verification, README.md for model workflows, and docs/server.md to run the +optional arcadia-server.exe. Examples are under examples/; Help is built in. + +Keep the bundled runtime DLL beside both executables when present. No compiler +installation or machine PATH change is required by the staged runtime closure. +This binary bundle does not include repository sources needed for Cargo/Docker +build commands. Packaging integrity is not native release acceptance; read +package.json and harness/release-current-coverage-20260906.md for limitations. +'@ + [IO.File]::WriteAllText((Join-Path $stage 'START-HERE.txt'), $start + "`n", $utf8) + $manifest = New-Object 'Collections.Generic.List[string]' + foreach ($file in Get-ChildItem -LiteralPath $stage -Recurse -File | Sort-Object FullName) { + $relative = $file.FullName.Substring($stage.Length + 1).Replace('\', '/') + $manifest.Add((Get-FileHash -LiteralPath $file.FullName -Algorithm SHA256).Hash.ToLowerInvariant() + ' ' + $relative) + } + [IO.File]::WriteAllText((Join-Path $stage 'SHA256SUMS.txt'), ($manifest -join "`n") + "`n", $utf8) + + Add-Type -AssemblyName System.IO.Compression + Add-Type -AssemblyName System.IO.Compression.FileSystem + $zipStream = [IO.File]::Open($archive, [IO.FileMode]::CreateNew) + $zip = New-Object IO.Compression.ZipArchive($zipStream, [IO.Compression.ZipArchiveMode]::Create) + try { + foreach ($file in Get-ChildItem -LiteralPath $stage -Recurse -File | Sort-Object FullName) { + $relative = $file.FullName.Substring($stage.Length + 1).Replace('\', '/') + $entry = $zip.CreateEntry("$packageName/$relative", [IO.Compression.CompressionLevel]::Optimal) + $entry.LastWriteTime = [DateTimeOffset]::new(2000, 1, 1, 0, 0, 0, [TimeSpan]::Zero) + $destination = $entry.Open() + $source = $file.OpenRead() + try { $source.CopyTo($destination) } finally { $source.Dispose(); $destination.Dispose() } + } + } finally { $zip.Dispose(); $zipStream.Dispose() } + + $zip = [IO.Compression.ZipFile]::OpenRead($archive) + try { + $seen = New-Object 'Collections.Generic.HashSet[string]' ([StringComparer]::OrdinalIgnoreCase) + foreach ($entry in $zip.Entries) { + if (-not $seen.Add($entry.FullName)) { throw "Duplicate archive entry: $($entry.FullName)" } + $relative = $entry.FullName.Substring($packageName.Length + 1) + $source = $entry.Open() + $sha = [Security.Cryptography.SHA256]::Create() + try { $actual = [BitConverter]::ToString($sha.ComputeHash($source)).Replace('-', '').ToLowerInvariant() } + finally { $sha.Dispose(); $source.Dispose() } + $expected = (Get-FileHash -LiteralPath (Join-Path $stage $relative) -Algorithm SHA256).Hash.ToLowerInvariant() + if ($actual -ne $expected) { throw "Archive mismatch: $relative" } + } + if ($seen.Count -ne @(Get-ChildItem -LiteralPath $stage -Recurse -File).Count) { throw 'Archive file count mismatch.' } + } finally { $zip.Dispose() } + $archiveHash = (Get-FileHash -LiteralPath $archive -Algorithm SHA256).Hash.ToLowerInvariant() + [IO.File]::WriteAllText($checksum, "$archiveHash $packageName.zip`n", $utf8) + [pscustomobject]@{ Directory = $stage; Archive = $archive; SHA256 = $archiveHash; Files = $seen.Count; NativeAcceptance = 'Pending' } +} finally { + $lock.Dispose() + # Keep the claim file and any partial outputs: retries never overwrite an earlier attempt. +} diff --git a/scripts/test-windows-notices.ps1 b/scripts/test-windows-notices.ps1 new file mode 100644 index 00000000..359d1985 --- /dev/null +++ b/scripts/test-windows-notices.ps1 @@ -0,0 +1,93 @@ +#Requires -Version 5.1 +# Standalone preflight contract checks; synthetic metadata, no Cargo/build/UI. +$ErrorActionPreference = 'Stop' +Set-StrictMode -Version Latest +. (Join-Path $PSScriptRoot 'windows-notices.ps1') +$fixture = Join-Path ([IO.Path]::GetTempPath()) ('arcadia-notices-' + [guid]::NewGuid().ToString('N')) +[void][IO.Directory]::CreateDirectory($fixture) +$utf8 = New-Object Text.UTF8Encoding($false) +function Write-Fixture([string]$Relative, [string]$Text) { + $path = Join-Path $fixture $Relative + [void][IO.Directory]::CreateDirectory([IO.Path]::GetDirectoryName($path)) + [IO.File]::WriteAllText($path, $Text, $utf8) + return $path +} +$null = Write-Fixture 'LICENSE' 'Synthetic workspace license for tests only' +$null = Write-Fixture 'Cargo.toml' '[workspace]' +$null = Write-Fixture 'rust/share/doc/rust/COPYRIGHT.html' 'Synthetic Rust distribution notice' +$null = Write-Fixture 'rust/share/doc/rust/COPYRIGHT-library.html' 'Synthetic Rust library notice' +$null = Write-Fixture 'rust/lib/fake.rlib' 'Synthetic target library' +$packages = @(); $nodes = @(); $lock = @() +foreach ($name in @('oa-desktop', 'oa-server', 'normal', 'build', 'dev-only', 'unreachable')) { + $external = $name -notlike 'oa-*' + $root = if ($external) { "registry/src/test/$name-1.0.0" } else { "workspace/$name" } + $manifest = Write-Fixture "$root/Cargo.toml" "[package]`nname = `"$name`"`nversion = `"1.0.0`"" + $null = Write-Fixture "$root/src/lib.rs" '// synthetic source' + $null = Write-Fixture "$root/LICENSE" "Synthetic notice for $name" + $source = if ($external) { 'registry+https://github.com/rust-lang/crates.io-index' } else { $null } + $id = $name + if ($external) { + $archive = Write-Fixture "registry/cache/test/$name-1.0.0.crate" "Synthetic cached archive $name" + $sha = (Get-FileHash -LiteralPath $archive -Algorithm SHA256).Hash.ToLowerInvariant() + $lock += "[[package]]`nname = `"$name`"`nversion = `"1.0.0`"`nsource = `"$source`"`nchecksum = `"$sha`"`n" + } + $packages += [pscustomobject]@{ id = $id; name = $name; version = '1.0.0'; source = $source; license = 'MIT'; license_file = $null; manifest_path = $manifest; repository = 'https://example.invalid/test' } + $deps = @() + if ($name -eq 'oa-desktop') { + foreach ($pair in @(@('normal', $null), @('build', 'build'), @('dev-only', 'dev'))) { + $deps += [pscustomobject]@{ pkg = $pair[0]; dep_kinds = @([pscustomobject]@{ kind = $pair[1] }) } + } + } + $nodes += [pscustomobject]@{ id = $id; deps = $deps; features = @() } +} +$null = Write-Fixture 'Cargo.lock' ($lock -join "`n") +$script:FixtureMetadata = [pscustomobject]@{ packages = $packages; workspace_members = @('oa-desktop', 'oa-server'); resolve = [pscustomobject]@{ nodes = $nodes } } +function cargo { + if (($args -join ' ') -notmatch '--locked --offline' -or ($args -join ' ') -notmatch '--filter-platform x86_64-pc-windows-msvc') { throw 'Unexpected Cargo command' } + $global:LASTEXITCODE = 0 + $script:FixtureMetadata | ConvertTo-Json -Depth 12 +} +function rustc { + $global:LASTEXITCODE = 0 + if ($args[0] -eq '-Vv') { return 'rustc synthetic-test' } + if ($args[1] -eq 'sysroot') { return (Join-Path $fixture 'rust') } + if ($args[1] -eq 'target-libdir') { return (Join-Path $fixture 'rust/lib') } + throw 'Unexpected Rust command' +} +$manifestPath = Write-Fixture 'supplements/manifest.json' '{"packages":[],"assets":[],"runtimes":[]}' +function Collect { Get-WindowsNotices $fixture x86_64-pc-windows-msvc $manifestPath '' } +function Assert([bool]$Condition, [string]$Message) { if (-not $Condition) { throw $Message } } +function Refuses([string]$Pattern) { + try { $null = Collect; throw 'Expected preflight refusal' } catch { + if ($_.Exception.Message -notmatch $Pattern) { throw } + } +} +$result = Collect +$names = @($result.Inventory.packages | ForEach-Object { $_.name }) +Assert ($names.Count -eq 4 -and $names -contains 'normal' -and $names -contains 'build' -and $names -notcontains 'dev-only' -and $names -notcontains 'unreachable') 'Normal/build graph selection failed' +Assert ($result.Inventory.rust.notices.Count -eq 2) 'Rust reports missing' +$before = ($result.Inventory.packages | Where-Object name -eq normal).local_source_sha256['src/lib.rs'] +$null = Write-Fixture 'registry/src/test/normal-1.0.0/src/lib.rs' '// locally changed source' +$after = ((Collect).Inventory.packages | Where-Object name -eq normal).local_source_sha256['src/lib.rs'] +Assert ($before -ne $after) 'Local source hash was replaced by a cached checksum' +$null = Write-Fixture 'registry/cache/test/normal-1.0.0.crate' 'Tampered archive' +Refuses 'Cached crate archive differs' +$null = Write-Fixture 'registry/cache/test/normal-1.0.0.crate' 'Synthetic cached archive normal' +$null = Write-Fixture 'registry/src/test/normal-1.0.0/src/lib.rs' 'static FONT: &[u8] = include_bytes!("../font.ttf");' +$null = Write-Fixture 'registry/src/test/normal-1.0.0/font.ttf' 'Synthetic font bytes' +Refuses 'Embedded font requires exact notice mapping' +$null = Write-Fixture 'registry/src/test/normal-1.0.0/src/lib.rs' '// no font' +$pkg = $script:FixtureMetadata.packages | Where-Object name -eq normal +$pkg.source = 'git+https://example.invalid/test#123' +Refuses 'Unsupported source requiring deliberate review' +$pkg.source = 'registry+https://github.com/rust-lang/crates.io-index' +$normalHash = (Get-FileHash -LiteralPath (Join-Path $fixture 'registry/cache/test/normal-1.0.0.crate') -Algorithm SHA256).Hash.ToLowerInvariant() +$bad = [ordered]@{ packages = @([ordered]@{ name = 'normal'; version = '1.0.0'; checksum = $normalHash; notices = @([ordered]@{ file = '../LICENSE'; sha256 = ('0' * 64); url = 'https://example.invalid/test' }) }); assets = @(); runtimes = @() } +$null = Write-Fixture 'supplements/manifest.json' ($bad | ConvertTo-Json -Depth 8) +Refuses 'Notice path escapes source root' +$bad.packages[0].notices[0].file = 'NOTICE.txt' +$null = Write-Fixture 'supplements/NOTICE.txt' 'Synthetic supplemental notice' +$null = Write-Fixture 'supplements/manifest.json' ($bad | ConvertTo-Json -Depth 8) +Refuses 'Supplement changed' +Write-Output 'PASS: normal/build selection, Rust reports, actual local source hashes, stale archive refusal, unmapped font refusal, unsupported source refusal, traversal refusal, altered supplement refusal.' +Write-Output "Synthetic fixture retained for inspection: $fixture" diff --git a/scripts/windows-notices.ps1 b/scripts/windows-notices.ps1 new file mode 100644 index 00000000..1bee39cf --- /dev/null +++ b/scripts/windows-notices.ps1 @@ -0,0 +1,160 @@ +#Requires -Version 5.1 +# Read-only dependency preflight. Does not build, download, or change a Cargo target. +function Get-WindowsNotices([string]$Repository, [string]$Target, [string]$NoticeManifest, [string]$LlvmMinGWRoot) { + function Hash-File([string]$Path) { + $stream = [IO.File]::OpenRead($Path) + $sha = [Security.Cryptography.SHA256]::Create() + try { return [BitConverter]::ToString($sha.ComputeHash($stream)).Replace('-', '').ToLowerInvariant() } + finally { $sha.Dispose(); $stream.Dispose() } + } + function Under-Root([string]$Root, [string]$Relative) { + $base = [IO.Path]::GetFullPath($Root).TrimEnd('\', '/') + [IO.Path]::DirectorySeparatorChar + $path = [IO.Path]::GetFullPath((Join-Path $base $Relative)) + if (-not $path.StartsWith($base, [StringComparison]::OrdinalIgnoreCase)) { throw "Notice path escapes source root: $Relative" } + if (-not [IO.File]::Exists($path)) { throw "Required notice/source missing: $path" } + return $path + } + $inputs = [ordered]@{} + $hashes = @{} + $failures = New-Object 'Collections.Generic.List[string]' + $records = New-Object 'Collections.Generic.List[object]' + $supplements = @{ packages = @(); assets = @(); runtimes = @() } + if (Test-Path -LiteralPath $NoticeManifest -PathType Leaf) { $supplements = Get-Content -LiteralPath $NoticeManifest -Raw | ConvertFrom-Json } + $supplementRoot = Split-Path -Parent ([IO.Path]::GetFullPath($NoticeManifest)) + function Add-Notice([string]$Source, [string]$Destination) { + if ($inputs.Contains($Destination) -and $inputs[$Destination] -ne $Source) { throw "Duplicate notice destination: $Destination" } + $inputs[$Destination] = $Source + $hashes[$Destination] = Hash-File $Source + return [ordered]@{ path = $Destination; sha256 = $hashes[$Destination] } + } + function Add-Supplements($Entries, [string]$Prefix) { + foreach ($entry in $Entries) { + if ($entry.sha256 -notmatch '^[a-f0-9]{64}$' -or $entry.url -notmatch '^https://') { throw 'Supplement requires SHA-256 and an HTTPS provenance URL.' } + $source = Under-Root $supplementRoot $entry.file + if ((Hash-File $source) -ne $entry.sha256) { throw "Supplement changed: $($entry.file)" } + $record = Add-Notice $source "$Prefix/$([IO.Path]::GetFileName($source))" + $record['origin'] = $entry.url + $record + } + } + $lock = Under-Root $Repository 'Cargo.lock' + $lockHash = Hash-File $lock + $locked = @{} + foreach ($block in ([IO.File]::ReadAllText($lock) -split '(?m)^\[\[package\]\]\s*$')) { + $fields = @{} + foreach ($field in [regex]::Matches($block, '(?m)^(name|version|source|checksum) = "([^"]+)"\s*$')) { $fields[$field.Groups[1].Value] = $field.Groups[2].Value } + if ($fields.ContainsKey('source')) { $locked["$($fields.name)|$($fields.version)|$($fields.source)"] = $fields } + } + $raw = @(& cargo metadata --locked --offline --manifest-path (Join-Path $Repository 'Cargo.toml') --filter-platform $Target --format-version 1) + if ($LASTEXITCODE -ne 0) { throw 'Locked offline Cargo metadata failed; prepare the matching dependency cache first.' } + $metadata = ($raw -join "`n") | ConvertFrom-Json + if ((Hash-File $lock) -ne $lockHash) { throw 'Cargo.lock changed during notice preflight.' } + $packages = @{}; $nodes = @{} + foreach ($package in $metadata.packages) { $packages[$package.id] = $package } + foreach ($node in $metadata.resolve.nodes) { $nodes[$node.id] = $node } + $queue = New-Object 'Collections.Generic.Queue[string]' + foreach ($name in @('oa-desktop', 'oa-server')) { + $roots = @($metadata.packages | Where-Object { $_.name -eq $name -and $metadata.workspace_members -contains $_.id }) + if ($roots.Count -ne 1) { throw "Expected one workspace package: $name" } + $queue.Enqueue($roots[0].id) + } + $selected = New-Object 'Collections.Generic.HashSet[string]' + while ($queue.Count -gt 0) { + $id = $queue.Dequeue() + if (-not $selected.Add($id)) { continue } + if (-not $nodes.ContainsKey($id)) { throw "Unresolved dependency node: $id" } + foreach ($dep in $nodes[$id].deps) { + if (@($dep.dep_kinds | Where-Object { $_.kind -ne 'dev' }).Count -gt 0) { $queue.Enqueue($dep.pkg) } + } + } + foreach ($id in @($selected | Sort-Object)) { + $package = $packages[$id] + $root = Split-Path -Parent $package.manifest_path + $key = "$($package.name)-$($package.version)" + if ($key -notmatch '^[A-Za-z0-9._+-]+$') { throw "Unsafe package identity: $key" } + $prefix = "licenses/dependencies/$key" + $registryChecksum = $null + if ($package.source) { + if ($package.source -notlike 'registry+*') { $failures.Add("Unsupported source requiring deliberate review: $id"); continue } + $lockKey = "$($package.name)|$($package.version)|$($package.source)" + if (-not $locked.ContainsKey($lockKey)) { throw "Source is absent from Cargo.lock: $id" } + $registryChecksum = $locked[$lockKey].checksum + if ($registryChecksum -notmatch '^[a-f0-9]{64}$') { throw "Missing registry checksum: $key" } + $registryRoot = Split-Path -Parent (Split-Path -Parent (Split-Path -Parent $root)) + $archivePath = Under-Root $registryRoot "cache/$([IO.Path]::GetFileName((Split-Path -Parent $root)))/$key.crate" + if ((Hash-File $archivePath) -ne $registryChecksum) { throw "Cached crate archive differs from Cargo.lock: $key" } + } + # Hash actual local files (not merely Cargo's cached checksum claims). + $sourceHashes = [ordered]@{} + $notices = New-Object 'Collections.Generic.List[object]' + $fontReferences = New-Object 'Collections.Generic.HashSet[string]' + foreach ($file in @(Get-ChildItem -LiteralPath $root -Recurse -File | Sort-Object FullName)) { + if (($file.Attributes -band [IO.FileAttributes]::ReparsePoint) -ne 0) { throw "Source reparse point requires review: $($file.FullName)" } + $relative = $file.FullName.Substring($root.Length + 1).Replace('\', '/') + if ($relative -match '^target/' -or $file.Name -eq '.cargo-ok') { continue } + $sourceHashes[$relative] = Hash-File $file.FullName + if ($file.Name -match '(^|[-._])(LICENSE|LICENCE|COPYING|NOTICE|OFL|UNLICENSE|COPYRIGHT)([-._]|$)') { + $notices.Add((Add-Notice $file.FullName "$prefix/$relative")) + } + # Literal embedded fonts in production/build source, including feature-gated ones + # conservatively. Tests/examples/benches are excluded, never all font files by glob. + if ($file.Extension -eq '.rs' -and $relative -notmatch '^(tests|examples|benches)/') { + $text = [IO.File]::ReadAllText($file.FullName) + foreach ($match in [regex]::Matches($text, 'include_bytes!\s*\(\s*"([^"]+\.(?:ttf|otf|woff2?))"')) { + $font = Under-Root $root ([IO.Path]::GetFullPath((Join-Path $file.DirectoryName $match.Groups[1].Value)).Substring($root.Length + 1)) + [void]$fontReferences.Add($font.Substring($root.Length + 1).Replace('\', '/')) + } + } + } + if (-not $package.source) { $notices.Add((Add-Notice (Under-Root $Repository 'LICENSE') "$prefix/workspace-LICENSE")) } + $matching = @($supplements.packages | Where-Object { $_.name -eq $package.name -and $_.version -eq $package.version }) + foreach ($supplement in $matching) { + if ($supplement.checksum -ne $registryChecksum) { throw "Supplement is for different registry source: $key" } + foreach ($notice in @(Add-Supplements $supplement.notices "$prefix/supplement")) { $notices.Add($notice) } + } + if (-not $package.license -and -not $package.license_file) { $failures.Add("No license declaration: $key") } + if ($package.license_file) { + $licensePath = Under-Root $root $package.license_file + $notices.Add((Add-Notice $licensePath "$prefix/declared-license/$([IO.Path]::GetFileName($licensePath))")) + } + if ($notices.Count -eq 0) { $failures.Add("No notice text: $key ($($package.license)); provide checksum-bound supplement") } + $assets = New-Object 'Collections.Generic.List[object]' + foreach ($relative in @($fontReferences | Sort-Object)) { + $assetHash = Hash-File (Under-Root $root $relative) + $matches = @($supplements.assets | Where-Object { $_.name -eq $package.name -and $_.version -eq $package.version -and $_.path -eq $relative }) + if ($matches.Count -ne 1 -or $matches[0].sha256 -ne $assetHash) { $failures.Add("Embedded font requires exact notice mapping: $key/$relative ($assetHash)"); continue } + $assetNotices = @(Add-Supplements $matches[0].notices "$prefix/assets/$([IO.Path]::GetFileName($relative))") + if ($assetNotices.Count -eq 0) { $failures.Add("No embedded font notice: $key/$relative") } + $assets.Add([ordered]@{ path = $relative; sha256 = $assetHash; license = $matches[0].license; notices = $assetNotices }) + } + $records.Add([ordered]@{ id = $id; name = $package.name; version = $package.version; license = $package.license; license_file = $package.license_file; repository = $package.repository; source = $package.source; registry_checksum = $registryChecksum; features = $nodes[$id].features; local_source_sha256 = $sourceHashes; notices = @($notices.ToArray()); embedded_fonts = @($assets.ToArray()) }) + } + $rustVersion = @(& rustc -Vv) + if ($LASTEXITCODE -ne 0) { throw 'Unable to identify the selected Rust compiler.' } + $sysroot = @(& rustc --print sysroot) + if ($LASTEXITCODE -ne 0 -or $sysroot.Count -ne 1) { throw 'Unable to locate the selected Rust distribution.' } + $targetLib = @(& rustc --print target-libdir --target $Target) + if ($LASTEXITCODE -ne 0 -or $targetLib.Count -ne 1 -or -not (Test-Path -LiteralPath $targetLib[0] -PathType Container)) { throw "Rust target libraries missing: $Target" } + $rustNotices = @() + foreach ($path in @('share/doc/rust/COPYRIGHT.html', 'share/doc/rust/COPYRIGHT-library.html')) { + $rustNotices += Add-Notice (Under-Root $sysroot[0] $path) "licenses/rust/$([IO.Path]::GetFileName($path))" + } + foreach ($path in @('share/doc/COPYING3', 'share/doc/COPYING.RUNTIME')) { + if (Test-Path -LiteralPath (Join-Path $sysroot[0] $path) -PathType Leaf) { $rustNotices += Add-Notice (Join-Path $sysroot[0] $path) "licenses/rust/$([IO.Path]::GetFileName($path))" } + } + $rustLibraries = [ordered]@{} + foreach ($file in @(Get-ChildItem -LiteralPath $targetLib[0] -File | Sort-Object Name)) { $rustLibraries[$file.Name] = Hash-File $file.FullName } + $runtime = $null + if ($LlvmMinGWRoot) { + $runtimeHash = Hash-File (Under-Root $LlvmMinGWRoot 'x86_64-w64-mingw32/bin/libunwind.dll') + $matches = @($supplements.runtimes | Where-Object { $_.sha256 -eq $runtimeHash -and $_.name -eq 'libunwind.dll' }) + if ($matches.Count -ne 1) { $failures.Add("libunwind.dll requires exact component notice mapping: $runtimeHash") } + else { + $runtimeNotices = @(Add-Supplements $matches[0].notices 'licenses/llvm-libunwind') + if (-not $runtimeNotices.Count) { $failures.Add('Missing libunwind component notices') } + $runtime = [ordered]@{ name = 'libunwind.dll'; sha256 = $runtimeHash; provenance = $matches[0].provenance; notices = $runtimeNotices } + } + } + if ($failures.Count -gt 0) { throw ("Notice preflight unresolved:`n - " + ($failures -join "`n - ")) } + return [pscustomobject]@{ Inputs = $inputs; Hashes = $hashes; Inventory = [ordered]@{ format_version = 1; target = $Target; cargo_lock_sha256 = $lockHash; scope = 'Target-filtered locked offline metadata; transitive normal/build edges from oa-desktop and oa-server. Workspace feature unification may include a conservative superset. No binary/source equivalence or universal license audit claimed.'; packages = @($records.ToArray()); rust = [ordered]@{ version = $rustVersion; notices = $rustNotices; target_libraries_sha256 = $rustLibraries }; runtime = $runtime } } +} diff --git a/scripts/windows-notices/CC0-1.0.txt b/scripts/windows-notices/CC0-1.0.txt new file mode 100644 index 00000000..0e259d42 --- /dev/null +++ b/scripts/windows-notices/CC0-1.0.txt @@ -0,0 +1,121 @@ +Creative Commons Legal Code + +CC0 1.0 Universal + + CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE + LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN + ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS + INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES + REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS + PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM + THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED + HEREUNDER. + +Statement of Purpose + +The laws of most jurisdictions throughout the world automatically confer +exclusive Copyright and Related Rights (defined below) upon the creator +and subsequent owner(s) (each and all, an "owner") of an original work of +authorship and/or a database (each, a "Work"). + +Certain owners wish to permanently relinquish those rights to a Work for +the purpose of contributing to a commons of creative, cultural and +scientific works ("Commons") that the public can reliably and without fear +of later claims of infringement build upon, modify, incorporate in other +works, reuse and redistribute as freely as possible in any form whatsoever +and for any purposes, including without limitation commercial purposes. +These owners may contribute to the Commons to promote the ideal of a free +culture and the further production of creative, cultural and scientific +works, or to gain reputation or greater distribution for their Work in +part through the use and efforts of others. + +For these and/or other purposes and motivations, and without any +expectation of additional consideration or compensation, the person +associating CC0 with a Work (the "Affirmer"), to the extent that he or she +is an owner of Copyright and Related Rights in the Work, voluntarily +elects to apply CC0 to the Work and publicly distribute the Work under its +terms, with knowledge of his or her Copyright and Related Rights in the +Work and the meaning and intended legal effect of CC0 on those rights. + +1. Copyright and Related Rights. A Work made available under CC0 may be +protected by copyright and related or neighboring rights ("Copyright and +Related Rights"). Copyright and Related Rights include, but are not +limited to, the following: + + i. the right to reproduce, adapt, distribute, perform, display, + communicate, and translate a Work; + ii. moral rights retained by the original author(s) and/or performer(s); +iii. publicity and privacy rights pertaining to a person's image or + likeness depicted in a Work; + iv. rights protecting against unfair competition in regards to a Work, + subject to the limitations in paragraph 4(a), below; + v. rights protecting the extraction, dissemination, use and reuse of data + in a Work; + vi. database rights (such as those arising under Directive 96/9/EC of the + European Parliament and of the Council of 11 March 1996 on the legal + protection of databases, and under any national implementation + thereof, including any amended or successor version of such + directive); and +vii. other similar, equivalent or corresponding rights throughout the + world based on applicable law or treaty, and any national + implementations thereof. + +2. Waiver. To the greatest extent permitted by, but not in contravention +of, applicable law, Affirmer hereby overtly, fully, permanently, +irrevocably and unconditionally waives, abandons, and surrenders all of +Affirmer's Copyright and Related Rights and associated claims and causes +of action, whether now known or unknown (including existing as well as +future claims and causes of action), in the Work (i) in all territories +worldwide, (ii) for the maximum duration provided by applicable law or +treaty (including future time extensions), (iii) in any current or future +medium and for any number of copies, and (iv) for any purpose whatsoever, +including without limitation commercial, advertising or promotional +purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each +member of the public at large and to the detriment of Affirmer's heirs and +successors, fully intending that such Waiver shall not be subject to +revocation, rescission, cancellation, termination, or any other legal or +equitable action to disrupt the quiet enjoyment of the Work by the public +as contemplated by Affirmer's express Statement of Purpose. + +3. Public License Fallback. Should any part of the Waiver for any reason +be judged legally invalid or ineffective under applicable law, then the +Waiver shall be preserved to the maximum extent permitted taking into +account Affirmer's express Statement of Purpose. In addition, to the +extent the Waiver is so judged Affirmer hereby grants to each affected +person a royalty-free, non transferable, non sublicensable, non exclusive, +irrevocable and unconditional license to exercise Affirmer's Copyright and +Related Rights in the Work (i) in all territories worldwide, (ii) for the +maximum duration provided by applicable law or treaty (including future +time extensions), (iii) in any current or future medium and for any number +of copies, and (iv) for any purpose whatsoever, including without +limitation commercial, advertising or promotional purposes (the +"License"). The License shall be deemed effective as of the date CC0 was +applied by Affirmer to the Work. Should any part of the License for any +reason be judged legally invalid or ineffective under applicable law, such +partial invalidity or ineffectiveness shall not invalidate the remainder +of the License, and in such case Affirmer hereby affirms that he or she +will not (i) exercise any of his or her remaining Copyright and Related +Rights in the Work or (ii) assert any associated claims and causes of +action with respect to the Work, in either case contrary to Affirmer's +express Statement of Purpose. + +4. Limitations and Disclaimers. + + a. No trademark or patent rights held by Affirmer are waived, abandoned, + surrendered, licensed or otherwise affected by this document. + b. Affirmer offers the Work as-is and makes no representations or + warranties of any kind concerning the Work, express, implied, + statutory or otherwise, including without limitation warranties of + title, merchantability, fitness for a particular purpose, non + infringement, or the absence of latent or other defects, accuracy, or + the present or absence of errors, whether or not discoverable, all to + the greatest extent permissible under applicable law. + c. Affirmer disclaims responsibility for clearing rights of other persons + that may apply to the Work or any use thereof, including without + limitation any person's Copyright and Related Rights in the Work. + Further, Affirmer disclaims responsibility for obtaining any necessary + consents, permissions or other rights required for any use of the + Work. + d. Affirmer understands and acknowledges that Creative Commons is not a + party to this document and has no duty or obligation with respect to + this CC0 or use of the Work. diff --git a/scripts/windows-notices/DoumanAsh-clipboard-win-3b27cf2bfd1a-LICENSE b/scripts/windows-notices/DoumanAsh-clipboard-win-3b27cf2bfd1a-LICENSE new file mode 100644 index 00000000..36b7cd93 --- /dev/null +++ b/scripts/windows-notices/DoumanAsh-clipboard-win-3b27cf2bfd1a-LICENSE @@ -0,0 +1,23 @@ +Boost Software License - Version 1.0 - August 17th, 2003 + +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/scripts/windows-notices/Fira-OFL.txt b/scripts/windows-notices/Fira-OFL.txt new file mode 100644 index 00000000..5e4608f2 --- /dev/null +++ b/scripts/windows-notices/Fira-OFL.txt @@ -0,0 +1,93 @@ +Digitized data copyright (c) 2012-2015, The Mozilla Foundation and Telefonica S.A. + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/scripts/windows-notices/FiraSans-Regular-NOTICE.txt b/scripts/windows-notices/FiraSans-Regular-NOTICE.txt new file mode 100644 index 00000000..0988a708 --- /dev/null +++ b/scripts/windows-notices/FiraSans-Regular-NOTICE.txt @@ -0,0 +1,6 @@ +Extracted from the exact FiraSans-Regular.ttf name table (asset SHA-256 3dabf3d48bf4599f95cffd92f99ea426a014d5311f52a5eb5ec3af265e97cd97). + +0: Digitized data copyright 2012-2016, The Mozilla Foundation and Telefonica S.A. +5: Version 4.203 +13: Licensed under the Open Font License, version 1.1 or later +14: http://scripts.sil.org/OFL diff --git a/scripts/windows-notices/Iced-Icons-NOTICE.txt b/scripts/windows-notices/Iced-Icons-NOTICE.txt new file mode 100644 index 00000000..22bd4f1a --- /dev/null +++ b/scripts/windows-notices/Iced-Icons-NOTICE.txt @@ -0,0 +1,33 @@ +Iced-Icons.ttf, distributed with iced_graphics 0.14.0 +SHA-256: 2289860dbd2201295bf451deb155561abf4b1fb06899d6f1f81eb294bfebb7ec + +The exact font's name table contains: + +Copyright (C) 2020 by original authors @ fontello.com +Iced-Icons +Regular +FontForge 2.0 : Iced Icons : 4-2-2023 +Iced Icons +Version 1.0 +Generated by svg2ttf from Fontello project. +http://fontello.com + +The distributed iced_graphics 0.14.0 package declares MIT. Its repository at +3997291f318a8bc06fa522f5579836fb3feb94df supplies the accompanying Iced MIT +license. The exact crate and pinned repository contain no separate font +license or explicit exception for this asset. We retain both that distributed +license and the font's separate copyright metadata; we do not replace the +font's authors with the Iced code copyright attribution. + +Provenance limitation: the inspected history does not contain a Fontello +configuration assigning original glyph authors. The font was introduced at +2c8ba652a7929ac6c2af28ac60a8bd4b8e8e2f10 under wgpu/src/text/icons.ttf, +subsequently moved/renamed, with an Iced logo and scroll arrows added later. +The copyright metadata alone does not establish an exclusion from the +distributed MIT declaration. This record is not a universal provenance audit. + +Exact asset source: +https://raw.githubusercontent.com/iced-rs/iced/3997291f318a8bc06fa522f5579836fb3feb94df/graphics/fonts/Iced-Icons.ttf + +Inspected glyph mapping: +U+E800 down-dir; U+E801 iced; U+E802..U+E805 scroll arrows; U+F00C ok. diff --git a/scripts/windows-notices/aclysma-profiling-8271551172eb-LICENSE-APACHE b/scripts/windows-notices/aclysma-profiling-8271551172eb-LICENSE-APACHE new file mode 100644 index 00000000..139dce95 --- /dev/null +++ b/scripts/windows-notices/aclysma-profiling-8271551172eb-LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. \ No newline at end of file diff --git a/scripts/windows-notices/aclysma-profiling-8271551172eb-LICENSE-MIT b/scripts/windows-notices/aclysma-profiling-8271551172eb-LICENSE-MIT new file mode 100644 index 00000000..6a84e059 --- /dev/null +++ b/scripts/windows-notices/aclysma-profiling-8271551172eb-LICENSE-MIT @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Philip Degarmo and other contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/scripts/windows-notices/brendanzab-gl-rs-ea503e8d5fb6-LICENSE b/scripts/windows-notices/brendanzab-gl-rs-ea503e8d5fb6-LICENSE new file mode 100644 index 00000000..d6456956 --- /dev/null +++ b/scripts/windows-notices/brendanzab-gl-rs-ea503e8d5fb6-LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/scripts/windows-notices/brendanzab-gl-rs-f150967b1c44-LICENSE b/scripts/windows-notices/brendanzab-gl-rs-f150967b1c44-LICENSE new file mode 100644 index 00000000..d6456956 --- /dev/null +++ b/scripts/windows-notices/brendanzab-gl-rs-f150967b1c44-LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/scripts/windows-notices/gfx-rs-rspirv-e91a5debc10c-LICENSE b/scripts/windows-notices/gfx-rs-rspirv-e91a5debc10c-LICENSE new file mode 100644 index 00000000..d6456956 --- /dev/null +++ b/scripts/windows-notices/gfx-rs-rspirv-e91a5debc10c-LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/scripts/windows-notices/hexf-parse-0.2.1-Cargo.toml b/scripts/windows-notices/hexf-parse-0.2.1-Cargo.toml new file mode 100644 index 00000000..2833f557 --- /dev/null +++ b/scripts/windows-notices/hexf-parse-0.2.1-Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "hexf-parse" +version = "0.2.1" +authors = ["Kang Seonghoon "] + +description = "Parses hexadecimal floats (see also hexf)" +homepage = "https://github.com/lifthrasiir/hexf" +documentation = "https://docs.rs/hexf-parse/" +repository = "https://github.com/lifthrasiir/hexf" +license = "CC0-1.0" +edition = "2018" diff --git a/scripts/windows-notices/iced-rs-iced-3997291f318a-LICENSE b/scripts/windows-notices/iced-rs-iced-3997291f318a-LICENSE new file mode 100644 index 00000000..f1148e26 --- /dev/null +++ b/scripts/windows-notices/iced-rs-iced-3997291f318a-LICENSE @@ -0,0 +1,18 @@ +Copyright 2019 Héctor Ramón, Iced contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/scripts/windows-notices/iced-rs-iced-54abf81d13fe-LICENSE b/scripts/windows-notices/iced-rs-iced-54abf81d13fe-LICENSE new file mode 100644 index 00000000..f1148e26 --- /dev/null +++ b/scripts/windows-notices/iced-rs-iced-54abf81d13fe-LICENSE @@ -0,0 +1,18 @@ +Copyright 2019 Héctor Ramón, Iced contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/scripts/windows-notices/llvm-ea7d852a70e8-libunwind-LICENSE.TXT b/scripts/windows-notices/llvm-ea7d852a70e8-libunwind-LICENSE.TXT new file mode 100644 index 00000000..1e312062 --- /dev/null +++ b/scripts/windows-notices/llvm-ea7d852a70e8-libunwind-LICENSE.TXT @@ -0,0 +1,311 @@ +============================================================================== +The LLVM Project is under the Apache License v2.0 with LLVM Exceptions: +============================================================================== + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +---- LLVM Exceptions to the Apache 2.0 License ---- + +As an exception, if, as a result of your compiling your source code, portions +of this Software are embedded into an Object form of such source code, you +may redistribute such embedded portions in such Object form without complying +with the conditions of Sections 4(a), 4(b) and 4(d) of the License. + +In addition, if you combine or link compiled forms of this Software with +software that is licensed under the GPLv2 ("Combined Software") and if a +court of competent jurisdiction determines that the patent provision (Section +3), the indemnity provision (Section 9) or other Section of the License +conflicts with the conditions of the GPLv2, you may retroactively and +prospectively choose to deem waived or otherwise exclude such Section(s) of +the License, but only in their entirety and only with respect to the Combined +Software. + +============================================================================== +Software from third parties included in the LLVM Project: +============================================================================== +The LLVM Project contains third party software which is under different license +terms. All such code will be identified clearly using at least one of two +mechanisms: +1) It will be in a separate directory tree with its own `LICENSE.txt` or + `LICENSE` file at the top containing the specific license and restrictions + which apply to that software, or +2) It will contain specific license and restriction terms at the top of every + file. + +============================================================================== +Legacy LLVM License (https://llvm.org/docs/DeveloperPolicy.html#legacy): +============================================================================== + +The libunwind library is dual licensed under both the University of Illinois +"BSD-Like" license and the MIT license. As a user of this code you may choose +to use it under either license. As a contributor, you agree to allow your code +to be used under both. + +Full text of the relevant licenses is included below. + +============================================================================== + +University of Illinois/NCSA +Open Source License + +Copyright (c) 2009-2019 by the contributors listed in CREDITS.TXT + +All rights reserved. + +Developed by: + + LLVM Team + + University of Illinois at Urbana-Champaign + + http://llvm.org + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal with +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimers. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimers in the + documentation and/or other materials provided with the distribution. + + * Neither the names of the LLVM Team, University of Illinois at + Urbana-Champaign, nor the names of its contributors may be used to + endorse or promote products derived from this Software without specific + prior written permission. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE +SOFTWARE. + +============================================================================== + +Copyright (c) 2009-2014 by the contributors listed in CREDITS.TXT + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/scripts/windows-notices/manifest.json b/scripts/windows-notices/manifest.json new file mode 100644 index 00000000..26dbbb63 --- /dev/null +++ b/scripts/windows-notices/manifest.json @@ -0,0 +1,417 @@ +{ + "packages": [ + { + "name": "iced_core", + "version": "0.14.0", + "checksum": "91ab1937d699403e7e69252ae743a902bcee9f4ab2052cc4c9a46fcf34729d85", + "notices": [ + { + "file": "iced-rs-iced-3997291f318a-LICENSE", + "sha256": "fc9086ba4eba4b77e4c603906362742bc3715ffa2c7810c6074fe7780d3a0398", + "url": "https://raw.githubusercontent.com/iced-rs/iced/3997291f318a8bc06fa522f5579836fb3feb94df/LICENSE" + } + ] + }, + { + "name": "iced_debug", + "version": "0.14.0", + "checksum": "25035ab0215a620e53f4103e36fc4e59a1fb2817e4bfc38a30ad27b4202ea0be", + "notices": [ + { + "file": "iced-rs-iced-3997291f318a-LICENSE", + "sha256": "fc9086ba4eba4b77e4c603906362742bc3715ffa2c7810c6074fe7780d3a0398", + "url": "https://raw.githubusercontent.com/iced-rs/iced/3997291f318a8bc06fa522f5579836fb3feb94df/LICENSE" + } + ] + }, + { + "name": "iced_futures", + "version": "0.14.0", + "checksum": "8c0c85ccad42dfbec7293c36c018af0ea0dbcc52d137a4a9a0b0f6822a3fdf0a", + "notices": [ + { + "file": "iced-rs-iced-3997291f318a-LICENSE", + "sha256": "fc9086ba4eba4b77e4c603906362742bc3715ffa2c7810c6074fe7780d3a0398", + "url": "https://raw.githubusercontent.com/iced-rs/iced/3997291f318a8bc06fa522f5579836fb3feb94df/LICENSE" + } + ] + }, + { + "name": "iced_renderer", + "version": "0.14.0", + "checksum": "250cc0802408e8c077986ec56c7d07c65f423ee658a4b9fd795a1f2aae5dac05", + "notices": [ + { + "file": "iced-rs-iced-3997291f318a-LICENSE", + "sha256": "fc9086ba4eba4b77e4c603906362742bc3715ffa2c7810c6074fe7780d3a0398", + "url": "https://raw.githubusercontent.com/iced-rs/iced/3997291f318a8bc06fa522f5579836fb3feb94df/LICENSE" + } + ] + }, + { + "name": "iced_runtime", + "version": "0.14.0", + "checksum": "d1889b819ce4c06674183242e336c8d49465665441396914dc07cc86f44fa8d4", + "notices": [ + { + "file": "iced-rs-iced-3997291f318a-LICENSE", + "sha256": "fc9086ba4eba4b77e4c603906362742bc3715ffa2c7810c6074fe7780d3a0398", + "url": "https://raw.githubusercontent.com/iced-rs/iced/3997291f318a8bc06fa522f5579836fb3feb94df/LICENSE" + } + ] + }, + { + "name": "iced_widget", + "version": "0.14.2", + "checksum": "b1596afa0d3109c2618e8bc12bae6c11d3064df8f95c42dfce570397dbe957ab", + "notices": [ + { + "file": "iced-rs-iced-54abf81d13fe-LICENSE", + "sha256": "fc9086ba4eba4b77e4c603906362742bc3715ffa2c7810c6074fe7780d3a0398", + "url": "https://raw.githubusercontent.com/iced-rs/iced/54abf81d13fec06d4d9ac754b03ce2c3313e8a1f/LICENSE" + } + ] + }, + { + "name": "iced_winit", + "version": "0.14.0", + "checksum": "8b7dbedc47562d1de3b9707d939f678b88c382004b7ab5a18f7a7dd723162d75", + "notices": [ + { + "file": "iced-rs-iced-3997291f318a-LICENSE", + "sha256": "fc9086ba4eba4b77e4c603906362742bc3715ffa2c7810c6074fe7780d3a0398", + "url": "https://raw.githubusercontent.com/iced-rs/iced/3997291f318a8bc06fa522f5579836fb3feb94df/LICENSE" + } + ] + }, + { + "name": "iced_graphics", + "version": "0.14.0", + "checksum": "234ca1c2cec4155055f68fa5fad1b5242c496ac8238d80a259bca382fb44a102", + "notices": [ + { + "file": "iced-rs-iced-3997291f318a-LICENSE", + "sha256": "fc9086ba4eba4b77e4c603906362742bc3715ffa2c7810c6074fe7780d3a0398", + "url": "https://raw.githubusercontent.com/iced-rs/iced/3997291f318a8bc06fa522f5579836fb3feb94df/LICENSE" + } + ] + }, + { + "name": "iced_tiny_skia", + "version": "0.14.0", + "checksum": "fe0acf8b75a3bc914aff5f2329fdffc1b36eeaea29dda0e4bd232f1c62e9cc3d", + "notices": [ + { + "file": "iced-rs-iced-3997291f318a-LICENSE", + "sha256": "fc9086ba4eba4b77e4c603906362742bc3715ffa2c7810c6074fe7780d3a0398", + "url": "https://raw.githubusercontent.com/iced-rs/iced/3997291f318a8bc06fa522f5579836fb3feb94df/LICENSE" + } + ] + }, + { + "name": "iced_wgpu", + "version": "0.14.0", + "checksum": "ff144a999b0ca0f8a10257934500060240825c42e950ec0ebee9c8ae30561c13", + "notices": [ + { + "file": "iced-rs-iced-3997291f318a-LICENSE", + "sha256": "fc9086ba4eba4b77e4c603906362742bc3715ffa2c7810c6074fe7780d3a0398", + "url": "https://raw.githubusercontent.com/iced-rs/iced/3997291f318a8bc06fa522f5579836fb3feb94df/LICENSE" + } + ] + }, + { + "name": "iced_program", + "version": "0.14.0", + "checksum": "6dfafec2947cda688d8eb00dac337ba11aa60f9ef6335aed343e189d26e4a673", + "notices": [ + { + "file": "iced-rs-iced-3997291f318a-LICENSE", + "sha256": "fc9086ba4eba4b77e4c603906362742bc3715ffa2c7810c6074fe7780d3a0398", + "url": "https://raw.githubusercontent.com/iced-rs/iced/3997291f318a8bc06fa522f5579836fb3feb94df/LICENSE" + } + ] + }, + { + "name": "lyon_path", + "version": "1.0.19", + "checksum": "5c463f9c428b7fc5ec885dcd39ce4aa61e29111d0e33483f6f98c74e89d8621e", + "notices": [ + { + "file": "nical-lyon-56219c95c658-LICENSE-APACHE", + "sha256": "216486f29671a4262efe32af6d84a75bef398127f8c5f369b5c8305983887a06", + "url": "https://raw.githubusercontent.com/nical/lyon/56219c95c658d4f39de7b190d98e57d5773ba241/LICENSE-APACHE" + }, + { + "file": "nical-lyon-56219c95c658-LICENSE-MIT", + "sha256": "80f275e90d799911ed3830a7f242a2ef5a4ade2092fe0aa07bfb2d2cf2f2b95e", + "url": "https://raw.githubusercontent.com/nical/lyon/56219c95c658d4f39de7b190d98e57d5773ba241/LICENSE-MIT" + } + ] + }, + { + "name": "lyon", + "version": "1.0.19", + "checksum": "bd0578bdecb7d6d88987b8b2b1e3a4e2f81df9d0ece1078623324a567904e7b7", + "notices": [ + { + "file": "nical-lyon-56219c95c658-LICENSE-APACHE", + "sha256": "216486f29671a4262efe32af6d84a75bef398127f8c5f369b5c8305983887a06", + "url": "https://raw.githubusercontent.com/nical/lyon/56219c95c658d4f39de7b190d98e57d5773ba241/LICENSE-APACHE" + }, + { + "file": "nical-lyon-56219c95c658-LICENSE-MIT", + "sha256": "80f275e90d799911ed3830a7f242a2ef5a4ade2092fe0aa07bfb2d2cf2f2b95e", + "url": "https://raw.githubusercontent.com/nical/lyon/56219c95c658d4f39de7b190d98e57d5773ba241/LICENSE-MIT" + } + ] + }, + { + "name": "clipboard-win", + "version": "5.4.1", + "checksum": "bde03770d3df201d4fb868f2c9c59e66a3e4e2bd06692a0fe701e7103c7e84d4", + "notices": [ + { + "file": "DoumanAsh-clipboard-win-3b27cf2bfd1a-LICENSE", + "sha256": "c9bff75738922193e67fa726fa225535870d2aa1059f91452c411736284ad566", + "url": "https://raw.githubusercontent.com/DoumanAsh/clipboard-win/3b27cf2bfd1adcfa6e0264eb51c1025ddaf0f342/LICENSE" + } + ] + }, + { + "name": "lyon_geom", + "version": "1.0.19", + "checksum": "4336502e29e32af93cf2dad2214ed6003c17ceb5bd499df77b1de663b9042b92", + "notices": [ + { + "file": "nical-lyon-56219c95c658-LICENSE-APACHE", + "sha256": "216486f29671a4262efe32af6d84a75bef398127f8c5f369b5c8305983887a06", + "url": "https://raw.githubusercontent.com/nical/lyon/56219c95c658d4f39de7b190d98e57d5773ba241/LICENSE-APACHE" + }, + { + "file": "nical-lyon-56219c95c658-LICENSE-MIT", + "sha256": "80f275e90d799911ed3830a7f242a2ef5a4ade2092fe0aa07bfb2d2cf2f2b95e", + "url": "https://raw.githubusercontent.com/nical/lyon/56219c95c658d4f39de7b190d98e57d5773ba241/LICENSE-MIT" + } + ] + }, + { + "name": "svg_fmt", + "version": "0.4.5", + "checksum": "0193cc4331cfd2f3d2011ef287590868599a2f33c3e69bc22c1a3d3acf9e02fb", + "notices": [ + { + "file": "nical-rust_debug-93f414cd572b-LICENSE", + "sha256": "7e98bed7f100747aaae7d82ab94fbe6b76fd6b84b09a3aaa413bff8196d0fbb1", + "url": "https://raw.githubusercontent.com/nical/rust_debug/93f414cd572b5c2781df93dbdc068ed13443d05a/LICENSE" + } + ] + }, + { + "name": "lyon_algorithms", + "version": "1.0.21", + "checksum": "cdfa8785f95e57914ddb35e3b59994aeba6f5e79e9cfd03da1c269f010f36009", + "notices": [ + { + "file": "nical-lyon-994526f2766c-LICENSE-APACHE", + "sha256": "216486f29671a4262efe32af6d84a75bef398127f8c5f369b5c8305983887a06", + "url": "https://raw.githubusercontent.com/nical/lyon/994526f2766c7e9bc5bb16687232fd7e8999dbdf/LICENSE-APACHE" + }, + { + "file": "nical-lyon-994526f2766c-LICENSE-MIT", + "sha256": "80f275e90d799911ed3830a7f242a2ef5a4ade2092fe0aa07bfb2d2cf2f2b95e", + "url": "https://raw.githubusercontent.com/nical/lyon/994526f2766c7e9bc5bb16687232fd7e8999dbdf/LICENSE-MIT" + } + ] + }, + { + "name": "lyon_tessellation", + "version": "1.0.21", + "checksum": "dabea159dc6eea9171a541b458134b70ca95e3162d068132b2b3b9bc01aad06e", + "notices": [ + { + "file": "nical-lyon-994526f2766c-LICENSE-APACHE", + "sha256": "216486f29671a4262efe32af6d84a75bef398127f8c5f369b5c8305983887a06", + "url": "https://raw.githubusercontent.com/nical/lyon/994526f2766c7e9bc5bb16687232fd7e8999dbdf/LICENSE-APACHE" + }, + { + "file": "nical-lyon-994526f2766c-LICENSE-MIT", + "sha256": "80f275e90d799911ed3830a7f242a2ef5a4ade2092fe0aa07bfb2d2cf2f2b95e", + "url": "https://raw.githubusercontent.com/nical/lyon/994526f2766c7e9bc5bb16687232fd7e8999dbdf/LICENSE-MIT" + } + ] + }, + { + "name": "profiling", + "version": "1.0.18", + "checksum": "3d595e54a326bc53c1c197b32d295e14b169e3cfeaa8dc82b529f947fba6bcf5", + "notices": [ + { + "file": "aclysma-profiling-8271551172eb-LICENSE-APACHE", + "sha256": "10d30a673cd5e9349bdc02aeb48f14b3386d27d0da32df8f0a555d4aa16aa551", + "url": "https://raw.githubusercontent.com/aclysma/profiling/8271551172eb6fa4cba47369aedd93790c623df9/LICENSE-APACHE" + }, + { + "file": "aclysma-profiling-8271551172eb-LICENSE-MIT", + "sha256": "c8167fdeeed46d3f244d3f85c5bf998ce889343691c32be2c61a8bc4b5c08333", + "url": "https://raw.githubusercontent.com/aclysma/profiling/8271551172eb6fa4cba47369aedd93790c623df9/LICENSE-MIT" + } + ] + }, + { + "name": "hexf-parse", + "version": "0.2.1", + "checksum": "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df", + "notices": [ + { + "file": "CC0-1.0.txt", + "sha256": "a2010f343487d3f7618affe54f789f5487602331c0a8d03f49e9a7c547cf0499", + "url": "https://creativecommons.org/publicdomain/zero/1.0/legalcode.txt" + }, + { + "file": "hexf-parse-0.2.1-Cargo.toml", + "sha256": "4a56dbdbc12054fd33b7a2634a45cbbe6fe12f5e6f4081002cbfa527f7220c70", + "url": "https://raw.githubusercontent.com/lifthrasiir/hexf/4225763d744183d720f575ae96d04161b4d08ea0/parse/Cargo.toml" + } + ] + }, + { + "name": "spirv", + "version": "0.3.0+sdk-1.3.268.0", + "checksum": "eda41003dc44290527a59b13432d4a0379379fa074b70174882adfbdfd917844", + "notices": [ + { + "file": "gfx-rs-rspirv-e91a5debc10c-LICENSE", + "sha256": "cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30", + "url": "https://raw.githubusercontent.com/gfx-rs/rspirv/e91a5debc10c395b36db7721dfff6326d697a1e2/LICENSE" + } + ] + }, + { + "name": "gpu-alloc", + "version": "0.6.2", + "checksum": "45cf04b2726f02df5508c6de726acdc90cdf97ac771a9a0ffd8ba10a6e696bf9", + "notices": [ + { + "file": "zakarumych-gpu-alloc-61020bdcb4eb-COPYING", + "sha256": "12121dfa588677007848ab922a45a1e3248868f6ff063598fceb3141d7897314", + "url": "https://raw.githubusercontent.com/zakarumych/gpu-alloc/61020bdcb4eb04fca6bbf748aeb9570e859236e0/COPYING" + } + ] + }, + { + "name": "gpu-descriptor", + "version": "0.3.2", + "checksum": "b89c83349105e3732062a895becfc71a8f921bb71ecbbdd8ff99263e3b53a0ca", + "notices": [ + { + "file": "zakarumych-gpu-descriptor-bb28b4e55b74-COPYING", + "sha256": "f72143f7e9d9fb8bd336b139fa22f825a299591c29f2f7338c5566347b137613", + "url": "https://raw.githubusercontent.com/zakarumych/gpu-descriptor/bb28b4e55b742e0ea3e7a0968e7ee6287ba0e06a/COPYING" + } + ] + }, + { + "name": "gl_generator", + "version": "0.14.0", + "checksum": "1a95dfc23a2b4a9a2f5ab41d194f8bfda3cabec42af4e39f08c339eb2a0c124d", + "notices": [ + { + "file": "brendanzab-gl-rs-ea503e8d5fb6-LICENSE", + "sha256": "cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30", + "url": "https://raw.githubusercontent.com/brendanzab/gl-rs/ea503e8d5fb6d73c6030e6191ce738cd3bf3433e/LICENSE" + } + ] + }, + { + "name": "gpu-alloc-types", + "version": "0.3.1", + "checksum": "b2bbed164dd10ed526c2e4fe3e721ca4a71c61730e5aafac6844b417b3227058", + "notices": [ + { + "file": "zakarumych-gpu-alloc-7ddd1fe576fc-COPYING", + "sha256": "12121dfa588677007848ab922a45a1e3248868f6ff063598fceb3141d7897314", + "url": "https://raw.githubusercontent.com/zakarumych/gpu-alloc/7ddd1fe576fcfc503347eb97e476a47581479f22/COPYING" + } + ] + }, + { + "name": "gpu-descriptor-types", + "version": "0.2.0", + "checksum": "fdf242682df893b86f33a73828fb09ca4b2d3bb6cc95249707fc684d27484b91", + "notices": [ + { + "file": "zakarumych-gpu-descriptor-9f2fb9546591-COPYING", + "sha256": "f72143f7e9d9fb8bd336b139fa22f825a299591c29f2f7338c5566347b137613", + "url": "https://raw.githubusercontent.com/zakarumych/gpu-descriptor/9f2fb9546591322e7b77ee1e79385e01effd85e4/COPYING" + } + ] + }, + { + "name": "khronos_api", + "version": "3.1.0", + "checksum": "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc", + "notices": [ + { + "file": "brendanzab-gl-rs-f150967b1c44-LICENSE", + "sha256": "cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30", + "url": "https://raw.githubusercontent.com/brendanzab/gl-rs/f150967b1c44ae888e6676f93f639ebc82771bdc/LICENSE" + } + ] + } + ], + "assets": [ + { + "name": "iced_graphics", + "version": "0.14.0", + "path": "fonts/FiraSans-Regular.ttf", + "sha256": "3dabf3d48bf4599f95cffd92f99ea426a014d5311f52a5eb5ec3af265e97cd97", + "license": "OFL-1.1", + "notices": [ + { + "file": "Fira-OFL.txt", + "sha256": "99bfb7c1e845b79d1031fcf37e678178eb4a8c7cc74a3f218ddf7223083a6fcd", + "url": "https://raw.githubusercontent.com/mozilla/Fira/fd8c8c0a3d353cd99e8ca1662942d165e6961407/LICENSE" + }, + { + "file": "FiraSans-Regular-NOTICE.txt", + "sha256": "1e0baaa389b1647d06322ac9ea37822f31995408200ae5a2805a6b9bea0810d1", + "url": "https://raw.githubusercontent.com/iced-rs/iced/3997291f318a8bc06fa522f5579836fb3feb94df/graphics/fonts/FiraSans-Regular.ttf" + } + ] + }, + { + "name": "iced_graphics", + "version": "0.14.0", + "path": "fonts/Iced-Icons.ttf", + "sha256": "2289860dbd2201295bf451deb155561abf4b1fb06899d6f1f81eb294bfebb7ec", + "license": "MIT", + "notices": [ + { + "file": "iced-rs-iced-3997291f318a-LICENSE", + "sha256": "fc9086ba4eba4b77e4c603906362742bc3715ffa2c7810c6074fe7780d3a0398", + "url": "https://raw.githubusercontent.com/iced-rs/iced/3997291f318a8bc06fa522f5579836fb3feb94df/LICENSE" + }, + { + "file": "Iced-Icons-NOTICE.txt", + "sha256": "8175634e30f84ef72a24c4965fb0e87f45a3c1274cc0e79e0473af582cffd1c4", + "url": "https://raw.githubusercontent.com/iced-rs/iced/3997291f318a8bc06fa522f5579836fb3feb94df/graphics/fonts/Iced-Icons.ttf" + } + ] + } + ], + "runtimes": [ + { + "name": "libunwind.dll", + "sha256": "f45604a12e8bcd839fd86f667c1be1cc7c4f9da2b3c69ff381ac708f0e1ae7d4", + "provenance": "LLVM-MinGW 20260826 UCRT x86_64 distribution. Its clang --version identifies LLVM commit ea7d852a70e8bdfaf601d6626a760f9771b2c4b4. Component notice fetched at that commit; DLL bytes bound by SHA-256. Distribution build correspondence remains external evidence.", + "notices": [ + { + "file": "llvm-ea7d852a70e8-libunwind-LICENSE.TXT", + "sha256": "b5efebcaca80879234098e52d1725e6d9eb8fb96a19fce625d39184b705f7b6d", + "url": "https://raw.githubusercontent.com/llvm/llvm-project/ea7d852a70e8bdfaf601d6626a760f9771b2c4b4/libunwind/LICENSE.TXT" + } + ] + } + ] +} diff --git a/scripts/windows-notices/nical-lyon-56219c95c658-LICENSE-APACHE b/scripts/windows-notices/nical-lyon-56219c95c658-LICENSE-APACHE new file mode 100644 index 00000000..47f4fd0e --- /dev/null +++ b/scripts/windows-notices/nical-lyon-56219c95c658-LICENSE-APACHE @@ -0,0 +1,13 @@ +Copyright 2016 Nicolas Silva + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/scripts/windows-notices/nical-lyon-56219c95c658-LICENSE-MIT b/scripts/windows-notices/nical-lyon-56219c95c658-LICENSE-MIT new file mode 100644 index 00000000..e6d4b179 --- /dev/null +++ b/scripts/windows-notices/nical-lyon-56219c95c658-LICENSE-MIT @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2013 Nicolas Silva + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/scripts/windows-notices/nical-lyon-994526f2766c-LICENSE-APACHE b/scripts/windows-notices/nical-lyon-994526f2766c-LICENSE-APACHE new file mode 100644 index 00000000..47f4fd0e --- /dev/null +++ b/scripts/windows-notices/nical-lyon-994526f2766c-LICENSE-APACHE @@ -0,0 +1,13 @@ +Copyright 2016 Nicolas Silva + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/scripts/windows-notices/nical-lyon-994526f2766c-LICENSE-MIT b/scripts/windows-notices/nical-lyon-994526f2766c-LICENSE-MIT new file mode 100644 index 00000000..e6d4b179 --- /dev/null +++ b/scripts/windows-notices/nical-lyon-994526f2766c-LICENSE-MIT @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2013 Nicolas Silva + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/scripts/windows-notices/nical-rust_debug-93f414cd572b-LICENSE b/scripts/windows-notices/nical-rust_debug-93f414cd572b-LICENSE new file mode 100644 index 00000000..9ad7b40b --- /dev/null +++ b/scripts/windows-notices/nical-rust_debug-93f414cd572b-LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Nicolas Silva + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/scripts/windows-notices/zakarumych-gpu-alloc-61020bdcb4eb-COPYING b/scripts/windows-notices/zakarumych-gpu-alloc-61020bdcb4eb-COPYING new file mode 100644 index 00000000..1787983c --- /dev/null +++ b/scripts/windows-notices/zakarumych-gpu-alloc-61020bdcb4eb-COPYING @@ -0,0 +1,6 @@ +Copyright 2020 The gpu-alloc Project Developers + +Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be +copied, modified, or distributed except according to those terms. diff --git a/scripts/windows-notices/zakarumych-gpu-alloc-7ddd1fe576fc-COPYING b/scripts/windows-notices/zakarumych-gpu-alloc-7ddd1fe576fc-COPYING new file mode 100644 index 00000000..1787983c --- /dev/null +++ b/scripts/windows-notices/zakarumych-gpu-alloc-7ddd1fe576fc-COPYING @@ -0,0 +1,6 @@ +Copyright 2020 The gpu-alloc Project Developers + +Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be +copied, modified, or distributed except according to those terms. diff --git a/scripts/windows-notices/zakarumych-gpu-descriptor-9f2fb9546591-COPYING b/scripts/windows-notices/zakarumych-gpu-descriptor-9f2fb9546591-COPYING new file mode 100644 index 00000000..9eca5fed --- /dev/null +++ b/scripts/windows-notices/zakarumych-gpu-descriptor-9f2fb9546591-COPYING @@ -0,0 +1,6 @@ +Copyright 2020 The gpu-descriptor Project Developers + +Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be +copied, modified, or distributed except according to those terms. diff --git a/scripts/windows-notices/zakarumych-gpu-descriptor-bb28b4e55b74-COPYING b/scripts/windows-notices/zakarumych-gpu-descriptor-bb28b4e55b74-COPYING new file mode 100644 index 00000000..9eca5fed --- /dev/null +++ b/scripts/windows-notices/zakarumych-gpu-descriptor-bb28b4e55b74-COPYING @@ -0,0 +1,6 @@ +Copyright 2020 The gpu-descriptor Project Developers + +Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be +copied, modified, or distributed except according to those terms.